Merge tag 'v1.4.32' into sc
Change-Id: Ib3724625b1aada2541e030f9fdaf47a032538c85 Conflicts: .gitignore library/ui-styles/src/main/res/values/styles_toolbar.xml vector/build.gradle vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt vector/src/main/java/im/vector/app/VectorApplication.kt vector/src/main/java/im/vector/app/features/home/HomeActivity.kt vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt vector/src/main/res/layout/fragment_timeline.xml
This commit is contained in:
commit
5d1722054e
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@ -7,10 +7,8 @@ on:
|
|||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
CI_GRADLE_ARG_PROPERTIES: >
|
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
|
||||||
-Porg.gradle.jvmargs=-Xmx4g
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
|
||||||
-Porg.gradle.parallel=false
|
|
||||||
--no-daemon
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
debug:
|
debug:
|
||||||
@ -36,7 +34,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-gradle-
|
${{ runner.os }}-gradle-
|
||||||
- name: Assemble ${{ matrix.target }} debug apk
|
- name: Assemble ${{ matrix.target }} debug apk
|
||||||
run: ./gradlew assemble${{ matrix.target }}Debug $CI_GRADLE_ARG_PROPERTIES --stacktrace
|
run: ./gradlew assemble${{ matrix.target }}Debug $CI_GRADLE_ARG_PROPERTIES
|
||||||
- name: Upload ${{ matrix.target }} debug APKs
|
- name: Upload ${{ matrix.target }} debug APKs
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
@ -61,7 +59,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-gradle-
|
${{ runner.os }}-gradle-
|
||||||
- name: Assemble GPlay unsigned apk
|
- name: Assemble GPlay unsigned apk
|
||||||
run: ./gradlew clean assembleGplayRelease $CI_GRADLE_ARG_PROPERTIES --stacktrace
|
run: ./gradlew clean assembleGplayRelease $CI_GRADLE_ARG_PROPERTIES
|
||||||
- name: Upload Gplay unsigned APKs
|
- name: Upload Gplay unsigned APKs
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
18
.github/workflows/danger.yml
vendored
Normal file
18
.github/workflows/danger.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
name: Danger CI
|
||||||
|
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Danger
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- run: |
|
||||||
|
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||||
|
- name: Danger
|
||||||
|
uses: danger/danger-js@11.1.1
|
||||||
|
with:
|
||||||
|
args: "--dangerfile tools/danger/dangerfile.js"
|
||||||
|
env:
|
||||||
|
DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
@ -1,5 +1,8 @@
|
|||||||
name: "Validate Gradle Wrapper"
|
name: "Validate Gradle Wrapper"
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
pull_request: { }
|
||||||
|
push:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
validation:
|
validation:
|
||||||
|
8
.github/workflows/nightly.yml
vendored
8
.github/workflows/nightly.yml
vendored
@ -6,10 +6,8 @@ on:
|
|||||||
- cron: "0 4 * * *"
|
- cron: "0 4 * * *"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CI_GRADLE_ARG_PROPERTIES: >
|
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
|
||||||
-Porg.gradle.jvmargs=-Xmx4g
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
|
||||||
-Porg.gradle.parallel=false
|
|
||||||
--no-daemon
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
nightly:
|
nightly:
|
||||||
@ -40,7 +38,7 @@ jobs:
|
|||||||
yes n | towncrier build --version nightly
|
yes n | towncrier build --version nightly
|
||||||
- name: Build and upload Gplay Nightly APK
|
- name: Build and upload Gplay Nightly APK
|
||||||
run: |
|
run: |
|
||||||
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES --stacktrace
|
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES
|
||||||
env:
|
env:
|
||||||
ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }}
|
ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }}
|
||||||
ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }}
|
ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }}
|
||||||
|
6
.github/workflows/post-pr.yml
vendored
6
.github/workflows/post-pr.yml
vendored
@ -10,10 +10,8 @@ on:
|
|||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
CI_GRADLE_ARG_PROPERTIES: >
|
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
|
||||||
-Porg.gradle.jvmargs=-Xmx4g
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
|
||||||
-Porg.gradle.parallel=false
|
|
||||||
--no-daemon
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
|
192
.github/workflows/quality.yml
vendored
192
.github/workflows/quality.yml
vendored
@ -7,10 +7,8 @@ on:
|
|||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
CI_GRADLE_ARG_PROPERTIES: >
|
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
|
||||||
-Porg.gradle.jvmargs=-Xmx4g
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
|
||||||
-Porg.gradle.parallel=false
|
|
||||||
--no-daemon
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
@ -29,80 +27,50 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Run knit
|
- name: Run knit
|
||||||
run: |
|
run: |
|
||||||
./gradlew knit
|
./gradlew knitCheck
|
||||||
|
|
||||||
# ktlint for all the modules
|
# Check the project: ktlint, detekt, lint
|
||||||
ktlint:
|
lint:
|
||||||
name: Kotlin Linter
|
name: Android Linter
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Allow all jobs on main and develop. Just one per PR.
|
# Allow all jobs on main and develop. Just one per PR.
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.ref == 'refs/heads/main' && format('ktlint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('ktlint-develop-{0}', github.sha) || format('ktlint-{0}', github.ref) }}
|
group: ${{ github.ref == 'refs/heads/main' && format('lint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('lint-develop-{0}', github.sha) || format('lint-{0}', github.ref) }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Run ktlint
|
- name: Run ktlint
|
||||||
run: |
|
run: |
|
||||||
./gradlew ktlintCheck --continue
|
./gradlew ktlintCheck --continue
|
||||||
|
- name: Run detekt
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
./gradlew detekt $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
- name: Run lint
|
||||||
|
# Not always, if ktlint or detekt fail, avoid running the long lint check.
|
||||||
|
run: |
|
||||||
|
./gradlew lintGplayRelease $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
./gradlew lintFdroidRelease $CI_GRADLE_ARG_PROPERTIES
|
||||||
- name: Upload reports
|
- name: Upload reports
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: ktlinting-report
|
name: linting-report
|
||||||
path: |
|
path: |
|
||||||
*/build/reports/ktlint/ktlint*/ktlint*.txt
|
*/build/reports/**/*.*
|
||||||
- name: Handle Results
|
- name: Prepare Danger
|
||||||
if: always()
|
if: always()
|
||||||
id: ktlint-results
|
|
||||||
run: |
|
run: |
|
||||||
results="$(cat */*/build/reports/ktlint/ktlint*/ktlint*.txt */build/reports/ktlint/ktlint*/ktlint*.txt | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g")"
|
npm install --save-dev @babel/core
|
||||||
if [ -z "$results" ]; then
|
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||||
echo "::set-output name=add_comment::false"
|
yarn add danger-plugin-lint-report --dev
|
||||||
else
|
- name: Danger lint
|
||||||
body="👎\`Failed${results}\`"
|
if: always()
|
||||||
body="${body//'%'/'%25'}"
|
uses: danger/danger-js@11.1.1
|
||||||
body="${body//$'\n'/'%0A'}"
|
|
||||||
body="${body//$'\r'/'%0D'}"
|
|
||||||
body="$( echo $body | sed 's/\/home\/runner\/work\/element-android\/element-android\//\`<br\/>\`/g')"
|
|
||||||
body="$( echo $body | sed 's/\/src\/main\/java\// 🔸 /g')"
|
|
||||||
body="$( echo $body | sed 's/im\/vector\/app\///g')"
|
|
||||||
body="$( echo $body | sed 's/im\/vector\/lib\/attachmentviewer\///g')"
|
|
||||||
body="$( echo $body | sed 's/im\/vector\/lib\/multipicker\///g')"
|
|
||||||
body="$( echo $body | sed 's/im\/vector\/lib\///g')"
|
|
||||||
body="$( echo $body | sed 's/org\/matrix\/android\/sdk\///g')"
|
|
||||||
body="$( echo $body | sed 's/\/src\/androidTest\/java\// 🔸 /g')"
|
|
||||||
echo "::set-output name=add_comment::true"
|
|
||||||
echo "::set-output name=body::$body"
|
|
||||||
fi
|
|
||||||
- name: Find Comment
|
|
||||||
if: always() && github.event_name == 'pull_request'
|
|
||||||
uses: peter-evans/find-comment@v2
|
|
||||||
id: fc
|
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
args: "--dangerfile tools/danger/dangerfile-lint.js"
|
||||||
comment-author: 'github-actions[bot]'
|
env:
|
||||||
body-includes: Ktlint Results
|
DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
||||||
- name: Add comment if needed
|
|
||||||
if: always() && github.event_name == 'pull_request' && steps.ktlint-results.outputs.add_comment == 'true'
|
|
||||||
uses: peter-evans/create-or-update-comment@v2
|
|
||||||
with:
|
|
||||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
|
||||||
body: |
|
|
||||||
### Ktlint Results
|
|
||||||
|
|
||||||
${{ steps.ktlint-results.outputs.body }}
|
|
||||||
edit-mode: replace
|
|
||||||
- name: Delete comment if needed
|
|
||||||
if: always() && github.event_name == 'pull_request' && steps.fc.outputs.comment-id != '' && steps.ktlint-results.outputs.add_comment == 'false'
|
|
||||||
uses: actions/github-script@v3
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
github.issues.deleteComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
comment_id: ${{ steps.fc.outputs.comment-id }}
|
|
||||||
})
|
|
||||||
|
|
||||||
# Gradle dependency analysis using https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin
|
# Gradle dependency analysis using https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin
|
||||||
dependency-analysis:
|
dependency-analysis:
|
||||||
@ -122,107 +90,3 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: dependency-analysis
|
name: dependency-analysis
|
||||||
path: build/reports/dependency-check-report.html
|
path: build/reports/dependency-check-report.html
|
||||||
|
|
||||||
# Lint for main module
|
|
||||||
android-lint:
|
|
||||||
name: Android Linter
|
|
||||||
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:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.gradle/caches
|
|
||||||
~/.gradle/wrapper
|
|
||||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gradle-
|
|
||||||
- name: Lint analysis
|
|
||||||
run: ./gradlew clean :vector:lint --stacktrace $CI_GRADLE_ARG_PROPERTIES
|
|
||||||
- name: Upload reports
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: lint-report
|
|
||||||
path: |
|
|
||||||
vector/build/reports/*.*
|
|
||||||
|
|
||||||
# Lint for Gplay and Fdroid release APK
|
|
||||||
apk-lint:
|
|
||||||
name: Lint APK (${{ matrix.target }})
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.ref != 'refs/heads/main'
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
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:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.gradle/caches
|
|
||||||
~/.gradle/wrapper
|
|
||||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gradle-
|
|
||||||
- name: Lint ${{ matrix.target }} release
|
|
||||||
run: ./gradlew clean lint${{ matrix.target }}Release --stacktrace $CI_GRADLE_ARG_PROPERTIES
|
|
||||||
- name: Upload ${{ matrix.target }} linting report
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: release-lint-report-${{ matrix.target }}
|
|
||||||
path: |
|
|
||||||
vector/build/reports/*.*
|
|
||||||
|
|
||||||
detekt:
|
|
||||||
name: Detekt Analysis
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# Allow all jobs on main and develop. Just one per PR.
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.ref == 'refs/heads/main' && format('detekt-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('detekt-develop-{0}', github.sha) || format('detekt-{0}', github.ref) }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Run detekt
|
|
||||||
run: |
|
|
||||||
./gradlew detekt $CI_GRADLE_ARG_PROPERTIES
|
|
||||||
- name: Upload reports
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: detekt-report
|
|
||||||
path: |
|
|
||||||
*/build/reports/detekt/detekt.html
|
|
||||||
|
|
||||||
# towncrier:
|
|
||||||
# name: Towncrier check
|
|
||||||
# runs-on: ubuntu-latest
|
|
||||||
# if: github.event_name == 'pull_request' && github.head_ref == 'develop'
|
|
||||||
# steps:
|
|
||||||
# - uses: actions/checkout@v3
|
|
||||||
# - name: Set up Python 3.8
|
|
||||||
# uses: actions/setup-python@v4
|
|
||||||
# with:
|
|
||||||
# python-version: 3.8
|
|
||||||
# - name: Install towncrier
|
|
||||||
# run: |
|
|
||||||
# python3 -m pip install towncrier
|
|
||||||
# - name: Run towncrier
|
|
||||||
# # Fetch the pull request' base branch so towncrier will be able to
|
|
||||||
# # compare the current branch with the base branch.
|
|
||||||
# # Source: https://github.com/actions/checkout/#fetch-all-branches.
|
|
||||||
# run: |
|
|
||||||
# git fetch --no-tags origin +refs/heads/${BASE_BRANCH}:refs/remotes/origin/${BASE_BRANCH}
|
|
||||||
# towncrier check --compare-with origin/${BASE_BRANCH}
|
|
||||||
# env:
|
|
||||||
# BASE_BRANCH: ${{ github.base_ref }}
|
|
||||||
|
22
.github/workflows/tests.yml
vendored
22
.github/workflows/tests.yml
vendored
@ -7,10 +7,8 @@ on:
|
|||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
CI_GRADLE_ARG_PROPERTIES: >
|
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
|
||||||
-Porg.gradle.jvmargs=-Xmx4g
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
|
||||||
-Porg.gradle.parallel=false
|
|
||||||
--no-daemon
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
@ -51,9 +49,9 @@ jobs:
|
|||||||
disable-animations: true
|
disable-animations: true
|
||||||
emulator-build: 7425822
|
emulator-build: 7425822
|
||||||
script: |
|
script: |
|
||||||
./gradlew unitTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES
|
./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
|
||||||
./gradlew instrumentationTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES
|
./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
|
||||||
./gradlew generateCoverageReport --stacktrace $CI_GRADLE_ARG_PROPERTIES
|
./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES
|
||||||
# NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves stes.tests.outcome = 'failure'
|
# NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves stes.tests.outcome = 'failure'
|
||||||
- name: Run all the codecoverage tests at once (retry if emulator failed)
|
- name: Run all the codecoverage tests at once (retry if emulator failed)
|
||||||
uses: reactivecircus/android-emulator-runner@v2
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
@ -67,13 +65,13 @@ jobs:
|
|||||||
disable-animations: true
|
disable-animations: true
|
||||||
emulator-build: 7425822
|
emulator-build: 7425822
|
||||||
script: |
|
script: |
|
||||||
./gradlew unitTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES
|
./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
|
||||||
./gradlew instrumentationTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES
|
./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
|
||||||
./gradlew generateCoverageReport --stacktrace $CI_GRADLE_ARG_PROPERTIES
|
./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES
|
||||||
- run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
|
- run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
|
||||||
if: always() # we may have failed a previous step and retried, that's OK
|
if: always() # we may have failed a previous step and retried, that's OK
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
GITHUB_TOKEN: ${{ secrets.SONARQUBE_GITHUB_API_TOKEN }} # Needed to get PR information, if any
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
|
ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
|
||||||
@ -114,5 +112,5 @@ jobs:
|
|||||||
# restore-keys: |
|
# restore-keys: |
|
||||||
# ${{ runner.os }}-gradle-
|
# ${{ runner.os }}-gradle-
|
||||||
# - name: Build Android Tests
|
# - name: Build Android Tests
|
||||||
# run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
|
# run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -19,5 +19,11 @@
|
|||||||
|
|
||||||
/**/build
|
/**/build
|
||||||
|
|
||||||
|
# Added by SC
|
||||||
.tmp_unpatched_strings
|
.tmp_unpatched_strings
|
||||||
.tmp_new_patched_strings
|
.tmp_new_patched_strings
|
||||||
|
|
||||||
|
# Added by yarn
|
||||||
|
/package.json
|
||||||
|
/yarn.lock
|
||||||
|
/node_modules
|
||||||
|
18
.travis.yml
18
.travis.yml
@ -1,18 +0,0 @@
|
|||||||
# FTR: Configuration on https://travis-ci.org/github/vector-im/element-android/settings
|
|
||||||
#
|
|
||||||
# - Build only if .travis.yml is present -> On
|
|
||||||
# - Limit concurrent jobs -> Off
|
|
||||||
# - Build pushed branches -> On (build the branch)
|
|
||||||
# - Build pushed pull request -> On (build the PR after auto-merge)
|
|
||||||
#
|
|
||||||
# - Auto cancel branch builds -> On
|
|
||||||
# - Auto cancel pull request builds -> On
|
|
||||||
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
||||||
|
|
||||||
# Just run a simple script here
|
|
||||||
script:
|
|
||||||
- ./tools/travis/check_pr.sh
|
|
35
CHANGES.md
35
CHANGES.md
@ -1,3 +1,38 @@
|
|||||||
|
Changes in Element v1.4.32 (2022-08-10)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
Features ✨
|
||||||
|
----------
|
||||||
|
- [Location Share] Render fallback UI when map fails to load ([#6711](https://github.com/vector-im/element-android/issues/6711))
|
||||||
|
|
||||||
|
Bugfixes 🐛
|
||||||
|
----------
|
||||||
|
- Fix message content sometimes appearing in the log ([#6706](https://github.com/vector-im/element-android/issues/6706))
|
||||||
|
- Disable 'Enable biometrics' option if there are not biometric authenticators enrolled. ([#6713](https://github.com/vector-im/element-android/issues/6713))
|
||||||
|
- Fix crash when biometric key is used when coming back to foreground and KeyStore reports that the device is still locked. ([#6768](https://github.com/vector-im/element-android/issues/6768))
|
||||||
|
- Catch all exceptions on lockscreen system key migrations. ([#6769](https://github.com/vector-im/element-android/issues/6769))
|
||||||
|
- Fixes crash when entering non ascii characters during account creation ([#6735](https://github.com/vector-im/element-android/issues/6735))
|
||||||
|
- Fixes onboarding login/account creation errors showing after navigation ([#6737](https://github.com/vector-im/element-android/issues/6737))
|
||||||
|
- [Location sharing] Invisible text on map symbol ([#6687](https://github.com/vector-im/element-android/issues/6687))
|
||||||
|
|
||||||
|
In development 🚧
|
||||||
|
----------------
|
||||||
|
- Adds new app layout toolbar ([#6655](https://github.com/vector-im/element-android/issues/6655))
|
||||||
|
|
||||||
|
Other changes
|
||||||
|
-------------
|
||||||
|
- [Modularization] Provides abstraction to avoid direct usages of BuildConfig ([#6406](https://github.com/vector-im/element-android/issues/6406))
|
||||||
|
- Refactors SpaceStateHandler (previously AppStateHandler) and adds unit tests for it ([#6598](https://github.com/vector-im/element-android/issues/6598))
|
||||||
|
- Setup Danger to the project ([#6637](https://github.com/vector-im/element-android/issues/6637))
|
||||||
|
- [Location Share] Open maximized map on tapping on live sharing notification ([#6642](https://github.com/vector-im/element-android/issues/6642))
|
||||||
|
- [Location sharing] Align naming of components for live location feature ([#6647](https://github.com/vector-im/element-android/issues/6647))
|
||||||
|
- [Location share] Update minimum sending period to 5 seconds for a live ([#6653](https://github.com/vector-im/element-android/issues/6653))
|
||||||
|
- [Location sharing] - Fix the memory leaks ([#6674](https://github.com/vector-im/element-android/issues/6674))
|
||||||
|
- [Timeline] Memory leak in audio message playback tracker ([#6678](https://github.com/vector-im/element-android/issues/6678))
|
||||||
|
- [FTUE] Memory leak on FtueAuthSplashCarouselFragment ([#6680](https://github.com/vector-im/element-android/issues/6680))
|
||||||
|
- Link directly to DCO docs from danger message. ([#6739](https://github.com/vector-im/element-android/issues/6739))
|
||||||
|
|
||||||
|
|
||||||
Changes in Element v1.4.31 (2022-08-01)
|
Changes in Element v1.4.31 (2022-08-01)
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
|
1
Gemfile
1
Gemfile
@ -1,3 +1,4 @@
|
|||||||
source "https://rubygems.org"
|
source "https://rubygems.org"
|
||||||
|
|
||||||
gem "fastlane"
|
gem "fastlane"
|
||||||
|
gem 'danger'
|
||||||
|
39
Gemfile.lock
39
Gemfile.lock
@ -24,10 +24,29 @@ GEM
|
|||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.4)
|
babosa (1.0.4)
|
||||||
claide (1.0.3)
|
claide (1.0.3)
|
||||||
|
claide-plugins (0.9.2)
|
||||||
|
cork
|
||||||
|
nap
|
||||||
|
open4 (~> 1.3)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
commander (4.6.0)
|
commander (4.6.0)
|
||||||
highline (~> 2.0.0)
|
highline (~> 2.0.0)
|
||||||
|
cork (0.3.0)
|
||||||
|
colored2 (~> 3.1)
|
||||||
|
danger (8.6.1)
|
||||||
|
claide (~> 1.0)
|
||||||
|
claide-plugins (>= 0.9.2)
|
||||||
|
colored2 (~> 3.1)
|
||||||
|
cork (~> 0.1)
|
||||||
|
faraday (>= 0.9.0, < 2.0)
|
||||||
|
faraday-http-cache (~> 2.0)
|
||||||
|
git (~> 1.7)
|
||||||
|
kramdown (~> 2.3)
|
||||||
|
kramdown-parser-gfm (~> 1.0)
|
||||||
|
no_proxy_fix
|
||||||
|
octokit (~> 4.7)
|
||||||
|
terminal-table (>= 1, < 4)
|
||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
digest-crc (0.6.3)
|
digest-crc (0.6.3)
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
@ -52,6 +71,8 @@ GEM
|
|||||||
faraday-em_http (1.0.0)
|
faraday-em_http (1.0.0)
|
||||||
faraday-em_synchrony (1.0.0)
|
faraday-em_synchrony (1.0.0)
|
||||||
faraday-excon (1.1.0)
|
faraday-excon (1.1.0)
|
||||||
|
faraday-http-cache (2.4.0)
|
||||||
|
faraday (>= 0.8)
|
||||||
faraday-httpclient (1.0.1)
|
faraday-httpclient (1.0.1)
|
||||||
faraday-net_http (1.0.1)
|
faraday-net_http (1.0.1)
|
||||||
faraday-net_http_persistent (1.2.0)
|
faraday-net_http_persistent (1.2.0)
|
||||||
@ -98,6 +119,8 @@ GEM
|
|||||||
xcpretty (~> 0.3.0)
|
xcpretty (~> 0.3.0)
|
||||||
xcpretty-travis-formatter (>= 0.0.3)
|
xcpretty-travis-formatter (>= 0.0.3)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
|
git (1.11.0)
|
||||||
|
rchardet (~> 1.8)
|
||||||
google-apis-androidpublisher_v3 (0.8.0)
|
google-apis-androidpublisher_v3 (0.8.0)
|
||||||
google-apis-core (>= 0.4, < 2.a)
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
google-apis-core (0.4.0)
|
google-apis-core (0.4.0)
|
||||||
@ -143,17 +166,28 @@ GEM
|
|||||||
jmespath (1.4.0)
|
jmespath (1.4.0)
|
||||||
json (2.5.1)
|
json (2.5.1)
|
||||||
jwt (2.2.3)
|
jwt (2.2.3)
|
||||||
|
kramdown (2.4.0)
|
||||||
|
rexml
|
||||||
|
kramdown-parser-gfm (1.1.0)
|
||||||
|
kramdown (~> 2.0)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
mini_magick (4.11.0)
|
mini_magick (4.11.0)
|
||||||
mini_mime (1.1.0)
|
mini_mime (1.1.0)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
nanaimo (0.3.0)
|
nanaimo (0.3.0)
|
||||||
|
nap (1.1.0)
|
||||||
naturally (2.2.1)
|
naturally (2.2.1)
|
||||||
|
no_proxy_fix (0.1.2)
|
||||||
|
octokit (4.25.1)
|
||||||
|
faraday (>= 1, < 3)
|
||||||
|
sawyer (~> 0.9)
|
||||||
|
open4 (1.3.4)
|
||||||
os (1.1.1)
|
os (1.1.1)
|
||||||
plist (3.6.0)
|
plist (3.6.0)
|
||||||
public_suffix (4.0.6)
|
public_suffix (4.0.6)
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
|
rchardet (1.8.0)
|
||||||
representable (3.1.1)
|
representable (3.1.1)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
@ -163,6 +197,9 @@ GEM
|
|||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
|
sawyer (0.9.2)
|
||||||
|
addressable (>= 2.3.5)
|
||||||
|
faraday (>= 0.17.3, < 3)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.15.0)
|
signet (0.15.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
@ -200,9 +237,11 @@ GEM
|
|||||||
xcpretty (~> 0.2, >= 0.0.7)
|
xcpretty (~> 0.2, >= 0.0.7)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
|
universal-darwin-21
|
||||||
x86_64-darwin-20
|
x86_64-darwin-20
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
danger
|
||||||
fastlane
|
fastlane
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
|
11
build.gradle
11
build.gradle
@ -30,7 +30,7 @@ buildscript {
|
|||||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
|
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
|
||||||
classpath "com.likethesalad.android:stem-plugin:2.1.1"
|
classpath "com.likethesalad.android:stem-plugin:2.1.1"
|
||||||
classpath 'org.owasp:dependency-check-gradle:7.1.1'
|
classpath 'org.owasp:dependency-check-gradle:7.1.1'
|
||||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.0"
|
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.10"
|
||||||
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
|
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.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
|
||||||
@ -41,10 +41,10 @@ plugins {
|
|||||||
// ktlint Plugin
|
// ktlint Plugin
|
||||||
id "org.jlleitschuh.gradle.ktlint" version "10.3.0"
|
id "org.jlleitschuh.gradle.ktlint" version "10.3.0"
|
||||||
// Detekt
|
// Detekt
|
||||||
id "io.gitlab.arturbosch.detekt" version "1.20.0"
|
id "io.gitlab.arturbosch.detekt" version "1.21.0"
|
||||||
|
|
||||||
// Dependency Analysis
|
// Dependency Analysis
|
||||||
id 'com.autonomousapps.dependency-analysis' version "1.9.0"
|
id 'com.autonomousapps.dependency-analysis' version "1.11.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/jeremylong/DependencyCheck
|
// https://github.com/jeremylong/DependencyCheck
|
||||||
@ -126,6 +126,11 @@ allprojects {
|
|||||||
enableExperimentalRules = true
|
enableExperimentalRules = true
|
||||||
// display the corresponding rule
|
// display the corresponding rule
|
||||||
verbose = true
|
verbose = true
|
||||||
|
reporters {
|
||||||
|
reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN)
|
||||||
|
// To have XML report for Danger
|
||||||
|
reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE)
|
||||||
|
}
|
||||||
disabledRules = [
|
disabledRules = [
|
||||||
// TODO Re-enable these 4 rules after reformatting project
|
// TODO Re-enable these 4 rules after reformatting project
|
||||||
"indent",
|
"indent",
|
||||||
|
@ -20,9 +20,9 @@ def retrofit = "2.9.0"
|
|||||||
def arrow = "0.8.2"
|
def arrow = "0.8.2"
|
||||||
def markwon = "4.6.2"
|
def markwon = "4.6.2"
|
||||||
def moshi = "1.13.0"
|
def moshi = "1.13.0"
|
||||||
def lifecycle = "2.5.0"
|
def lifecycle = "2.5.1"
|
||||||
def flowBinding = "1.2.0"
|
def flowBinding = "1.2.0"
|
||||||
def flipper = "0.154.0"
|
def flipper = "0.156.0"
|
||||||
def epoxy = "4.6.2"
|
def epoxy = "4.6.2"
|
||||||
def mavericks = "2.7.0"
|
def mavericks = "2.7.0"
|
||||||
def glide = "4.13.2"
|
def glide = "4.13.2"
|
||||||
@ -30,7 +30,7 @@ def bigImageViewer = "1.8.1"
|
|||||||
def jjwt = "0.11.5"
|
def jjwt = "0.11.5"
|
||||||
def vanniktechEmoji = "0.15.0"
|
def vanniktechEmoji = "0.15.0"
|
||||||
|
|
||||||
def fragment = "1.5.0"
|
def fragment = "1.5.1"
|
||||||
|
|
||||||
// Testing
|
// Testing
|
||||||
def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819
|
def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819
|
||||||
@ -51,7 +51,7 @@ ext.libs = [
|
|||||||
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
|
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
|
||||||
],
|
],
|
||||||
androidx : [
|
androidx : [
|
||||||
'activity' : "androidx.activity:activity:1.5.0",
|
'activity' : "androidx.activity:activity:1.5.1",
|
||||||
'appCompat' : "androidx.appcompat:appcompat:1.4.2",
|
'appCompat' : "androidx.appcompat:appcompat:1.4.2",
|
||||||
'biometric' : "androidx.biometric:biometric:1.1.0",
|
'biometric' : "androidx.biometric:biometric:1.1.0",
|
||||||
'core' : "androidx.core:core-ktx:1.8.0",
|
'core' : "androidx.core:core-ktx:1.8.0",
|
||||||
|
@ -75,6 +75,7 @@ ext.groups = [
|
|||||||
'com.github.javaparser',
|
'com.github.javaparser',
|
||||||
'com.github.piasy',
|
'com.github.piasy',
|
||||||
'com.github.shyiko.klob',
|
'com.github.shyiko.klob',
|
||||||
|
'com.github.rubensousa',
|
||||||
'com.google',
|
'com.google',
|
||||||
'com.google.android',
|
'com.google.android',
|
||||||
'com.google.api.grpc',
|
'com.google.api.grpc',
|
||||||
@ -126,6 +127,7 @@ ext.groups = [
|
|||||||
'info.picocli',
|
'info.picocli',
|
||||||
'io.arrow-kt',
|
'io.arrow-kt',
|
||||||
'io.element.android',
|
'io.element.android',
|
||||||
|
'io.github.davidburstrom.contester',
|
||||||
'io.github.detekt.sarif4k',
|
'io.github.detekt.sarif4k',
|
||||||
'io.github.microutils',
|
'io.github.microutils',
|
||||||
'io.github.reactivecircus.flowbinding',
|
'io.github.reactivecircus.flowbinding',
|
||||||
|
102
docs/danger.md
Normal file
102
docs/danger.md
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
## Danger
|
||||||
|
|
||||||
|
<!--- TOC -->
|
||||||
|
|
||||||
|
* [What does danger checks](#what-does-danger-checks)
|
||||||
|
* [PR check](#pr-check)
|
||||||
|
* [Quality check](#quality-check)
|
||||||
|
* [Setup](#setup)
|
||||||
|
* [Run danger locally](#run-danger-locally)
|
||||||
|
* [Danger user](#danger-user)
|
||||||
|
* [Useful links](#useful-links)
|
||||||
|
|
||||||
|
<!--- END -->
|
||||||
|
|
||||||
|
## What does danger checks
|
||||||
|
|
||||||
|
### PR check
|
||||||
|
|
||||||
|
See the [dangerfile](../tools/danger/dangerfile.js). If you add rules in the dangerfile, please update the list below!
|
||||||
|
|
||||||
|
Here are the checks that Danger does so far:
|
||||||
|
|
||||||
|
- PR description is not empty
|
||||||
|
- Big PR got a warning to recommend to split
|
||||||
|
- PR contains a file for towncrier and extension is checked
|
||||||
|
- PR contains a Sign-Off, with exception for Element employee contributors
|
||||||
|
- PR with change on layout should include screenshot in the description
|
||||||
|
- PR which adds png file warn about the usage of vector drawables
|
||||||
|
- non draft PR should have a reviewer
|
||||||
|
|
||||||
|
### Quality check
|
||||||
|
|
||||||
|
After all the checks that generate checkstyle XML report, such as Ktlint, lint, or Detekt, Danger is run with this [dangerfile](../tools/danger/dangerfile-lint.js), in order to post comments to the PR with the detected error and warnings.
|
||||||
|
|
||||||
|
To run locally, you will have to install the plugin `danger-plugin-lint-report` using:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn add danger-plugin-lint-report --dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
This operation should not be necessary, since Danger is already setup for the project.
|
||||||
|
|
||||||
|
To setup danger to the project, run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
bundle exec danger init
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run danger locally
|
||||||
|
|
||||||
|
When modifying the [dangerfile](../tools/danger/dangerfile.js), you can check it by running Danger locally.
|
||||||
|
|
||||||
|
To run danger locally, install it and run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
bundle exec danger pr <PR_URL> --dangerfile=./tools/danger/dangerfile.js
|
||||||
|
```
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
bundle exec danger pr https://github.com/vector-im/element-android/pull/6637 --dangerfile=./tools/danger/dangerfile.js
|
||||||
|
```
|
||||||
|
|
||||||
|
We may need to create a GitHub token to have less API rate limiting, and then set the env var:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
export DANGER_GITHUB_API_TOKEN='YOUR_TOKEN'
|
||||||
|
```
|
||||||
|
|
||||||
|
Swift and Kotlin (just in case)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
bundle exec danger-swift pr <PR_URL> --dangerfile=./tools/danger/dangerfile.js
|
||||||
|
bundle exec danger-kotlin pr <PR_URL> --dangerfile=./tools/danger/dangerfile.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Danger user
|
||||||
|
|
||||||
|
To let Danger check all the PRs, including PRs form forks, a GitHub account have been created:
|
||||||
|
- login: ElementBot
|
||||||
|
- password: Stored on Passbolt
|
||||||
|
- GitHub token: A token with limited access has been created and added to the repository https://github.com/vector-im/element-android as secret DANGER_GITHUB_API_TOKEN. This token is not saved anywhere else. In case of problem, just delete it and create a new one, then update the secret.
|
||||||
|
|
||||||
|
## Useful links
|
||||||
|
|
||||||
|
- https://danger.systems/
|
||||||
|
- https://danger.systems/js/
|
||||||
|
- https://danger.systems/js/guides/getting_started.html
|
||||||
|
- https://danger.systems/js/reference.html
|
||||||
|
- https://github.com/danger/awesome-danger
|
||||||
|
|
||||||
|
Some danger files to get inspired from
|
||||||
|
|
||||||
|
- https://github.com/artsy/emission/blob/master/dangerfile.ts
|
||||||
|
- https://github.com/facebook/react-native/blob/master/bots/dangerfile.js
|
||||||
|
- https://github.com/apollographql/apollo-client/blob/master/config/dangerfile.ts
|
||||||
|
- https://github.com/styleguidist/react-styleguidist/blob/master/dangerfile.js
|
||||||
|
- https://github.com/storybooks/storybook/blob/master/dangerfile.js
|
||||||
|
- https://github.com/ReactiveX/rxjs/blob/master/dangerfile.js
|
@ -48,7 +48,7 @@ mv towncrier.toml towncrier.toml.bak
|
|||||||
sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml
|
sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml
|
||||||
rm towncrier.toml.bak
|
rm towncrier.toml.bak
|
||||||
yes n | towncrier --version nightly
|
yes n | towncrier --version nightly
|
||||||
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES --stacktrace
|
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you can reset the change on the codebase.
|
Then you can reset the change on the codebase.
|
||||||
|
2
fastlane/metadata/android/en-US/changelogs/40104320.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40104320.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Main changes in this version: Various bug fixes and stability improvements.
|
||||||
|
Full changelog: https://github.com/vector-im/element-android/releases
|
@ -0,0 +1,4 @@
|
|||||||
|
<vector android:height="22dp" android:viewportHeight="22"
|
||||||
|
android:viewportWidth="22" android:width="22dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#737D8C" android:fillType="evenOdd" android:pathData="M16.999,14.899C18.07,13.407 18.7,11.577 18.7,9.6C18.7,4.574 14.626,0.5 9.6,0.5C4.574,0.5 0.5,4.574 0.5,9.6C0.5,14.626 4.574,18.7 9.6,18.7C11.577,18.7 13.406,18.07 14.899,16.999C14.941,17.055 14.988,17.109 15.039,17.161L18.939,21.061C19.525,21.646 20.475,21.646 21.06,21.061C21.646,20.475 21.646,19.525 21.06,18.939L17.16,15.039C17.109,14.988 17.055,14.941 16.999,14.899ZM15.7,9.6C15.7,12.969 12.969,15.7 9.6,15.7C6.231,15.7 3.5,12.969 3.5,9.6C3.5,6.231 6.231,3.5 9.6,3.5C12.969,3.5 15.7,6.231 15.7,9.6Z"/>
|
||||||
|
</vector>
|
@ -75,4 +75,7 @@
|
|||||||
<dimen name="location_sharing_compass_button_margin_horizontal">8dp</dimen>
|
<dimen name="location_sharing_compass_button_margin_horizontal">8dp</dimen>
|
||||||
<dimen name="location_sharing_live_duration_choice_margin_horizontal">12dp</dimen>
|
<dimen name="location_sharing_live_duration_choice_margin_horizontal">12dp</dimen>
|
||||||
<dimen name="location_sharing_live_duration_choice_margin_vertical">22dp</dimen>
|
<dimen name="location_sharing_live_duration_choice_margin_vertical">22dp</dimen>
|
||||||
|
|
||||||
|
<!-- Material 3 -->
|
||||||
|
<dimen name="collapsing_toolbar_layout_medium_size">112dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<declare-styleable name="LocationLiveEndedBannerView">
|
<declare-styleable name="LiveLocationEndedBannerView">
|
||||||
<attr name="locLiveEndedBkgWithAlpha" format="boolean" />
|
<attr name="locLiveEndedBkgWithAlpha" format="boolean" />
|
||||||
<attr name="locLiveEndedIconMarginStart" format="dimension" />
|
<attr name="locLiveEndedIconMarginStart" format="dimension" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<declare-styleable name="MapLoadingErrorView">
|
||||||
|
<attr name="mapErrorDescription" format="string" />
|
||||||
|
</declare-styleable>
|
||||||
|
|
||||||
|
</resources>
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Widget.Vector.Button.Text.OnPrimary.LocationLive">
|
<style name="Widget.Vector.Button.Text.OnPrimary.LiveLocation">
|
||||||
<item name="android:foreground">?selectableItemBackground</item>
|
<item name="android:foreground">?selectableItemBackground</item>
|
||||||
<item name="android:background">@android:color/transparent</item>
|
<item name="android:background">@android:color/transparent</item>
|
||||||
<item name="android:textSize">12sp</item>
|
<item name="android:textSize">12sp</item>
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<item name="android:insetLeft">8dp</item>
|
<item name="android:insetLeft">8dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.Vector.Button.Text.LocationLive">
|
<style name="Widget.Vector.Button.Text.LiveLocation">
|
||||||
<item name="android:foreground">?selectableItemBackground</item>
|
<item name="android:foreground">?selectableItemBackground</item>
|
||||||
<item name="android:background">@android:color/transparent</item>
|
<item name="android:background">@android:color/transparent</item>
|
||||||
<item name="android:textAppearance">@style/TextAppearance.Vector.Body.Medium</item>
|
<item name="android:textAppearance">@style/TextAppearance.Vector.Body.Medium</item>
|
||||||
|
@ -39,4 +39,14 @@
|
|||||||
<item name="android:textSize">13sp</item>
|
<item name="android:textSize">13sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- Material 3 -->
|
||||||
|
|
||||||
|
<style name="Widget.Vector.Material3.Toolbar" parent="Widget.Material3.Toolbar" />
|
||||||
|
|
||||||
|
<style name="Widget.Vector.Material3.CollapsingToolbar.Medium" parent="Widget.Material3.CollapsingToolbar.Medium">
|
||||||
|
<item name="expandedTitleTextAppearance">@style/TextAppearance.Vector.Title.Medium</item>
|
||||||
|
<item name="expandedTitleMarginBottom">20dp</item>
|
||||||
|
<item name="collapsedTitleTextAppearance">@style/TextAppearance.Vector.Headline.Bold</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -32,6 +32,15 @@
|
|||||||
<item name="android:textColor">?vctr_content_primary</item>
|
<item name="android:textColor">?vctr_content_primary</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="TextAppearance.Vector.Headline.Bold" parent="TextAppearance.MaterialComponents.Headline1">
|
||||||
|
<item name="fontFamily">sans-serif</item>
|
||||||
|
<item name="android:fontFamily">sans-serif</item>
|
||||||
|
<item name="android:textStyle">bold</item>
|
||||||
|
<item name="android:textSize">@dimen/text_size_headline</item>
|
||||||
|
<item name="android:letterSpacing">0</item>
|
||||||
|
<item name="android:textColor">?vctr_content_primary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Vector.Subtitle" parent="TextAppearance.MaterialComponents.Subtitle1">
|
<style name="TextAppearance.Vector.Subtitle" parent="TextAppearance.MaterialComponents.Subtitle1">
|
||||||
<item name="fontFamily">sans-serif</item>
|
<item name="fontFamily">sans-serif</item>
|
||||||
<item name="android:fontFamily">sans-serif</item>
|
<item name="android:fontFamily">sans-serif</item>
|
||||||
|
@ -189,6 +189,9 @@
|
|||||||
|
|
||||||
<!-- Location sharing -->
|
<!-- Location sharing -->
|
||||||
<item name="vctr_live_location">@color/vctr_live_location_dark</item>
|
<item name="vctr_live_location">@color/vctr_live_location_dark</item>
|
||||||
|
|
||||||
|
<!-- Material 3 -->
|
||||||
|
<item name="collapsingToolbarLayoutMediumSize">@dimen/collapsing_toolbar_layout_medium_size</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" />
|
<style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" />
|
||||||
|
@ -190,8 +190,12 @@
|
|||||||
|
|
||||||
<!-- Location sharing -->
|
<!-- Location sharing -->
|
||||||
<item name="vctr_live_location">@color/vctr_live_location_light</item>
|
<item name="vctr_live_location">@color/vctr_live_location_light</item>
|
||||||
|
|
||||||
|
<!-- Material 3 -->
|
||||||
|
<item name="collapsingToolbarLayoutMediumSize">@dimen/collapsing_toolbar_layout_medium_size</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" />
|
<style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" />
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ android {
|
|||||||
// that the app's state is completely cleared between tests.
|
// that the app's state is completely cleared between tests.
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||||
|
|
||||||
buildConfigField "String", "SDK_VERSION", "\"1.4.31\""
|
buildConfigField "String", "SDK_VERSION", "\"1.4.32\""
|
||||||
|
|
||||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||||
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
||||||
@ -199,7 +199,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.52'
|
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.53'
|
||||||
|
|
||||||
testImplementation libs.tests.junit
|
testImplementation libs.tests.junit
|
||||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||||
|
@ -62,7 +62,10 @@ fun Throwable.isUsernameInUse() = this is Failure.ServerError &&
|
|||||||
error.code == MatrixError.M_USER_IN_USE
|
error.code == MatrixError.M_USER_IN_USE
|
||||||
|
|
||||||
fun Throwable.isInvalidUsername() = this is Failure.ServerError &&
|
fun Throwable.isInvalidUsername() = this is Failure.ServerError &&
|
||||||
error.code == MatrixError.M_INVALID_USERNAME
|
(error.code == MatrixError.M_INVALID_USERNAME || usernameContainsNonAsciiCharacters())
|
||||||
|
|
||||||
|
private fun Failure.ServerError.usernameContainsNonAsciiCharacters() = error.code == MatrixError.M_UNKNOWN &&
|
||||||
|
error.message == "Query parameter \'username\' must be ascii"
|
||||||
|
|
||||||
fun Throwable.isInvalidPassword() = this is Failure.ServerError &&
|
fun Throwable.isInvalidPassword() = this is Failure.ServerError &&
|
||||||
error.code == MatrixError.M_FORBIDDEN &&
|
error.code == MatrixError.M_FORBIDDEN &&
|
||||||
|
@ -76,7 +76,7 @@ object StringOrderUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun stringToBase(x: String, alphabet: CharArray): BigInteger {
|
fun stringToBase(x: String, alphabet: CharArray): BigInteger {
|
||||||
if (x.isEmpty()) throw IllegalArgumentException()
|
require(x.isNotEmpty())
|
||||||
val len = alphabet.size.toBigInteger()
|
val len = alphabet.size.toBigInteger()
|
||||||
var result = BigInteger("0")
|
var result = BigInteger("0")
|
||||||
x.reversed().forEachIndexed { index, c ->
|
x.reversed().forEachIndexed { index, c ->
|
||||||
|
@ -535,7 +535,7 @@ internal class MXMegolmEncryption(
|
|||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
override suspend fun shareHistoryKeysWithDevice(inboundSessionWrapper: InboundGroupSessionHolder, deviceInfo: CryptoDeviceInfo) {
|
override suspend fun shareHistoryKeysWithDevice(inboundSessionWrapper: InboundGroupSessionHolder, deviceInfo: CryptoDeviceInfo) {
|
||||||
if (!inboundSessionWrapper.wrapper.sessionData.sharedHistory) throw IllegalArgumentException("This key can't be shared")
|
require(inboundSessionWrapper.wrapper.sessionData.sharedHistory) { "This key can't be shared" }
|
||||||
Timber.tag(loggerTag.value).i("process shareHistoryKeys for ${inboundSessionWrapper.wrapper.safeSessionId} to ${deviceInfo.shortDebugString()}")
|
Timber.tag(loggerTag.value).i("process shareHistoryKeys for ${inboundSessionWrapper.wrapper.safeSessionId} to ${deviceInfo.shortDebugString()}")
|
||||||
val userId = deviceInfo.userId
|
val userId = deviceInfo.userId
|
||||||
val deviceId = deviceInfo.deviceId
|
val deviceId = deviceInfo.deviceId
|
||||||
|
@ -119,7 +119,7 @@ internal class EventSenderProcessorThread @Inject constructor(
|
|||||||
|
|
||||||
override fun cancel(eventId: String, roomId: String) {
|
override fun cancel(eventId: String, roomId: String) {
|
||||||
(currentTask as? SendEventQueuedTask)
|
(currentTask as? SendEventQueuedTask)
|
||||||
?.takeIf { it -> it.event.eventId == eventId && it.event.roomId == roomId }
|
?.takeIf { it.event.eventId == eventId && it.event.roomId == roomId }
|
||||||
?.cancel()
|
?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
tools/danger/dangerfile-lint.js
Normal file
29
tools/danger/dangerfile-lint.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { schedule } from 'danger'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref and documentation: https://github.com/damian-burke/danger-plugin-lint-report
|
||||||
|
* This file will check all the error in XML Checkstyle format.
|
||||||
|
* It covers, lint, ktlint, and detekt errors
|
||||||
|
*/
|
||||||
|
|
||||||
|
const reporter = require("danger-plugin-lint-report")
|
||||||
|
schedule(reporter.scan({
|
||||||
|
/**
|
||||||
|
* File mask used to find XML checkstyle reports.
|
||||||
|
*/
|
||||||
|
fileMask: "**/reports/**/**.xml",
|
||||||
|
/**
|
||||||
|
* If set to true, the severity will be used to switch between the different message formats (message, warn, fail).
|
||||||
|
*/
|
||||||
|
reportSeverity: true,
|
||||||
|
/**
|
||||||
|
* If set to true, only issues will be reported that are contained in the current changeset (line comparison).
|
||||||
|
* If set to false, all issues that are in modified files will be reported.
|
||||||
|
*/
|
||||||
|
requireLineModification: false,
|
||||||
|
/**
|
||||||
|
* Optional: Sets a prefix foreach violation message.
|
||||||
|
* This can be useful if there are multiple reports being parsed to make them distinguishable.
|
||||||
|
*/
|
||||||
|
// outputPrefix?: ""
|
||||||
|
}))
|
107
tools/danger/dangerfile.js
Normal file
107
tools/danger/dangerfile.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
const {danger, warn} = require('danger')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: if you update the checks in this file, please also update the file ./docs/danger.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Useful to see what we got in danger object
|
||||||
|
// warn(JSON.stringify(danger))
|
||||||
|
|
||||||
|
const pr = danger.github.pr
|
||||||
|
const github = danger.github
|
||||||
|
// User who has created the PR.
|
||||||
|
const user = pr.user.login
|
||||||
|
const modified = danger.git.modified_files
|
||||||
|
const created = danger.git.created_files
|
||||||
|
const editedFiles = [...modified, ...created]
|
||||||
|
|
||||||
|
// Check that the PR has a description
|
||||||
|
if (pr.body.length == 0) {
|
||||||
|
warn("Please provide a description for this PR.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn when there is a big PR
|
||||||
|
if (editedFiles.length > 50) {
|
||||||
|
message("This pull request seems relatively large. Please consider splitting it into multiple smaller ones.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request a changelog for each PR
|
||||||
|
const changelogAllowList = [
|
||||||
|
"dependabot[bot]",
|
||||||
|
]
|
||||||
|
|
||||||
|
const requiresChangelog = !changelogAllowList.includes(user)
|
||||||
|
|
||||||
|
if (requiresChangelog) {
|
||||||
|
const changelogFiles = editedFiles.filter(file => file.startsWith("changelog.d/"))
|
||||||
|
|
||||||
|
if (changelogFiles.length == 0) {
|
||||||
|
warn("Please add a changelog. See instructions [here](https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md#changelog)")
|
||||||
|
} else {
|
||||||
|
const validTowncrierExtensions = [
|
||||||
|
"bugfix",
|
||||||
|
"doc",
|
||||||
|
"feature",
|
||||||
|
"misc",
|
||||||
|
"sdk",
|
||||||
|
"wip",
|
||||||
|
]
|
||||||
|
if (!changelogFiles.every(file => validTowncrierExtensions.includes(file.split(".").pop()))) {
|
||||||
|
fail("Invalid extension for changelog. See instructions [here](https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md#changelog)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a sign-off
|
||||||
|
const signOff = "Signed-off-by:"
|
||||||
|
|
||||||
|
// Please add new names following the alphabetical order.
|
||||||
|
const allowList = [
|
||||||
|
"aringenbach",
|
||||||
|
"BillCarsonFr",
|
||||||
|
"bmarty",
|
||||||
|
"Claire1817",
|
||||||
|
"dependabot[bot]",
|
||||||
|
"ericdecanini",
|
||||||
|
"fedrunov",
|
||||||
|
"Florian14",
|
||||||
|
"ganfra",
|
||||||
|
"jmartinesp",
|
||||||
|
"langleyd",
|
||||||
|
"MadLittleMods",
|
||||||
|
"manuroe",
|
||||||
|
"mnaturel",
|
||||||
|
"onurays",
|
||||||
|
"ouchadam",
|
||||||
|
"stefanceriu",
|
||||||
|
"yostyle",
|
||||||
|
]
|
||||||
|
|
||||||
|
const requiresSignOff = !allowList.includes(user)
|
||||||
|
|
||||||
|
if (requiresSignOff) {
|
||||||
|
const hasPRBodySignOff = pr.body.includes(signOff)
|
||||||
|
const hasCommitSignOff = danger.git.commits.every(commit => commit.message.includes(signOff))
|
||||||
|
if (!hasPRBodySignOff && !hasCommitSignOff) {
|
||||||
|
fail("Please add a sign-off to either the PR description or to the commits themselves. See instructions [here](https://matrix-org.github.io/synapse/latest/development/contributing_guide.html#sign-off).")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for screenshots on view changes
|
||||||
|
const hasChangedViews = editedFiles.filter(file => file.includes("/layout")).length > 0
|
||||||
|
if (hasChangedViews) {
|
||||||
|
if (!pr.body.includes("user-images")) {
|
||||||
|
warn("You seem to have made changes to views. Please consider adding screenshots.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for pngs on resources
|
||||||
|
const hasPngs = editedFiles.filter(file => file.toLowerCase().endsWith(".png")).length > 0
|
||||||
|
if (hasPngs) {
|
||||||
|
warn("You seem to have made changes to some images. Please consider using an vector drawable.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for reviewers
|
||||||
|
if (github.requested_reviewers.users.length == 0 && !pr.draft) {
|
||||||
|
warn("Please add a reviewer to your PR.")
|
||||||
|
}
|
@ -23,6 +23,8 @@ style:
|
|||||||
active: false
|
active: false
|
||||||
ProtectedMemberInFinalClass:
|
ProtectedMemberInFinalClass:
|
||||||
active: false
|
active: false
|
||||||
|
UseCheckOrError:
|
||||||
|
active: false
|
||||||
|
|
||||||
empty-blocks:
|
empty-blocks:
|
||||||
EmptyFunctionBlock:
|
EmptyFunctionBlock:
|
||||||
@ -43,6 +45,8 @@ exceptions:
|
|||||||
active: false
|
active: false
|
||||||
TooGenericExceptionThrown:
|
TooGenericExceptionThrown:
|
||||||
active: false
|
active: false
|
||||||
|
InstanceOfCheckForException:
|
||||||
|
active: false
|
||||||
|
|
||||||
complexity:
|
complexity:
|
||||||
TooManyFunctions:
|
TooManyFunctions:
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
#
|
|
||||||
# Copyright 2018 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.
|
|
||||||
#
|
|
||||||
|
|
||||||
branch=${TRAVIS_BRANCH}
|
|
||||||
|
|
||||||
# echo ${TRAVIS_BRANCH}
|
|
||||||
|
|
||||||
# If not on develop, exit, else we cannot get the list of modified files
|
|
||||||
# It is ok to check only when on develop branch
|
|
||||||
if [[ "${branch}" -eq 'develop' ]]; then
|
|
||||||
echo "Check that a file has been added to /changelog.d"
|
|
||||||
else
|
|
||||||
echo "Not on develop branch"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# git status
|
|
||||||
|
|
||||||
listOfModifiedFiles=`git diff --name-only HEAD ${branch}`
|
|
||||||
|
|
||||||
# echo "List of modified files by this PR:"
|
|
||||||
# echo ${listOfModifiedFiles}
|
|
||||||
|
|
||||||
|
|
||||||
if [[ ${listOfModifiedFiles} = *"changelog.d"* ]]; then
|
|
||||||
echo "A file has been added to /changelog.d!"
|
|
||||||
else
|
|
||||||
echo "❌ Please add a file describing your changes in /changelog.d. See https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md#changelog"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.config
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The types of analytics Element currently supports.
|
||||||
|
*/
|
||||||
|
sealed interface Analytics {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the analytics integrations.
|
||||||
|
*/
|
||||||
|
object Disabled : Analytics
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analytics integration via PostHog.
|
||||||
|
*/
|
||||||
|
data class PostHog(
|
||||||
|
/**
|
||||||
|
* The PostHog instance url.
|
||||||
|
*/
|
||||||
|
val postHogHost: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The PostHog instance API key.
|
||||||
|
*/
|
||||||
|
val postHogApiKey: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A URL to more information about the analytics collection.
|
||||||
|
*/
|
||||||
|
val policyLink: String,
|
||||||
|
) : Analytics
|
||||||
|
}
|
@ -36,4 +36,57 @@ object Config {
|
|||||||
* - Changing the value from `true` to `false` will force the app to return to the background sync / Firebase Push.
|
* - Changing the value from `true` to `false` will force the app to return to the background sync / Firebase Push.
|
||||||
*/
|
*/
|
||||||
const val ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS = true
|
const val ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS = true
|
||||||
|
|
||||||
|
const val ENABLE_LOCATION_SHARING = true
|
||||||
|
const val LOCATION_MAP_TILER_KEY = "fU3vlMsMn4Jb6dnEIFsx"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum length of voice messages in milliseconds.
|
||||||
|
*/
|
||||||
|
const val VOICE_MESSAGE_LIMIT_MS = 120_000L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The strategy for sharing device keys.
|
||||||
|
*/
|
||||||
|
val KEY_SHARING_STRATEGY = KeySharingStrategy.WhenTyping
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The onboarding flow.
|
||||||
|
*/
|
||||||
|
val ONBOARDING_VARIANT = OnboardingVariant.LEGACY
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, MSC3086 asserted identity messages sent on VoIP calls will cause the call to appear in the room corresponding to the asserted identity.
|
||||||
|
* This *must* only be set in trusted environments.
|
||||||
|
*/
|
||||||
|
const val HANDLE_CALL_ASSERTED_IDENTITY_EVENTS = false
|
||||||
|
|
||||||
|
const val LOW_PRIVACY_LOG_ENABLE = false
|
||||||
|
const val ENABLE_STRICT_MODE_LOGS = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The analytics configuration to use for the Debug build type.
|
||||||
|
* Can be disabled by providing Analytics.Disabled
|
||||||
|
*/
|
||||||
|
val DEBUG_ANALYTICS_CONFIG = Analytics.PostHog(
|
||||||
|
postHogHost = "https://posthog.element.dev",
|
||||||
|
postHogApiKey = "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN",
|
||||||
|
policyLink = "https://element.io/cookie-policy",
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The analytics configuration to use for the Release build type.
|
||||||
|
* Can be disabled by providing Analytics.Disabled
|
||||||
|
*/
|
||||||
|
val RELEASE_ANALYTICS_CONFIG = Analytics.PostHog(
|
||||||
|
postHogHost = "https://posthog.hss.element.io",
|
||||||
|
postHogApiKey = "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO",
|
||||||
|
policyLink = "https://element.io/cookie-policy",
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The analytics configuration to use for the Nightly build type.
|
||||||
|
* Can be disabled by providing Analytics.Disabled
|
||||||
|
*/
|
||||||
|
val NIGHTLY_ANALYTICS_CONFIG = RELEASE_ANALYTICS_CONFIG
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2021 New Vector Ltd
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,17 +16,20 @@
|
|||||||
|
|
||||||
package im.vector.app.config
|
package im.vector.app.config
|
||||||
|
|
||||||
import im.vector.app.BuildConfig
|
enum class KeySharingStrategy {
|
||||||
import im.vector.app.features.analytics.AnalyticsConfig
|
/**
|
||||||
|
* Keys will be sent for the first time when the first message is sent.
|
||||||
|
* This is handled by the Matrix SDK so there's no need to do it in Vector.
|
||||||
|
*/
|
||||||
|
WhenSendingEvent,
|
||||||
|
|
||||||
private val allowedPackageList = listOf(
|
/**
|
||||||
"im.vector.app",
|
* Keys will be sent for the first time when the timeline displayed.
|
||||||
"im.vector.app.nightly",
|
*/
|
||||||
)
|
WhenEnteringRoom,
|
||||||
|
|
||||||
val analyticsConfig: AnalyticsConfig = object : AnalyticsConfig {
|
/**
|
||||||
override val isEnabled = BuildConfig.APPLICATION_ID in allowedPackageList
|
* Keys will be sent for the first time when a typing started.
|
||||||
override val postHogHost = "https://posthog.hss.element.io"
|
*/
|
||||||
override val postHogApiKey = "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO"
|
WhenTyping
|
||||||
override val policyLink = "https://element.io/cookie-policy"
|
|
||||||
}
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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.config
|
||||||
|
|
||||||
|
enum class OnboardingVariant {
|
||||||
|
LEGACY,
|
||||||
|
LOGIN_2,
|
||||||
|
FTUE_AUTH
|
||||||
|
}
|
@ -27,6 +27,7 @@ knit {
|
|||||||
exclude '**/.gradle/**'
|
exclude '**/.gradle/**'
|
||||||
exclude '**/towncrier/template.md'
|
exclude '**/towncrier/template.md'
|
||||||
exclude '**/CHANGES.md'
|
exclude '**/CHANGES.md'
|
||||||
|
exclude '/node_modules'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +37,7 @@ ext.versionMinor = 4
|
|||||||
// Note: even values are reserved for regular release, odd values for hotfix release.
|
// Note: even values are reserved for regular release, odd values for hotfix release.
|
||||||
// When creating a hotfix, you should decrease the value, since the current value
|
// When creating a hotfix, you should decrease the value, since the current value
|
||||||
// is the value for the next regular release.
|
// is the value for the next regular release.
|
||||||
ext.versionPatch = 31
|
ext.versionPatch = 32
|
||||||
|
|
||||||
ext.scVersion = 56
|
ext.scVersion = 56
|
||||||
|
|
||||||
@ -157,19 +158,6 @@ android {
|
|||||||
buildConfigField "String", "GIT_BRANCH_NAME", "\"${gitBranchName()}\""
|
buildConfigField "String", "GIT_BRANCH_NAME", "\"${gitBranchName()}\""
|
||||||
buildConfigField "String", "BUILD_NUMBER", "\"${buildNumber}\""
|
buildConfigField "String", "BUILD_NUMBER", "\"${buildNumber}\""
|
||||||
|
|
||||||
buildConfigField "im.vector.app.features.VectorFeatures.OnboardingVariant", "ONBOARDING_VARIANT", "im.vector.app.features.VectorFeatures.OnboardingVariant.LEGACY"
|
|
||||||
|
|
||||||
buildConfigField "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy", "outboundSessionKeySharingStrategy", "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy.WhenTyping"
|
|
||||||
|
|
||||||
buildConfigField "Long", "VOICE_MESSAGE_DURATION_LIMIT_MS", "120_000L"
|
|
||||||
|
|
||||||
// If set, MSC3086 asserted identity messages sent on VoIP calls will cause the call to appear in the room corresponding to the asserted identity.
|
|
||||||
// This *must* only be set in trusted environments.
|
|
||||||
buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false"
|
|
||||||
|
|
||||||
buildConfigField "Boolean", "enableLocationSharing", "true"
|
|
||||||
buildConfigField "String", "mapTilerKey", "\"fU3vlMsMn4Jb6dnEIFsx\""
|
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
// Keep abiFilter for the universalApk
|
// Keep abiFilter for the universalApk
|
||||||
@ -253,10 +241,6 @@ android {
|
|||||||
resValue "string", "app_name", "SchildiChat dbg"
|
resValue "string", "app_name", "SchildiChat dbg"
|
||||||
resValue "color", "launcher_background", "#0DBD8B"
|
resValue "color", "launcher_background", "#0DBD8B"
|
||||||
|
|
||||||
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
|
|
||||||
// Set to true if you want to enable strict mode in debug
|
|
||||||
buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false"
|
|
||||||
|
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
|
|
||||||
if (project.hasProperty("coverage")) {
|
if (project.hasProperty("coverage")) {
|
||||||
@ -268,10 +252,6 @@ android {
|
|||||||
resValue "string", "app_name", "SchildiChat"
|
resValue "string", "app_name", "SchildiChat"
|
||||||
resValue "color", "launcher_background", "#0DBD8B"
|
resValue "color", "launcher_background", "#0DBD8B"
|
||||||
|
|
||||||
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
|
|
||||||
buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false"
|
|
||||||
|
|
||||||
// When updating this block, please also update the same block in the `nightly` buildType below
|
|
||||||
postprocessing {
|
postprocessing {
|
||||||
removeUnusedCode true
|
removeUnusedCode true
|
||||||
removeUnusedResources true
|
removeUnusedResources true
|
||||||
@ -333,8 +313,6 @@ android {
|
|||||||
versionName "1.4.31.sc56"
|
versionName "1.4.31.sc56"
|
||||||
|
|
||||||
resValue "bool", "isGplay", "true"
|
resValue "bool", "isGplay", "true"
|
||||||
buildConfigField "boolean", "ALLOW_FCM_USE", "true"
|
|
||||||
buildConfigField "boolean", "ALLOW_EXTERNAL_UNIFIEDPUSH_DISTRIB", "true"
|
|
||||||
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\""
|
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\""
|
||||||
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"GooglePlay\""
|
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"GooglePlay\""
|
||||||
}
|
}
|
||||||
@ -345,8 +323,6 @@ android {
|
|||||||
versionName "1.4.31.sc56"
|
versionName "1.4.31.sc56"
|
||||||
|
|
||||||
resValue "bool", "isGplay", "false"
|
resValue "bool", "isGplay", "false"
|
||||||
buildConfigField "boolean", "ALLOW_FCM_USE", "false"
|
|
||||||
buildConfigField "boolean", "ALLOW_EXTERNAL_UNIFIEDPUSH_DISTRIB", "true"
|
|
||||||
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\""
|
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\""
|
||||||
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"FDroid\""
|
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"FDroid\""
|
||||||
}
|
}
|
||||||
@ -450,7 +426,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.52'
|
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.53'
|
||||||
|
|
||||||
// FlowBinding
|
// FlowBinding
|
||||||
implementation libs.github.flowBinding
|
implementation libs.github.flowBinding
|
||||||
@ -462,6 +438,9 @@ dependencies {
|
|||||||
implementation libs.airbnb.epoxyPaging
|
implementation libs.airbnb.epoxyPaging
|
||||||
implementation libs.airbnb.mavericks
|
implementation libs.airbnb.mavericks
|
||||||
|
|
||||||
|
// Snap Helper https://github.com/rubensousa/GravitySnapHelper
|
||||||
|
implementation 'com.github.rubensousa:gravitysnaphelper:2.2.2'
|
||||||
|
|
||||||
// Nightly
|
// Nightly
|
||||||
// API-only library
|
// API-only library
|
||||||
gplayImplementation libs.google.appdistributionApi
|
gplayImplementation libs.google.appdistributionApi
|
||||||
@ -634,4 +613,5 @@ dependencies {
|
|||||||
androidTestImplementation libs.mockk.mockkAndroid
|
androidTestImplementation libs.mockk.mockkAndroid
|
||||||
androidTestUtil libs.androidx.orchestrator
|
androidTestUtil libs.androidx.orchestrator
|
||||||
debugImplementation libs.androidx.fragmentTesting
|
debugImplementation libs.androidx.fragmentTesting
|
||||||
|
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.10"
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,7 @@
|
|||||||
<!-- Wording -->
|
<!-- Wording -->
|
||||||
<issue id="Typos" severity="error" />
|
<issue id="Typos" severity="error" />
|
||||||
<issue id="TypographyDashes" severity="error" />
|
<issue id="TypographyDashes" severity="error" />
|
||||||
|
<issue id="PluralsCandidate" severity="error" />
|
||||||
|
|
||||||
<!-- DI -->
|
<!-- DI -->
|
||||||
<issue id="JvmStaticProvidesInObjectDetector" severity="error" />
|
<issue id="JvmStaticProvidesInObjectDetector" severity="error" />
|
||||||
|
@ -27,6 +27,7 @@ import im.vector.app.features.MainActivity
|
|||||||
import im.vector.app.ui.robot.ElementRobot
|
import im.vector.app.ui.robot.ElementRobot
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.rules.RuleChain
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@ -35,7 +36,9 @@ import java.util.UUID
|
|||||||
class CantVerifyTest : VerificationTestBase() {
|
class CantVerifyTest : VerificationTestBase() {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val activityRule = ActivityScenarioRule(MainActivity::class.java)
|
val testRule = RuleChain
|
||||||
|
.outerRule(ActivityScenarioRule(MainActivity::class.java))
|
||||||
|
.around(ClearCurrentSessionRule())
|
||||||
|
|
||||||
private val elementRobot = ElementRobot()
|
private val elementRobot = ElementRobot()
|
||||||
var userName: String = "loginTest_${UUID.randomUUID()}"
|
var userName: String = "loginTest_${UUID.randomUUID()}"
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import im.vector.app.features.analytics.store.AnalyticsStore
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.rules.TestWatcher
|
||||||
|
import org.junit.runner.Description
|
||||||
|
import org.junit.runners.model.Statement
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A TestRule to reset and clear the current Session.
|
||||||
|
* If a Session is active it will be signed out and cleared from the ActiveSessionHolder.
|
||||||
|
* The VectorPreferences and AnalyticsDatastore are also cleared in an attempt to recreate a fresh base.
|
||||||
|
*/
|
||||||
|
class ClearCurrentSessionRule : TestWatcher() {
|
||||||
|
override fun apply(base: Statement, description: Description): Statement {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
runBlocking {
|
||||||
|
reflectAnalyticDatastore(context).edit { it.clear() }
|
||||||
|
runCatching {
|
||||||
|
val holder = (context.applicationContext as VectorApplication).activeSessionHolder
|
||||||
|
holder.getSafeActiveSession()?.signOutService()?.signOut(true)
|
||||||
|
(context.applicationContext as VectorApplication).vectorPreferences.clearPreferences()
|
||||||
|
holder.clearActiveSession()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.apply(base, description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KClass<*>.asTopLevel() = Class.forName("${qualifiedName}Kt")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the top level, private [Context.dataStore] extension property from [im.vector.app.features.analytics.store.AnalyticsStore]
|
||||||
|
* via reflection to avoid exposing property to all callers.
|
||||||
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun reflectAnalyticDatastore(context: Context): DataStore<Preferences> {
|
||||||
|
val klass = AnalyticsStore::class.asTopLevel()
|
||||||
|
val method = klass.getMethod("access\$getDataStore", Context::class.java)
|
||||||
|
return method.invoke(klass, context) as DataStore<Preferences>
|
||||||
|
}
|
@ -31,7 +31,6 @@ import androidx.test.filters.SdkSuppress
|
|||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import im.vector.app.TestBuildVersionSdkIntProvider
|
import im.vector.app.TestBuildVersionSdkIntProvider
|
||||||
import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration
|
import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration
|
||||||
import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider
|
|
||||||
import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode
|
import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode
|
||||||
import im.vector.app.features.pin.lockscreen.crypto.LockScreenCryptoConstants
|
import im.vector.app.features.pin.lockscreen.crypto.LockScreenCryptoConstants
|
||||||
import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository
|
import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository
|
||||||
@ -40,6 +39,7 @@ import im.vector.app.features.pin.lockscreen.ui.fallbackprompt.FallbackBiometric
|
|||||||
import im.vector.app.features.pin.lockscreen.utils.DevicePromptCheck
|
import im.vector.app.features.pin.lockscreen.utils.DevicePromptCheck
|
||||||
import io.mockk.clearAllMocks
|
import io.mockk.clearAllMocks
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
import io.mockk.justRun
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.mockkObject
|
import io.mockk.mockkObject
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
@ -54,8 +54,10 @@ import kotlinx.coroutines.flow.flowOf
|
|||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.coInvoking
|
||||||
import org.amshove.kluent.shouldBeFalse
|
import org.amshove.kluent.shouldBeFalse
|
||||||
import org.amshove.kluent.shouldBeTrue
|
import org.amshove.kluent.shouldBeTrue
|
||||||
|
import org.amshove.kluent.shouldThrow
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -239,36 +241,35 @@ class BiometricHelperTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R) // Due to some issues with mockk and CryptoObject initialization
|
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R) // Due to some issues with mockk and CryptoObject initialization
|
||||||
fun authenticateCreatesSystemKeyIfNeededOnSuccessOnAndroidM() = runTest {
|
fun enableAuthenticationDeletesSystemKeyOnFailure() = runTest {
|
||||||
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
|
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
|
||||||
every { lockScreenKeyRepository.isSystemKeyValid() } returns true
|
|
||||||
val mockAuthChannel = Channel<Boolean>(capacity = 1)
|
val mockAuthChannel = Channel<Boolean>(capacity = 1)
|
||||||
val biometricUtils = spyk(createBiometricHelper(createDefaultConfiguration(isBiometricsEnabled = true))) {
|
val biometricUtils = spyk(createBiometricHelper(createDefaultConfiguration(isBiometricsEnabled = true))) {
|
||||||
every { createAuthChannel() } returns mockAuthChannel
|
every { createAuthChannel() } returns mockAuthChannel
|
||||||
every { authenticateWithPromptInternal(any(), any(), any()) } returns mockk()
|
every { authenticateWithPromptInternal(any(), any(), any()) } returns mockk()
|
||||||
}
|
}
|
||||||
|
justRun { lockScreenKeyRepository.deleteSystemKey() }
|
||||||
|
|
||||||
val latch = CountDownLatch(1)
|
val latch = CountDownLatch(1)
|
||||||
val intent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, LockScreenTestActivity::class.java)
|
val intent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, LockScreenTestActivity::class.java)
|
||||||
ActivityScenario.launch<LockScreenTestActivity>(intent).onActivity { activity ->
|
ActivityScenario.launch<LockScreenTestActivity>(intent).onActivity { activity ->
|
||||||
activity.lifecycleScope.launch {
|
activity.lifecycleScope.launch {
|
||||||
|
val exception = IllegalStateException("Some error")
|
||||||
launch {
|
launch {
|
||||||
mockAuthChannel.send(true)
|
mockAuthChannel.close(exception)
|
||||||
mockAuthChannel.close()
|
|
||||||
}
|
}
|
||||||
biometricUtils.authenticate(activity).collect()
|
coInvoking { biometricUtils.enableAuthentication(activity).collect() } shouldThrow exception
|
||||||
latch.countDown()
|
latch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
latch.await(1, TimeUnit.SECONDS)
|
latch.await(1, TimeUnit.SECONDS)
|
||||||
verify { lockScreenKeyRepository.ensureSystemKey() }
|
verify { lockScreenKeyRepository.deleteSystemKey() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createBiometricHelper(configuration: LockScreenConfiguration): BiometricHelper {
|
private fun createBiometricHelper(configuration: LockScreenConfiguration): BiometricHelper {
|
||||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
val configProvider = LockScreenConfiguratorProvider(configuration)
|
return BiometricHelper(configuration, context, lockScreenKeyRepository, biometricManager, buildVersionSdkIntProvider)
|
||||||
return BiometricHelper(context, lockScreenKeyRepository, configProvider, biometricManager, buildVersionSdkIntProvider)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDefaultConfiguration(
|
private fun createDefaultConfiguration(
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.app.features.pin.lockscreen.crypto
|
|||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.security.keystore.KeyPermanentlyInvalidatedException
|
import android.security.keystore.KeyPermanentlyInvalidatedException
|
||||||
|
import android.security.keystore.UserNotAuthenticatedException
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import im.vector.app.TestBuildVersionSdkIntProvider
|
import im.vector.app.TestBuildVersionSdkIntProvider
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
@ -69,10 +70,12 @@ class KeyStoreCryptoTests {
|
|||||||
runCatching { keyStoreCrypto.ensureKey() }
|
runCatching { keyStoreCrypto.ensureKey() }
|
||||||
keyStoreCrypto.hasValidKey() shouldBe true
|
keyStoreCrypto.hasValidKey() shouldBe true
|
||||||
|
|
||||||
val exception = KeyPermanentlyInvalidatedException()
|
val keyInvalidatedException = KeyPermanentlyInvalidatedException()
|
||||||
every { secretStoringUtils.getEncryptCipher(any()) } throws exception
|
every { secretStoringUtils.getEncryptCipher(any()) } throws keyInvalidatedException
|
||||||
|
keyStoreCrypto.hasValidKey() shouldBe false
|
||||||
|
|
||||||
runCatching { keyStoreCrypto.ensureKey() }
|
val userNotAuthenticatedException = UserNotAuthenticatedException()
|
||||||
|
every { secretStoringUtils.getEncryptCipher(any()) } throws userNotAuthenticatedException
|
||||||
keyStoreCrypto.hasValidKey() shouldBe false
|
keyStoreCrypto.hasValidKey() shouldBe false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
package im.vector.app.features.pin.lockscreen.crypto
|
package im.vector.app.features.pin.lockscreen.crypto
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import im.vector.app.features.pin.lockscreen.crypto.migrations.LegacyPinCodeMigrator
|
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
|
||||||
import io.mockk.clearAllMocks
|
import io.mockk.clearAllMocks
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
@ -44,8 +42,6 @@ class LockScreenKeyRepositoryTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var lockScreenKeyRepository: LockScreenKeyRepository
|
private lateinit var lockScreenKeyRepository: LockScreenKeyRepository
|
||||||
private val legacyPinCodeMigrator: LegacyPinCodeMigrator = mockk(relaxed = true)
|
|
||||||
private val vectorPreferences: VectorPreferences = mockk(relaxed = true)
|
|
||||||
|
|
||||||
private val keyStore: KeyStore by lazy {
|
private val keyStore: KeyStore by lazy {
|
||||||
KeyStore.getInstance(LockScreenCryptoConstants.ANDROID_KEY_STORE).also { it.load(null) }
|
KeyStore.getInstance(LockScreenCryptoConstants.ANDROID_KEY_STORE).also { it.load(null) }
|
||||||
|
@ -70,6 +70,11 @@ class DebugFeaturesStateFactory @Inject constructor(
|
|||||||
key = DebugFeatureKeys.allowExternalUnifiedPushDistributors,
|
key = DebugFeatureKeys.allowExternalUnifiedPushDistributors,
|
||||||
factory = VectorFeatures::allowExternalUnifiedPushDistributors
|
factory = VectorFeatures::allowExternalUnifiedPushDistributors
|
||||||
),
|
),
|
||||||
|
createBooleanFeature(
|
||||||
|
label = "Enable Live Location Sharing",
|
||||||
|
key = DebugFeatureKeys.liveLocationSharing,
|
||||||
|
factory = VectorFeatures::isLocationSharingEnabled
|
||||||
|
),
|
||||||
createBooleanFeature(
|
createBooleanFeature(
|
||||||
label = "Force usage of OpusEncoder library",
|
label = "Force usage of OpusEncoder library",
|
||||||
key = DebugFeatureKeys.forceUsageOfOpusEncoder,
|
key = DebugFeatureKeys.forceUsageOfOpusEncoder,
|
||||||
|
@ -24,6 +24,7 @@ import androidx.datastore.preferences.core.booleanPreferencesKey
|
|||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import im.vector.app.config.OnboardingVariant
|
||||||
import im.vector.app.features.DefaultVectorFeatures
|
import im.vector.app.features.DefaultVectorFeatures
|
||||||
import im.vector.app.features.VectorFeatures
|
import im.vector.app.features.VectorFeatures
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
@ -39,8 +40,8 @@ class DebugVectorFeatures(
|
|||||||
|
|
||||||
private val dataStore = context.dataStore
|
private val dataStore = context.dataStore
|
||||||
|
|
||||||
override fun onboardingVariant(): VectorFeatures.OnboardingVariant {
|
override fun onboardingVariant(): OnboardingVariant {
|
||||||
return readPreferences().getEnum<VectorFeatures.OnboardingVariant>() ?: vectorFeatures.onboardingVariant()
|
return readPreferences().getEnum<OnboardingVariant>() ?: vectorFeatures.onboardingVariant()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean = read(DebugFeatureKeys.onboardingAlreadyHaveAnAccount)
|
override fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean = read(DebugFeatureKeys.onboardingAlreadyHaveAnAccount)
|
||||||
@ -66,6 +67,9 @@ class DebugVectorFeatures(
|
|||||||
override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing)
|
override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing)
|
||||||
?: vectorFeatures.isScreenSharingEnabled()
|
?: vectorFeatures.isScreenSharingEnabled()
|
||||||
|
|
||||||
|
override fun isLocationSharingEnabled(): Boolean = read(DebugFeatureKeys.liveLocationSharing)
|
||||||
|
?: vectorFeatures.isLocationSharingEnabled()
|
||||||
|
|
||||||
override fun forceUsageOfOpusEncoder(): Boolean = read(DebugFeatureKeys.forceUsageOfOpusEncoder)
|
override fun forceUsageOfOpusEncoder(): Boolean = read(DebugFeatureKeys.forceUsageOfOpusEncoder)
|
||||||
?: vectorFeatures.forceUsageOfOpusEncoder()
|
?: vectorFeatures.forceUsageOfOpusEncoder()
|
||||||
|
|
||||||
|
@ -351,7 +351,7 @@
|
|||||||
<activity android:name=".features.spaces.leave.SpaceLeaveAdvancedActivity" />
|
<activity android:name=".features.spaces.leave.SpaceLeaveAdvancedActivity" />
|
||||||
<activity android:name=".features.poll.create.CreatePollActivity" />
|
<activity android:name=".features.poll.create.CreatePollActivity" />
|
||||||
<activity android:name=".features.location.LocationSharingActivity" />
|
<activity android:name=".features.location.LocationSharingActivity" />
|
||||||
<activity android:name=".features.location.live.map.LocationLiveMapViewActivity" />
|
<activity android:name=".features.location.live.map.LiveLocationMapViewActivity" />
|
||||||
<activity android:name=".features.settings.font.FontScaleSettingActivity"/>
|
<activity android:name=".features.settings.font.FontScaleSettingActivity"/>
|
||||||
|
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
@ -381,7 +381,7 @@
|
|||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".features.location.LocationSharingAndroidService"
|
android:name=".features.location.live.tracking.LocationSharingAndroidService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:foregroundServiceType="location" />
|
android:foregroundServiceType="location" />
|
||||||
|
|
||||||
|
76
vector/src/main/java/im/vector/app/SpaceStateHandler.kt
Normal file
76
vector/src/main/java/im/vector/app/SpaceStateHandler.kt
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import arrow.core.Option
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets info about the current space the user has navigated to, any space backstack they may have
|
||||||
|
* and handles switching to different spaces.
|
||||||
|
*/
|
||||||
|
interface SpaceStateHandler : DefaultLifecycleObserver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current space the current user has navigated to.
|
||||||
|
*
|
||||||
|
* @return null if the user is not in
|
||||||
|
*/
|
||||||
|
fun getCurrentSpace(): RoomSummary?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the new space the current user is navigating to.
|
||||||
|
*
|
||||||
|
* @param spaceId the id of the space being navigated to
|
||||||
|
* @param session the current active session
|
||||||
|
* @param persistNow if true, the current space will immediately be persisted in shared prefs
|
||||||
|
* @param isForwardNavigation whether this navigation is a forward action to properly handle backstack
|
||||||
|
*/
|
||||||
|
fun setCurrentSpace(
|
||||||
|
spaceId: String?,
|
||||||
|
session: Session? = null,
|
||||||
|
persistNow: Boolean = false,
|
||||||
|
isForwardNavigation: Boolean = true,
|
||||||
|
from: SelectSpaceFrom = SelectSpaceFrom.SELECT,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current backstack of spaces (via their id).
|
||||||
|
*
|
||||||
|
* null may be an entry in the ArrayDeque to indicate the root space (All Chats)
|
||||||
|
*/
|
||||||
|
fun getSpaceBackstack(): ArrayDeque<String?>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a flow of the selected space for clients to react immediately to space changes.
|
||||||
|
*/
|
||||||
|
fun getSelectedSpaceFlow(): Flow<Option<RoomSummary?>>
|
||||||
|
|
||||||
|
// SC: no viewpager swipes included
|
||||||
|
fun getSelectedSpaceFlowIgnoreSwipe(): Flow<Option<Pair<RoomSummary?, SelectSpaceFrom>>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the id of the active space, or null if there is none.
|
||||||
|
*/
|
||||||
|
fun getSafeActiveSpaceId(): String?
|
||||||
|
|
||||||
|
// Some SC additions
|
||||||
|
fun persistSelectedSpace();
|
||||||
|
}
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.app
|
package im.vector.app
|
||||||
|
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import arrow.core.Option
|
import arrow.core.Option
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
@ -61,16 +60,15 @@ enum class SelectSpaceFrom {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This class handles the global app state.
|
* This class handles the global app state.
|
||||||
* It requires to be added to ProcessLifecycleOwner.get().lifecycle
|
* It is required that this class is added as an observer to ProcessLifecycleOwner.get().lifecycle in [VectorApplication]
|
||||||
*/
|
*/
|
||||||
// TODO Keep this class for now, will maybe be used fro Space
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AppStateHandler @Inject constructor(
|
class SpaceStateHandlerImpl @Inject constructor(
|
||||||
private val sessionDataSource: ActiveSessionDataSource,
|
private val sessionDataSource: ActiveSessionDataSource,
|
||||||
private val uiStateRepository: UiStateRepository,
|
private val uiStateRepository: UiStateRepository,
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val analyticsTracker: AnalyticsTracker
|
private val analyticsTracker: AnalyticsTracker
|
||||||
) : DefaultLifecycleObserver {
|
) : SpaceStateHandler {
|
||||||
|
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||||
|
|
||||||
@ -78,26 +76,34 @@ class AppStateHandler @Inject constructor(
|
|||||||
|
|
||||||
// SC: Call it different then the upstream one so we don't forget adding `first` to upstream's logic.
|
// SC: Call it different then the upstream one so we don't forget adding `first` to upstream's logic.
|
||||||
private val selectedSpaceDataSourceSc = BehaviorDataSource<Option<Pair<RoomSummary?, SelectSpaceFrom>>>(Option.empty())
|
private val selectedSpaceDataSourceSc = BehaviorDataSource<Option<Pair<RoomSummary?, SelectSpaceFrom>>>(Option.empty())
|
||||||
|
//private val selectedSpaceFlow = selectedSpaceDataSource.stream()
|
||||||
//val selectedSpaceFlow = selectedSpaceDataSource.stream()
|
private val selectedSpaceFlow = selectedSpaceDataSourceSc.stream().map { it.map { it.first } }
|
||||||
|
private val selectedSpaceFlowIgnoreSwipe = selectedSpaceDataSourceSc.stream()
|
||||||
val selectedSpaceFlow = selectedSpaceDataSourceSc.stream().map { it.map { it.first } }
|
|
||||||
val selectedSpaceFlowIgnoreSwipe = selectedSpaceDataSourceSc.stream()
|
|
||||||
.filter { it.getOrElse{ null }?.second != SelectSpaceFrom.SWIPE }
|
.filter { it.getOrElse{ null }?.second != SelectSpaceFrom.SWIPE }
|
||||||
|
|
||||||
private val spaceBackstack = ArrayDeque<String?>()
|
private val spaceBackstack = ArrayDeque<String?>()
|
||||||
|
|
||||||
fun getCurrentSpace(): RoomSummary? {
|
override fun getCurrentSpace(): RoomSummary? {
|
||||||
return selectedSpaceDataSourceSc.currentValue?.orNull()?.first?.let { spaceSummary ->
|
return selectedSpaceDataSourceSc.currentValue?.orNull()?.first?.let { spaceSummary ->
|
||||||
activeSessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(spaceSummary.roomId)
|
activeSessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(spaceSummary.roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCurrentSpace(spaceId: String?, session: Session? = null, persistNow: Boolean = false, isForwardNavigation: Boolean = true, from: SelectSpaceFrom = SelectSpaceFrom.SELECT) {
|
override fun setCurrentSpace(
|
||||||
|
spaceId: String?,
|
||||||
|
session: Session?,
|
||||||
|
persistNow: Boolean,
|
||||||
|
isForwardNavigation: Boolean,
|
||||||
|
from: SelectSpaceFrom,
|
||||||
|
) {
|
||||||
|
val activeSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
|
||||||
val currentSpace = selectedSpaceDataSourceSc.currentValue?.orNull()?.first
|
val currentSpace = selectedSpaceDataSourceSc.currentValue?.orNull()?.first
|
||||||
val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
|
val spaceSummary = spaceId?.let { activeSession.getRoomSummary(spaceId) }
|
||||||
if (currentSpace != null && spaceId == currentSpace.roomId) return
|
val sameSpaceSelected = currentSpace != null && spaceId == currentSpace.roomId
|
||||||
val spaceSum = spaceId?.let { uSession.getRoomSummary(spaceId) }
|
|
||||||
|
if (sameSpaceSelected) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (DbgUtil.isDbgEnabled(DbgUtil.DBG_VIEW_PAGER) && from == SelectSpaceFrom.SELECT) {
|
if (DbgUtil.isDbgEnabled(DbgUtil.DBG_VIEW_PAGER) && from == SelectSpaceFrom.SELECT) {
|
||||||
// We want a stack trace
|
// We want a stack trace
|
||||||
@ -109,20 +115,20 @@ class AppStateHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (persistNow) {
|
if (persistNow) {
|
||||||
uiStateRepository.storeSelectedSpace(spaceSum?.roomId, uSession.sessionId)
|
uiStateRepository.storeSelectedSpace(spaceSummary?.roomId, activeSession.sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spaceSum == null) {
|
if (spaceSummary == null) {
|
||||||
//selectedSpaceDataSourceSc.post(Option.empty())
|
//selectedSpaceDataSourceSc.post(Option.empty())
|
||||||
selectedSpaceDataSourceSc.post(Option.just(Pair(null, from)))
|
selectedSpaceDataSourceSc.post(Option.just(Pair(null, from)))
|
||||||
} else {
|
} else {
|
||||||
selectedSpaceDataSourceSc.post(Option.just(Pair(spaceSum, from)))
|
selectedSpaceDataSourceSc.post(Option.just(Pair(spaceSummary, from)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spaceId != null) {
|
if (spaceId != null) {
|
||||||
uSession.coroutineScope.launch(Dispatchers.IO) {
|
activeSession.coroutineScope.launch(Dispatchers.IO) {
|
||||||
tryOrNull {
|
tryOrNull {
|
||||||
uSession.getRoom(spaceId)?.membershipService()?.loadRoomMembersIfNeeded()
|
activeSession.getRoom(spaceId)?.membershipService()?.loadRoomMembersIfNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,9 +159,13 @@ class AppStateHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fun getSpaceBackstack() = spaceBackstack
|
override fun getSpaceBackstack() = spaceBackstack
|
||||||
|
|
||||||
fun safeActiveSpaceId(): String? {
|
override fun getSelectedSpaceFlow() = selectedSpaceFlow
|
||||||
|
|
||||||
|
override fun getSelectedSpaceFlowIgnoreSwipe() = selectedSpaceFlowIgnoreSwipe
|
||||||
|
|
||||||
|
override fun getSafeActiveSpaceId(): String? {
|
||||||
return selectedSpaceDataSourceSc.currentValue?.orNull()?.first?.roomId
|
return selectedSpaceDataSourceSc.currentValue?.orNull()?.first?.roomId
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +179,7 @@ class AppStateHandler @Inject constructor(
|
|||||||
uiStateRepository.storeSelectedSpace(selectedSpaceDataSourceSc.currentValue?.orNull()?.first?.roomId, session.sessionId)
|
uiStateRepository.storeSelectedSpace(selectedSpaceDataSourceSc.currentValue?.orNull()?.first?.roomId, session.sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun persistSelectedSpace() {
|
override fun persistSelectedSpace() {
|
||||||
val currentValue = selectedSpaceDataSourceSc.currentValue?.orNull() ?: return
|
val currentValue = selectedSpaceDataSourceSc.currentValue?.orNull() ?: return
|
||||||
val currentMethod = currentValue.first
|
val currentMethod = currentValue.first
|
||||||
val uSession = activeSessionHolder.getSafeActiveSession() ?: return
|
val uSession = activeSessionHolder.getSafeActiveSession() ?: return
|
@ -25,24 +25,30 @@ import android.content.res.Configuration
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.HandlerThread
|
import android.os.HandlerThread
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
|
import android.view.Gravity
|
||||||
import androidx.core.provider.FontRequest
|
import androidx.core.provider.FontRequest
|
||||||
import androidx.core.provider.FontsContractCompat
|
import androidx.core.provider.FontsContractCompat
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.multidex.MultiDex
|
import androidx.multidex.MultiDex
|
||||||
|
import androidx.recyclerview.widget.SnapHelper
|
||||||
|
import com.airbnb.epoxy.Carousel
|
||||||
import com.airbnb.epoxy.EpoxyAsyncUtil
|
import com.airbnb.epoxy.EpoxyAsyncUtil
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
import com.airbnb.mvrx.Mavericks
|
import com.airbnb.mvrx.Mavericks
|
||||||
import com.facebook.stetho.Stetho
|
import com.facebook.stetho.Stetho
|
||||||
import com.gabrielittner.threetenbp.LazyThreeTen
|
import com.gabrielittner.threetenbp.LazyThreeTen
|
||||||
|
import com.github.rubensousa.gravitysnaphelper.GravitySnapHelper
|
||||||
import com.mapbox.mapboxsdk.Mapbox
|
import com.mapbox.mapboxsdk.Mapbox
|
||||||
import com.vanniktech.emoji.EmojiManager
|
import com.vanniktech.emoji.EmojiManager
|
||||||
import com.vanniktech.emoji.google.GoogleEmojiProvider
|
import com.vanniktech.emoji.google.GoogleEmojiProvider
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
import de.spiritcroc.matrixsdk.StaticScSdkHelper
|
import de.spiritcroc.matrixsdk.StaticScSdkHelper
|
||||||
import de.spiritcroc.matrixsdk.util.DbgUtil
|
import de.spiritcroc.matrixsdk.util.DbgUtil
|
||||||
|
import im.vector.app.config.Config
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import im.vector.app.features.analytics.VectorAnalytics
|
import im.vector.app.features.analytics.VectorAnalytics
|
||||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||||
import im.vector.app.features.configuration.VectorConfiguration
|
import im.vector.app.features.configuration.VectorConfiguration
|
||||||
@ -90,7 +96,7 @@ class VectorApplication :
|
|||||||
@Inject lateinit var vectorPreferences: VectorPreferences
|
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||||
@Inject lateinit var versionProvider: VersionProvider
|
@Inject lateinit var versionProvider: VersionProvider
|
||||||
@Inject lateinit var notificationUtils: NotificationUtils
|
@Inject lateinit var notificationUtils: NotificationUtils
|
||||||
@Inject lateinit var appStateHandler: AppStateHandler
|
@Inject lateinit var spaceStateHandler: SpaceStateHandler
|
||||||
@Inject lateinit var popupAlertManager: PopupAlertManager
|
@Inject lateinit var popupAlertManager: PopupAlertManager
|
||||||
@Inject lateinit var pinLocker: PinLocker
|
@Inject lateinit var pinLocker: PinLocker
|
||||||
@Inject lateinit var callManager: WebRtcCallManager
|
@Inject lateinit var callManager: WebRtcCallManager
|
||||||
@ -101,6 +107,7 @@ class VectorApplication :
|
|||||||
@Inject lateinit var flipperProxy: FlipperProxy
|
@Inject lateinit var flipperProxy: FlipperProxy
|
||||||
@Inject lateinit var matrix: Matrix
|
@Inject lateinit var matrix: Matrix
|
||||||
@Inject lateinit var fcmHelper: FcmHelper
|
@Inject lateinit var fcmHelper: FcmHelper
|
||||||
|
@Inject lateinit var buildMeta: BuildMeta
|
||||||
|
|
||||||
// font thread handler
|
// font thread handler
|
||||||
private var fontThreadHandler: Handler? = null
|
private var fontThreadHandler: Handler? = null
|
||||||
@ -133,19 +140,20 @@ class VectorApplication :
|
|||||||
.filterIsInstance(JitsiMeetDefaultLogHandler::class.java)
|
.filterIsInstance(JitsiMeetDefaultLogHandler::class.java)
|
||||||
.forEach { Timber.uproot(it) }
|
.forEach { Timber.uproot(it) }
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (buildMeta.isDebug) {
|
||||||
Timber.plant(Timber.DebugTree())
|
Timber.plant(Timber.DebugTree())
|
||||||
}
|
}
|
||||||
Timber.plant(vectorFileLogger)
|
Timber.plant(vectorFileLogger)
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (buildMeta.isDebug) {
|
||||||
Stetho.initializeWithDefaults(this)
|
Stetho.initializeWithDefaults(this)
|
||||||
}
|
}
|
||||||
logInfo()
|
logInfo()
|
||||||
LazyThreeTen.init(this)
|
LazyThreeTen.init(this)
|
||||||
Mavericks.initialize(debugMode = false)
|
Mavericks.initialize(debugMode = false)
|
||||||
EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
|
||||||
EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
configureEpoxy()
|
||||||
|
|
||||||
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks(popupAlertManager))
|
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks(popupAlertManager))
|
||||||
val fontRequest = FontRequest(
|
val fontRequest = FontRequest(
|
||||||
"com.google.android.gms.fonts",
|
"com.google.android.gms.fonts",
|
||||||
@ -154,7 +162,7 @@ class VectorApplication :
|
|||||||
R.array.com_google_android_gms_fonts_certs
|
R.array.com_google_android_gms_fonts_certs
|
||||||
)
|
)
|
||||||
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
|
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
|
||||||
VectorLocale.init(this)
|
VectorLocale.init(this, buildMeta)
|
||||||
ThemeUtils.init(this)
|
ThemeUtils.init(this)
|
||||||
vectorConfiguration.applyToApplicationContext()
|
vectorConfiguration.applyToApplicationContext()
|
||||||
|
|
||||||
@ -183,7 +191,7 @@ class VectorApplication :
|
|||||||
fcmHelper.onEnterBackground(activeSessionHolder)
|
fcmHelper.onEnterBackground(activeSessionHolder)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(appStateHandler)
|
ProcessLifecycleOwner.get().lifecycle.addObserver(spaceStateHandler)
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(pinLocker)
|
ProcessLifecycleOwner.get().lifecycle.addObserver(pinLocker)
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(callManager)
|
ProcessLifecycleOwner.get().lifecycle.addObserver(callManager)
|
||||||
// This should be done as early as possible
|
// This should be done as early as possible
|
||||||
@ -201,8 +209,18 @@ class VectorApplication :
|
|||||||
Mapbox.getInstance(this)
|
Mapbox.getInstance(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun configureEpoxy() {
|
||||||
|
EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
||||||
|
EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
||||||
|
Carousel.setDefaultGlobalSnapHelperFactory(object : Carousel.SnapHelperFactory() {
|
||||||
|
override fun buildSnapHelper(context: Context?): SnapHelper {
|
||||||
|
return GravitySnapHelper(Gravity.START)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private fun enableStrictModeIfNeeded() {
|
private fun enableStrictModeIfNeeded() {
|
||||||
if (BuildConfig.ENABLE_STRICT_MODE_LOGS) {
|
if (Config.ENABLE_STRICT_MODE_LOGS) {
|
||||||
StrictMode.setThreadPolicy(
|
StrictMode.setThreadPolicy(
|
||||||
StrictMode.ThreadPolicy.Builder()
|
StrictMode.ThreadPolicy.Builder()
|
||||||
.detectAll()
|
.detectAll()
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.di
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import im.vector.app.BuildConfig
|
||||||
|
import im.vector.app.config.Analytics
|
||||||
|
import im.vector.app.config.Config
|
||||||
|
import im.vector.app.config.KeySharingStrategy
|
||||||
|
import im.vector.app.features.analytics.AnalyticsConfig
|
||||||
|
import im.vector.app.features.call.webrtc.VoipConfig
|
||||||
|
import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy
|
||||||
|
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageConfig
|
||||||
|
import im.vector.app.features.location.LocationSharingConfig
|
||||||
|
import im.vector.app.features.raw.wellknown.CryptoConfig
|
||||||
|
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
@Module
|
||||||
|
object ConfigurationModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun providesAnalyticsConfig(): AnalyticsConfig {
|
||||||
|
// Schildi: always disable
|
||||||
|
val config: Analytics = if (true) Analytics.Disabled else when (BuildConfig.BUILD_TYPE) {
|
||||||
|
"debug" -> Config.DEBUG_ANALYTICS_CONFIG
|
||||||
|
"nightly" -> Config.NIGHTLY_ANALYTICS_CONFIG
|
||||||
|
"release" -> Config.RELEASE_ANALYTICS_CONFIG
|
||||||
|
else -> throw IllegalStateException("Unhandled build type: ${BuildConfig.BUILD_TYPE}")
|
||||||
|
}
|
||||||
|
return when (config) {
|
||||||
|
Analytics.Disabled -> AnalyticsConfig(isEnabled = false, "", "", "")
|
||||||
|
is Analytics.PostHog -> AnalyticsConfig(
|
||||||
|
isEnabled = true,
|
||||||
|
postHogHost = config.postHogHost,
|
||||||
|
postHogApiKey = config.postHogApiKey,
|
||||||
|
policyLink = config.policyLink
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun providesVoiceMessageConfig() = VoiceMessageConfig(
|
||||||
|
lengthLimitMs = Config.VOICE_MESSAGE_LIMIT_MS
|
||||||
|
)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun providesCryptoConfig() = CryptoConfig(
|
||||||
|
fallbackKeySharingStrategy = when (Config.KEY_SHARING_STRATEGY) {
|
||||||
|
KeySharingStrategy.WhenSendingEvent -> OutboundSessionKeySharingStrategy.WhenSendingEvent
|
||||||
|
KeySharingStrategy.WhenEnteringRoom -> OutboundSessionKeySharingStrategy.WhenSendingEvent
|
||||||
|
KeySharingStrategy.WhenTyping -> OutboundSessionKeySharingStrategy.WhenSendingEvent
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun providesLocationSharingConfig() = LocationSharingConfig(
|
||||||
|
mapTilerKey = Config.LOCATION_MAP_TILER_KEY,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun providesVoipConfig() = VoipConfig(
|
||||||
|
handleCallAssertedIdentityEvents = Config.HANDLE_CALL_ASSERTED_IDENTITY_EVENTS
|
||||||
|
)
|
||||||
|
}
|
@ -57,14 +57,15 @@ import im.vector.app.features.discovery.change.SetIdentityServerFragment
|
|||||||
import im.vector.app.features.home.HomeDetailFragment
|
import im.vector.app.features.home.HomeDetailFragment
|
||||||
import im.vector.app.features.home.HomeDrawerFragment
|
import im.vector.app.features.home.HomeDrawerFragment
|
||||||
import im.vector.app.features.home.LoadingFragment
|
import im.vector.app.features.home.LoadingFragment
|
||||||
|
import im.vector.app.features.home.NewHomeDetailFragment
|
||||||
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
|
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
|
||||||
import im.vector.app.features.home.room.detail.TimelineFragment
|
import im.vector.app.features.home.room.detail.TimelineFragment
|
||||||
import im.vector.app.features.home.room.detail.search.SearchFragment
|
import im.vector.app.features.home.room.detail.search.SearchFragment
|
||||||
import im.vector.app.features.home.room.list.RoomListFragment
|
import im.vector.app.features.home.room.list.RoomListFragment
|
||||||
import im.vector.app.features.home.room.list.home.HomeRoomListFragment
|
import im.vector.app.features.home.room.list.home.HomeRoomListFragment
|
||||||
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
|
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
|
||||||
import im.vector.app.features.location.LocationPreviewFragment
|
|
||||||
import im.vector.app.features.location.LocationSharingFragment
|
import im.vector.app.features.location.LocationSharingFragment
|
||||||
|
import im.vector.app.features.location.preview.LocationPreviewFragment
|
||||||
import im.vector.app.features.login.LoginCaptchaFragment
|
import im.vector.app.features.login.LoginCaptchaFragment
|
||||||
import im.vector.app.features.login.LoginFragment
|
import im.vector.app.features.login.LoginFragment
|
||||||
import im.vector.app.features.login.LoginGenericTextInputFormFragment
|
import im.vector.app.features.login.LoginGenericTextInputFormFragment
|
||||||
@ -260,6 +261,11 @@ interface FragmentModule {
|
|||||||
@FragmentKey(HomeDetailFragment::class)
|
@FragmentKey(HomeDetailFragment::class)
|
||||||
fun bindHomeDetailFragment(fragment: HomeDetailFragment): Fragment
|
fun bindHomeDetailFragment(fragment: HomeDetailFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(NewHomeDetailFragment::class)
|
||||||
|
fun bindNewHomeDetailFragment(fragment: NewHomeDetailFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(EmojiSearchResultFragment::class)
|
@FragmentKey(EmojiSearchResultFragment::class)
|
||||||
|
@ -55,7 +55,8 @@ import im.vector.app.features.home.room.list.home.HomeRoomListViewModel
|
|||||||
import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel
|
import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel
|
||||||
import im.vector.app.features.invite.InviteUsersToRoomViewModel
|
import im.vector.app.features.invite.InviteUsersToRoomViewModel
|
||||||
import im.vector.app.features.location.LocationSharingViewModel
|
import im.vector.app.features.location.LocationSharingViewModel
|
||||||
import im.vector.app.features.location.live.map.LocationLiveMapViewModel
|
import im.vector.app.features.location.live.map.LiveLocationMapViewModel
|
||||||
|
import im.vector.app.features.location.preview.LocationPreviewViewModel
|
||||||
import im.vector.app.features.login.LoginViewModel
|
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
|
||||||
@ -605,6 +606,11 @@ interface MavericksViewModelModule {
|
|||||||
@MavericksViewModelKey(LocationSharingViewModel::class)
|
@MavericksViewModelKey(LocationSharingViewModel::class)
|
||||||
fun createLocationSharingViewModelFactory(factory: LocationSharingViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
fun createLocationSharingViewModelFactory(factory: LocationSharingViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@MavericksViewModelKey(LocationPreviewViewModel::class)
|
||||||
|
fun createLocationPreviewViewModelFactory(factory: LocationPreviewViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(VectorAttachmentViewerViewModel::class)
|
@MavericksViewModelKey(VectorAttachmentViewerViewModel::class)
|
||||||
@ -612,8 +618,8 @@ interface MavericksViewModelModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(LocationLiveMapViewModel::class)
|
@MavericksViewModelKey(LiveLocationMapViewModel::class)
|
||||||
fun locationLiveMapViewModelFactory(factory: LocationLiveMapViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
fun liveLocationMapViewModelFactory(factory: LiveLocationMapViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
|
@ -31,7 +31,9 @@ import dagger.hilt.components.SingletonComponent
|
|||||||
import im.vector.app.BuildConfig
|
import im.vector.app.BuildConfig
|
||||||
import im.vector.app.EmojiCompatWrapper
|
import im.vector.app.EmojiCompatWrapper
|
||||||
import im.vector.app.EmojiSpanify
|
import im.vector.app.EmojiSpanify
|
||||||
import im.vector.app.config.analyticsConfig
|
import im.vector.app.SpaceStateHandler
|
||||||
|
import im.vector.app.SpaceStateHandlerImpl
|
||||||
|
import im.vector.app.config.Config
|
||||||
import im.vector.app.core.dispatchers.CoroutineDispatchers
|
import im.vector.app.core.dispatchers.CoroutineDispatchers
|
||||||
import im.vector.app.core.error.DefaultErrorFormatter
|
import im.vector.app.core.error.DefaultErrorFormatter
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
@ -40,7 +42,6 @@ import im.vector.app.core.time.Clock
|
|||||||
import im.vector.app.core.time.DefaultClock
|
import im.vector.app.core.time.DefaultClock
|
||||||
import im.vector.app.core.utils.AndroidSystemSettingsProvider
|
import im.vector.app.core.utils.AndroidSystemSettingsProvider
|
||||||
import im.vector.app.core.utils.SystemSettingsProvider
|
import im.vector.app.core.utils.SystemSettingsProvider
|
||||||
import im.vector.app.features.analytics.AnalyticsConfig
|
|
||||||
import im.vector.app.features.analytics.AnalyticsTracker
|
import im.vector.app.features.analytics.AnalyticsTracker
|
||||||
import im.vector.app.features.analytics.VectorAnalytics
|
import im.vector.app.features.analytics.VectorAnalytics
|
||||||
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
|
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
|
||||||
@ -108,6 +109,9 @@ abstract class VectorBindModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSystemSettingsProvide(provider: AndroidSystemSettingsProvider): SystemSettingsProvider
|
abstract fun bindSystemSettingsProvide(provider: AndroidSystemSettingsProvider): SystemSettingsProvider
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSpaceStateHandler(spaceStateHandlerImpl: SpaceStateHandlerImpl): SpaceStateHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
@ -200,17 +204,23 @@ object VectorStaticModule {
|
|||||||
return GlobalScope
|
return GlobalScope
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun providesAnalyticsConfig(): AnalyticsConfig {
|
|
||||||
return analyticsConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun providesPhoneNumberUtil(): PhoneNumberUtil = PhoneNumberUtil.getInstance()
|
fun providesPhoneNumberUtil(): PhoneNumberUtil = PhoneNumberUtil.getInstance()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesBuildMeta() = BuildMeta()
|
fun providesBuildMeta() = BuildMeta(
|
||||||
|
isDebug = BuildConfig.DEBUG,
|
||||||
|
applicationId = BuildConfig.APPLICATION_ID,
|
||||||
|
lowPrivacyLoggingEnabled = Config.LOW_PRIVACY_LOG_ENABLE,
|
||||||
|
versionName = BuildConfig.VERSION_NAME,
|
||||||
|
gitRevision = BuildConfig.GIT_REVISION,
|
||||||
|
gitRevisionDate = BuildConfig.GIT_REVISION_DATE,
|
||||||
|
gitBranchName = BuildConfig.GIT_BRANCH_NAME,
|
||||||
|
buildNumber = BuildConfig.BUILD_NUMBER,
|
||||||
|
flavorDescription = BuildConfig.FLAVOR_DESCRIPTION,
|
||||||
|
flavorShortDescription = BuildConfig.SHORT_FLAVOR_DESCRIPTION,
|
||||||
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
|
@ -37,7 +37,7 @@ import androidx.datastore.preferences.core.Preferences
|
|||||||
import dagger.hilt.EntryPoints
|
import dagger.hilt.EntryPoints
|
||||||
import im.vector.app.core.datastore.dataStoreProvider
|
import im.vector.app.core.datastore.dataStoreProvider
|
||||||
import im.vector.app.core.di.SingletonEntryPoint
|
import im.vector.app.core.di.SingletonEntryPoint
|
||||||
import im.vector.app.core.resources.BuildMeta
|
import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@ -93,9 +93,9 @@ fun Context.safeOpenOutputStream(uri: Uri): OutputStream? {
|
|||||||
*/
|
*/
|
||||||
@Suppress("deprecation")
|
@Suppress("deprecation")
|
||||||
@SuppressLint("NewApi") // false positive
|
@SuppressLint("NewApi") // false positive
|
||||||
fun Context.inferNoConnectivity(buildMeta: BuildMeta): Boolean {
|
fun Context.inferNoConnectivity(sdkIntProvider: BuildVersionSdkIntProvider): Boolean {
|
||||||
val connectivityManager = getSystemService<ConnectivityManager>()!!
|
val connectivityManager = getSystemService<ConnectivityManager>()!!
|
||||||
return if (buildMeta.sdkInt > Build.VERSION_CODES.M) {
|
return if (sdkIntProvider.get() > Build.VERSION_CODES.M) {
|
||||||
val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
||||||
when {
|
when {
|
||||||
networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> false
|
networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> false
|
||||||
|
@ -28,7 +28,7 @@ import im.vector.app.R
|
|||||||
import im.vector.app.core.platform.SimpleTextWatcher
|
import im.vector.app.core.platform.SimpleTextWatcher
|
||||||
|
|
||||||
fun EditText.setupAsSearch(
|
fun EditText.setupAsSearch(
|
||||||
@DrawableRes searchIconRes: Int = R.drawable.ic_search,
|
@DrawableRes searchIconRes: Int = R.drawable.ic_home_search,
|
||||||
@DrawableRes clearIconRes: Int = R.drawable.ic_x_gray
|
@DrawableRes clearIconRes: Int = R.drawable.ic_x_gray
|
||||||
) {
|
) {
|
||||||
addTextChangedListener(object : SimpleTextWatcher() {
|
addTextChangedListener(object : SimpleTextWatcher() {
|
||||||
|
@ -78,10 +78,14 @@ fun TextInputLayout.setOnImeDoneListener(action: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TextInputLayout.setOnFocusLostListener(action: () -> Unit) {
|
/**
|
||||||
|
* Set a listener for when the input has lost focus, such as moving to the another input field.
|
||||||
|
* The listener is only called when the view is in a resumed state to avoid triggers when exiting a screen.
|
||||||
|
*/
|
||||||
|
fun TextInputLayout.setOnFocusLostListener(lifecycleOwner: LifecycleOwner, action: () -> Unit) {
|
||||||
editText().setOnFocusChangeListener { _, hasFocus ->
|
editText().setOnFocusChangeListener { _, hasFocus ->
|
||||||
when (hasFocus) {
|
when (hasFocus) {
|
||||||
false -> action()
|
false -> lifecycleOwner.lifecycleScope.launchWhenResumed { action() }
|
||||||
else -> {
|
else -> {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
@ -50,10 +50,10 @@ import androidx.viewbinding.ViewBinding
|
|||||||
import com.airbnb.mvrx.MavericksView
|
import com.airbnb.mvrx.MavericksView
|
||||||
import com.bumptech.glide.util.Util
|
import com.bumptech.glide.util.Util
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import dagger.hilt.android.EntryPointAccessors
|
import dagger.hilt.android.EntryPointAccessors
|
||||||
import im.vector.app.BuildConfig
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.di.ActivityEntryPoint
|
import im.vector.app.core.di.ActivityEntryPoint
|
||||||
@ -67,11 +67,13 @@ import im.vector.app.core.extensions.restart
|
|||||||
import im.vector.app.core.extensions.setTextOrHide
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
import im.vector.app.core.extensions.singletonEntryPoint
|
import im.vector.app.core.extensions.singletonEntryPoint
|
||||||
import im.vector.app.core.extensions.toMvRxBundle
|
import im.vector.app.core.extensions.toMvRxBundle
|
||||||
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import im.vector.app.core.utils.AndroidSystemSettingsProvider
|
import im.vector.app.core.utils.AndroidSystemSettingsProvider
|
||||||
import im.vector.app.core.utils.ToolbarConfig
|
import im.vector.app.core.utils.ToolbarConfig
|
||||||
import im.vector.app.core.utils.toast
|
import im.vector.app.core.utils.toast
|
||||||
import im.vector.app.features.MainActivity
|
import im.vector.app.features.MainActivity
|
||||||
import im.vector.app.features.MainActivityArgs
|
import im.vector.app.features.MainActivityArgs
|
||||||
|
import im.vector.app.features.VectorFeatures
|
||||||
import im.vector.app.features.analytics.AnalyticsTracker
|
import im.vector.app.features.analytics.AnalyticsTracker
|
||||||
import im.vector.app.features.analytics.plan.MobileScreen
|
import im.vector.app.features.analytics.plan.MobileScreen
|
||||||
import im.vector.app.features.configuration.VectorConfiguration
|
import im.vector.app.features.configuration.VectorConfiguration
|
||||||
@ -155,11 +157,12 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||||||
protected lateinit var bugReporter: BugReporter
|
protected lateinit var bugReporter: BugReporter
|
||||||
private lateinit var pinLocker: PinLocker
|
private lateinit var pinLocker: PinLocker
|
||||||
|
|
||||||
@Inject
|
@Inject lateinit var rageShake: RageShake
|
||||||
lateinit var rageShake: RageShake
|
@Inject lateinit var buildMeta: BuildMeta
|
||||||
|
@Inject lateinit var fontScalePreferences: FontScalePreferences
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var fontScalePreferences: FontScalePreferences
|
lateinit var vectorFeatures: VectorFeatures
|
||||||
|
|
||||||
lateinit var navigator: Navigator
|
lateinit var navigator: Navigator
|
||||||
private set
|
private set
|
||||||
@ -253,6 +256,14 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||||||
|
|
||||||
initUiAndData()
|
initUiAndData()
|
||||||
|
|
||||||
|
if (vectorFeatures.isNewAppLayoutEnabled()) {
|
||||||
|
tryOrNull { // Add to XML theme when feature flag is removed
|
||||||
|
val toolbarBackground = MaterialColors.getColor(views.root, R.attr.vctr_toolbar_background)
|
||||||
|
window.statusBarColor = toolbarBackground
|
||||||
|
window.navigationBarColor = toolbarBackground
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val titleRes = getTitleRes()
|
val titleRes = getTitleRes()
|
||||||
if (titleRes != -1) {
|
if (titleRes != -1) {
|
||||||
supportActionBar?.let {
|
supportActionBar?.let {
|
||||||
@ -409,7 +420,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||||||
}
|
}
|
||||||
DebugReceiver
|
DebugReceiver
|
||||||
.getIntentFilter(this)
|
.getIntentFilter(this)
|
||||||
.takeIf { BuildConfig.DEBUG }
|
.takeIf { buildMeta.isDebug }
|
||||||
?.let {
|
?.let {
|
||||||
debugReceiver = DebugReceiver()
|
debugReceiver = DebugReceiver()
|
||||||
registerReceiver(debugReceiver, it)
|
registerReceiver(debugReceiver, it)
|
||||||
|
@ -25,14 +25,14 @@ import androidx.lifecycle.Lifecycle
|
|||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.BuildConfig
|
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.network.WifiDetector
|
import im.vector.app.core.network.WifiDetector
|
||||||
import im.vector.app.core.pushers.model.PushData
|
import im.vector.app.core.pushers.model.PushData
|
||||||
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import im.vector.app.core.services.GuardServiceStarter
|
import im.vector.app.core.services.GuardServiceStarter
|
||||||
import im.vector.app.features.notifications.NotifiableEventResolver
|
import im.vector.app.features.notifications.NotifiableEventResolver
|
||||||
|
import im.vector.app.features.notifications.NotificationActionIds
|
||||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||||
import im.vector.app.features.notifications.NotificationUtils
|
|
||||||
import im.vector.app.features.settings.BackgroundSyncMode
|
import im.vector.app.features.settings.BackgroundSyncMode
|
||||||
import im.vector.app.features.settings.VectorDataStore
|
import im.vector.app.features.settings.VectorDataStore
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
@ -68,6 +68,8 @@ class VectorMessagingReceiver : MessagingReceiver() {
|
|||||||
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
|
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
|
||||||
@Inject lateinit var unifiedPushStore: UnifiedPushStore
|
@Inject lateinit var unifiedPushStore: UnifiedPushStore
|
||||||
@Inject lateinit var pushParser: PushParser
|
@Inject lateinit var pushParser: PushParser
|
||||||
|
@Inject lateinit var actionIds: NotificationActionIds
|
||||||
|
@Inject lateinit var buildMeta: BuildMeta
|
||||||
|
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
private val coroutineScope = CoroutineScope(SupervisorJob())
|
||||||
|
|
||||||
@ -87,7 +89,7 @@ class VectorMessagingReceiver : MessagingReceiver() {
|
|||||||
Timber.tag(loggerTag.value).d("## onMessage() received")
|
Timber.tag(loggerTag.value).d("## onMessage() received")
|
||||||
|
|
||||||
val sMessage = String(message)
|
val sMessage = String(message)
|
||||||
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
if (buildMeta.lowPrivacyLoggingEnabled) {
|
||||||
Timber.tag(loggerTag.value).d("## onMessage() $sMessage")
|
Timber.tag(loggerTag.value).d("## onMessage() $sMessage")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +102,7 @@ class VectorMessagingReceiver : MessagingReceiver() {
|
|||||||
|
|
||||||
// Diagnostic Push
|
// Diagnostic Push
|
||||||
if (pushData.eventId == PushersManager.TEST_EVENT_ID) {
|
if (pushData.eventId == PushersManager.TEST_EVENT_ID) {
|
||||||
val intent = Intent(NotificationUtils.PUSH_ACTION)
|
val intent = Intent(actionIds.push)
|
||||||
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
|
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -171,7 +173,7 @@ class VectorMessagingReceiver : MessagingReceiver() {
|
|||||||
*/
|
*/
|
||||||
private suspend fun onMessageReceivedInternal(pushData: PushData) {
|
private suspend fun onMessageReceivedInternal(pushData: PushData) {
|
||||||
try {
|
try {
|
||||||
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
if (buildMeta.lowPrivacyLoggingEnabled) {
|
||||||
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $pushData")
|
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $pushData")
|
||||||
} else {
|
} else {
|
||||||
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
|
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
|
||||||
|
@ -16,8 +16,15 @@
|
|||||||
|
|
||||||
package im.vector.app.core.resources
|
package im.vector.app.core.resources
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
|
|
||||||
data class BuildMeta(
|
data class BuildMeta(
|
||||||
val sdkInt: Int = Build.VERSION.SDK_INT
|
val isDebug: Boolean,
|
||||||
|
val applicationId: String,
|
||||||
|
val lowPrivacyLoggingEnabled: Boolean,
|
||||||
|
val versionName: String,
|
||||||
|
val gitRevision: String,
|
||||||
|
val gitRevisionDate: String,
|
||||||
|
val gitBranchName: String,
|
||||||
|
val buildNumber: String,
|
||||||
|
val flavorDescription: String,
|
||||||
|
val flavorShortDescription: String,
|
||||||
)
|
)
|
||||||
|
@ -39,7 +39,6 @@ import androidx.browser.customtabs.CustomTabsSession
|
|||||||
import androidx.core.app.ShareCompat
|
import androidx.core.app.ShareCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import im.vector.app.BuildConfig
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.features.notifications.NotificationUtils
|
import im.vector.app.features.notifications.NotificationUtils
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
@ -182,7 +181,7 @@ fun openUri(activity: Activity, uri: String) {
|
|||||||
*/
|
*/
|
||||||
fun openMedia(activity: Activity, savedMediaPath: String, mimeType: String) {
|
fun openMedia(activity: Activity, savedMediaPath: String, mimeType: String) {
|
||||||
val file = File(savedMediaPath)
|
val file = File(savedMediaPath)
|
||||||
val uri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".fileProvider", file)
|
val uri = FileProvider.getUriForFile(activity, activity.packageName + ".fileProvider", file)
|
||||||
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||||
setDataAndType(uri, mimeType)
|
setDataAndType(uri, mimeType)
|
||||||
@ -214,7 +213,7 @@ fun openLocation(activity: Activity, latitude: Double, longitude: Double) {
|
|||||||
|
|
||||||
fun shareMedia(context: Context, file: File, mediaMimeType: String?) {
|
fun shareMedia(context: Context, file: File, mediaMimeType: String?) {
|
||||||
val mediaUri = try {
|
val mediaUri = try {
|
||||||
FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", file)
|
FileProvider.getUriForFile(context, context.packageName + ".fileProvider", file)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "onMediaAction Selected File cannot be shared")
|
Timber.e(e, "onMediaAction Selected File cannot be shared")
|
||||||
return
|
return
|
||||||
@ -376,7 +375,7 @@ private fun addToGallery(savedFile: File, mediaMimeType: String?, context: Conte
|
|||||||
/**
|
/**
|
||||||
* Open the play store to the provided application Id, default to this app.
|
* Open the play store to the provided application Id, default to this app.
|
||||||
*/
|
*/
|
||||||
fun openPlayStore(activity: Activity, appId: String = BuildConfig.APPLICATION_ID) {
|
fun openPlayStore(activity: Activity, appId: String) {
|
||||||
try {
|
try {
|
||||||
activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$appId")))
|
activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$appId")))
|
||||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||||
|
@ -159,7 +159,7 @@ fun startInstallFromSourceIntent(context: Context, activityResultLauncher: Activ
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun startSharePlainTextIntent(
|
fun startSharePlainTextIntent(
|
||||||
fragment: Fragment,
|
context: Context,
|
||||||
activityResultLauncher: ActivityResultLauncher<Intent>?,
|
activityResultLauncher: ActivityResultLauncher<Intent>?,
|
||||||
chooserTitle: String?,
|
chooserTitle: String?,
|
||||||
text: String,
|
text: String,
|
||||||
@ -182,10 +182,10 @@ fun startSharePlainTextIntent(
|
|||||||
if (activityResultLauncher != null) {
|
if (activityResultLauncher != null) {
|
||||||
activityResultLauncher.launch(intent)
|
activityResultLauncher.launch(intent)
|
||||||
} else {
|
} else {
|
||||||
fragment.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||||
fragment.activity?.toast(R.string.error_no_external_application_found)
|
context.toast(R.string.error_no_external_application_found)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
package im.vector.app.features
|
package im.vector.app.features
|
||||||
|
|
||||||
import im.vector.app.BuildConfig
|
|
||||||
import im.vector.app.config.Config
|
import im.vector.app.config.Config
|
||||||
|
import im.vector.app.config.OnboardingVariant
|
||||||
|
|
||||||
interface VectorFeatures {
|
interface VectorFeatures {
|
||||||
|
|
||||||
@ -30,19 +30,14 @@ interface VectorFeatures {
|
|||||||
fun isOnboardingCombinedLoginEnabled(): Boolean
|
fun isOnboardingCombinedLoginEnabled(): Boolean
|
||||||
fun allowExternalUnifiedPushDistributors(): Boolean
|
fun allowExternalUnifiedPushDistributors(): Boolean
|
||||||
fun isScreenSharingEnabled(): Boolean
|
fun isScreenSharingEnabled(): Boolean
|
||||||
|
fun isLocationSharingEnabled(): Boolean
|
||||||
fun forceUsageOfOpusEncoder(): Boolean
|
fun forceUsageOfOpusEncoder(): Boolean
|
||||||
fun shouldStartDmOnFirstMessage(): Boolean
|
fun shouldStartDmOnFirstMessage(): Boolean
|
||||||
fun isNewAppLayoutEnabled(): Boolean
|
fun isNewAppLayoutEnabled(): Boolean
|
||||||
|
|
||||||
enum class OnboardingVariant {
|
|
||||||
LEGACY,
|
|
||||||
LOGIN_2,
|
|
||||||
FTUE_AUTH
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultVectorFeatures : VectorFeatures {
|
class DefaultVectorFeatures : VectorFeatures {
|
||||||
override fun onboardingVariant(): VectorFeatures.OnboardingVariant = BuildConfig.ONBOARDING_VARIANT
|
override fun onboardingVariant() = Config.ONBOARDING_VARIANT
|
||||||
override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true
|
override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true
|
||||||
override fun isOnboardingSplashCarouselEnabled() = true
|
override fun isOnboardingSplashCarouselEnabled() = true
|
||||||
override fun isOnboardingUseCaseEnabled() = true
|
override fun isOnboardingUseCaseEnabled() = true
|
||||||
@ -51,6 +46,7 @@ class DefaultVectorFeatures : VectorFeatures {
|
|||||||
override fun isOnboardingCombinedLoginEnabled() = true
|
override fun isOnboardingCombinedLoginEnabled() = true
|
||||||
override fun allowExternalUnifiedPushDistributors(): Boolean = Config.ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS
|
override fun allowExternalUnifiedPushDistributors(): Boolean = Config.ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS
|
||||||
override fun isScreenSharingEnabled(): Boolean = true
|
override fun isScreenSharingEnabled(): Boolean = true
|
||||||
|
override fun isLocationSharingEnabled() = Config.ENABLE_LOCATION_SHARING
|
||||||
override fun forceUsageOfOpusEncoder(): Boolean = false
|
override fun forceUsageOfOpusEncoder(): Boolean = false
|
||||||
override fun shouldStartDmOnFirstMessage(): Boolean = false
|
override fun shouldStartDmOnFirstMessage(): Boolean = false
|
||||||
override fun isNewAppLayoutEnabled(): Boolean = false
|
override fun isNewAppLayoutEnabled(): Boolean = false
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2021 New Vector Ltd
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,9 +16,9 @@
|
|||||||
|
|
||||||
package im.vector.app.features.analytics
|
package im.vector.app.features.analytics
|
||||||
|
|
||||||
interface AnalyticsConfig {
|
data class AnalyticsConfig(
|
||||||
val isEnabled: Boolean
|
val isEnabled: Boolean,
|
||||||
val postHogHost: String
|
val postHogHost: String,
|
||||||
val postHogApiKey: String
|
val postHogApiKey: String,
|
||||||
val policyLink: String
|
val policyLink: String,
|
||||||
}
|
)
|
||||||
|
@ -18,11 +18,15 @@ package im.vector.app.features.analytics.impl
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.posthog.android.PostHog
|
import com.posthog.android.PostHog
|
||||||
import im.vector.app.BuildConfig
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import im.vector.app.config.analyticsConfig
|
import im.vector.app.features.analytics.AnalyticsConfig
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class PostHogFactory @Inject constructor(private val context: Context) {
|
class PostHogFactory @Inject constructor(
|
||||||
|
private val context: Context,
|
||||||
|
private val analyticsConfig: AnalyticsConfig,
|
||||||
|
private val buildMeta: BuildMeta,
|
||||||
|
) {
|
||||||
|
|
||||||
fun createPosthog(): PostHog {
|
fun createPosthog(): PostHog {
|
||||||
return PostHog.Builder(context, analyticsConfig.postHogApiKey, analyticsConfig.postHogHost)
|
return PostHog.Builder(context, analyticsConfig.postHogApiKey, analyticsConfig.postHogHost)
|
||||||
@ -43,7 +47,7 @@ class PostHogFactory @Inject constructor(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getLogLevel(): PostHog.LogLevel {
|
private fun getLogLevel(): PostHog.LogLevel {
|
||||||
return if (BuildConfig.DEBUG) {
|
return if (buildMeta.isDebug) {
|
||||||
PostHog.LogLevel.DEBUG
|
PostHog.LogLevel.DEBUG
|
||||||
} else {
|
} else {
|
||||||
PostHog.LogLevel.INFO
|
PostHog.LogLevel.INFO
|
||||||
|
@ -29,6 +29,9 @@ import kotlinx.coroutines.flow.map
|
|||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Also accessed via reflection by the instrumentation tests @see [im.vector.app.ClearCurrentSessionRule].
|
||||||
|
*/
|
||||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "vector_analytics")
|
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "vector_analytics")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,17 +22,17 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.config.analyticsConfig
|
|
||||||
import im.vector.app.core.extensions.setTextWithColoredPart
|
import im.vector.app.core.extensions.setTextWithColoredPart
|
||||||
import im.vector.app.core.platform.OnBackPressed
|
import im.vector.app.core.platform.OnBackPressed
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||||
import im.vector.app.databinding.FragmentAnalyticsOptinBinding
|
import im.vector.app.databinding.FragmentAnalyticsOptinBinding
|
||||||
|
import im.vector.app.features.analytics.AnalyticsConfig
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AnalyticsOptInFragment @Inject constructor() :
|
class AnalyticsOptInFragment @Inject constructor(
|
||||||
VectorBaseFragment<FragmentAnalyticsOptinBinding>(),
|
private val analyticsConfig: AnalyticsConfig,
|
||||||
OnBackPressed {
|
) : VectorBaseFragment<FragmentAnalyticsOptinBinding>(), OnBackPressed {
|
||||||
|
|
||||||
// Share the view model with the Activity so that the Activity
|
// Share the view model with the Activity so that the Activity
|
||||||
// can decide what to do when the data has been saved
|
// can decide what to do when the data has been saved
|
||||||
|
@ -26,9 +26,9 @@ import androidx.activity.result.ActivityResultLauncher
|
|||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.dialogs.PhotoOrVideoDialog
|
import im.vector.app.core.dialogs.PhotoOrVideoDialog
|
||||||
import im.vector.app.core.platform.Restorable
|
import im.vector.app.core.platform.Restorable
|
||||||
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.lib.multipicker.MultiPicker
|
import im.vector.lib.multipicker.MultiPicker
|
||||||
import org.matrix.android.sdk.BuildConfig
|
|
||||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@ -38,15 +38,14 @@ private const val PENDING_TYPE_KEY = "PENDING_TYPE_KEY"
|
|||||||
/**
|
/**
|
||||||
* This class helps to handle attachments by providing simple methods.
|
* This class helps to handle attachments by providing simple methods.
|
||||||
*/
|
*/
|
||||||
class AttachmentsHelper(val context: Context, val callback: Callback) : Restorable {
|
class AttachmentsHelper(
|
||||||
|
val context: Context,
|
||||||
|
val callback: Callback,
|
||||||
|
private val buildMeta: BuildMeta,
|
||||||
|
) : Restorable {
|
||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
fun onContactAttachmentReady(contactAttachment: ContactAttachment) {
|
fun onContactAttachmentReady(contactAttachment: ContactAttachment)
|
||||||
if (BuildConfig.LOG_PRIVATE_DATA) {
|
|
||||||
Timber.v("On contact attachment ready: $contactAttachment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>)
|
fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +154,9 @@ class AttachmentsHelper(val context: Context, val callback: Callback) : Restorab
|
|||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.toContactAttachment()
|
?.toContactAttachment()
|
||||||
?.let {
|
?.let {
|
||||||
|
if (buildMeta.lowPrivacyLoggingEnabled) {
|
||||||
|
Timber.v("On contact attachment ready: $it")
|
||||||
|
}
|
||||||
callback.onContactAttachmentReady(it)
|
callback.onContactAttachmentReady(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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.call.webrtc
|
||||||
|
|
||||||
|
data class VoipConfig(
|
||||||
|
val handleCallAssertedIdentityEvents: Boolean
|
||||||
|
)
|
@ -20,7 +20,6 @@ import android.content.Context
|
|||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import im.vector.app.ActiveSessionDataSource
|
import im.vector.app.ActiveSessionDataSource
|
||||||
import im.vector.app.BuildConfig
|
|
||||||
import im.vector.app.core.pushers.UnifiedPushHelper
|
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||||
import im.vector.app.core.services.CallAndroidService
|
import im.vector.app.core.services.CallAndroidService
|
||||||
import im.vector.app.features.analytics.AnalyticsTracker
|
import im.vector.app.features.analytics.AnalyticsTracker
|
||||||
@ -74,6 +73,7 @@ class WebRtcCallManager @Inject constructor(
|
|||||||
private val activeSessionDataSource: ActiveSessionDataSource,
|
private val activeSessionDataSource: ActiveSessionDataSource,
|
||||||
private val analyticsTracker: AnalyticsTracker,
|
private val analyticsTracker: AnalyticsTracker,
|
||||||
private val unifiedPushHelper: UnifiedPushHelper,
|
private val unifiedPushHelper: UnifiedPushHelper,
|
||||||
|
private val voipConfig: VoipConfig,
|
||||||
) : CallListener,
|
) : CallListener,
|
||||||
DefaultLifecycleObserver {
|
DefaultLifecycleObserver {
|
||||||
|
|
||||||
@ -444,7 +444,7 @@ class WebRtcCallManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) {
|
override fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) {
|
||||||
if (!BuildConfig.handleCallAssertedIdentityEvents) {
|
if (!voipConfig.handleCallAssertedIdentityEvents) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val call = callsByCallId[callAssertedIdentityContent.callId]
|
val call = callsByCallId[callAssertedIdentityContent.callId]
|
||||||
|
@ -142,7 +142,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr
|
|||||||
|
|
||||||
dialog.findViewById<View>(R.id.keys_backup_setup_share)?.debouncedClicks {
|
dialog.findViewById<View>(R.id.keys_backup_setup_share)?.debouncedClicks {
|
||||||
startSharePlainTextIntent(
|
startSharePlainTextIntent(
|
||||||
fragment = this,
|
context = requireContext(),
|
||||||
activityResultLauncher = null,
|
activityResultLauncher = null,
|
||||||
chooserTitle = context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
|
chooserTitle = context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
|
||||||
text = recoveryKey,
|
text = recoveryKey,
|
||||||
|
@ -104,7 +104,7 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
|
|||||||
?: return@withState
|
?: return@withState
|
||||||
|
|
||||||
startSharePlainTextIntent(
|
startSharePlainTextIntent(
|
||||||
this,
|
requireContext(),
|
||||||
copyStartForActivityResult,
|
copyStartForActivityResult,
|
||||||
context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
|
context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
|
||||||
recoveryKey,
|
recoveryKey,
|
||||||
|
@ -36,8 +36,8 @@ import com.airbnb.mvrx.viewModel
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import de.spiritcroc.menu.ArrayOptionsMenuHelper
|
import de.spiritcroc.menu.ArrayOptionsMenuHelper
|
||||||
import im.vector.app.AppStateHandler
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.SpaceStateHandler
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.extensions.hideKeyboard
|
import im.vector.app.core.extensions.hideKeyboard
|
||||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||||
@ -49,6 +49,7 @@ import im.vector.app.core.platform.VectorMenuProvider
|
|||||||
import im.vector.app.core.pushers.PushersManager
|
import im.vector.app.core.pushers.PushersManager
|
||||||
import im.vector.app.core.pushers.UnifiedPushHelper
|
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.utils.startSharePlainTextIntent
|
||||||
import im.vector.app.core.utils.toast
|
import im.vector.app.core.utils.toast
|
||||||
import im.vector.app.databinding.ActivityHomeBinding
|
import im.vector.app.databinding.ActivityHomeBinding
|
||||||
import im.vector.app.features.MainActivity
|
import im.vector.app.features.MainActivity
|
||||||
@ -137,7 +138,7 @@ class HomeActivity :
|
|||||||
@Inject lateinit var permalinkHandler: PermalinkHandler
|
@Inject lateinit var permalinkHandler: PermalinkHandler
|
||||||
@Inject lateinit var avatarRenderer: AvatarRenderer
|
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||||
@Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter
|
@Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter
|
||||||
@Inject lateinit var appStateHandler: AppStateHandler
|
@Inject lateinit var spaceStateHandler: SpaceStateHandler
|
||||||
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
|
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
|
||||||
@Inject lateinit var fcmHelper: FcmHelper
|
@Inject lateinit var fcmHelper: FcmHelper
|
||||||
@Inject lateinit var nightlyProxy: NightlyProxy
|
@Inject lateinit var nightlyProxy: NightlyProxy
|
||||||
@ -210,11 +211,16 @@ class HomeActivity :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java)
|
sharedActionViewModel = viewModelProvider[HomeSharedActionViewModel::class.java]
|
||||||
views.drawerLayout.addDrawerListener(drawerListener)
|
views.drawerLayout.addDrawerListener(drawerListener)
|
||||||
if (isFirstCreation()) {
|
if (isFirstCreation()) {
|
||||||
replaceFragment(views.homeDetailFragmentContainer, HomeDetailFragment::class.java)
|
if (vectorFeatures.isNewAppLayoutEnabled()) {
|
||||||
replaceFragment(views.homeDrawerFragmentContainer, HomeDrawerFragment::class.java)
|
views.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||||
|
replaceFragment(views.homeDetailFragmentContainer, NewHomeDetailFragment::class.java)
|
||||||
|
} else {
|
||||||
|
replaceFragment(views.homeDetailFragmentContainer, HomeDetailFragment::class.java)
|
||||||
|
replaceFragment(views.homeDrawerFragmentContainer, HomeDrawerFragment::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedActionViewModel
|
sharedActionViewModel
|
||||||
@ -568,7 +574,7 @@ class HomeActivity :
|
|||||||
nightlyProxy.onHomeResumed()
|
nightlyProxy.onHomeResumed()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMenuRes() = R.menu.home
|
override fun getMenuRes() = if (vectorFeatures.isNewAppLayoutEnabled()) R.menu.menu_new_home else R.menu.menu_home
|
||||||
|
|
||||||
override fun handlePrepareMenu(menu: Menu) {
|
override fun handlePrepareMenu(menu: Menu) {
|
||||||
menu.findItem(R.id.menu_home_init_sync_legacy)?.isVisible = vectorPreferences.developerMode()
|
menu.findItem(R.id.menu_home_init_sync_legacy)?.isVisible = vectorPreferences.developerMode()
|
||||||
@ -640,6 +646,10 @@ class HomeActivity :
|
|||||||
navigator.openSettings(this)
|
navigator.openSettings(this)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.menu_home_invite_friends -> {
|
||||||
|
launchInviteFriends()
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,6 +659,21 @@ class HomeActivity :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun launchInviteFriends() {
|
||||||
|
activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink ->
|
||||||
|
analyticsTracker.screen(MobileScreen(screenName = MobileScreen.ScreenName.InviteFriends))
|
||||||
|
val text = getString(R.string.invite_friends_text, permalink)
|
||||||
|
|
||||||
|
startSharePlainTextIntent(
|
||||||
|
context = this,
|
||||||
|
activityResultLauncher = null,
|
||||||
|
chooserTitle = getString(R.string.invite_friends),
|
||||||
|
text = text,
|
||||||
|
extraTitle = getString(R.string.invite_friends_rich_title)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (views.drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
if (views.drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
||||||
views.drawerLayout.closeDrawer(GravityCompat.START)
|
views.drawerLayout.closeDrawer(GravityCompat.START)
|
||||||
|
@ -22,11 +22,11 @@ import com.airbnb.mvrx.ViewModelContext
|
|||||||
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 im.vector.app.config.analyticsConfig
|
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import im.vector.app.features.analytics.AnalyticsConfig
|
||||||
import im.vector.app.features.analytics.AnalyticsTracker
|
import im.vector.app.features.analytics.AnalyticsTracker
|
||||||
import im.vector.app.features.analytics.extensions.toAnalyticsType
|
import im.vector.app.features.analytics.extensions.toAnalyticsType
|
||||||
import im.vector.app.features.analytics.plan.Signup
|
import im.vector.app.features.analytics.plan.Signup
|
||||||
@ -80,7 +80,8 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||||||
private val analyticsStore: AnalyticsStore,
|
private val analyticsStore: AnalyticsStore,
|
||||||
private val lightweightSettingsStorage: LightweightSettingsStorage,
|
private val lightweightSettingsStorage: LightweightSettingsStorage,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val analyticsTracker: AnalyticsTracker
|
private val analyticsTracker: AnalyticsTracker,
|
||||||
|
private val analyticsConfig: AnalyticsConfig,
|
||||||
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
|
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -35,9 +35,9 @@ import com.google.android.material.badge.BadgeDrawable
|
|||||||
import de.spiritcroc.matrixsdk.util.DbgUtil
|
import de.spiritcroc.matrixsdk.util.DbgUtil
|
||||||
import de.spiritcroc.matrixsdk.util.Dimber
|
import de.spiritcroc.matrixsdk.util.Dimber
|
||||||
import de.spiritcroc.viewpager.reduceDragSensitivity
|
import de.spiritcroc.viewpager.reduceDragSensitivity
|
||||||
import im.vector.app.AppStateHandler
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.SelectSpaceFrom
|
import im.vector.app.SelectSpaceFrom
|
||||||
|
import im.vector.app.SpaceStateHandler
|
||||||
import im.vector.app.core.extensions.commitTransaction
|
import im.vector.app.core.extensions.commitTransaction
|
||||||
import im.vector.app.core.extensions.restart
|
import im.vector.app.core.extensions.restart
|
||||||
import im.vector.app.core.extensions.toMvRxBundle
|
import im.vector.app.core.extensions.toMvRxBundle
|
||||||
@ -51,7 +51,6 @@ import im.vector.app.core.ui.views.CurrentCallsView
|
|||||||
import im.vector.app.core.ui.views.CurrentCallsViewPresenter
|
import im.vector.app.core.ui.views.CurrentCallsViewPresenter
|
||||||
import im.vector.app.core.ui.views.KeysBackupBanner
|
import im.vector.app.core.ui.views.KeysBackupBanner
|
||||||
import im.vector.app.databinding.FragmentHomeDetailBinding
|
import im.vector.app.databinding.FragmentHomeDetailBinding
|
||||||
import im.vector.app.features.VectorFeatures
|
|
||||||
import im.vector.app.features.call.SharedKnownCallsViewModel
|
import im.vector.app.features.call.SharedKnownCallsViewModel
|
||||||
import im.vector.app.features.call.VectorCallActivity
|
import im.vector.app.features.call.VectorCallActivity
|
||||||
import im.vector.app.features.call.dialpad.DialPadFragment
|
import im.vector.app.features.call.dialpad.DialPadFragment
|
||||||
@ -60,7 +59,6 @@ import im.vector.app.features.home.room.list.RoomListFragment
|
|||||||
import im.vector.app.features.home.room.list.RoomListParams
|
import im.vector.app.features.home.room.list.RoomListParams
|
||||||
import im.vector.app.features.home.room.list.RoomListSectionBuilder.Companion.SPACE_ID_FOLLOW_APP
|
import im.vector.app.features.home.room.list.RoomListSectionBuilder.Companion.SPACE_ID_FOLLOW_APP
|
||||||
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
|
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
|
||||||
import im.vector.app.features.home.room.list.home.HomeRoomListFragment
|
|
||||||
import im.vector.app.features.popup.PopupAlertManager
|
import im.vector.app.features.popup.PopupAlertManager
|
||||||
import im.vector.app.features.popup.VerificationVectorAlert
|
import im.vector.app.features.popup.VerificationVectorAlert
|
||||||
import im.vector.app.features.settings.VectorLocale
|
import im.vector.app.features.settings.VectorLocale
|
||||||
@ -81,8 +79,7 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
private val alertManager: PopupAlertManager,
|
private val alertManager: PopupAlertManager,
|
||||||
private val callManager: WebRtcCallManager,
|
private val callManager: WebRtcCallManager,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val appStateHandler: AppStateHandler,
|
private val spaceStateHandler: SpaceStateHandler,
|
||||||
private val vectorFeatures: VectorFeatures,
|
|
||||||
) : VectorBaseFragment<FragmentHomeDetailBinding>(),
|
) : VectorBaseFragment<FragmentHomeDetailBinding>(),
|
||||||
KeysBackupBanner.Delegate,
|
KeysBackupBanner.Delegate,
|
||||||
CurrentCallsView.Callback,
|
CurrentCallsView.Callback,
|
||||||
@ -157,7 +154,7 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
// Reduce sensitivity of viewpager to avoid scrolling horizontally by accident too easily
|
// Reduce sensitivity of viewpager to avoid scrolling horizontally by accident too easily
|
||||||
views.roomListContainerPager.reduceDragSensitivity(4)
|
views.roomListContainerPager.reduceDragSensitivity(4)
|
||||||
|
|
||||||
// space pager: update appStateHandler's current page to update rest of the UI accordingly
|
// space pager: update spaceStateHandler's current page to update rest of the UI accordingly
|
||||||
views.roomListContainerPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
views.roomListContainerPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
viewPagerDimber.i{"Home pager: selected page $position $initialPageSelected"}
|
viewPagerDimber.i{"Home pager: selected page $position $initialPageSelected"}
|
||||||
@ -251,7 +248,7 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
|
|
||||||
private fun selectSpaceFromSwipe(position: Int) {
|
private fun selectSpaceFromSwipe(position: Int) {
|
||||||
val selectedId = getSpaceIdForPageIndex(position)
|
val selectedId = getSpaceIdForPageIndex(position)
|
||||||
appStateHandler.setCurrentSpace(selectedId, from = SelectSpaceFrom.SWIPE)
|
spaceStateHandler.setCurrentSpace(selectedId, from = SelectSpaceFrom.SWIPE)
|
||||||
if (pagerPagingEnabled) {
|
if (pagerPagingEnabled) {
|
||||||
onSpaceChange(
|
onSpaceChange(
|
||||||
selectedId?.let { viewModel.getRoom(it)?.roomSummary() }
|
selectedId?.let { viewModel.getRoom(it)?.roomSummary() }
|
||||||
@ -260,13 +257,13 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateBack() {
|
private fun navigateBack() {
|
||||||
val previousSpaceId = appStateHandler.getSpaceBackstack().removeLastOrNull()
|
val previousSpaceId = spaceStateHandler.getSpaceBackstack().removeLastOrNull()
|
||||||
val parentSpaceId = appStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull()
|
val parentSpaceId = spaceStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull()
|
||||||
setCurrentSpace(previousSpaceId ?: parentSpaceId)
|
setCurrentSpace(previousSpaceId ?: parentSpaceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setCurrentSpace(spaceId: String?) {
|
private fun setCurrentSpace(spaceId: String?) {
|
||||||
appStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false)
|
spaceStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false)
|
||||||
sharedActionViewModel.post(HomeActivitySharedAction.OnCloseSpace)
|
sharedActionViewModel.post(HomeActivitySharedAction.OnCloseSpace)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,13 +295,13 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
|
|
||||||
private fun refreshSpaceState() {
|
private fun refreshSpaceState() {
|
||||||
/* Upstream impl without care for viewPager
|
/* Upstream impl without care for viewPager
|
||||||
appStateHandler.getCurrentSpace()?.let {
|
spaceStateHandler.getCurrentSpace()?.let {
|
||||||
onSpaceChange(it)
|
onSpaceChange(it)
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Current space/group is not live so at least refresh toolbar on resume
|
// Current space/group is not live so at least refresh toolbar on resume
|
||||||
appStateHandler.getCurrentSpace()?.let { currentSpace ->
|
spaceStateHandler.getCurrentSpace()?.let { currentSpace ->
|
||||||
// While paging is enabled, make title follow the view pager directly
|
// While paging is enabled, make title follow the view pager directly
|
||||||
if (pagerPagingEnabled) {
|
if (pagerPagingEnabled) {
|
||||||
return@let
|
return@let
|
||||||
@ -317,7 +314,7 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
super.onPause()
|
super.onPause()
|
||||||
|
|
||||||
// Persist swiped
|
// Persist swiped
|
||||||
appStateHandler.persistSelectedSpace()
|
spaceStateHandler.persistSelectedSpace()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkNotificationTabStatus(enableDialPad: Boolean? = null) {
|
private fun checkNotificationTabStatus(enableDialPad: Boolean? = null) {
|
||||||
@ -513,12 +510,8 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
if (fragmentToShow == null) {
|
if (fragmentToShow == null) {
|
||||||
when (tab) {
|
when (tab) {
|
||||||
is HomeTab.RoomList -> {
|
is HomeTab.RoomList -> {
|
||||||
if (vectorFeatures.isNewAppLayoutEnabled()) {
|
val params = RoomListParams(tab.displayMode)
|
||||||
add(R.id.roomListContainer, HomeRoomListFragment::class.java, null, fragmentTag)
|
add(R.id.roomListContainer, RoomListFragment::class.java, params.toMvRxBundle(), fragmentTag)
|
||||||
} else {
|
|
||||||
val params = RoomListParams(tab.displayMode)
|
|
||||||
add(R.id.roomListContainer, RoomListFragment::class.java, params.toMvRxBundle(), fragmentTag)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is HomeTab.DialPad -> {
|
is HomeTab.DialPad -> {
|
||||||
add(R.id.roomListContainer, createDialPadFragment(), fragmentTag)
|
add(R.id.roomListContainer, createDialPadFragment(), fragmentTag)
|
||||||
@ -601,7 +594,7 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// In case the last space change was caused by swiping, we don't want to lose it
|
// In case the last space change was caused by swiping, we don't want to lose it
|
||||||
appStateHandler.persistSelectedSpace()
|
spaceStateHandler.persistSelectedSpace()
|
||||||
pagerSpaces = safeSpaces
|
pagerSpaces = safeSpaces
|
||||||
pagerTab = tab
|
pagerTab = tab
|
||||||
pagerPagingEnabled = pagingEnabled
|
pagerPagingEnabled = pagingEnabled
|
||||||
@ -796,7 +789,7 @@ class HomeDetailFragment @Inject constructor(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed(toolbarButton: Boolean) = if (vectorPreferences.spaceBackNavigation() && appStateHandler.getCurrentSpace() != null) {
|
override fun onBackPressed(toolbarButton: Boolean) = if (vectorPreferences.spaceBackNavigation() && spaceStateHandler.getCurrentSpace() != null) {
|
||||||
navigateBack()
|
navigateBack()
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
@ -22,7 +22,7 @@ import com.airbnb.mvrx.ViewModelContext
|
|||||||
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 im.vector.app.AppStateHandler
|
import im.vector.app.SpaceStateHandler
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.extensions.singletonEntryPoint
|
import im.vector.app.core.extensions.singletonEntryPoint
|
||||||
@ -76,7 +76,7 @@ class HomeDetailViewModel @AssistedInject constructor(
|
|||||||
private val vectorDataStore: VectorDataStore,
|
private val vectorDataStore: VectorDataStore,
|
||||||
private val callManager: WebRtcCallManager,
|
private val callManager: WebRtcCallManager,
|
||||||
private val directRoomHelper: DirectRoomHelper,
|
private val directRoomHelper: DirectRoomHelper,
|
||||||
private val appStateHandler: AppStateHandler,
|
private val spaceStateHandler: SpaceStateHandler,
|
||||||
private val autoAcceptInvites: AutoAcceptInvites,
|
private val autoAcceptInvites: AutoAcceptInvites,
|
||||||
private val vectorOverrides: VectorOverrides
|
private val vectorOverrides: VectorOverrides
|
||||||
) : VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
|
) : VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
|
||||||
@ -215,13 +215,13 @@ class HomeDetailViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun observeSelectedSpace() {
|
private fun observeSelectedSpace() {
|
||||||
appStateHandler.selectedSpaceFlow
|
spaceStateHandler.getSelectedSpaceFlow()
|
||||||
.setOnEach {
|
.setOnEach {
|
||||||
copy(
|
copy(
|
||||||
selectedSpace = it.orNull()
|
selectedSpace = it.orNull()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
appStateHandler.selectedSpaceFlowIgnoreSwipe
|
spaceStateHandler.getSelectedSpaceFlowIgnoreSwipe()
|
||||||
.setOnEach {
|
.setOnEach {
|
||||||
copy(
|
copy(
|
||||||
selectedSpaceIgnoreSwipe = it.orNull()
|
selectedSpaceIgnoreSwipe = it.orNull()
|
||||||
@ -230,7 +230,7 @@ class HomeDetailViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRoomSummaries() {
|
private fun observeRoomSummaries() {
|
||||||
appStateHandler.selectedSpaceFlow.distinctUntilChanged().flatMapLatest {
|
spaceStateHandler.getSelectedSpaceFlow().distinctUntilChanged().flatMapLatest {
|
||||||
// we use it as a trigger to all changes in room, but do not really load
|
// we use it as a trigger to all changes in room, but do not really load
|
||||||
// the actual models
|
// the actual models
|
||||||
session.roomService().getPagedRoomSummariesLive(
|
session.roomService().getPagedRoomSummariesLive(
|
||||||
@ -242,7 +242,7 @@ class HomeDetailViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
.throttleFirst(300)
|
.throttleFirst(300)
|
||||||
.onEach {
|
.onEach {
|
||||||
val activeSpaceRoomId = appStateHandler.getCurrentSpace()?.roomId
|
val activeSpaceRoomId = spaceStateHandler.getCurrentSpace()?.roomId
|
||||||
var dmInvites = 0
|
var dmInvites = 0
|
||||||
var roomsInvite = 0
|
var roomsInvite = 0
|
||||||
if (autoAcceptInvites.showInvites()) {
|
if (autoAcceptInvites.showInvites()) {
|
||||||
|
@ -23,11 +23,11 @@ import android.view.ViewGroup
|
|||||||
import androidx.core.app.ActivityOptionsCompat
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import im.vector.app.BuildConfig
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.observeK
|
import im.vector.app.core.extensions.observeK
|
||||||
import im.vector.app.core.extensions.replaceChildFragment
|
import im.vector.app.core.extensions.replaceChildFragment
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import im.vector.app.core.utils.startSharePlainTextIntent
|
import im.vector.app.core.utils.startSharePlainTextIntent
|
||||||
import im.vector.app.databinding.FragmentHomeDrawerBinding
|
import im.vector.app.databinding.FragmentHomeDrawerBinding
|
||||||
import im.vector.app.features.analytics.plan.MobileScreen
|
import im.vector.app.features.analytics.plan.MobileScreen
|
||||||
@ -44,7 +44,8 @@ import javax.inject.Inject
|
|||||||
class HomeDrawerFragment @Inject constructor(
|
class HomeDrawerFragment @Inject constructor(
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val avatarRenderer: AvatarRenderer
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val buildMeta: BuildMeta,
|
||||||
) : VectorBaseFragment<FragmentHomeDrawerBinding>() {
|
) : VectorBaseFragment<FragmentHomeDrawerBinding>() {
|
||||||
|
|
||||||
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
|
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
|
||||||
@ -103,7 +104,7 @@ class HomeDrawerFragment @Inject constructor(
|
|||||||
val text = getString(R.string.invite_friends_text, permalink)
|
val text = getString(R.string.invite_friends_text, permalink)
|
||||||
|
|
||||||
startSharePlainTextIntent(
|
startSharePlainTextIntent(
|
||||||
fragment = this,
|
context = requireContext(),
|
||||||
activityResultLauncher = null,
|
activityResultLauncher = null,
|
||||||
chooserTitle = getString(R.string.invite_friends),
|
chooserTitle = getString(R.string.invite_friends),
|
||||||
text = text,
|
text = text,
|
||||||
@ -113,7 +114,7 @@ class HomeDrawerFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Debug menu
|
// Debug menu
|
||||||
views.homeDrawerHeaderDebugView.isVisible = BuildConfig.DEBUG && vectorPreferences.developerMode()
|
views.homeDrawerHeaderDebugView.isVisible = buildMeta.isDebug && vectorPreferences.developerMode()
|
||||||
views.homeDrawerHeaderDebugView.debouncedClicks {
|
views.homeDrawerHeaderDebugView.debouncedClicks {
|
||||||
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
|
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
|
||||||
navigator.openDebug(requireActivity())
|
navigator.openDebug(requireActivity())
|
||||||
|
@ -0,0 +1,459 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.home
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.airbnb.mvrx.activityViewModel
|
||||||
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import com.google.android.material.badge.BadgeDrawable
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.SpaceStateHandler
|
||||||
|
import im.vector.app.core.extensions.commitTransaction
|
||||||
|
import im.vector.app.core.platform.OnBackPressed
|
||||||
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.platform.VectorMenuProvider
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.ui.views.CurrentCallsView
|
||||||
|
import im.vector.app.core.ui.views.CurrentCallsViewPresenter
|
||||||
|
import im.vector.app.core.ui.views.KeysBackupBanner
|
||||||
|
import im.vector.app.databinding.FragmentNewHomeDetailBinding
|
||||||
|
import im.vector.app.features.call.SharedKnownCallsViewModel
|
||||||
|
import im.vector.app.features.call.VectorCallActivity
|
||||||
|
import im.vector.app.features.call.dialpad.DialPadFragment
|
||||||
|
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||||
|
import im.vector.app.features.home.room.list.home.HomeRoomListFragment
|
||||||
|
import im.vector.app.features.popup.PopupAlertManager
|
||||||
|
import im.vector.app.features.popup.VerificationVectorAlert
|
||||||
|
import im.vector.app.features.settings.VectorLocale
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS
|
||||||
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
|
import im.vector.app.features.workers.signout.BannerState
|
||||||
|
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class NewHomeDetailFragment @Inject constructor(
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val colorProvider: ColorProvider,
|
||||||
|
private val alertManager: PopupAlertManager,
|
||||||
|
private val callManager: WebRtcCallManager,
|
||||||
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
private val appStateHandler: SpaceStateHandler,
|
||||||
|
private val session: Session,
|
||||||
|
) : VectorBaseFragment<FragmentNewHomeDetailBinding>(),
|
||||||
|
KeysBackupBanner.Delegate,
|
||||||
|
CurrentCallsView.Callback,
|
||||||
|
OnBackPressed,
|
||||||
|
VectorMenuProvider {
|
||||||
|
|
||||||
|
private val viewModel: HomeDetailViewModel by fragmentViewModel()
|
||||||
|
private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel()
|
||||||
|
private val unreadMessagesSharedViewModel: UnreadMessagesSharedViewModel by activityViewModel()
|
||||||
|
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by activityViewModel()
|
||||||
|
|
||||||
|
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
|
||||||
|
private lateinit var sharedCallActionViewModel: SharedKnownCallsViewModel
|
||||||
|
|
||||||
|
private var hasUnreadRooms = false
|
||||||
|
set(value) {
|
||||||
|
if (value != field) {
|
||||||
|
field = value
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMenuRes() = R.menu.room_list
|
||||||
|
|
||||||
|
override fun handleMenuItemSelected(item: MenuItem): Boolean {
|
||||||
|
return when (item.itemId) {
|
||||||
|
R.id.menu_home_mark_all_as_read -> {
|
||||||
|
viewModel.handle(HomeDetailAction.MarkAllRoomsRead)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handlePrepareMenu(menu: Menu) {
|
||||||
|
withState(viewModel) { state ->
|
||||||
|
val isRoomList = state.currentTab is HomeTab.RoomList
|
||||||
|
menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = isRoomList && hasUnreadRooms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentNewHomeDetailBinding {
|
||||||
|
return FragmentNewHomeDetailBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val currentCallsViewPresenter = CurrentCallsViewPresenter()
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
|
||||||
|
sharedCallActionViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java)
|
||||||
|
setupBottomNavigationView()
|
||||||
|
setupToolbar()
|
||||||
|
setupKeysBackupBanner()
|
||||||
|
setupActiveCallView()
|
||||||
|
|
||||||
|
withState(viewModel) {
|
||||||
|
// Update the navigation view if needed (for when we restore the tabs)
|
||||||
|
views.bottomNavigationView.selectedItemId = it.currentTab.toMenuId()
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.onEach(HomeDetailViewState::selectedSpace) { selectedSpace ->
|
||||||
|
onSpaceChange(selectedSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.onEach(HomeDetailViewState::currentTab) { currentTab ->
|
||||||
|
updateUIForTab(currentTab)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.onEach(HomeDetailViewState::showDialPadTab) { showDialPadTab ->
|
||||||
|
updateTabVisibilitySafely(R.id.bottom_action_dial_pad, showDialPadTab)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.observeViewEvents { viewEvent ->
|
||||||
|
when (viewEvent) {
|
||||||
|
HomeDetailViewEvents.CallStarted -> handleCallStarted()
|
||||||
|
is HomeDetailViewEvents.FailToCall -> showFailure(viewEvent.failure)
|
||||||
|
HomeDetailViewEvents.Loading -> showLoadingDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unknownDeviceDetectorSharedViewModel.onEach { state ->
|
||||||
|
state.unknownSessions.invoke()?.let { unknownDevices ->
|
||||||
|
if (unknownDevices.firstOrNull()?.currentSessionTrust == true) {
|
||||||
|
val uid = "review_login"
|
||||||
|
alertManager.cancelAlert(uid)
|
||||||
|
val olderUnverified = unknownDevices.filter { !it.isNew }
|
||||||
|
val newest = unknownDevices.firstOrNull { it.isNew }?.deviceInfo
|
||||||
|
if (newest != null) {
|
||||||
|
promptForNewUnknownDevices(uid, state, newest)
|
||||||
|
} else if (olderUnverified.isNotEmpty()) {
|
||||||
|
// In this case we prompt to go to settings to review logins
|
||||||
|
promptToReviewChanges(uid, state, olderUnverified.map { it.deviceInfo })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedCallActionViewModel
|
||||||
|
.liveKnownCalls
|
||||||
|
.observe(viewLifecycleOwner) {
|
||||||
|
currentCallsViewPresenter.updateCall(callManager.getCurrentCall(), callManager.getCalls())
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateBack() {
|
||||||
|
val previousSpaceId = appStateHandler.getSpaceBackstack().removeLastOrNull()
|
||||||
|
val parentSpaceId = appStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull()
|
||||||
|
setCurrentSpace(previousSpaceId ?: parentSpaceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setCurrentSpace(spaceId: String?) {
|
||||||
|
appStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false)
|
||||||
|
sharedActionViewModel.post(HomeActivitySharedAction.OnCloseSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCallStarted() {
|
||||||
|
dismissLoadingDialog()
|
||||||
|
val fragmentTag = HomeTab.DialPad.toFragmentTag()
|
||||||
|
(childFragmentManager.findFragmentByTag(fragmentTag) as? DialPadFragment)?.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
currentCallsViewPresenter.unBind()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
updateTabVisibilitySafely(R.id.bottom_action_notification, vectorPreferences.labAddNotificationTab())
|
||||||
|
callManager.checkForProtocolsSupportIfNeeded()
|
||||||
|
refreshSpaceState()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshSpaceState() {
|
||||||
|
appStateHandler.getCurrentSpace()?.let {
|
||||||
|
onSpaceChange(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun promptForNewUnknownDevices(uid: String, state: UnknownDevicesState, newest: DeviceInfo) {
|
||||||
|
val user = state.myMatrixItem
|
||||||
|
alertManager.postVectorAlert(
|
||||||
|
VerificationVectorAlert(
|
||||||
|
uid = uid,
|
||||||
|
title = getString(R.string.new_session),
|
||||||
|
description = getString(R.string.verify_this_session, newest.displayName ?: newest.deviceId ?: ""),
|
||||||
|
iconId = R.drawable.ic_shield_warning
|
||||||
|
).apply {
|
||||||
|
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
|
||||||
|
colorInt = colorProvider.getColorFromAttribute(R.attr.colorPrimary)
|
||||||
|
contentAction = Runnable {
|
||||||
|
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)
|
||||||
|
?.navigator
|
||||||
|
?.requestSessionVerification(requireContext(), newest.deviceId ?: "")
|
||||||
|
unknownDeviceDetectorSharedViewModel.handle(
|
||||||
|
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dismissedAction = Runnable {
|
||||||
|
unknownDeviceDetectorSharedViewModel.handle(
|
||||||
|
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun promptToReviewChanges(uid: String, state: UnknownDevicesState, oldUnverified: List<DeviceInfo>) {
|
||||||
|
val user = state.myMatrixItem
|
||||||
|
alertManager.postVectorAlert(
|
||||||
|
VerificationVectorAlert(
|
||||||
|
uid = uid,
|
||||||
|
title = getString(R.string.review_logins),
|
||||||
|
description = getString(R.string.verify_other_sessions),
|
||||||
|
iconId = R.drawable.ic_shield_warning
|
||||||
|
).apply {
|
||||||
|
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
|
||||||
|
colorInt = colorProvider.getColorFromAttribute(R.attr.colorPrimary)
|
||||||
|
contentAction = Runnable {
|
||||||
|
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let { activity ->
|
||||||
|
// mark as ignored to avoid showing it again
|
||||||
|
unknownDeviceDetectorSharedViewModel.handle(
|
||||||
|
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(oldUnverified.mapNotNull { it.deviceId })
|
||||||
|
)
|
||||||
|
activity.navigator.openSettings(activity, EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dismissedAction = Runnable {
|
||||||
|
unknownDeviceDetectorSharedViewModel.handle(
|
||||||
|
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(oldUnverified.mapNotNull { it.deviceId })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSpaceChange(spaceSummary: RoomSummary?) {
|
||||||
|
// Reimplement in next PR
|
||||||
|
println(spaceSummary)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupKeysBackupBanner() {
|
||||||
|
serverBackupStatusViewModel
|
||||||
|
.onEach {
|
||||||
|
when (val banState = it.bannerState.invoke()) {
|
||||||
|
is BannerState.Setup -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false)
|
||||||
|
BannerState.BackingUp -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false)
|
||||||
|
null,
|
||||||
|
BannerState.Hidden -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Hidden, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
views.homeKeysBackupBanner.delegate = this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupActiveCallView() {
|
||||||
|
currentCallsViewPresenter.bind(views.currentCallsView, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupToolbar() {
|
||||||
|
setupToolbar(views.toolbar)
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
session.userService().getUser(session.myUserId)?.let { user ->
|
||||||
|
avatarRenderer.render(user.toMatrixItem(), views.avatar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
views.avatar.debouncedClicks {
|
||||||
|
navigator.openSettings(requireContext())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupBottomNavigationView() {
|
||||||
|
views.bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab()
|
||||||
|
views.bottomNavigationView.setOnItemSelectedListener {
|
||||||
|
val tab = when (it.itemId) {
|
||||||
|
R.id.bottom_action_people -> HomeTab.RoomList(RoomListDisplayMode.PEOPLE)
|
||||||
|
R.id.bottom_action_rooms -> HomeTab.RoomList(RoomListDisplayMode.ROOMS)
|
||||||
|
R.id.bottom_action_notification -> HomeTab.RoomList(RoomListDisplayMode.NOTIFICATIONS)
|
||||||
|
else -> HomeTab.DialPad
|
||||||
|
}
|
||||||
|
viewModel.handle(HomeDetailAction.SwitchTab(tab))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUIForTab(tab: HomeTab) {
|
||||||
|
views.bottomNavigationView.menu.findItem(tab.toMenuId()).isChecked = true
|
||||||
|
updateSelectedFragment(tab)
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun HomeTab.toFragmentTag() = "FRAGMENT_TAG_$this"
|
||||||
|
|
||||||
|
private fun updateSelectedFragment(tab: HomeTab) {
|
||||||
|
val fragmentTag = tab.toFragmentTag()
|
||||||
|
val fragmentToShow = childFragmentManager.findFragmentByTag(fragmentTag)
|
||||||
|
childFragmentManager.commitTransaction {
|
||||||
|
childFragmentManager.fragments
|
||||||
|
.filter { it != fragmentToShow }
|
||||||
|
.forEach {
|
||||||
|
detach(it)
|
||||||
|
}
|
||||||
|
if (fragmentToShow == null) {
|
||||||
|
when (tab) {
|
||||||
|
is HomeTab.RoomList -> {
|
||||||
|
add(R.id.roomListContainer, HomeRoomListFragment::class.java, null, fragmentTag)
|
||||||
|
}
|
||||||
|
is HomeTab.DialPad -> {
|
||||||
|
add(R.id.roomListContainer, createDialPadFragment(), fragmentTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (tab is HomeTab.DialPad) {
|
||||||
|
(fragmentToShow as? DialPadFragment)?.applyCallback()
|
||||||
|
}
|
||||||
|
attach(fragmentToShow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDialPadFragment(): Fragment {
|
||||||
|
val fragment = childFragmentManager.fragmentFactory.instantiate(vectorBaseActivity.classLoader, DialPadFragment::class.java.name)
|
||||||
|
return (fragment as DialPadFragment).apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true)
|
||||||
|
putBoolean(DialPadFragment.EXTRA_ENABLE_OK, true)
|
||||||
|
putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country)
|
||||||
|
}
|
||||||
|
applyCallback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTabVisibilitySafely(tabId: Int, isVisible: Boolean) {
|
||||||
|
val wasVisible = views.bottomNavigationView.menu.findItem(tabId).isVisible
|
||||||
|
views.bottomNavigationView.menu.findItem(tabId).isVisible = isVisible
|
||||||
|
if (wasVisible && !isVisible) {
|
||||||
|
// As we hide it check if it's not the current item!
|
||||||
|
withState(viewModel) {
|
||||||
|
if (it.currentTab.toMenuId() == tabId) {
|
||||||
|
viewModel.handle(HomeDetailAction.SwitchTab(HomeTab.RoomList(RoomListDisplayMode.PEOPLE)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================================
|
||||||
|
* KeysBackupBanner Listener
|
||||||
|
* ========================================================================================== */
|
||||||
|
|
||||||
|
override fun setupKeysBackup() {
|
||||||
|
navigator.openKeysBackupSetup(requireActivity(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun recoverKeysBackup() {
|
||||||
|
navigator.openKeysBackupManager(requireActivity())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) {
|
||||||
|
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople)
|
||||||
|
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
|
||||||
|
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
|
||||||
|
views.syncStateView.render(
|
||||||
|
it.syncState,
|
||||||
|
it.incrementalSyncRequestState,
|
||||||
|
it.pushCounter,
|
||||||
|
vectorPreferences.developerShowDebugInfo()
|
||||||
|
)
|
||||||
|
|
||||||
|
hasUnreadRooms = it.hasUnreadMessages
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BadgeDrawable.render(count: Int, highlight: Boolean) {
|
||||||
|
isVisible = count > 0
|
||||||
|
number = count
|
||||||
|
maxCharacterCount = 3
|
||||||
|
badgeTextColor = ThemeUtils.getColor(requireContext(), R.attr.colorOnPrimary)
|
||||||
|
backgroundColor = if (highlight) {
|
||||||
|
ThemeUtils.getColor(requireContext(), R.attr.colorError)
|
||||||
|
} else {
|
||||||
|
ThemeUtils.getColor(requireContext(), R.attr.vctr_unread_background)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun HomeTab.toMenuId() = when (this) {
|
||||||
|
is HomeTab.DialPad -> R.id.bottom_action_dial_pad
|
||||||
|
is HomeTab.RoomList -> when (displayMode) {
|
||||||
|
RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people
|
||||||
|
RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms
|
||||||
|
else -> R.id.bottom_action_notification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTapToReturnToCall() {
|
||||||
|
callManager.getCurrentCall()?.let { call ->
|
||||||
|
VectorCallActivity.newIntent(
|
||||||
|
context = requireContext(),
|
||||||
|
callId = call.callId,
|
||||||
|
signalingRoomId = call.signalingRoomId,
|
||||||
|
otherUserId = call.mxCall.opponentUserId,
|
||||||
|
isIncomingCall = !call.mxCall.isOutgoing,
|
||||||
|
isVideoCall = call.mxCall.isVideoCall,
|
||||||
|
mode = null
|
||||||
|
).let {
|
||||||
|
startActivity(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DialPadFragment.applyCallback(): DialPadFragment {
|
||||||
|
callback = object : DialPadFragment.Callback {
|
||||||
|
override fun onOkClicked(formatted: String?, raw: String?) {
|
||||||
|
if (raw.isNullOrEmpty()) return
|
||||||
|
viewModel.handle(HomeDetailAction.StartCallWithPhoneNumber(raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed(toolbarButton: Boolean) = if (appStateHandler.getCurrentSpace() != null) {
|
||||||
|
navigateBack()
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
@ -24,8 +24,8 @@ import androidx.annotation.WorkerThread
|
|||||||
import androidx.core.content.pm.ShortcutInfoCompat
|
import androidx.core.content.pm.ShortcutInfoCompat
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import im.vector.app.BuildConfig
|
|
||||||
import im.vector.app.core.glide.GlideApp
|
import im.vector.app.core.glide.GlideApp
|
||||||
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import im.vector.app.core.utils.DimensionConverter
|
import im.vector.app.core.utils.DimensionConverter
|
||||||
import im.vector.app.features.MainActivity
|
import im.vector.app.features.MainActivity
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
@ -35,13 +35,15 @@ import javax.inject.Inject
|
|||||||
private val useAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
private val useAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||||
private const val adaptiveIconSizeDp = 108
|
private const val adaptiveIconSizeDp = 108
|
||||||
private const val adaptiveIconOuterSidesDp = 18
|
private const val adaptiveIconOuterSidesDp = 18
|
||||||
private const val directShareCategory = BuildConfig.APPLICATION_ID + ".SHORTCUT_SHARE"
|
|
||||||
|
|
||||||
class ShortcutCreator @Inject constructor(
|
class ShortcutCreator @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val dimensionConverter: DimensionConverter
|
private val dimensionConverter: DimensionConverter,
|
||||||
|
buildMeta: BuildMeta,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
private val directShareCategory = buildMeta.applicationId + ".SHORTCUT_SHARE"
|
||||||
private val adaptiveIconSize = dimensionConverter.dpToPx(adaptiveIconSizeDp)
|
private val adaptiveIconSize = dimensionConverter.dpToPx(adaptiveIconSizeDp)
|
||||||
private val adaptiveIconOuterSides = dimensionConverter.dpToPx(adaptiveIconOuterSidesDp)
|
private val adaptiveIconOuterSides = dimensionConverter.dpToPx(adaptiveIconOuterSidesDp)
|
||||||
private val iconSize by lazy {
|
private val iconSize by lazy {
|
||||||
|
@ -22,7 +22,7 @@ import com.airbnb.mvrx.MavericksViewModelFactory
|
|||||||
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 im.vector.app.AppStateHandler
|
import im.vector.app.SpaceStateHandler
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.EmptyAction
|
import im.vector.app.core.platform.EmptyAction
|
||||||
@ -58,7 +58,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(
|
|||||||
@Assisted initialState: UnreadMessagesState,
|
@Assisted initialState: UnreadMessagesState,
|
||||||
session: Session,
|
session: Session,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
//appStateHandler: AppStateHandler,
|
//spaceStateHandler: SpaceStateHandler,
|
||||||
private val autoAcceptInvites: AutoAcceptInvites
|
private val autoAcceptInvites: AutoAcceptInvites
|
||||||
) :
|
) :
|
||||||
VectorViewModel<UnreadMessagesState, EmptyAction, EmptyViewEvents>(initialState) {
|
VectorViewModel<UnreadMessagesState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||||
@ -118,8 +118,8 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(
|
|||||||
.execute {
|
.execute {
|
||||||
/*
|
/*
|
||||||
combine(
|
combine(
|
||||||
appStateHandler.selectedSpaceFlow.distinctUntilChanged(),
|
spaceStateHandler.getSelectedSpaceFlow().distinctUntilChanged(),
|
||||||
appStateHandler.selectedSpaceFlow.flatMapLatest {
|
spaceStateHandler.getSelectedSpaceFlow().flatMapLatest {
|
||||||
roomService.getPagedRoomSummariesLive(
|
roomService.getPagedRoomSummariesLive(
|
||||||
roomSummaryQueryParams {
|
roomSummaryQueryParams {
|
||||||
this.memberships = Membership.activeMemberships()
|
this.memberships = Membership.activeMemberships()
|
||||||
@ -201,10 +201,10 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(
|
|||||||
CountInfo(
|
CountInfo(
|
||||||
homeCount = counts,
|
homeCount = counts,
|
||||||
otherCount = RoomAggregateNotificationCount(
|
otherCount = RoomAggregateNotificationCount(
|
||||||
notificationCount = rootCounts.fold(0, { acc, rs -> acc + rs.notificationCount }) +
|
notificationCount = rootCounts.fold(0) { acc, rs -> acc + rs.notificationCount } +
|
||||||
(counts.notificationCount.takeIf { selectedSpace != null } ?: 0) +
|
(counts.notificationCount.takeIf { selectedSpace != null } ?: 0) +
|
||||||
spaceInviteCount,
|
spaceInviteCount,
|
||||||
highlightCount = rootCounts.fold(0, { acc, rs -> acc + rs.highlightCount }) +
|
highlightCount = rootCounts.fold(0) { acc, rs -> acc + rs.highlightCount } +
|
||||||
(counts.highlightCount.takeIf { selectedSpace != null } ?: 0) +
|
(counts.highlightCount.takeIf { selectedSpace != null } ?: 0) +
|
||||||
spaceInviteCount,
|
spaceInviteCount,
|
||||||
|
|
||||||
|
@ -78,7 +78,6 @@ import de.spiritcroc.recyclerview.StickyHeaderItemDecoration
|
|||||||
import de.spiritcroc.recyclerview.widget.BetterLinearLayoutManager
|
import de.spiritcroc.recyclerview.widget.BetterLinearLayoutManager
|
||||||
import de.spiritcroc.recyclerview.widget.LinearLayoutManager
|
import de.spiritcroc.recyclerview.widget.LinearLayoutManager
|
||||||
import de.spiritcroc.util.ThumbnailGenerationVideoDownloadDecider
|
import de.spiritcroc.util.ThumbnailGenerationVideoDownloadDecider
|
||||||
import im.vector.app.BuildConfig
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.animations.play
|
import im.vector.app.core.animations.play
|
||||||
import im.vector.app.core.dialogs.ConfirmationDialogBuilder
|
import im.vector.app.core.dialogs.ConfirmationDialogBuilder
|
||||||
@ -104,6 +103,7 @@ import im.vector.app.core.platform.VectorBaseFragment
|
|||||||
import im.vector.app.core.platform.VectorMenuProvider
|
import im.vector.app.core.platform.VectorMenuProvider
|
||||||
import im.vector.app.core.platform.lifecycleAwareLazy
|
import im.vector.app.core.platform.lifecycleAwareLazy
|
||||||
import im.vector.app.core.platform.showOptimizedSnackbar
|
import im.vector.app.core.platform.showOptimizedSnackbar
|
||||||
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.resources.UserPreferencesProvider
|
import im.vector.app.core.resources.UserPreferencesProvider
|
||||||
import im.vector.app.core.time.Clock
|
import im.vector.app.core.time.Clock
|
||||||
@ -137,6 +137,7 @@ import im.vector.app.core.utils.startInstallFromSourceIntent
|
|||||||
import im.vector.app.core.utils.toast
|
import im.vector.app.core.utils.toast
|
||||||
import im.vector.app.databinding.DialogReportContentBinding
|
import im.vector.app.databinding.DialogReportContentBinding
|
||||||
import im.vector.app.databinding.FragmentTimelineBinding
|
import im.vector.app.databinding.FragmentTimelineBinding
|
||||||
|
import im.vector.app.features.VectorFeatures
|
||||||
import im.vector.app.features.analytics.extensions.toAnalyticsInteraction
|
import im.vector.app.features.analytics.extensions.toAnalyticsInteraction
|
||||||
import im.vector.app.features.analytics.plan.Interaction
|
import im.vector.app.features.analytics.plan.Interaction
|
||||||
import im.vector.app.features.analytics.plan.MobileScreen
|
import im.vector.app.features.analytics.plan.MobileScreen
|
||||||
@ -299,7 +300,9 @@ class TimelineFragment @Inject constructor(
|
|||||||
private val typingHelper: TypingHelper,
|
private val typingHelper: TypingHelper,
|
||||||
private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker,
|
private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker,
|
||||||
private val shareIntentHandler: ShareIntentHandler,
|
private val shareIntentHandler: ShareIntentHandler,
|
||||||
private val clock: Clock
|
private val clock: Clock,
|
||||||
|
private val vectorFeatures: VectorFeatures,
|
||||||
|
private val buildMeta: BuildMeta,
|
||||||
) :
|
) :
|
||||||
VectorBaseFragment<FragmentTimelineBinding>(),
|
VectorBaseFragment<FragmentTimelineBinding>(),
|
||||||
TimelineEventController.Callback,
|
TimelineEventController.Callback,
|
||||||
@ -401,7 +404,7 @@ class TimelineFragment @Inject constructor(
|
|||||||
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
|
||||||
sharedActivityActionViewModel = activityViewModelProvider.get(RoomDetailSharedActionViewModel::class.java)
|
sharedActivityActionViewModel = activityViewModelProvider.get(RoomDetailSharedActionViewModel::class.java)
|
||||||
knownCallsViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java)
|
knownCallsViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java)
|
||||||
attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
|
attachmentsHelper = AttachmentsHelper(requireContext(), this, buildMeta).register()
|
||||||
callActionsHandler = StartCallActionsHandler(
|
callActionsHandler = StartCallActionsHandler(
|
||||||
roomId = timelineArgs.roomId,
|
roomId = timelineArgs.roomId,
|
||||||
fragment = this,
|
fragment = this,
|
||||||
@ -699,8 +702,8 @@ class TimelineFragment @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToLocationLiveMap() {
|
private fun navigateToLiveLocationMap() {
|
||||||
navigator.openLocationLiveMap(
|
navigator.openLiveLocationMap(
|
||||||
context = requireContext(),
|
context = requireContext(),
|
||||||
roomId = timelineArgs.roomId
|
roomId = timelineArgs.roomId
|
||||||
)
|
)
|
||||||
@ -900,11 +903,11 @@ class TimelineFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupLiveLocationIndicator() {
|
private fun setupLiveLocationIndicator() {
|
||||||
views.locationLiveStatusIndicator.stopButton.debouncedClicks {
|
views.liveLocationStatusIndicator.stopButton.debouncedClicks {
|
||||||
timelineViewModel.handle(RoomDetailAction.StopLiveLocationSharing)
|
timelineViewModel.handle(RoomDetailAction.StopLiveLocationSharing)
|
||||||
}
|
}
|
||||||
views.locationLiveStatusIndicator.debouncedClicks {
|
views.liveLocationStatusIndicator.debouncedClicks {
|
||||||
navigateToLocationLiveMap()
|
navigateToLiveLocationMap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1005,7 +1008,7 @@ class TimelineFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
audioMessagePlaybackTracker.makeAllPlaybacksIdle()
|
messageComposerViewModel.endAllVoiceActions()
|
||||||
lazyLoadedViews.unBind()
|
lazyLoadedViews.unBind()
|
||||||
timelineEventController.callback = null
|
timelineEventController.callback = null
|
||||||
timelineEventController.removeModelBuildListener(modelBuildListener)
|
timelineEventController.removeModelBuildListener(modelBuildListener)
|
||||||
@ -1157,7 +1160,7 @@ class TimelineFragment @Inject constructor(
|
|||||||
else -> state.isAllowedToManageWidgets
|
else -> state.isAllowedToManageWidgets
|
||||||
}
|
}
|
||||||
menu.findItem(R.id.video_call).icon?.alpha = if (callButtonsEnabled) 0xFF else 0x40
|
menu.findItem(R.id.video_call).icon?.alpha = if (callButtonsEnabled) 0xFF else 0x40
|
||||||
menu.findItem(R.id.voice_call).icon?.alpha = if (callButtonsEnabled || state.hasActiveElementCallWidget()) 0xFF else 0x40
|
menu.findItem(R.id.voice_call).icon?.alpha = if (callButtonsEnabled || state.hasActiveElementCallWidget()) 0xFF else 0x40
|
||||||
|
|
||||||
val matrixAppsMenuItem = menu.findItem(R.id.open_matrix_apps)
|
val matrixAppsMenuItem = menu.findItem(R.id.open_matrix_apps)
|
||||||
val widgetsCount = state.activeRoomWidgets.invoke()?.size ?: 0
|
val widgetsCount = state.activeRoomWidgets.invoke()?.size ?: 0
|
||||||
@ -1827,7 +1830,7 @@ class TimelineFragment @Inject constructor(
|
|||||||
attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@TimelineFragment)
|
attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@TimelineFragment)
|
||||||
attachmentTypeSelector.setAttachmentVisibility(
|
attachmentTypeSelector.setAttachmentVisibility(
|
||||||
AttachmentTypeSelectorView.Type.LOCATION,
|
AttachmentTypeSelectorView.Type.LOCATION,
|
||||||
BuildConfig.enableLocationSharing
|
vectorFeatures.isLocationSharingEnabled(),
|
||||||
)
|
)
|
||||||
attachmentTypeSelector.setAttachmentVisibility(
|
attachmentTypeSelector.setAttachmentVisibility(
|
||||||
AttachmentTypeSelectorView.Type.POLL, !isThreadTimeLine()
|
AttachmentTypeSelectorView.Type.POLL, !isThreadTimeLine()
|
||||||
@ -1965,7 +1968,7 @@ class TimelineFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateLiveLocationIndicator(isSharingLiveLocation: Boolean) {
|
private fun updateLiveLocationIndicator(isSharingLiveLocation: Boolean) {
|
||||||
views.locationLiveStatusIndicator.isVisible = isSharingLiveLocation
|
views.liveLocationStatusIndicator.isVisible = isSharingLiveLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun FragmentTimelineBinding.hideComposerViews() {
|
private fun FragmentTimelineBinding.hideComposerViews() {
|
||||||
@ -2380,7 +2383,7 @@ class TimelineFragment @Inject constructor(
|
|||||||
handleShowLocationPreview(messageContent, informationData.senderId)
|
handleShowLocationPreview(messageContent, informationData.senderId)
|
||||||
}
|
}
|
||||||
is MessageBeaconInfoContent -> {
|
is MessageBeaconInfoContent -> {
|
||||||
navigateToLocationLiveMap()
|
navigateToLiveLocationMap()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val handled = onThreadSummaryClicked(informationData.eventId, isRootThreadEvent)
|
val handled = onThreadSummaryClicked(informationData.eventId, isRootThreadEvent)
|
||||||
@ -2993,7 +2996,6 @@ class TimelineFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onContactAttachmentReady(contactAttachment: ContactAttachment) {
|
override fun onContactAttachmentReady(contactAttachment: ContactAttachment) {
|
||||||
super.onContactAttachmentReady(contactAttachment)
|
|
||||||
val formattedContact = contactAttachment.toHumanReadable()
|
val formattedContact = contactAttachment.toHumanReadable()
|
||||||
messageComposerViewModel.handle(MessageComposerAction.SendMessage(formattedContact, false))
|
messageComposerViewModel.handle(MessageComposerAction.SendMessage(formattedContact, false))
|
||||||
}
|
}
|
||||||
|
@ -29,12 +29,13 @@ import dagger.assisted.AssistedFactory
|
|||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import de.spiritcroc.matrixsdk.util.DbgUtil
|
import de.spiritcroc.matrixsdk.util.DbgUtil
|
||||||
import de.spiritcroc.matrixsdk.util.Dimber
|
import de.spiritcroc.matrixsdk.util.Dimber
|
||||||
import im.vector.app.AppStateHandler
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.SpaceStateHandler
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.mvrx.runCatchingToAsync
|
import im.vector.app.core.mvrx.runCatchingToAsync
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.core.utils.BehaviorDataSource
|
import im.vector.app.core.utils.BehaviorDataSource
|
||||||
import im.vector.app.features.analytics.AnalyticsTracker
|
import im.vector.app.features.analytics.AnalyticsTracker
|
||||||
@ -54,10 +55,11 @@ import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandle
|
|||||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory
|
import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory
|
||||||
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
||||||
import im.vector.app.features.home.room.typing.TypingHelper
|
import im.vector.app.features.home.room.typing.TypingHelper
|
||||||
import im.vector.app.features.location.LocationSharingServiceConnection
|
|
||||||
import im.vector.app.features.location.live.StopLiveLocationShareUseCase
|
import im.vector.app.features.location.live.StopLiveLocationShareUseCase
|
||||||
|
import im.vector.app.features.location.live.tracking.LocationSharingServiceConnection
|
||||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||||
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
||||||
|
import im.vector.app.features.raw.wellknown.CryptoConfig
|
||||||
import im.vector.app.features.raw.wellknown.getOutboundSessionKeySharingStrategyOrDefault
|
import im.vector.app.features.raw.wellknown.getOutboundSessionKeySharingStrategyOrDefault
|
||||||
import im.vector.app.features.raw.wellknown.withElementWellKnown
|
import im.vector.app.features.raw.wellknown.withElementWellKnown
|
||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
@ -140,8 +142,10 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
private val locationSharingServiceConnection: LocationSharingServiceConnection,
|
private val locationSharingServiceConnection: LocationSharingServiceConnection,
|
||||||
private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase,
|
private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase,
|
||||||
private val redactLiveLocationShareEventUseCase: RedactLiveLocationShareEventUseCase,
|
private val redactLiveLocationShareEventUseCase: RedactLiveLocationShareEventUseCase,
|
||||||
|
private val cryptoConfig: CryptoConfig,
|
||||||
|
buildMeta: BuildMeta,
|
||||||
timelineFactory: TimelineFactory,
|
timelineFactory: TimelineFactory,
|
||||||
appStateHandler: AppStateHandler,
|
spaceStateHandler: SpaceStateHandler,
|
||||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
||||||
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback {
|
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback {
|
||||||
|
|
||||||
@ -160,7 +164,7 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
private val rmDimber = Dimber("ReadMarkerDbg", DbgUtil.DBG_READ_MARKER)
|
private val rmDimber = Dimber("ReadMarkerDbg", DbgUtil.DBG_READ_MARKER)
|
||||||
|
|
||||||
// Same lifecycle than the ViewModel (survive to screen rotation)
|
// Same lifecycle than the ViewModel (survive to screen rotation)
|
||||||
val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope)
|
val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope, buildMeta)
|
||||||
|
|
||||||
// Slot to keep a pending action during permission request
|
// Slot to keep a pending action during permission request
|
||||||
var pendingAction: RoomDetailAction? = null
|
var pendingAction: RoomDetailAction? = null
|
||||||
@ -233,7 +237,7 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
// Ensure to share the outbound session keys with all members
|
// Ensure to share the outbound session keys with all members
|
||||||
if (room.roomCryptoService().isEncrypted()) {
|
if (room.roomCryptoService().isEncrypted()) {
|
||||||
rawService.withElementWellKnown(viewModelScope, session.sessionParams) {
|
rawService.withElementWellKnown(viewModelScope, session.sessionParams) {
|
||||||
val strategy = it.getOutboundSessionKeySharingStrategyOrDefault()
|
val strategy = it.getOutboundSessionKeySharingStrategyOrDefault(cryptoConfig.fallbackKeySharingStrategy)
|
||||||
if (strategy == OutboundSessionKeySharingStrategy.WhenEnteringRoom) {
|
if (strategy == OutboundSessionKeySharingStrategy.WhenEnteringRoom) {
|
||||||
prepareForEncryption()
|
prepareForEncryption()
|
||||||
}
|
}
|
||||||
@ -248,16 +252,16 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
if (initialState.switchToParentSpace) {
|
if (initialState.switchToParentSpace) {
|
||||||
// We are coming from a notification, try to switch to the most relevant space
|
// We are coming from a notification, try to switch to the most relevant space
|
||||||
// so that when hitting back the room will appear in the list
|
// so that when hitting back the room will appear in the list
|
||||||
appStateHandler.getCurrentSpace().let { currentSpace ->
|
spaceStateHandler.getCurrentSpace().let { currentSpace ->
|
||||||
val currentRoomSummary = room.roomSummary() ?: return@let
|
val currentRoomSummary = room.roomSummary() ?: return@let
|
||||||
// nothing we are good
|
// nothing we are good
|
||||||
if ((currentSpace == null && !vectorPreferences.prefSpacesShowAllRoomInHome()) ||
|
if ((currentSpace == null && !vectorPreferences.prefSpacesShowAllRoomInHome()) ||
|
||||||
(currentSpace != null && !currentRoomSummary.flattenParentIds.contains(currentSpace.roomId))) {
|
(currentSpace != null && !currentRoomSummary.flattenParentIds.contains(currentSpace.roomId))) {
|
||||||
// take first one or switch to home
|
// take first one or switch to home
|
||||||
appStateHandler.setCurrentSpace(
|
spaceStateHandler.setCurrentSpace(
|
||||||
currentRoomSummary
|
currentRoomSummary
|
||||||
.flattenParentIds.firstOrNull { it.isNotBlank() },
|
.flattenParentIds.firstOrNull { it.isNotBlank() },
|
||||||
// force persist, because if not on resume the AppStateHandler will resume
|
// force persist, because if not on resume the SpaceStateHandler will resume
|
||||||
// the current space from what was persisted on enter background
|
// the current space from what was persisted on enter background
|
||||||
persistNow = true
|
persistNow = true
|
||||||
)
|
)
|
||||||
@ -731,7 +735,7 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
// Ensure outbound session keys
|
// Ensure outbound session keys
|
||||||
if (room.roomCryptoService().isEncrypted()) {
|
if (room.roomCryptoService().isEncrypted()) {
|
||||||
rawService.withElementWellKnown(viewModelScope, session.sessionParams) {
|
rawService.withElementWellKnown(viewModelScope, session.sessionParams) {
|
||||||
val strategy = it.getOutboundSessionKeySharingStrategyOrDefault()
|
val strategy = it.getOutboundSessionKeySharingStrategyOrDefault(cryptoConfig.fallbackKeySharingStrategy)
|
||||||
if (strategy == OutboundSessionKeySharingStrategy.WhenTyping && action.focused) {
|
if (strategy == OutboundSessionKeySharingStrategy.WhenTyping && action.focused) {
|
||||||
// Should we add some rate limit here, or do it only once per model lifecycle?
|
// Should we add some rate limit here, or do it only once per model lifecycle?
|
||||||
prepareForEncryption()
|
prepareForEncryption()
|
||||||
|
@ -20,7 +20,7 @@ import android.content.Context
|
|||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import im.vector.app.BuildConfig
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
|
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
|
||||||
import im.vector.app.features.voice.VoiceFailure
|
import im.vector.app.features.voice.VoiceFailure
|
||||||
import im.vector.app.features.voice.VoiceRecorder
|
import im.vector.app.features.voice.VoiceRecorder
|
||||||
@ -43,6 +43,7 @@ import javax.inject.Inject
|
|||||||
class AudioMessageHelper @Inject constructor(
|
class AudioMessageHelper @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val playbackTracker: AudioMessagePlaybackTracker,
|
private val playbackTracker: AudioMessagePlaybackTracker,
|
||||||
|
private val buildMeta: BuildMeta,
|
||||||
voiceRecorderProvider: VoiceRecorderProvider
|
voiceRecorderProvider: VoiceRecorderProvider
|
||||||
) {
|
) {
|
||||||
private var mediaPlayer: MediaPlayer? = null
|
private var mediaPlayer: MediaPlayer? = null
|
||||||
@ -88,7 +89,7 @@ class AudioMessageHelper @Inject constructor(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
voiceMessageFile?.let {
|
voiceMessageFile?.let {
|
||||||
val outputFileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", it, "Voice message.${it.extension}")
|
val outputFileUri = FileProvider.getUriForFile(context, buildMeta.applicationId + ".fileProvider", it, "Voice message.${it.extension}")
|
||||||
return outputFileUri
|
return outputFileUri
|
||||||
.toMultiPickerAudioType(context)
|
.toMultiPickerAudioType(context)
|
||||||
?.apply {
|
?.apply {
|
||||||
|
@ -41,7 +41,6 @@ sealed class MessageComposerAction : VectorViewModelAction {
|
|||||||
object PauseRecordingVoiceMessage : MessageComposerAction()
|
object PauseRecordingVoiceMessage : MessageComposerAction()
|
||||||
data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction()
|
data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction()
|
||||||
object PlayOrPauseRecordingPlayback : MessageComposerAction()
|
object PlayOrPauseRecordingPlayback : MessageComposerAction()
|
||||||
data class EndAllVoiceActions(val deleteRecord: Boolean = true) : MessageComposerAction()
|
|
||||||
data class VoiceWaveformTouchedUp(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
|
data class VoiceWaveformTouchedUp(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
|
||||||
data class VoiceWaveformMovedTo(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
|
data class VoiceWaveformMovedTo(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
|
||||||
data class AudioSeekBarMovedTo(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
|
data class AudioSeekBarMovedTo(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
|
||||||
|
@ -107,7 +107,6 @@ class MessageComposerViewModel @AssistedInject constructor(
|
|||||||
is MessageComposerAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action)
|
is MessageComposerAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action)
|
||||||
MessageComposerAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage()
|
MessageComposerAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage()
|
||||||
MessageComposerAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback()
|
MessageComposerAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback()
|
||||||
is MessageComposerAction.EndAllVoiceActions -> handleEndAllVoiceActions(action.deleteRecord)
|
|
||||||
is MessageComposerAction.InitializeVoiceRecorder -> handleInitializeVoiceRecorder(action.attachmentData)
|
is MessageComposerAction.InitializeVoiceRecorder -> handleInitializeVoiceRecorder(action.attachmentData)
|
||||||
is MessageComposerAction.OnEntersBackground -> handleEntersBackground(action.composerText)
|
is MessageComposerAction.OnEntersBackground -> handleEntersBackground(action.composerText)
|
||||||
is MessageComposerAction.VoiceWaveformTouchedUp -> handleVoiceWaveformTouchedUp(action)
|
is MessageComposerAction.VoiceWaveformTouchedUp -> handleVoiceWaveformTouchedUp(action)
|
||||||
@ -889,7 +888,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
|||||||
audioMessageHelper.startOrPauseRecordingPlayback()
|
audioMessageHelper.startOrPauseRecordingPlayback()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEndAllVoiceActions(deleteRecord: Boolean) {
|
fun endAllVoiceActions(deleteRecord: Boolean = true) {
|
||||||
audioMessageHelper.clearTracker()
|
audioMessageHelper.clearTracker()
|
||||||
audioMessageHelper.stopAllVoiceActions(deleteRecord)
|
audioMessageHelper.stopAllVoiceActions(deleteRecord)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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.home.room.detail.composer.voice
|
||||||
|
|
||||||
|
data class VoiceMessageConfig(
|
||||||
|
val lengthLimitMs: Long
|
||||||
|
)
|
@ -21,7 +21,6 @@ import android.util.AttributeSet
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.BuildConfig
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.hardware.vibrate
|
import im.vector.app.core.hardware.vibrate
|
||||||
import im.vector.app.core.time.Clock
|
import im.vector.app.core.time.Clock
|
||||||
@ -57,6 +56,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Inject lateinit var clock: Clock
|
@Inject lateinit var clock: Clock
|
||||||
|
@Inject lateinit var voiceMessageConfig: VoiceMessageConfig
|
||||||
|
|
||||||
// We need to define views as lateinit var to be able to check if initialized for the bug fix on api 21 and 22.
|
// We need to define views as lateinit var to be able to check if initialized for the bug fix on api 21 and 22.
|
||||||
@Suppress("UNNECESSARY_LATEINIT")
|
@Suppress("UNNECESSARY_LATEINIT")
|
||||||
@ -202,7 +202,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
|
|||||||
|
|
||||||
private fun onRecordingTick(isLocked: Boolean, milliseconds: Long) {
|
private fun onRecordingTick(isLocked: Boolean, milliseconds: Long) {
|
||||||
voiceMessageViews.renderRecordingTimer(isLocked, milliseconds / 1_000)
|
voiceMessageViews.renderRecordingTimer(isLocked, milliseconds / 1_000)
|
||||||
val timeDiffToRecordingLimit = BuildConfig.VOICE_MESSAGE_DURATION_LIMIT_MS - milliseconds
|
val timeDiffToRecordingLimit = voiceMessageConfig.lengthLimitMs - milliseconds
|
||||||
if (timeDiffToRecordingLimit <= 0) {
|
if (timeDiffToRecordingLimit <= 0) {
|
||||||
post {
|
post {
|
||||||
callback.onRecordingLimitReached()
|
callback.onRecordingLimitReached()
|
||||||
|
@ -36,6 +36,8 @@ import im.vector.app.core.utils.DimensionConverter
|
|||||||
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
||||||
import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
|
import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
|
||||||
|
import im.vector.app.features.location.MapLoadingErrorView
|
||||||
|
import im.vector.app.features.location.MapLoadingErrorViewState
|
||||||
|
|
||||||
abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
|
abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
|
||||||
@LayoutRes layoutId: Int = R.layout.item_timeline_event_base
|
@LayoutRes layoutId: Int = R.layout.item_timeline_event_base
|
||||||
@ -88,8 +90,10 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
|
|||||||
target: Target<Drawable>?,
|
target: Target<Drawable>?,
|
||||||
isFirstResource: Boolean
|
isFirstResource: Boolean
|
||||||
): Boolean {
|
): Boolean {
|
||||||
holder.staticMapPinImageView.setImageResource(R.drawable.ic_location_pin_failed)
|
holder.staticMapPinImageView.setImageDrawable(null)
|
||||||
holder.staticMapErrorTextView.isVisible = true
|
holder.staticMapLoadingErrorView.isVisible = true
|
||||||
|
val mapErrorViewState = MapLoadingErrorViewState(imageCornerTransformation)
|
||||||
|
holder.staticMapLoadingErrorView.render(mapErrorViewState)
|
||||||
holder.staticMapCopyrightTextView.isVisible = false
|
holder.staticMapCopyrightTextView.isVisible = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -105,7 +109,7 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
|
|||||||
// we are not using Glide since it does not display it correctly when there is no user photo
|
// we are not using Glide since it does not display it correctly when there is no user photo
|
||||||
holder.staticMapPinImageView.setImageDrawable(pinDrawable)
|
holder.staticMapPinImageView.setImageDrawable(pinDrawable)
|
||||||
}
|
}
|
||||||
holder.staticMapErrorTextView.isVisible = false
|
holder.staticMapLoadingErrorView.isVisible = false
|
||||||
holder.staticMapCopyrightTextView.isVisible = true
|
holder.staticMapCopyrightTextView.isVisible = true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -117,7 +121,7 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
|
|||||||
abstract class Holder(@IdRes stubId: Int) : AbsMessageItem.Holder(stubId) {
|
abstract class Holder(@IdRes stubId: Int) : AbsMessageItem.Holder(stubId) {
|
||||||
val staticMapImageView by bind<ImageView>(R.id.staticMapImageView)
|
val staticMapImageView by bind<ImageView>(R.id.staticMapImageView)
|
||||||
val staticMapPinImageView by bind<ImageView>(R.id.staticMapPinImageView)
|
val staticMapPinImageView by bind<ImageView>(R.id.staticMapPinImageView)
|
||||||
val staticMapErrorTextView by bind<TextView>(R.id.staticMapErrorTextView)
|
val staticMapLoadingErrorView by bind<MapLoadingErrorView>(R.id.staticMapLoadingError)
|
||||||
val staticMapCopyrightTextView by bind<TextView>(R.id.staticMapCopyrightTextView)
|
val staticMapCopyrightTextView by bind<TextView>(R.id.staticMapCopyrightTextView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,8 @@ abstract class MessageLiveLocationInactiveItem :
|
|||||||
override fun getViewStubId() = STUB_ID
|
override fun getViewStubId() = STUB_ID
|
||||||
|
|
||||||
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||||
val bannerImageView by bind<ImageView>(R.id.locationLiveEndedBannerBackground)
|
val bannerImageView by bind<ImageView>(R.id.liveLocationEndedBannerBackground)
|
||||||
val noLocationMapImageView by bind<ImageView>(R.id.locationLiveInactiveMap)
|
val noLocationMapImageView by bind<ImageView>(R.id.liveLocationInactiveMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -26,8 +26,8 @@ import im.vector.app.core.resources.toTimestamp
|
|||||||
import im.vector.app.core.utils.DimensionConverter
|
import im.vector.app.core.utils.DimensionConverter
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailAction
|
import im.vector.app.features.home.room.detail.RoomDetailAction
|
||||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
||||||
import im.vector.app.features.location.live.LocationLiveMessageBannerViewState
|
import im.vector.app.features.location.live.LiveLocationMessageBannerViewState
|
||||||
import im.vector.app.features.location.live.LocationLiveRunningBannerView
|
import im.vector.app.features.location.live.LiveLocationRunningBannerView
|
||||||
import org.threeten.bp.LocalDateTime
|
import org.threeten.bp.LocalDateTime
|
||||||
|
|
||||||
@EpoxyModelClass
|
@EpoxyModelClass
|
||||||
@ -44,17 +44,17 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat
|
|||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
bindLocationLiveBanner(holder)
|
bindLiveLocationBanner(holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindLocationLiveBanner(holder: Holder) {
|
private fun bindLiveLocationBanner(holder: Holder) {
|
||||||
// TODO in a future PR add check on device id to confirm that is the one that sent the beacon
|
// TODO in a future PR add check on device id to confirm that is the one that sent the beacon
|
||||||
val isEmitter = currentUserId != null && currentUserId == locationUserId
|
val isEmitter = currentUserId != null && currentUserId == locationUserId
|
||||||
val messageLayout = attributes.informationData.messageLayout
|
val messageLayout = attributes.informationData.messageLayout
|
||||||
val viewState = buildViewState(holder, messageLayout, isEmitter)
|
val viewState = buildViewState(holder, messageLayout, isEmitter)
|
||||||
holder.locationLiveRunningBanner.isVisible = true
|
holder.liveLocationRunningBanner.isVisible = true
|
||||||
holder.locationLiveRunningBanner.render(viewState)
|
holder.liveLocationRunningBanner.render(viewState)
|
||||||
holder.locationLiveRunningBanner.stopButton.setOnClickListener {
|
holder.liveLocationRunningBanner.stopButton.setOnClickListener {
|
||||||
attributes.callback?.onTimelineItemAction(RoomDetailAction.StopLiveLocationSharing)
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.StopLiveLocationSharing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,24 +63,24 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat
|
|||||||
holder: Holder,
|
holder: Holder,
|
||||||
messageLayout: TimelineMessageLayout,
|
messageLayout: TimelineMessageLayout,
|
||||||
isEmitter: Boolean
|
isEmitter: Boolean
|
||||||
): LocationLiveMessageBannerViewState {
|
): LiveLocationMessageBannerViewState {
|
||||||
return when {
|
return when {
|
||||||
messageLayout is TimelineMessageLayout.Bubble && isEmitter ->
|
messageLayout is TimelineMessageLayout.Bubble && isEmitter ->
|
||||||
LocationLiveMessageBannerViewState.Emitter(
|
LiveLocationMessageBannerViewState.Emitter(
|
||||||
remainingTimeInMillis = getRemainingTimeOfLiveInMillis(),
|
remainingTimeInMillis = getRemainingTimeOfLiveInMillis(),
|
||||||
bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius,
|
bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius,
|
||||||
bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius,
|
bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius,
|
||||||
isStopButtonCenteredVertically = false
|
isStopButtonCenteredVertically = false
|
||||||
)
|
)
|
||||||
messageLayout is TimelineMessageLayout.Bubble ->
|
messageLayout is TimelineMessageLayout.Bubble ->
|
||||||
LocationLiveMessageBannerViewState.Watcher(
|
LiveLocationMessageBannerViewState.Watcher(
|
||||||
bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius,
|
bottomStartCornerRadiusInDp = messageLayout.cornersRadius.bottomStartRadius,
|
||||||
bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius,
|
bottomEndCornerRadiusInDp = messageLayout.cornersRadius.bottomEndRadius,
|
||||||
formattedLocalTimeOfEndOfLive = getFormattedLocalTimeEndOfLive(),
|
formattedLocalTimeOfEndOfLive = getFormattedLocalTimeEndOfLive(),
|
||||||
)
|
)
|
||||||
messageLayout is TimelineMessageLayout.ScBubble && isEmitter -> {
|
messageLayout is TimelineMessageLayout.ScBubble && isEmitter -> {
|
||||||
val radius = messageLayout.bubbleAppearance.getBubbleRadiusDp(holder.view.context)
|
val radius = messageLayout.bubbleAppearance.getBubbleRadiusDp(holder.view.context)
|
||||||
LocationLiveMessageBannerViewState.Emitter(
|
LiveLocationMessageBannerViewState.Emitter(
|
||||||
remainingTimeInMillis = getRemainingTimeOfLiveInMillis(),
|
remainingTimeInMillis = getRemainingTimeOfLiveInMillis(),
|
||||||
bottomStartCornerRadiusInDp = radius,
|
bottomStartCornerRadiusInDp = radius,
|
||||||
bottomEndCornerRadiusInDp = radius,
|
bottomEndCornerRadiusInDp = radius,
|
||||||
@ -89,7 +89,7 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat
|
|||||||
}
|
}
|
||||||
messageLayout is TimelineMessageLayout.ScBubble -> {
|
messageLayout is TimelineMessageLayout.ScBubble -> {
|
||||||
val radius = messageLayout.bubbleAppearance.getBubbleRadiusDp(holder.view.context)
|
val radius = messageLayout.bubbleAppearance.getBubbleRadiusDp(holder.view.context)
|
||||||
LocationLiveMessageBannerViewState.Watcher(
|
LiveLocationMessageBannerViewState.Watcher(
|
||||||
bottomStartCornerRadiusInDp = radius,
|
bottomStartCornerRadiusInDp = radius,
|
||||||
bottomEndCornerRadiusInDp = radius,
|
bottomEndCornerRadiusInDp = radius,
|
||||||
formattedLocalTimeOfEndOfLive = getFormattedLocalTimeEndOfLive(),
|
formattedLocalTimeOfEndOfLive = getFormattedLocalTimeEndOfLive(),
|
||||||
@ -97,7 +97,7 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat
|
|||||||
}
|
}
|
||||||
isEmitter -> {
|
isEmitter -> {
|
||||||
val cornerRadius = getBannerCornerRadiusForDefaultLayout(holder)
|
val cornerRadius = getBannerCornerRadiusForDefaultLayout(holder)
|
||||||
LocationLiveMessageBannerViewState.Emitter(
|
LiveLocationMessageBannerViewState.Emitter(
|
||||||
remainingTimeInMillis = getRemainingTimeOfLiveInMillis(),
|
remainingTimeInMillis = getRemainingTimeOfLiveInMillis(),
|
||||||
bottomStartCornerRadiusInDp = cornerRadius,
|
bottomStartCornerRadiusInDp = cornerRadius,
|
||||||
bottomEndCornerRadiusInDp = cornerRadius,
|
bottomEndCornerRadiusInDp = cornerRadius,
|
||||||
@ -106,7 +106,7 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val cornerRadius = getBannerCornerRadiusForDefaultLayout(holder)
|
val cornerRadius = getBannerCornerRadiusForDefaultLayout(holder)
|
||||||
LocationLiveMessageBannerViewState.Watcher(
|
LiveLocationMessageBannerViewState.Watcher(
|
||||||
bottomStartCornerRadiusInDp = cornerRadius,
|
bottomStartCornerRadiusInDp = cornerRadius,
|
||||||
bottomEndCornerRadiusInDp = cornerRadius,
|
bottomEndCornerRadiusInDp = cornerRadius,
|
||||||
formattedLocalTimeOfEndOfLive = getFormattedLocalTimeEndOfLive(),
|
formattedLocalTimeOfEndOfLive = getFormattedLocalTimeEndOfLive(),
|
||||||
@ -129,7 +129,7 @@ abstract class MessageLiveLocationItem : AbsMessageLocationItem<MessageLiveLocat
|
|||||||
override fun getViewStubId() = STUB_ID
|
override fun getViewStubId() = STUB_ID
|
||||||
|
|
||||||
class Holder : AbsMessageLocationItem.Holder(STUB_ID) {
|
class Holder : AbsMessageLocationItem.Holder(STUB_ID) {
|
||||||
val locationLiveRunningBanner by bind<LocationLiveRunningBannerView>(R.id.locationLiveRunningBanner)
|
val liveLocationRunningBanner by bind<LiveLocationRunningBannerView>(R.id.liveLocationRunningBanner)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -42,8 +42,8 @@ abstract class MessageLiveLocationStartItem :
|
|||||||
override fun getViewStubId() = STUB_ID
|
override fun getViewStubId() = STUB_ID
|
||||||
|
|
||||||
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||||
val bannerImageView by bind<ImageView>(R.id.locationLiveStartBanner)
|
val bannerImageView by bind<ImageView>(R.id.liveLocationStartBanner)
|
||||||
val noLocationMapImageView by bind<ImageView>(R.id.locationLiveStartMap)
|
val noLocationMapImageView by bind<ImageView>(R.id.liveLocationStartMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.app.features.home.room.detail.timeline.url
|
package im.vector.app.features.home.room.detail.timeline.url
|
||||||
|
|
||||||
import im.vector.app.BuildConfig
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -27,7 +27,8 @@ import org.matrix.android.sdk.api.session.room.timeline.getLatestEventId
|
|||||||
|
|
||||||
class PreviewUrlRetriever(
|
class PreviewUrlRetriever(
|
||||||
session: Session,
|
session: Session,
|
||||||
private val coroutineScope: CoroutineScope
|
private val coroutineScope: CoroutineScope,
|
||||||
|
private val buildMeta: BuildMeta,
|
||||||
) {
|
) {
|
||||||
private val mediaService = session.mediaService()
|
private val mediaService = session.mediaService()
|
||||||
|
|
||||||
@ -77,7 +78,7 @@ class PreviewUrlRetriever(
|
|||||||
mediaService.getPreviewUrl(
|
mediaService.getPreviewUrl(
|
||||||
url = urlToRetrieve,
|
url = urlToRetrieve,
|
||||||
timestamp = null,
|
timestamp = null,
|
||||||
cacheStrategy = if (BuildConfig.DEBUG) CacheStrategy.NoCache else CacheStrategy.TtlCache(CACHE_VALIDITY, false)
|
cacheStrategy = if (buildMeta.isDebug) CacheStrategy.NoCache else CacheStrategy.TtlCache(CACHE_VALIDITY, false)
|
||||||
)
|
)
|
||||||
}.fold(
|
}.fold(
|
||||||
{
|
{
|
||||||
|
@ -40,13 +40,15 @@ abstract class FilteredRoomFooterItem : VectorEpoxyModel<FilteredRoomFooterItem.
|
|||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var inSpace: Boolean = false
|
var inSpace: Boolean = false
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var simplifiedMode: Boolean = false
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
val vectorPreferences = VectorPreferences(holder.createRoomButton.context, DefaultClock())
|
|
||||||
holder.createRoomButton.onClick { listener?.createRoom(currentFilter) }
|
holder.createRoomButton.onClick { listener?.createRoom(currentFilter) }
|
||||||
holder.createDirectChat.onClick { listener?.createDirectChat() }
|
holder.createDirectChat.onClick { listener?.createDirectChat() }
|
||||||
holder.openRoomDirectory.onClick { listener?.openRoomDirectory(currentFilter) }
|
holder.openRoomDirectory.onClick { listener?.openRoomDirectory(currentFilter) }
|
||||||
holder.openRoomDirectory.visibility = if (vectorPreferences.simplifiedMode()) View.GONE else View.VISIBLE
|
holder.openRoomDirectory.visibility = if (simplifiedMode) View.GONE else View.VISIBLE
|
||||||
|
|
||||||
holder.openRoomDirectory.setText(
|
holder.openRoomDirectory.setText(
|
||||||
if (inSpace) R.string.space_explore_activity_title else R.string.room_filtering_footer_open_room_directory
|
if (inSpace) R.string.space_explore_activity_title else R.string.room_filtering_footer_open_room_directory
|
||||||
|
@ -24,11 +24,13 @@ import im.vector.app.core.resources.UserPreferencesProvider
|
|||||||
import im.vector.app.features.home.RoomListDisplayMode
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
|
import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
|
||||||
import im.vector.app.features.home.room.filtered.filteredRoomFooterItem
|
import im.vector.app.features.home.room.filtered.filteredRoomFooterItem
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomListFooterController @Inject constructor(
|
class RoomListFooterController @Inject constructor(
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val userPreferencesProvider: UserPreferencesProvider
|
private val userPreferencesProvider: UserPreferencesProvider,
|
||||||
|
private val vectorPreferences: VectorPreferences,
|
||||||
) : TypedEpoxyController<RoomListViewState>() {
|
) : TypedEpoxyController<RoomListViewState>() {
|
||||||
|
|
||||||
var listener: FilteredRoomFooterItem.Listener? = null
|
var listener: FilteredRoomFooterItem.Listener? = null
|
||||||
@ -42,6 +44,7 @@ class RoomListFooterController @Inject constructor(
|
|||||||
listener(host.listener)
|
listener(host.listener)
|
||||||
currentFilter(data.roomFilter)
|
currentFilter(data.roomFilter)
|
||||||
inSpace(data.asyncSelectedSpace.invoke() != null)
|
inSpace(data.asyncSelectedSpace.invoke() != null)
|
||||||
|
simplifiedMode(host.vectorPreferences.simplifiedMode())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -37,7 +37,6 @@ import com.airbnb.epoxy.OnModelBuildFinishedListener
|
|||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import im.vector.app.AppStateHandler
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import de.spiritcroc.matrixsdk.util.DbgUtil
|
import de.spiritcroc.matrixsdk.util.DbgUtil
|
||||||
import de.spiritcroc.matrixsdk.util.Dimber
|
import de.spiritcroc.matrixsdk.util.Dimber
|
||||||
@ -81,7 +80,6 @@ data class RoomListParams(
|
|||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
class RoomListFragment @Inject constructor(
|
class RoomListFragment @Inject constructor(
|
||||||
private val appStateHandler: AppStateHandler,
|
|
||||||
private val pagedControllerFactory: RoomSummaryPagedControllerFactory,
|
private val pagedControllerFactory: RoomSummaryPagedControllerFactory,
|
||||||
private val notificationDrawerManager: NotificationDrawerManager,
|
private val notificationDrawerManager: NotificationDrawerManager,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
@ -25,8 +25,8 @@ import androidx.paging.PagedList
|
|||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import de.spiritcroc.matrixsdk.util.DbgUtil
|
import de.spiritcroc.matrixsdk.util.DbgUtil
|
||||||
import de.spiritcroc.matrixsdk.util.Dimber
|
import de.spiritcroc.matrixsdk.util.Dimber
|
||||||
import im.vector.app.AppStateHandler
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.SpaceStateHandler
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.home.RoomListDisplayMode
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
import im.vector.app.features.invite.AutoAcceptInvites
|
import im.vector.app.features.invite.AutoAcceptInvites
|
||||||
@ -61,7 +61,7 @@ import timber.log.Timber
|
|||||||
class RoomListSectionBuilder(
|
class RoomListSectionBuilder(
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val appStateHandler: AppStateHandler,
|
private val spaceStateHandler: SpaceStateHandler,
|
||||||
private val viewModelScope: CoroutineScope,
|
private val viewModelScope: CoroutineScope,
|
||||||
private val autoAcceptInvites: AutoAcceptInvites,
|
private val autoAcceptInvites: AutoAcceptInvites,
|
||||||
private val onUpdatable: (UpdatableLivePageResult) -> Unit,
|
private val onUpdatable: (UpdatableLivePageResult) -> Unit,
|
||||||
@ -108,7 +108,7 @@ class RoomListSectionBuilder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (explicitSpaceId == SPACE_ID_FOLLOW_APP) {
|
if (explicitSpaceId == SPACE_ID_FOLLOW_APP) {
|
||||||
appStateHandler.selectedSpaceFlow
|
spaceStateHandler.getSelectedSpaceFlow()
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.onEach { selectedSpaceOption ->
|
.onEach { selectedSpaceOption ->
|
||||||
val selectedSpace = selectedSpaceOption.orNull()
|
val selectedSpace = selectedSpaceOption.orNull()
|
||||||
@ -256,7 +256,7 @@ class RoomListSectionBuilder(
|
|||||||
) {
|
) {
|
||||||
it.memberships = listOf(Membership.JOIN)
|
it.memberships = listOf(Membership.JOIN)
|
||||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||||
it.roomTagQueryFilter = RoomTagQueryFilter(false, false, false)
|
it.roomTagQueryFilter = RoomTagQueryFilter(isFavorite = false, isLowPriority = false, isServerNotice = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
addSection(
|
addSection(
|
||||||
@ -300,7 +300,7 @@ class RoomListSectionBuilder(
|
|||||||
explicitSpaceId: String?) {
|
explicitSpaceId: String?) {
|
||||||
// add suggested rooms
|
// add suggested rooms
|
||||||
val suggestedRoomsFlow = if (explicitSpaceId == SPACE_ID_FOLLOW_APP) { // MutableLiveData<List<SpaceChildInfo>>()
|
val suggestedRoomsFlow = if (explicitSpaceId == SPACE_ID_FOLLOW_APP) { // MutableLiveData<List<SpaceChildInfo>>()
|
||||||
appStateHandler.selectedSpaceFlow
|
spaceStateHandler.getSelectedSpaceFlow()
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.flatMapLatest { selectedSpaceOption ->
|
.flatMapLatest { selectedSpaceOption ->
|
||||||
val selectedSpace = selectedSpaceOption.orNull()
|
val selectedSpace = selectedSpaceOption.orNull()
|
||||||
@ -418,7 +418,7 @@ class RoomListSectionBuilder(
|
|||||||
) {
|
) {
|
||||||
it.memberships = listOf(Membership.JOIN)
|
it.memberships = listOf(Membership.JOIN)
|
||||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||||
it.roomTagQueryFilter = RoomTagQueryFilter(false, false, null)
|
it.roomTagQueryFilter = RoomTagQueryFilter(isFavorite = false, isLowPriority = false, isServerNotice = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
addSection(
|
addSection(
|
||||||
@ -435,14 +435,14 @@ class RoomListSectionBuilder(
|
|||||||
) {
|
) {
|
||||||
it.memberships = listOf(Membership.JOIN)
|
it.memberships = listOf(Membership.JOIN)
|
||||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||||
it.roomTagQueryFilter = RoomTagQueryFilter(false, true, null)
|
it.roomTagQueryFilter = RoomTagQueryFilter(isFavorite = false, isLowPriority = true, isServerNotice = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AppStateHandler.explicitOrSafeActiveSpaceId(explicitSpaceId: String?): String? {
|
private fun SpaceStateHandler.explicitOrSafeActiveSpaceId(explicitSpaceId: String?): String? {
|
||||||
return if (explicitSpaceId == SPACE_ID_FOLLOW_APP) {
|
return if (explicitSpaceId == SPACE_ID_FOLLOW_APP) {
|
||||||
safeActiveSpaceId()
|
getSafeActiveSpaceId()
|
||||||
} else {
|
} else {
|
||||||
explicitSpaceId
|
explicitSpaceId
|
||||||
}
|
}
|
||||||
@ -525,7 +525,7 @@ class RoomListSectionBuilder(
|
|||||||
query: (RoomSummaryQueryParams.Builder) -> Unit
|
query: (RoomSummaryQueryParams.Builder) -> Unit
|
||||||
) {
|
) {
|
||||||
withQueryParams(query) { roomQueryParams ->
|
withQueryParams(query) { roomQueryParams ->
|
||||||
val updatedQueryParams = roomQueryParams.process(spaceFilterStrategy, appStateHandler.explicitOrSafeActiveSpaceId(explicitSpaceId))
|
val updatedQueryParams = roomQueryParams.process(spaceFilterStrategy, spaceStateHandler.explicitOrSafeActiveSpaceId(explicitSpaceId))
|
||||||
val liveQueryParams = MutableStateFlow(updatedQueryParams)
|
val liveQueryParams = MutableStateFlow(updatedQueryParams)
|
||||||
val itemCountFlow = liveQueryParams
|
val itemCountFlow = liveQueryParams
|
||||||
.flatMapLatest {
|
.flatMapLatest {
|
||||||
@ -536,7 +536,7 @@ class RoomListSectionBuilder(
|
|||||||
|
|
||||||
val name = stringProvider.getString(nameRes)
|
val name = stringProvider.getString(nameRes)
|
||||||
val filteredPagedRoomSummariesLive = session.roomService().getFilteredPagedRoomSummariesLive(
|
val filteredPagedRoomSummariesLive = session.roomService().getFilteredPagedRoomSummariesLive(
|
||||||
roomQueryParams.process(spaceFilterStrategy, appStateHandler.explicitOrSafeActiveSpaceId(explicitSpaceId)),
|
roomQueryParams.process(spaceFilterStrategy, spaceStateHandler.explicitOrSafeActiveSpaceId(explicitSpaceId)),
|
||||||
pagedListConfig
|
pagedListConfig
|
||||||
)
|
)
|
||||||
when (spaceFilterStrategy) {
|
when (spaceFilterStrategy) {
|
||||||
@ -583,7 +583,7 @@ class RoomListSectionBuilder(
|
|||||||
RoomAggregateNotificationCount(it.size, it.size, 0)
|
RoomAggregateNotificationCount(it.size, it.size, 0)
|
||||||
} else {
|
} else {
|
||||||
session.roomService().getNotificationCountForRooms(
|
session.roomService().getNotificationCountForRooms(
|
||||||
roomQueryParams.process(spaceFilterStrategy, appStateHandler.explicitOrSafeActiveSpaceId(explicitSpaceId))
|
roomQueryParams.process(spaceFilterStrategy, spaceStateHandler.explicitOrSafeActiveSpaceId(explicitSpaceId))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -27,7 +27,7 @@ import dagger.assisted.AssistedFactory
|
|||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import de.spiritcroc.matrixsdk.util.DbgUtil
|
import de.spiritcroc.matrixsdk.util.DbgUtil
|
||||||
import de.spiritcroc.matrixsdk.util.Dimber
|
import de.spiritcroc.matrixsdk.util.Dimber
|
||||||
import im.vector.app.AppStateHandler
|
import im.vector.app.SpaceStateHandler
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
@ -62,7 +62,7 @@ class RoomListViewModel @AssistedInject constructor(
|
|||||||
@Assisted initialState: RoomListViewState,
|
@Assisted initialState: RoomListViewState,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
stringProvider: StringProvider,
|
stringProvider: StringProvider,
|
||||||
appStateHandler: AppStateHandler,
|
spaceStateHandler: SpaceStateHandler,
|
||||||
vectorPreferences: VectorPreferences,
|
vectorPreferences: VectorPreferences,
|
||||||
autoAcceptInvites: AutoAcceptInvites,
|
autoAcceptInvites: AutoAcceptInvites,
|
||||||
private val analyticsTracker: AnalyticsTracker
|
private val analyticsTracker: AnalyticsTracker
|
||||||
@ -112,7 +112,7 @@ class RoomListViewModel @AssistedInject constructor(
|
|||||||
observeMembershipChanges()
|
observeMembershipChanges()
|
||||||
observeLocalRooms()
|
observeLocalRooms()
|
||||||
|
|
||||||
appStateHandler.selectedSpaceFlow
|
spaceStateHandler.getSelectedSpaceFlow()
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.execute {
|
.execute {
|
||||||
copy(
|
copy(
|
||||||
@ -158,17 +158,17 @@ class RoomListViewModel @AssistedInject constructor(
|
|||||||
companion object : MavericksViewModelFactory<RoomListViewModel, RoomListViewState> by hiltMavericksViewModelFactory()
|
companion object : MavericksViewModelFactory<RoomListViewModel, RoomListViewState> by hiltMavericksViewModelFactory()
|
||||||
|
|
||||||
private val roomListSectionBuilder = RoomListSectionBuilder(
|
private val roomListSectionBuilder = RoomListSectionBuilder(
|
||||||
session,
|
session,
|
||||||
stringProvider,
|
stringProvider,
|
||||||
appStateHandler,
|
spaceStateHandler,
|
||||||
viewModelScope,
|
viewModelScope,
|
||||||
autoAcceptInvites,
|
autoAcceptInvites,
|
||||||
{
|
{
|
||||||
updatableQuery = it
|
updatableQuery = it
|
||||||
},
|
},
|
||||||
suggestedRoomJoiningState,
|
suggestedRoomJoiningState,
|
||||||
!vectorPreferences.prefSpacesShowAllRoomInHome()
|
!vectorPreferences.prefSpacesShowAllRoomInHome()
|
||||||
)
|
)
|
||||||
|
|
||||||
val sections: List<RoomsSection> by lazy {
|
val sections: List<RoomsSection> by lazy {
|
||||||
viewPagerDimber.i { "Build sections for ${initialState.displayMode}, ${initialState.explicitSpaceId}" }
|
viewPagerDimber.i { "Build sections for ${initialState.displayMode}, ${initialState.explicitSpaceId}" }
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user