Merge branch 'release/0.6.5' into main
This commit is contained in:
commit
1e918fa134
2144 changed files with 10352 additions and 6873 deletions
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
|
|
@ -9,8 +9,8 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
debug:
|
debug:
|
||||||
|
|
@ -30,11 +30,11 @@ jobs:
|
||||||
# Ensure we are building the branch and not the branch after being merged on develop
|
# Ensure we are building the branch and not the branch after being merged on develop
|
||||||
# https://github.com/actions/checkout/issues/881
|
# https://github.com/actions/checkout/issues/881
|
||||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
8
.github/workflows/build_enterprise.yml
vendored
8
.github/workflows/build_enterprise.yml
vendored
|
|
@ -9,8 +9,8 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
@ -38,11 +38,11 @@ jobs:
|
||||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||||
- name: Clone submodules
|
- name: Clone submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
4
.github/workflows/generate_github_pages.yml
vendored
4
.github/workflows/generate_github_pages.yml
vendored
|
|
@ -13,11 +13,11 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: ⏬ Checkout with LFS
|
- name: ⏬ Checkout with LFS
|
||||||
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
6
.github/workflows/gradle-wrapper-update.yml
vendored
6
.github/workflows/gradle-wrapper-update.yml
vendored
|
|
@ -13,13 +13,13 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-java@v4
|
- uses: actions/setup-java@v4
|
||||||
name: Use JDK 17
|
name: Use JDK 21
|
||||||
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Update Gradle Wrapper
|
- name: Update Gradle Wrapper
|
||||||
uses: gradle-update/update-gradle-wrapper-action@v1
|
uses: gradle-update/update-gradle-wrapper-action@v2
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
repo-token: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
||||||
target-branch: develop
|
target-branch: develop
|
||||||
|
|
|
||||||
8
.github/workflows/maestro.yml
vendored
8
.github/workflows/maestro.yml
vendored
|
|
@ -8,8 +8,8 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-apk:
|
build-apk:
|
||||||
|
|
@ -33,11 +33,11 @@ jobs:
|
||||||
# https://github.com/actions/checkout/issues/881
|
# https://github.com/actions/checkout/issues/881
|
||||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||||
- uses: actions/setup-java@v4
|
- uses: actions/setup-java@v4
|
||||||
name: Use JDK 17
|
name: Use JDK 21
|
||||||
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
8
.github/workflows/nightly.yml
vendored
8
.github/workflows/nightly.yml
vendored
|
|
@ -7,8 +7,8 @@ on:
|
||||||
- cron: "0 4 * * *"
|
- cron: "0 4 * * *"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
nightly:
|
nightly:
|
||||||
|
|
@ -17,11 +17,11 @@ jobs:
|
||||||
if: ${{ github.repository == 'element-hq/element-x-android' }}
|
if: ${{ github.repository == 'element-hq/element-x-android' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Build and upload Nightly application
|
- name: Build and upload Nightly application
|
||||||
run: |
|
run: |
|
||||||
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES
|
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
|
||||||
12
.github/workflows/nightlyReports.yml
vendored
12
.github/workflows/nightlyReports.yml
vendored
|
|
@ -8,8 +8,8 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
nightlyReports:
|
nightlyReports:
|
||||||
|
|
@ -20,11 +20,11 @@ jobs:
|
||||||
- name: ⏬ Checkout with LFS
|
- name: ⏬ Checkout with LFS
|
||||||
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
||||||
|
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
|
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
@ -61,11 +61,11 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
8
.github/workflows/nightly_enterprise.yml
vendored
8
.github/workflows/nightly_enterprise.yml
vendored
|
|
@ -7,8 +7,8 @@ on:
|
||||||
- cron: "0 4 * * *"
|
- cron: "0 4 * * *"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
nightly:
|
nightly:
|
||||||
|
|
@ -23,11 +23,11 @@ jobs:
|
||||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||||
- name: Clone submodules
|
- name: Clone submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Build and upload Nightly application
|
- name: Build and upload Nightly application
|
||||||
run: |
|
run: |
|
||||||
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES
|
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
|
||||||
28
.github/workflows/quality.yml
vendored
28
.github/workflows/quality.yml
vendored
|
|
@ -9,8 +9,8 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
checkScript:
|
checkScript:
|
||||||
|
|
@ -46,11 +46,11 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
|
|
@ -84,11 +84,11 @@ jobs:
|
||||||
- name: Clone submodules
|
- name: Clone submodules
|
||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
|
|
@ -124,11 +124,11 @@ jobs:
|
||||||
- name: Clone submodules
|
- name: Clone submodules
|
||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
|
|
@ -168,11 +168,11 @@ jobs:
|
||||||
- name: Clone submodules
|
- name: Clone submodules
|
||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
|
|
@ -208,11 +208,11 @@ jobs:
|
||||||
- name: Clone submodules
|
- name: Clone submodules
|
||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
|
|
@ -248,11 +248,11 @@ jobs:
|
||||||
- name: Clone submodules
|
- name: Clone submodules
|
||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
8
.github/workflows/recordScreenshots.yml
vendored
8
.github/workflows/recordScreenshots.yml
vendored
|
|
@ -7,7 +7,8 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false -Dsonar.gradle.skipCompile=true
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dsonar.gradle.skipCompile=true
|
||||||
|
CI_GRADLE_ARG_PROPERTIES: --no-configuration-cache
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
record:
|
record:
|
||||||
|
|
@ -32,11 +33,11 @@ jobs:
|
||||||
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: ☕️ Use JDK 17
|
- name: ☕️ Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
# Add gradle cache, this should speed up the process
|
# Add gradle cache, this should speed up the process
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
@ -48,3 +49,4 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN || secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
GITHUB_REPOSITORY: ${{ secrets.GITHUB_REPOSITORY }}
|
GITHUB_REPOSITORY: ${{ secrets.GITHUB_REPOSITORY }}
|
||||||
|
GRADLE_ARGS: ${{ env.CI_GRADLE_ARG_PROPERTIES }}
|
||||||
|
|
|
||||||
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
|
|
@ -7,8 +7,8 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
gplay:
|
gplay:
|
||||||
|
|
@ -19,11 +19,11 @@ jobs:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
- name: Create app bundle
|
- name: Create app bundle
|
||||||
|
|
@ -55,11 +55,11 @@ jobs:
|
||||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||||
- name: Clone submodules
|
- name: Clone submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
- name: Create Enterprise app bundle
|
- name: Create Enterprise app bundle
|
||||||
|
|
@ -83,11 +83,11 @@ jobs:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
- name: Create APKs
|
- name: Create APKs
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,10 @@ if [[ -z ${REPO} ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Deleting previous screenshots"
|
echo "Deleting previous screenshots"
|
||||||
./gradlew removeOldSnapshots --stacktrace --warn
|
./gradlew removeOldSnapshots --stacktrace --warn $GRADLE_ARGS
|
||||||
|
|
||||||
echo "Record screenshots"
|
echo "Record screenshots"
|
||||||
./gradlew recordPaparazziDebug --stacktrace
|
./gradlew recordPaparazziDebug --stacktrace $GRADLE_ARGS
|
||||||
|
|
||||||
echo "Committing changes"
|
echo "Committing changes"
|
||||||
git config http.sslVerify false
|
git config http.sslVerify false
|
||||||
|
|
|
||||||
8
.github/workflows/sonar.yml
vendored
8
.github/workflows/sonar.yml
vendored
|
|
@ -9,8 +9,8 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --warn -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --warn -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||||
GROUP: ${{ format('sonar-{0}', github.ref) }}
|
GROUP: ${{ format('sonar-{0}', github.ref) }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
@ -27,11 +27,11 @@ jobs:
|
||||||
# Ensure we are building the branch and not the branch after being merged on develop
|
# Ensure we are building the branch and not the branch after being merged on develop
|
||||||
# https://github.com/actions/checkout/issues/881
|
# https://github.com/actions/checkout/issues/881
|
||||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
4
.github/workflows/sync-localazy.yml
vendored
4
.github/workflows/sync-localazy.yml
vendored
|
|
@ -12,11 +12,11 @@ jobs:
|
||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Use JDK 17
|
- name: Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
20
.github/workflows/tests.yml
vendored
20
.github/workflows/tests.yml
vendored
|
|
@ -9,8 +9,8 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx7g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.incremental=false -Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseG1GC
|
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx7g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseG1GC
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
|
|
@ -46,24 +46,18 @@ jobs:
|
||||||
- name: Clone submodules
|
- name: Clone submodules
|
||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
- name: ☕️ Use JDK 17
|
- name: ☕️ Use JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
- name: Configure gradle
|
- name: Configure gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
|
|
||||||
- name: ⚙️ Run unit tests for debug variant
|
- name: ⚙️ Check coverage for debug variant (includes unit & screenshot tests)
|
||||||
run: ./gradlew testDebugUnitTest $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew testDebugUnitTest :tests:uitests:verifyPaparazziDebug :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyAll $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
- name: 📸 Run screenshot tests
|
|
||||||
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
|
||||||
|
|
||||||
- name: 📈Generate kover report and verify coverage
|
|
||||||
run: ./gradlew :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyAll $CI_GRADLE_ARG_PROPERTIES
|
|
||||||
|
|
||||||
- name: 🚫 Upload kover failed coverage reports
|
- name: 🚫 Upload kover failed coverage reports
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|
@ -71,7 +65,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: kover-error-report
|
name: kover-error-report
|
||||||
path: |
|
path: |
|
||||||
app/build/reports/kover/verifyGplayDebug.err
|
app/build/reports/kover
|
||||||
|
|
||||||
- name: ✅ Upload kover report (disabled)
|
- name: ✅ Upload kover report (disabled)
|
||||||
if: always()
|
if: always()
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -53,6 +53,7 @@ captures/
|
||||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||||
.idea/navEditor.xml
|
.idea/navEditor.xml
|
||||||
.idea/other.xml
|
.idea/other.xml
|
||||||
|
.idea/runConfigurations.xml
|
||||||
.idea/tasks.xml
|
.idea/tasks.xml
|
||||||
.idea/workspace.xml
|
.idea/workspace.xml
|
||||||
.idea/libraries
|
.idea/libraries
|
||||||
|
|
|
||||||
36
CHANGES.md
36
CHANGES.md
|
|
@ -1,3 +1,39 @@
|
||||||
|
Changes in Element X v0.6.4 (2024-09-25)
|
||||||
|
========================================
|
||||||
|
|
||||||
|
### 🙌 Improvements
|
||||||
|
* Pinned messages : add pin icon in timeline for pinned events. by @ganfra in https://github.com/element-hq/element-x-android/pull/3500
|
||||||
|
* Include inviter in the notification for invitation by @bmarty in https://github.com/element-hq/element-x-android/pull/3503
|
||||||
|
|
||||||
|
### 🐛 Bugfixes
|
||||||
|
* Fix crash when session is deleted on another client by @bmarty in https://github.com/element-hq/element-x-android/pull/3515
|
||||||
|
* Fix pinned events banner reappearing when loading by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3519
|
||||||
|
* Fix various crashes by @bmarty in https://github.com/element-hq/element-x-android/pull/3533
|
||||||
|
* Perform the migration, even if the current version is not known. by @bmarty in https://github.com/element-hq/element-x-android/pull/3535
|
||||||
|
* timeline : makes sure to emit empty list if initial reset has no item. by @ganfra in https://github.com/element-hq/element-x-android/pull/3538
|
||||||
|
|
||||||
|
### 🗣 Translations
|
||||||
|
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3513
|
||||||
|
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3517
|
||||||
|
|
||||||
|
### Dependency upgrades
|
||||||
|
* Update dependency io.nlopez.compose.rules:detekt to v0.4.12 by @renovate in https://github.com/element-hq/element-x-android/pull/3436
|
||||||
|
* Update dependency com.posthog:posthog-android to v3.7.3 by @renovate in https://github.com/element-hq/element-x-android/pull/3443
|
||||||
|
* Update dependency com.otaliastudios:transcoder to v0.11.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3440
|
||||||
|
* Update dependency org.maplibre.gl:android-sdk to v11.4.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3408
|
||||||
|
* Update dependencyAnalysis to v2.0.2 by @renovate in https://github.com/element-hq/element-x-android/pull/3508
|
||||||
|
* Update dependency org.maplibre.gl:android-sdk-ktx-v7 to v3.0.1 by @renovate in https://github.com/element-hq/element-x-android/pull/3507
|
||||||
|
* Update dependencyAnalysis to v2.1.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3526
|
||||||
|
* Update dependency net.java.dev.jna:jna to v5.15.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3525
|
||||||
|
* Update dependency androidx.startup:startup-runtime to v1.2.0 by @renovate in https://github.com/element-hq/element-x-android/pull/3516
|
||||||
|
* dependencies : update rust sdk to 0.2.48 by @ganfra in https://github.com/element-hq/element-x-android/pull/3532
|
||||||
|
|
||||||
|
### Others
|
||||||
|
* Change ElementBot mail to android@element.io by @bmarty in https://github.com/element-hq/element-x-android/pull/3497
|
||||||
|
* Test RustMatrixClient and other classes in the matrix module by @bmarty in https://github.com/element-hq/element-x-android/pull/3501
|
||||||
|
* Pinned messages analytics by @ganfra in https://github.com/element-hq/element-x-android/pull/3523
|
||||||
|
* Remove ability to configure default log level by @bmarty in https://github.com/element-hq/element-x-android/pull/3531
|
||||||
|
|
||||||
Changes in Element X v0.6.3 (2024-09-19)
|
Changes in Element X v0.6.3 (2024-09-19)
|
||||||
========================================
|
========================================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.kotlin.jvm)
|
alias(libs.plugins.kotlin.jvm)
|
||||||
alias(libs.plugins.kapt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
@ -16,6 +15,6 @@ dependencies {
|
||||||
implementation(libs.anvil.compiler.utils)
|
implementation(libs.anvil.compiler.utils)
|
||||||
implementation(libs.kotlinpoet)
|
implementation(libs.kotlinpoet)
|
||||||
implementation(libs.dagger)
|
implementation(libs.dagger)
|
||||||
compileOnly(libs.google.autoservice.annotations)
|
implementation(libs.ksp.plugin)
|
||||||
kapt(libs.google.autoservice)
|
implementation(libs.kotlinpoet.ksp)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@file:OptIn(ExperimentalAnvilApi::class)
|
|
||||||
|
|
||||||
package io.element.android.anvilcodegen
|
|
||||||
|
|
||||||
import com.google.auto.service.AutoService
|
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
|
||||||
import com.squareup.anvil.annotations.ExperimentalAnvilApi
|
|
||||||
import com.squareup.anvil.compiler.api.AnvilCompilationException
|
|
||||||
import com.squareup.anvil.compiler.api.AnvilContext
|
|
||||||
import com.squareup.anvil.compiler.api.CodeGenerator
|
|
||||||
import com.squareup.anvil.compiler.api.GeneratedFile
|
|
||||||
import com.squareup.anvil.compiler.api.createGeneratedFile
|
|
||||||
import com.squareup.anvil.compiler.internal.asClassName
|
|
||||||
import com.squareup.anvil.compiler.internal.buildFile
|
|
||||||
import com.squareup.anvil.compiler.internal.fqName
|
|
||||||
import com.squareup.anvil.compiler.internal.reference.ClassReference
|
|
||||||
import com.squareup.anvil.compiler.internal.reference.asClassName
|
|
||||||
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
|
|
||||||
import com.squareup.kotlinpoet.AnnotationSpec
|
|
||||||
import com.squareup.kotlinpoet.ClassName
|
|
||||||
import com.squareup.kotlinpoet.FileSpec
|
|
||||||
import com.squareup.kotlinpoet.FunSpec
|
|
||||||
import com.squareup.kotlinpoet.KModifier
|
|
||||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
|
||||||
import com.squareup.kotlinpoet.STAR
|
|
||||||
import com.squareup.kotlinpoet.TypeSpec
|
|
||||||
import dagger.Binds
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.assisted.Assisted
|
|
||||||
import dagger.assisted.AssistedFactory
|
|
||||||
import dagger.assisted.AssistedInject
|
|
||||||
import dagger.multibindings.IntoMap
|
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
|
||||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
|
||||||
import org.jetbrains.kotlin.name.FqName
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an anvil plugin that allows Node to use [ContributesNode] alone and let this plugin automatically
|
|
||||||
* handle the rest of the Dagger wiring required for constructor injection.
|
|
||||||
*/
|
|
||||||
@AutoService(CodeGenerator::class)
|
|
||||||
class ContributesNodeCodeGenerator : CodeGenerator {
|
|
||||||
override fun isApplicable(context: AnvilContext): Boolean = true
|
|
||||||
|
|
||||||
override fun generateCode(codeGenDir: File, module: ModuleDescriptor, projectFiles: Collection<KtFile>): Collection<GeneratedFile> {
|
|
||||||
return projectFiles.classAndInnerClassReferences(module)
|
|
||||||
.filter { it.isAnnotatedWith(ContributesNode::class.fqName) }
|
|
||||||
.flatMap { listOf(generateModule(it, codeGenDir, module), generateAssistedFactory(it, codeGenDir, module)) }
|
|
||||||
.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateModule(nodeClass: ClassReference.Psi, codeGenDir: File, module: ModuleDescriptor): GeneratedFile {
|
|
||||||
val generatedPackage = nodeClass.packageFqName.toString()
|
|
||||||
val moduleClassName = "${nodeClass.shortName}_Module"
|
|
||||||
val scope = nodeClass.annotations.single { it.fqName == ContributesNode::class.fqName }.scope()
|
|
||||||
val content = FileSpec.buildFile(generatedPackage, moduleClassName) {
|
|
||||||
addType(
|
|
||||||
TypeSpec.classBuilder(moduleClassName)
|
|
||||||
.addModifiers(KModifier.ABSTRACT)
|
|
||||||
.addAnnotation(Module::class)
|
|
||||||
.addAnnotation(AnnotationSpec.builder(ContributesTo::class).addMember("%T::class", scope.asClassName()).build())
|
|
||||||
.addFunction(
|
|
||||||
FunSpec.builder("bind${nodeClass.shortName}Factory")
|
|
||||||
.addModifiers(KModifier.ABSTRACT)
|
|
||||||
.addParameter("factory", ClassName(generatedPackage, "${nodeClass.shortName}_AssistedFactory"))
|
|
||||||
.returns(assistedNodeFactoryFqName.asClassName(module).parameterizedBy(STAR))
|
|
||||||
.addAnnotation(Binds::class)
|
|
||||||
.addAnnotation(IntoMap::class)
|
|
||||||
.addAnnotation(
|
|
||||||
AnnotationSpec.Companion.builder(nodeKeyFqName.asClassName(module)).addMember(
|
|
||||||
"%T::class",
|
|
||||||
nodeClass.asClassName()
|
|
||||||
).build()
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return createGeneratedFile(codeGenDir, generatedPackage, moduleClassName, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateAssistedFactory(nodeClass: ClassReference.Psi, codeGenDir: File, module: ModuleDescriptor): GeneratedFile {
|
|
||||||
val generatedPackage = nodeClass.packageFqName.toString()
|
|
||||||
val assistedFactoryClassName = "${nodeClass.shortName}_AssistedFactory"
|
|
||||||
val constructor = nodeClass.constructors.singleOrNull { it.isAnnotatedWith(AssistedInject::class.fqName) }
|
|
||||||
val assistedParameters = constructor?.parameters?.filter { it.isAnnotatedWith(Assisted::class.fqName) }.orEmpty()
|
|
||||||
if (constructor == null || assistedParameters.size != 2) {
|
|
||||||
throw AnvilCompilationException(
|
|
||||||
"${nodeClass.fqName} must have an @AssistedInject constructor with 2 @Assisted parameters",
|
|
||||||
element = nodeClass.clazz,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val contextAssistedParam = assistedParameters[0]
|
|
||||||
if (contextAssistedParam.name != "buildContext") {
|
|
||||||
throw AnvilCompilationException(
|
|
||||||
"${nodeClass.fqName} @Assisted parameter must be named buildContext",
|
|
||||||
element = contextAssistedParam.parameter,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val pluginsAssistedParam = assistedParameters[1]
|
|
||||||
if (pluginsAssistedParam.name != "plugins") {
|
|
||||||
throw AnvilCompilationException(
|
|
||||||
"${nodeClass.fqName} @Assisted parameter must be named plugins",
|
|
||||||
element = pluginsAssistedParam.parameter,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val nodeClassName = nodeClass.asClassName()
|
|
||||||
val buildContextClassName = contextAssistedParam.type().asTypeName()
|
|
||||||
val pluginsClassName = pluginsAssistedParam.type().asTypeName()
|
|
||||||
val content = FileSpec.buildFile(generatedPackage, assistedFactoryClassName) {
|
|
||||||
addType(
|
|
||||||
TypeSpec.interfaceBuilder(assistedFactoryClassName)
|
|
||||||
.addSuperinterface(assistedNodeFactoryFqName.asClassName(module).parameterizedBy(nodeClassName))
|
|
||||||
.addAnnotation(AssistedFactory::class)
|
|
||||||
.addFunction(
|
|
||||||
FunSpec.builder("create")
|
|
||||||
.addModifiers(KModifier.OVERRIDE, KModifier.ABSTRACT)
|
|
||||||
.addParameter("buildContext", buildContextClassName)
|
|
||||||
.addParameter("plugins", pluginsClassName)
|
|
||||||
.returns(nodeClassName)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return createGeneratedFile(codeGenDir, generatedPackage, assistedFactoryClassName, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val assistedNodeFactoryFqName = FqName("io.element.android.libraries.architecture.AssistedNodeFactory")
|
|
||||||
private val nodeKeyFqName = FqName("io.element.android.libraries.architecture.NodeKey")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022-2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.anvilcodegen
|
||||||
|
|
||||||
|
import com.google.devtools.ksp.KspExperimental
|
||||||
|
import com.google.devtools.ksp.getConstructors
|
||||||
|
import com.google.devtools.ksp.isAnnotationPresent
|
||||||
|
import com.google.devtools.ksp.processing.CodeGenerator
|
||||||
|
import com.google.devtools.ksp.processing.Dependencies
|
||||||
|
import com.google.devtools.ksp.processing.KSPLogger
|
||||||
|
import com.google.devtools.ksp.processing.Resolver
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||||
|
import com.google.devtools.ksp.symbol.KSAnnotated
|
||||||
|
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||||
|
import com.google.devtools.ksp.symbol.KSType
|
||||||
|
import com.google.devtools.ksp.validate
|
||||||
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
|
import com.squareup.kotlinpoet.AnnotationSpec
|
||||||
|
import com.squareup.kotlinpoet.ClassName
|
||||||
|
import com.squareup.kotlinpoet.FileSpec
|
||||||
|
import com.squareup.kotlinpoet.FunSpec
|
||||||
|
import com.squareup.kotlinpoet.KModifier
|
||||||
|
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||||
|
import com.squareup.kotlinpoet.STAR
|
||||||
|
import com.squareup.kotlinpoet.TypeSpec
|
||||||
|
import com.squareup.kotlinpoet.ksp.toTypeName
|
||||||
|
import com.squareup.kotlinpoet.ksp.writeTo
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import dagger.multibindings.IntoMap
|
||||||
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
|
||||||
|
class ContributesNodeProcessor(
|
||||||
|
private val logger: KSPLogger,
|
||||||
|
private val codeGenerator: CodeGenerator,
|
||||||
|
private val config: Config,
|
||||||
|
) : SymbolProcessor {
|
||||||
|
data class Config(
|
||||||
|
val enableLogging: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||||
|
val annotatedSymbols = resolver.getSymbolsWithAnnotation(ContributesNode::class.qualifiedName!!)
|
||||||
|
.filterIsInstance<KSClassDeclaration>()
|
||||||
|
|
||||||
|
val (validSymbols, invalidSymbols) = annotatedSymbols.partition { it.validate() }
|
||||||
|
|
||||||
|
if (validSymbols.isEmpty()) return invalidSymbols
|
||||||
|
|
||||||
|
for (ksClass in validSymbols) {
|
||||||
|
if (config.enableLogging) {
|
||||||
|
logger.warn("Processing ${ksClass.qualifiedName?.asString()}")
|
||||||
|
}
|
||||||
|
generateModule(ksClass)
|
||||||
|
generateFactory(ksClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
return invalidSymbols
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateModule(ksClass: KSClassDeclaration) {
|
||||||
|
val annotation = ksClass.annotations.find { it.shortName.asString() == "ContributesNode" }!!
|
||||||
|
val scope = annotation.arguments.find { it.name?.asString() == "scope" }!!.value as KSType
|
||||||
|
val modulePackage = ksClass.packageName.asString()
|
||||||
|
val moduleClassName = "${ksClass.simpleName.asString()}_Module"
|
||||||
|
val content = FileSpec.builder(
|
||||||
|
packageName = modulePackage,
|
||||||
|
fileName = moduleClassName,
|
||||||
|
)
|
||||||
|
.addType(
|
||||||
|
TypeSpec.classBuilder(moduleClassName)
|
||||||
|
.addModifiers(KModifier.ABSTRACT)
|
||||||
|
.addAnnotation(Module::class)
|
||||||
|
.addAnnotation(AnnotationSpec.builder(ContributesTo::class).addMember("%T::class", scope.toTypeName()).build())
|
||||||
|
.addFunction(
|
||||||
|
FunSpec.builder("bind${ksClass.simpleName.asString()}Factory")
|
||||||
|
.addModifiers(KModifier.ABSTRACT)
|
||||||
|
.addParameter("factory", ClassName(modulePackage, "${ksClass.simpleName.asString()}_AssistedFactory"))
|
||||||
|
.returns(ClassName.bestGuess(assistedNodeFactoryFqName.asString()).parameterizedBy(STAR))
|
||||||
|
.addAnnotation(Binds::class)
|
||||||
|
.addAnnotation(IntoMap::class)
|
||||||
|
.addAnnotation(
|
||||||
|
AnnotationSpec.Companion.builder(ClassName.bestGuess(nodeKeyFqName.asString())).addMember(
|
||||||
|
"%T::class",
|
||||||
|
ClassName.bestGuess(ksClass.qualifiedName!!.asString())
|
||||||
|
).build()
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
content.writeTo(
|
||||||
|
codeGenerator = codeGenerator,
|
||||||
|
dependencies = Dependencies(
|
||||||
|
aggregating = true,
|
||||||
|
ksClass.containingFile!!
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(KspExperimental::class)
|
||||||
|
private fun generateFactory(ksClass: KSClassDeclaration) {
|
||||||
|
val generatedPackage = ksClass.packageName.asString()
|
||||||
|
val assistedFactoryClassName = "${ksClass.simpleName.asString()}_AssistedFactory"
|
||||||
|
val constructor = ksClass.getConstructors().singleOrNull { it.isAnnotationPresent(AssistedInject::class) }
|
||||||
|
val assistedParameters = constructor?.parameters?.filter { it.isAnnotationPresent(Assisted::class) }.orEmpty()
|
||||||
|
if (constructor == null || assistedParameters.size != 2) {
|
||||||
|
error(
|
||||||
|
"${ksClass.qualifiedName} must have an @AssistedInject constructor with 2 @Assisted parameters",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val contextAssistedParam = assistedParameters[0]
|
||||||
|
if (contextAssistedParam.name?.asString() != "buildContext") {
|
||||||
|
error(
|
||||||
|
"${ksClass.qualifiedName} @Assisted parameter must be named buildContext",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val pluginsAssistedParam = assistedParameters[1]
|
||||||
|
if (pluginsAssistedParam.name?.asString() != "plugins") {
|
||||||
|
error(
|
||||||
|
"${ksClass.qualifiedName} @Assisted parameter must be named plugins",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val nodeClassName = ClassName.bestGuess(ksClass.qualifiedName!!.asString())
|
||||||
|
val buildContextClassName = contextAssistedParam.type.toTypeName()
|
||||||
|
val pluginsClassName = pluginsAssistedParam.type.toTypeName()
|
||||||
|
val content = FileSpec.builder(generatedPackage, assistedFactoryClassName)
|
||||||
|
.addType(
|
||||||
|
TypeSpec.interfaceBuilder(assistedFactoryClassName)
|
||||||
|
.addSuperinterface(ClassName.bestGuess(assistedNodeFactoryFqName.asString()).parameterizedBy(nodeClassName))
|
||||||
|
.addAnnotation(AssistedFactory::class)
|
||||||
|
.addFunction(
|
||||||
|
FunSpec.builder("create")
|
||||||
|
.addModifiers(KModifier.OVERRIDE, KModifier.ABSTRACT)
|
||||||
|
.addParameter("buildContext", buildContextClassName)
|
||||||
|
.addParameter("plugins", pluginsClassName)
|
||||||
|
.returns(nodeClassName)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
content.writeTo(
|
||||||
|
codeGenerator = codeGenerator,
|
||||||
|
dependencies = Dependencies(
|
||||||
|
aggregating = true,
|
||||||
|
ksClass.containingFile!!
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val assistedNodeFactoryFqName = FqName("io.element.android.libraries.architecture.AssistedNodeFactory")
|
||||||
|
private val nodeKeyFqName = FqName("io.element.android.libraries.architecture.NodeKey")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022-2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.anvilcodegen
|
||||||
|
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||||
|
|
||||||
|
class ContributesNodeProcessorProvider : SymbolProcessorProvider {
|
||||||
|
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
|
||||||
|
val enableLogging = environment.options["enableLogging"]?.toBoolean() ?: false
|
||||||
|
return ContributesNodeProcessor(
|
||||||
|
logger = environment.logger,
|
||||||
|
codeGenerator = environment.codeGenerator,
|
||||||
|
config = ContributesNodeProcessor.Config(enableLogging = enableLogging),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
io.element.android.anvilcodegen.ContributesNodeProcessorProvider
|
||||||
|
|
@ -11,6 +11,7 @@ import com.android.build.api.variant.FilterConfiguration.FilterType.ABI
|
||||||
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
||||||
import com.android.build.gradle.tasks.GenerateBuildConfig
|
import com.android.build.gradle.tasks.GenerateBuildConfig
|
||||||
import extension.AssetCopyTask
|
import extension.AssetCopyTask
|
||||||
|
import extension.ComponentMergingStrategy
|
||||||
import extension.GitBranchNameValueSource
|
import extension.GitBranchNameValueSource
|
||||||
import extension.GitRevisionValueSource
|
import extension.GitRevisionValueSource
|
||||||
import extension.allEnterpriseImpl
|
import extension.allEnterpriseImpl
|
||||||
|
|
@ -19,14 +20,13 @@ import extension.allLibrariesImpl
|
||||||
import extension.allServicesImpl
|
import extension.allServicesImpl
|
||||||
import extension.koverDependencies
|
import extension.koverDependencies
|
||||||
import extension.locales
|
import extension.locales
|
||||||
|
import extension.setupAnvil
|
||||||
import extension.setupKover
|
import extension.setupKover
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-application")
|
id("io.element.android-compose-application")
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
alias(libs.plugins.kapt)
|
|
||||||
// When using precompiled plugins, we need to apply the firebase plugin like this
|
// When using precompiled plugins, we need to apply the firebase plugin like this
|
||||||
id(libs.plugins.firebaseAppDistribution.get().pluginId)
|
id(libs.plugins.firebaseAppDistribution.get().pluginId)
|
||||||
alias(libs.plugins.knit)
|
alias(libs.plugins.knit)
|
||||||
|
|
@ -161,9 +161,6 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "17"
|
|
||||||
}
|
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
|
|
@ -233,27 +230,34 @@ knit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupAnvil(
|
||||||
|
generateDaggerCode = true,
|
||||||
|
generateDaggerFactoriesUsingAnvil = false,
|
||||||
|
componentMergingStrategy = ComponentMergingStrategy.KSP,
|
||||||
|
)
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
allLibrariesImpl()
|
allLibrariesImpl()
|
||||||
allServicesImpl()
|
allServicesImpl()
|
||||||
if (isEnterpriseBuild) {
|
if (isEnterpriseBuild) {
|
||||||
allEnterpriseImpl(rootDir, logger)
|
allEnterpriseImpl(project)
|
||||||
implementation(projects.appicon.enterprise)
|
implementation(projects.appicon.enterprise)
|
||||||
} else {
|
} else {
|
||||||
implementation(projects.appicon.element)
|
implementation(projects.appicon.element)
|
||||||
}
|
}
|
||||||
allFeaturesImpl(rootDir, logger)
|
allFeaturesImpl(project)
|
||||||
implementation(projects.features.migration.api)
|
implementation(projects.features.migration.api)
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
implementation(projects.appnav)
|
implementation(projects.appnav)
|
||||||
implementation(projects.appconfig)
|
implementation(projects.appconfig)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
anvil(projects.anvilcodegen)
|
implementation(projects.services.analytics.compose)
|
||||||
|
|
||||||
// Comment to not include firebase in the project
|
if (ModulesConfig.pushProvidersConfig.includeFirebase) {
|
||||||
"gplayImplementation"(projects.libraries.pushproviders.firebase)
|
"gplayImplementation"(projects.libraries.pushproviders.firebase)
|
||||||
// Comment to not include unified push in the project
|
}
|
||||||
implementation(projects.libraries.pushproviders.unifiedpush)
|
if (ModulesConfig.pushProvidersConfig.includeUnifiedPush) {
|
||||||
|
implementation(projects.libraries.pushproviders.unifiedpush)
|
||||||
|
}
|
||||||
|
|
||||||
implementation(libs.appyx.core)
|
implementation(libs.appyx.core)
|
||||||
implementation(libs.androidx.splash)
|
implementation(libs.androidx.splash)
|
||||||
|
|
@ -272,9 +276,6 @@ dependencies {
|
||||||
|
|
||||||
implementation(libs.matrix.emojibase.bindings)
|
implementation(libs.matrix.emojibase.bindings)
|
||||||
|
|
||||||
implementation(libs.dagger)
|
|
||||||
kapt(libs.dagger.compiler)
|
|
||||||
|
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
testImplementation(libs.test.robolectric)
|
testImplementation(libs.test.robolectric)
|
||||||
testImplementation(libs.coroutines.test)
|
testImplementation(libs.coroutines.test)
|
||||||
|
|
@ -297,14 +298,13 @@ tasks.withType<GenerateBuildConfig>().configureEach {
|
||||||
licensee {
|
licensee {
|
||||||
allow("Apache-2.0")
|
allow("Apache-2.0")
|
||||||
allow("MIT")
|
allow("MIT")
|
||||||
allow("GPL-2.0-with-classpath-exception")
|
|
||||||
allow("BSD-2-Clause")
|
allow("BSD-2-Clause")
|
||||||
allowUrl("https://opensource.org/licenses/MIT")
|
allowUrl("https://opensource.org/licenses/MIT")
|
||||||
allowUrl("https://developer.android.com/studio/terms.html")
|
allowUrl("https://developer.android.com/studio/terms.html")
|
||||||
allowUrl("http://openjdk.java.net/legal/gplv2+ce.html")
|
|
||||||
allowUrl("https://www.zetetic.net/sqlcipher/license/")
|
allowUrl("https://www.zetetic.net/sqlcipher/license/")
|
||||||
allowUrl("https://jsoup.org/license")
|
allowUrl("https://jsoup.org/license")
|
||||||
allowUrl("https://asm.ow2.io/license.html")
|
allowUrl("https://asm.ow2.io/license.html")
|
||||||
|
allowUrl("https://www.gnu.org/licenses/agpl-3.0.txt")
|
||||||
ignoreDependencies("com.github.matrix-org", "matrix-analytics-events")
|
ignoreDependencies("com.github.matrix-org", "matrix-analytics-events")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ package io.element.android.x.di
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.squareup.anvil.annotations.MergeComponent
|
import com.squareup.anvil.annotations.MergeComponent
|
||||||
import dagger.BindsInstance
|
import dagger.BindsInstance
|
||||||
import dagger.Component
|
|
||||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
|
@ -19,7 +18,7 @@ import io.element.android.libraries.di.SingleIn
|
||||||
@SingleIn(AppScope::class)
|
@SingleIn(AppScope::class)
|
||||||
@MergeComponent(AppScope::class)
|
@MergeComponent(AppScope::class)
|
||||||
interface AppComponent : NodeFactoriesBindings {
|
interface AppComponent : NodeFactoriesBindings {
|
||||||
@Component.Factory
|
@MergeComponent.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(
|
fun create(
|
||||||
@ApplicationContext @BindsInstance
|
@ApplicationContext @BindsInstance
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ package io.element.android.x.di
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||||
import dagger.BindsInstance
|
import dagger.BindsInstance
|
||||||
import dagger.Subcomponent
|
|
||||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
@ -20,7 +19,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
@SingleIn(RoomScope::class)
|
@SingleIn(RoomScope::class)
|
||||||
@MergeSubcomponent(RoomScope::class)
|
@MergeSubcomponent(RoomScope::class)
|
||||||
interface RoomComponent : NodeFactoriesBindings {
|
interface RoomComponent : NodeFactoriesBindings {
|
||||||
@Subcomponent.Builder
|
@MergeSubcomponent.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
@BindsInstance
|
@BindsInstance
|
||||||
fun room(room: MatrixRoom): Builder
|
fun room(room: MatrixRoom): Builder
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ package io.element.android.x.di
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||||
import dagger.BindsInstance
|
import dagger.BindsInstance
|
||||||
import dagger.Subcomponent
|
|
||||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
@ -20,7 +19,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
@SingleIn(SessionScope::class)
|
@SingleIn(SessionScope::class)
|
||||||
@MergeSubcomponent(SessionScope::class)
|
@MergeSubcomponent(SessionScope::class)
|
||||||
interface SessionComponent : NodeFactoriesBindings {
|
interface SessionComponent : NodeFactoriesBindings {
|
||||||
@Subcomponent.Builder
|
@MergeSubcomponent.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
@BindsInstance
|
@BindsInstance
|
||||||
fun client(matrixClient: MatrixClient): Builder
|
fun client(matrixClient: MatrixClient): Builder
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
<locale android:name="en"/>
|
<locale android:name="en"/>
|
||||||
<locale android:name="es"/>
|
<locale android:name="es"/>
|
||||||
<locale android:name="et"/>
|
<locale android:name="et"/>
|
||||||
|
<locale android:name="fa"/>
|
||||||
<locale android:name="fr"/>
|
<locale android:name="fr"/>
|
||||||
<locale android:name="hu"/>
|
<locale android:name="hu"/>
|
||||||
<locale android:name="in"/>
|
<locale android:name="in"/>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
package io.element.android.appconfig
|
package io.element.android.appconfig
|
||||||
|
|
||||||
object SecureBackupConfig {
|
object LearnMoreConfig {
|
||||||
const val LEARN_MORE_URL: String = "https://element.io/help#encryption5"
|
const val SECURE_BACKUP_URL: String = "https://element.io/help#encryption5"
|
||||||
|
const val IDENTITY_CHANGE_URL: String = "https://element.io/help#encryption18"
|
||||||
}
|
}
|
||||||
|
|
@ -8,11 +8,10 @@
|
||||||
@file:Suppress("UnstableApiUsage")
|
@file:Suppress("UnstableApiUsage")
|
||||||
|
|
||||||
import extension.allFeaturesApi
|
import extension.allFeaturesApi
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
alias(libs.plugins.kapt)
|
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -20,13 +19,10 @@ android {
|
||||||
namespace = "io.element.android.appnav"
|
namespace = "io.element.android.appnav"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
setupAnvil()
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
anvil(projects.anvilcodegen)
|
|
||||||
implementation(libs.dagger)
|
|
||||||
kapt(libs.dagger.compiler)
|
|
||||||
|
|
||||||
allFeaturesApi(rootDir, logger)
|
dependencies {
|
||||||
|
allFeaturesApi(project)
|
||||||
|
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import im.vector.app.features.analytics.plan.SuperProperties
|
import im.vector.app.features.analytics.plan.SuperProperties
|
||||||
import io.element.android.features.rageshake.api.crash.CrashDetectionPresenter
|
import io.element.android.features.rageshake.api.crash.CrashDetectionState
|
||||||
import io.element.android.features.rageshake.api.detection.RageshakeDetectionPresenter
|
import io.element.android.features.rageshake.api.detection.RageshakeDetectionState
|
||||||
import io.element.android.features.share.api.ShareService
|
import io.element.android.features.share.api.ShareService
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.matrix.api.SdkMetadata
|
import io.element.android.libraries.matrix.api.SdkMetadata
|
||||||
|
|
@ -22,8 +22,8 @@ import io.element.android.services.apperror.api.AppErrorStateService
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RootPresenter @Inject constructor(
|
class RootPresenter @Inject constructor(
|
||||||
private val crashDetectionPresenter: CrashDetectionPresenter,
|
private val crashDetectionPresenter: Presenter<CrashDetectionState>,
|
||||||
private val rageshakeDetectionPresenter: RageshakeDetectionPresenter,
|
private val rageshakeDetectionPresenter: Presenter<RageshakeDetectionState>,
|
||||||
private val appErrorStateService: AppErrorStateService,
|
private val appErrorStateService: AppErrorStateService,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
private val shareService: ShareService,
|
private val shareService: ShareService,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="banner_migrate_to_native_sliding_sync_action">"Выйсці і абнавіць"</string>
|
<string name="banner_migrate_to_native_sliding_sync_action">"Выйсці і абнавіць"</string>
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Ваш хатні сервер больш не падтрымлівае стары пратакол. Калі ласка, выйдзіце і ўвайдзіце зноў, каб працягнуць выкарыстанне праграмы."</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
4
appnav/src/main/res/values-fa/translations.xml
Normal file
4
appnav/src/main/res/values-fa/translations.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_action">"خروج و ارتقا"</string>
|
||||||
|
</resources>
|
||||||
5
appnav/src/main/res/values-it/translations.xml
Normal file
5
appnav/src/main/res/values-it/translations.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_action">"Esci e aggiorna"</string>
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Il tuo homeserver non supporta più il vecchio protocollo. Esci e rientra per continuare a usare l\'app."</string>
|
||||||
|
</resources>
|
||||||
5
appnav/src/main/res/values-pl/translations.xml
Normal file
5
appnav/src/main/res/values-pl/translations.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_action">"Wyloguj się i zaktualizuj"</string>
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Twój serwer domowy już nie wspiera starego protokołu. Zaloguj się ponownie, aby kontynuować korzystanie z aplikacji."</string>
|
||||||
|
</resources>
|
||||||
5
appnav/src/main/res/values-pt/translations.xml
Normal file
5
appnav/src/main/res/values-pt/translations.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_action">"Sair & Atualizar"</string>
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Seu homeserver não suporta mais o protocolo antigo. Termine sessão e volte a iniciar sessão para continuar a utilizar a aplicação."</string>
|
||||||
|
</resources>
|
||||||
5
appnav/src/main/res/values-zh/translations.xml
Normal file
5
appnav/src/main/res/values-zh/translations.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_action">"登出并升级"</string>
|
||||||
|
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"您的服务器不再支持旧协议。请登出并重新登录以继续使用此应用。"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -12,17 +12,11 @@ import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.appnav.root.RootPresenter
|
import io.element.android.appnav.root.RootPresenter
|
||||||
import io.element.android.features.rageshake.impl.crash.DefaultCrashDetectionPresenter
|
import io.element.android.features.rageshake.api.crash.aCrashDetectionState
|
||||||
import io.element.android.features.rageshake.impl.detection.DefaultRageshakeDetectionPresenter
|
import io.element.android.features.rageshake.api.detection.aRageshakeDetectionState
|
||||||
import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter
|
|
||||||
import io.element.android.features.rageshake.test.crash.FakeCrashDataStore
|
|
||||||
import io.element.android.features.rageshake.test.rageshake.FakeRageShake
|
|
||||||
import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore
|
|
||||||
import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder
|
|
||||||
import io.element.android.features.share.api.ShareService
|
import io.element.android.features.share.api.ShareService
|
||||||
import io.element.android.features.share.test.FakeShareService
|
import io.element.android.features.share.test.FakeShareService
|
||||||
import io.element.android.libraries.matrix.test.FakeSdkMetadata
|
import io.element.android.libraries.matrix.test.FakeSdkMetadata
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
import io.element.android.services.apperror.api.AppErrorState
|
import io.element.android.services.apperror.api.AppErrorState
|
||||||
import io.element.android.services.apperror.api.AppErrorStateService
|
import io.element.android.services.apperror.api.AppErrorStateService
|
||||||
|
|
@ -44,7 +38,6 @@ class RootPresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(1)
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.crashDetectionState.crashDetected).isFalse()
|
assertThat(initialState.crashDetectionState.crashDetected).isFalse()
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +54,7 @@ class RootPresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(2)
|
skipItems(1)
|
||||||
lambda.assertions().isCalledOnce()
|
lambda.assertions().isCalledOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -76,8 +69,6 @@ class RootPresenterTest {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(1)
|
|
||||||
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.errorState).isInstanceOf(AppErrorState.Error::class.java)
|
assertThat(initialState.errorState).isInstanceOf(AppErrorState.Error::class.java)
|
||||||
val initialErrorState = initialState.errorState as AppErrorState.Error
|
val initialErrorState = initialState.errorState as AppErrorState.Error
|
||||||
|
|
@ -93,25 +84,9 @@ class RootPresenterTest {
|
||||||
appErrorService: AppErrorStateService = DefaultAppErrorStateService(),
|
appErrorService: AppErrorStateService = DefaultAppErrorStateService(),
|
||||||
shareService: ShareService = FakeShareService {},
|
shareService: ShareService = FakeShareService {},
|
||||||
): RootPresenter {
|
): RootPresenter {
|
||||||
val crashDataStore = FakeCrashDataStore()
|
|
||||||
val rageshakeDataStore = FakeRageshakeDataStore()
|
|
||||||
val rageshake = FakeRageShake()
|
|
||||||
val screenshotHolder = FakeScreenshotHolder()
|
|
||||||
val crashDetectionPresenter = DefaultCrashDetectionPresenter(
|
|
||||||
buildMeta = aBuildMeta(),
|
|
||||||
crashDataStore = crashDataStore
|
|
||||||
)
|
|
||||||
val rageshakeDetectionPresenter = DefaultRageshakeDetectionPresenter(
|
|
||||||
screenshotHolder = screenshotHolder,
|
|
||||||
rageShake = rageshake,
|
|
||||||
preferencesPresenter = DefaultRageshakePreferencesPresenter(
|
|
||||||
rageshake = rageshake,
|
|
||||||
rageshakeDataStore = rageshakeDataStore,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return RootPresenter(
|
return RootPresenter(
|
||||||
crashDetectionPresenter = crashDetectionPresenter,
|
crashDetectionPresenter = { aCrashDetectionState() },
|
||||||
rageshakeDetectionPresenter = rageshakeDetectionPresenter,
|
rageshakeDetectionPresenter = { aRageshakeDetectionState() },
|
||||||
appErrorStateService = appErrorService,
|
appErrorStateService = appErrorService,
|
||||||
analyticsService = FakeAnalyticsService(),
|
analyticsService = FakeAnalyticsService(),
|
||||||
shareService = shareService,
|
shareService = shareService,
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ allprojects {
|
||||||
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
detektPlugins("io.nlopez.compose.rules:detekt:0.4.12")
|
detektPlugins("io.nlopez.compose.rules:detekt:0.4.15")
|
||||||
}
|
}
|
||||||
|
|
||||||
// KtLint
|
// KtLint
|
||||||
|
|
@ -71,8 +71,9 @@ allprojects {
|
||||||
// To have XML report for Danger
|
// To have XML report for Danger
|
||||||
reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE)
|
reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE)
|
||||||
}
|
}
|
||||||
|
val generatedPath = "${layout.buildDirectory.asFile.get()}/generated/"
|
||||||
filter {
|
filter {
|
||||||
exclude { element -> element.file.path.contains("${layout.buildDirectory.asFile.get()}/generated/") }
|
exclude { element -> element.file.path.contains(generatedPath) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Dependency check
|
// Dependency check
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit ceb65e32d95052c028a37654a4a0410639f69053
|
Subproject commit b4f0427e3595049d39846aabcdc06e818f2e96ea
|
||||||
2
fastlane/metadata/android/en-US/changelogs/40006050.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40006050.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: bug fixes and performance improvement.
|
||||||
|
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.analytics.api.preferences
|
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.Presenter
|
|
||||||
|
|
||||||
interface AnalyticsPreferencesPresenter : Presenter<AnalyticsPreferencesState>
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_settings_read_terms_content_link">"اینجا"</string>
|
||||||
|
<string name="screen_analytics_settings_share_data">"هم رسانی دادههای تحلیلی"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,7 +9,6 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -15,13 +16,9 @@ android {
|
||||||
namespace = "io.element.android.features.analytics.impl"
|
namespace = "io.element.android.features.analytics.impl"
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
setupAnvil()
|
||||||
generateDaggerFactories.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
anvil(projects.anvilcodegen)
|
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.foundation.text.ClickableText
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -36,6 +35,7 @@ import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrgani
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
import io.element.android.libraries.designsystem.background.OnboardingBackground
|
import io.element.android.libraries.designsystem.background.OnboardingBackground
|
||||||
import io.element.android.libraries.designsystem.components.BigIcon
|
import io.element.android.libraries.designsystem.components.BigIcon
|
||||||
|
import io.element.android.libraries.designsystem.components.ClickableLinkText
|
||||||
import io.element.android.libraries.designsystem.components.PageTitle
|
import io.element.android.libraries.designsystem.components.PageTitle
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
@ -104,14 +104,9 @@ private fun AnalyticsOptInHeader(
|
||||||
bold = true,
|
bold = true,
|
||||||
tagAndLink = LINK_TAG to AnalyticsConfig.POLICY_LINK,
|
tagAndLink = LINK_TAG to AnalyticsConfig.POLICY_LINK,
|
||||||
)
|
)
|
||||||
ClickableText(
|
ClickableLinkText(
|
||||||
text = text,
|
annotatedString = text,
|
||||||
onClick = {
|
onClick = { onClickTerms() },
|
||||||
text
|
|
||||||
.getStringAnnotations(LINK_TAG, it, it)
|
|
||||||
.firstOrNull()
|
|
||||||
?.let { _ -> onClickTerms() }
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(8.dp),
|
.padding(8.dp),
|
||||||
style = ElementTheme.typography.fontBodyMdRegular
|
style = ElementTheme.typography.fontBodyMdRegular
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.analytics.impl.di
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesState
|
||||||
|
import io.element.android.features.analytics.impl.preferences.AnalyticsPreferencesPresenter
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
|
||||||
|
@ContributesTo(AppScope::class)
|
||||||
|
@Module
|
||||||
|
interface AnalyticsModule {
|
||||||
|
@Binds
|
||||||
|
fun bindAnalyticsPreferencesPresenter(presenter: AnalyticsPreferencesPresenter): Presenter<AnalyticsPreferencesState>
|
||||||
|
}
|
||||||
|
|
@ -10,23 +10,20 @@ package io.element.android.features.analytics.impl.preferences
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
|
||||||
import io.element.android.appconfig.AnalyticsConfig
|
import io.element.android.appconfig.AnalyticsConfig
|
||||||
import io.element.android.features.analytics.api.AnalyticsOptInEvents
|
import io.element.android.features.analytics.api.AnalyticsOptInEvents
|
||||||
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesPresenter
|
|
||||||
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesState
|
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesState
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.di.AppScope
|
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
class AnalyticsPreferencesPresenter @Inject constructor(
|
||||||
class DefaultAnalyticsPreferencesPresenter @Inject constructor(
|
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
) : AnalyticsPreferencesPresenter {
|
) : Presenter<AnalyticsPreferencesState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): AnalyticsPreferencesState {
|
override fun present(): AnalyticsPreferencesState {
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_analytics_prompt_read_terms_content_link">"اینجا"</string>
|
||||||
|
<string name="screen_analytics_prompt_settings">"میتوانید در هر زمان خاموشش کنید"</string>
|
||||||
|
<string name="screen_analytics_prompt_third_party_sharing">"دادههایتان را با سومشخصها همنمیرسانیم"</string>
|
||||||
|
<string name="screen_analytics_prompt_title">"کمک به بهبود %1$s"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -25,7 +25,7 @@ class AnalyticsPreferencesPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state available`() = runTest {
|
fun `present - initial state available`() = runTest {
|
||||||
val presenter = DefaultAnalyticsPreferencesPresenter(
|
val presenter = AnalyticsPreferencesPresenter(
|
||||||
FakeAnalyticsService(isEnabled = true, didAskUserConsent = true),
|
FakeAnalyticsService(isEnabled = true, didAskUserConsent = true),
|
||||||
aBuildMeta()
|
aBuildMeta()
|
||||||
)
|
)
|
||||||
|
|
@ -41,7 +41,7 @@ class AnalyticsPreferencesPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state not available`() = runTest {
|
fun `present - initial state not available`() = runTest {
|
||||||
val presenter = DefaultAnalyticsPreferencesPresenter(
|
val presenter = AnalyticsPreferencesPresenter(
|
||||||
FakeAnalyticsService(isEnabled = false, didAskUserConsent = false),
|
FakeAnalyticsService(isEnabled = false, didAskUserConsent = false),
|
||||||
aBuildMeta()
|
aBuildMeta()
|
||||||
)
|
)
|
||||||
|
|
@ -55,7 +55,7 @@ class AnalyticsPreferencesPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - enable and disable`() = runTest {
|
fun `present - enable and disable`() = runTest {
|
||||||
val presenter = DefaultAnalyticsPreferencesPresenter(
|
val presenter = AnalyticsPreferencesPresenter(
|
||||||
FakeAnalyticsService(isEnabled = true, didAskUserConsent = true),
|
FakeAnalyticsService(isEnabled = true, didAskUserConsent = true),
|
||||||
aBuildMeta()
|
aBuildMeta()
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,20 +9,15 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.features.cachecleaner.impl"
|
namespace = "io.element.android.features.cachecleaner.impl"
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
setupAnvil()
|
||||||
generateDaggerFactories.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
anvil(projects.anvilcodegen)
|
|
||||||
api(projects.features.cachecleaner.api)
|
api(projects.features.cachecleaner.api)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2024 New Vector Ltd.
|
* Copyright 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,7 +9,6 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
alias(libs.plugins.kotlin.serialization)
|
alias(libs.plugins.kotlin.serialization)
|
||||||
}
|
}
|
||||||
|
|
@ -24,13 +25,10 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
setupAnvil()
|
||||||
generateDaggerFactories.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.appconfig)
|
implementation(projects.appconfig)
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,6 @@ import io.element.android.features.call.impl.utils.WidgetMessageInterceptor
|
||||||
|
|
||||||
sealed interface CallScreenEvents {
|
sealed interface CallScreenEvents {
|
||||||
data object Hangup : CallScreenEvents
|
data object Hangup : CallScreenEvents
|
||||||
data class SetupMessageChannels(val widgetMessageInterceptor: WidgetMessageInterceptor) :
|
data class SetupMessageChannels(val widgetMessageInterceptor: WidgetMessageInterceptor) : CallScreenEvents
|
||||||
CallScreenEvents
|
data class OnWebViewError(val description: String?) : CallScreenEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,8 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
val callWidgetDriver = remember { mutableStateOf<MatrixWidgetDriver?>(null) }
|
val callWidgetDriver = remember { mutableStateOf<MatrixWidgetDriver?>(null) }
|
||||||
val messageInterceptor = remember { mutableStateOf<WidgetMessageInterceptor?>(null) }
|
val messageInterceptor = remember { mutableStateOf<WidgetMessageInterceptor?>(null) }
|
||||||
var isJoinedCall by rememberSaveable { mutableStateOf(false) }
|
var isJoinedCall by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var ignoreWebViewError by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var webViewError by remember { mutableStateOf<String?>(null) }
|
||||||
val languageTag = languageTagProvider.provideLanguageTag()
|
val languageTag = languageTagProvider.provideLanguageTag()
|
||||||
val theme = if (ElementTheme.isLightTheme) "light" else "dark"
|
val theme = if (ElementTheme.isLightTheme) "light" else "dark"
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
|
|
@ -125,6 +127,8 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
interceptor.interceptedMessages
|
interceptor.interceptedMessages
|
||||||
.onEach {
|
.onEach {
|
||||||
|
// We are receiving messages from the WebView, consider that the application is loaded
|
||||||
|
ignoreWebViewError = true
|
||||||
// Relay message to Widget Driver
|
// Relay message to Widget Driver
|
||||||
callWidgetDriver.value?.send(it)
|
callWidgetDriver.value?.send(it)
|
||||||
|
|
||||||
|
|
@ -163,11 +167,18 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||||
is CallScreenEvents.SetupMessageChannels -> {
|
is CallScreenEvents.SetupMessageChannels -> {
|
||||||
messageInterceptor.value = event.widgetMessageInterceptor
|
messageInterceptor.value = event.widgetMessageInterceptor
|
||||||
}
|
}
|
||||||
|
is CallScreenEvents.OnWebViewError -> {
|
||||||
|
if (!ignoreWebViewError) {
|
||||||
|
webViewError = event.description.orEmpty()
|
||||||
|
}
|
||||||
|
// Else ignore the error, give a chance the Element Call to recover by itself.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CallScreenState(
|
return CallScreenState(
|
||||||
urlState = urlState.value,
|
urlState = urlState.value,
|
||||||
|
webViewError = webViewError,
|
||||||
userAgent = userAgent,
|
userAgent = userAgent,
|
||||||
isInWidgetMode = isInWidgetMode,
|
isInWidgetMode = isInWidgetMode,
|
||||||
eventSink = { handleEvents(it) },
|
eventSink = { handleEvents(it) },
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
||||||
data class CallScreenState(
|
data class CallScreenState(
|
||||||
val urlState: AsyncData<String>,
|
val urlState: AsyncData<String>,
|
||||||
|
val webViewError: String?,
|
||||||
val userAgent: String,
|
val userAgent: String,
|
||||||
val isInWidgetMode: Boolean,
|
val isInWidgetMode: Boolean,
|
||||||
val eventSink: (CallScreenEvents) -> Unit,
|
val eventSink: (CallScreenEvents) -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,20 @@ open class CallScreenStateProvider : PreviewParameterProvider<CallScreenState> {
|
||||||
aCallScreenState(),
|
aCallScreenState(),
|
||||||
aCallScreenState(urlState = AsyncData.Loading()),
|
aCallScreenState(urlState = AsyncData.Loading()),
|
||||||
aCallScreenState(urlState = AsyncData.Failure(Exception("An error occurred"))),
|
aCallScreenState(urlState = AsyncData.Failure(Exception("An error occurred"))),
|
||||||
|
aCallScreenState(webViewError = "Error details from WebView"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun aCallScreenState(
|
internal fun aCallScreenState(
|
||||||
urlState: AsyncData<String> = AsyncData.Success("https://call.element.io/some-actual-call?with=parameters"),
|
urlState: AsyncData<String> = AsyncData.Success("https://call.element.io/some-actual-call?with=parameters"),
|
||||||
|
webViewError: String? = null,
|
||||||
userAgent: String = "",
|
userAgent: String = "",
|
||||||
isInWidgetMode: Boolean = false,
|
isInWidgetMode: Boolean = false,
|
||||||
eventSink: (CallScreenEvents) -> Unit = {},
|
eventSink: (CallScreenEvents) -> Unit = {},
|
||||||
): CallScreenState {
|
): CallScreenState {
|
||||||
return CallScreenState(
|
return CallScreenState(
|
||||||
urlState = urlState,
|
urlState = urlState,
|
||||||
|
webViewError = webViewError,
|
||||||
userAgent = userAgent,
|
userAgent = userAgent,
|
||||||
isInWidgetMode = isInWidgetMode,
|
isInWidgetMode = isInWidgetMode,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
|
|
|
||||||
|
|
@ -85,35 +85,48 @@ internal fun CallScreenView(
|
||||||
BackHandler {
|
BackHandler {
|
||||||
handleBack()
|
handleBack()
|
||||||
}
|
}
|
||||||
CallWebView(
|
if (state.webViewError != null) {
|
||||||
modifier = Modifier
|
ErrorDialog(
|
||||||
|
content = buildString {
|
||||||
|
append(stringResource(CommonStrings.error_unknown))
|
||||||
|
state.webViewError.takeIf { it.isNotEmpty() }?.let { append("\n\n").append(it) }
|
||||||
|
},
|
||||||
|
onSubmit = { state.eventSink(CallScreenEvents.Hangup) },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
CallWebView(
|
||||||
|
modifier = Modifier
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.consumeWindowInsets(padding)
|
.consumeWindowInsets(padding)
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
url = state.urlState,
|
url = state.urlState,
|
||||||
userAgent = state.userAgent,
|
userAgent = state.userAgent,
|
||||||
onPermissionsRequest = { request ->
|
onPermissionsRequest = { request ->
|
||||||
val androidPermissions = mapWebkitPermissions(request.resources)
|
val androidPermissions = mapWebkitPermissions(request.resources)
|
||||||
val callback: RequestPermissionCallback = { request.grant(it) }
|
val callback: RequestPermissionCallback = { request.grant(it) }
|
||||||
requestPermissions(androidPermissions.toTypedArray(), callback)
|
requestPermissions(androidPermissions.toTypedArray(), callback)
|
||||||
},
|
},
|
||||||
onWebViewCreate = { webView ->
|
onWebViewCreate = { webView ->
|
||||||
val interceptor = WebViewWidgetMessageInterceptor(webView)
|
val interceptor = WebViewWidgetMessageInterceptor(
|
||||||
state.eventSink(CallScreenEvents.SetupMessageChannels(interceptor))
|
webView = webView,
|
||||||
val pipController = WebViewPipController(webView)
|
onError = { state.eventSink(CallScreenEvents.OnWebViewError(it)) },
|
||||||
pipState.eventSink(PictureInPictureEvents.SetPipController(pipController))
|
)
|
||||||
|
state.eventSink(CallScreenEvents.SetupMessageChannels(interceptor))
|
||||||
|
val pipController = WebViewPipController(webView)
|
||||||
|
pipState.eventSink(PictureInPictureEvents.SetPipController(pipController))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
when (state.urlState) {
|
||||||
|
AsyncData.Uninitialized,
|
||||||
|
is AsyncData.Loading ->
|
||||||
|
ProgressDialog(text = stringResource(id = CommonStrings.common_please_wait))
|
||||||
|
is AsyncData.Failure ->
|
||||||
|
ErrorDialog(
|
||||||
|
content = state.urlState.error.message.orEmpty(),
|
||||||
|
onSubmit = { state.eventSink(CallScreenEvents.Hangup) },
|
||||||
|
)
|
||||||
|
is AsyncData.Success -> Unit
|
||||||
}
|
}
|
||||||
)
|
|
||||||
when (state.urlState) {
|
|
||||||
AsyncData.Uninitialized,
|
|
||||||
is AsyncData.Loading ->
|
|
||||||
ProgressDialog(text = stringResource(id = CommonStrings.common_please_wait))
|
|
||||||
is AsyncData.Failure ->
|
|
||||||
ErrorDialog(
|
|
||||||
content = state.urlState.error.message.orEmpty(),
|
|
||||||
onSubmit = { state.eventSink(CallScreenEvents.Hangup) },
|
|
||||||
)
|
|
||||||
is AsyncData.Success -> Unit
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,23 @@
|
||||||
package io.element.android.features.call.impl.utils
|
package io.element.android.features.call.impl.utils
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.net.http.SslError
|
||||||
import android.webkit.JavascriptInterface
|
import android.webkit.JavascriptInterface
|
||||||
|
import android.webkit.SslErrorHandler
|
||||||
|
import android.webkit.WebResourceError
|
||||||
|
import android.webkit.WebResourceRequest
|
||||||
|
import android.webkit.WebResourceResponse
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
import androidx.webkit.WebViewCompat
|
import androidx.webkit.WebViewCompat
|
||||||
import androidx.webkit.WebViewFeature
|
import androidx.webkit.WebViewFeature
|
||||||
import io.element.android.features.call.impl.BuildConfig
|
import io.element.android.features.call.impl.BuildConfig
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class WebViewWidgetMessageInterceptor(
|
class WebViewWidgetMessageInterceptor(
|
||||||
private val webView: WebView,
|
private val webView: WebView,
|
||||||
|
private val onError: (String?) -> Unit,
|
||||||
) : WidgetMessageInterceptor {
|
) : WidgetMessageInterceptor {
|
||||||
companion object {
|
companion object {
|
||||||
// We call both the WebMessageListener and the JavascriptInterface objects in JS with this
|
// We call both the WebMessageListener and the JavascriptInterface objects in JS with this
|
||||||
|
|
@ -45,16 +52,35 @@ class WebViewWidgetMessageInterceptor(
|
||||||
if (message.data.response && message.data.api == "toWidget"
|
if (message.data.response && message.data.api == "toWidget"
|
||||||
|| !message.data.response && message.data.api == "fromWidget") {
|
|| !message.data.response && message.data.api == "fromWidget") {
|
||||||
let json = JSON.stringify(event.data)
|
let json = JSON.stringify(event.data)
|
||||||
${"console.log('message sent: ' + json);".takeIf { BuildConfig.DEBUG } }
|
${"console.log('message sent: ' + json);".takeIf { BuildConfig.DEBUG }}
|
||||||
$LISTENER_NAME.postMessage(json);
|
$LISTENER_NAME.postMessage(json);
|
||||||
} else {
|
} else {
|
||||||
${"console.log('message received (ignored): ' + JSON.stringify(event.data));".takeIf { BuildConfig.DEBUG } }
|
${"console.log('message received (ignored): ' + JSON.stringify(event.data));".takeIf { BuildConfig.DEBUG }}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
|
||||||
|
// No network for instance, transmit the error
|
||||||
|
Timber.e("onReceivedError error: ${error?.errorCode} ${error?.description}")
|
||||||
|
onError(error?.description?.toString())
|
||||||
|
super.onReceivedError(view, request, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceivedHttpError(view: WebView?, request: WebResourceRequest?, errorResponse: WebResourceResponse?) {
|
||||||
|
Timber.e("onReceivedHttpError error: ${errorResponse?.statusCode} ${errorResponse?.reasonPhrase}")
|
||||||
|
onError(errorResponse?.statusCode.toString())
|
||||||
|
super.onReceivedHttpError(view, request, errorResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
|
||||||
|
Timber.e("onReceivedSslError error: ${error?.primaryError}")
|
||||||
|
onError(error?.primaryError?.toString())
|
||||||
|
super.onReceivedSslError(view, handler, error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a WebMessageListener, which will receive messages from the WebView and reply to them
|
// Create a WebMessageListener, which will receive messages from the WebView and reply to them
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="call_foreground_service_channel_title_android">"تماس خروجی"</string>
|
||||||
|
<string name="call_foreground_service_message_android">"زدن برای بازگشت به تماس"</string>
|
||||||
|
<string name="call_foreground_service_title_android">"تماس در جریان ☎️"</string>
|
||||||
|
<string name="screen_incoming_call_subtitle_android">"تماس المنتی ورودی"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -71,6 +71,7 @@ class CallScreenPresenterTest {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io"))
|
assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io"))
|
||||||
|
assertThat(initialState.webViewError).isNull()
|
||||||
assertThat(initialState.isInWidgetMode).isFalse()
|
assertThat(initialState.isInWidgetMode).isFalse()
|
||||||
analyticsLambda.assertions().isNeverCalled()
|
analyticsLambda.assertions().isNeverCalled()
|
||||||
joinedCallLambda.assertions().isCalledOnce()
|
joinedCallLambda.assertions().isCalledOnce()
|
||||||
|
|
@ -270,6 +271,48 @@ class CallScreenPresenterTest {
|
||||||
assert(stopSyncLambda).isCalledOnce()
|
assert(stopSyncLambda).isCalledOnce()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - error from WebView are updating the state`() = runTest {
|
||||||
|
val presenter = createCallScreenPresenter(
|
||||||
|
callType = CallType.ExternalUrl("https://call.element.io"),
|
||||||
|
activeCallManager = FakeActiveCallManager(),
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
// Wait until the URL is loaded
|
||||||
|
skipItems(1)
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink(CallScreenEvents.OnWebViewError("A Webview error"))
|
||||||
|
val finalState = awaitItem()
|
||||||
|
assertThat(finalState.webViewError).isEqualTo("A Webview error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - error from WebView are ignored if Element Call is loaded`() = runTest {
|
||||||
|
val presenter = createCallScreenPresenter(
|
||||||
|
callType = CallType.ExternalUrl("https://call.element.io"),
|
||||||
|
activeCallManager = FakeActiveCallManager(),
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
// Wait until the URL is loaded
|
||||||
|
skipItems(1)
|
||||||
|
val initialState = awaitItem()
|
||||||
|
|
||||||
|
val messageInterceptor = FakeWidgetMessageInterceptor()
|
||||||
|
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
||||||
|
// Emit a message
|
||||||
|
messageInterceptor.givenInterceptedMessage("A message")
|
||||||
|
// WebView emits an error, but it will be ignored
|
||||||
|
initialState.eventSink(CallScreenEvents.OnWebViewError("A Webview error"))
|
||||||
|
val finalState = awaitItem()
|
||||||
|
assertThat(finalState.webViewError).isNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun TestScope.createCallScreenPresenter(
|
private fun TestScope.createCallScreenPresenter(
|
||||||
callType: CallType,
|
callType: CallType,
|
||||||
navigator: CallScreenNavigator = FakeCallScreenNavigator(),
|
navigator: CallScreenNavigator = FakeCallScreenNavigator(),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import extension.ComponentMergingStrategy
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2022-2024 New Vector Ltd.
|
* Copyright 2022-2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,7 +10,6 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,13 +23,9 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
setupAnvil(componentMergingStrategy = ComponentMergingStrategy.KSP)
|
||||||
generateDaggerFactories.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
anvil(projects.anvilcodegen)
|
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ package io.element.android.features.createroom.impl.di
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||||
import dagger.Subcomponent
|
|
||||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.di.SingleIn
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
|
@ -17,7 +16,7 @@ import io.element.android.libraries.di.SingleIn
|
||||||
@SingleIn(CreateRoomScope::class)
|
@SingleIn(CreateRoomScope::class)
|
||||||
@MergeSubcomponent(CreateRoomScope::class)
|
@MergeSubcomponent(CreateRoomScope::class)
|
||||||
interface CreateRoomComponent : NodeFactoriesBindings {
|
interface CreateRoomComponent : NodeFactoriesBindings {
|
||||||
@Subcomponent.Builder
|
@MergeSubcomponent.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
fun build(): CreateRoomComponent
|
fun build(): CreateRoomComponent
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_create_room_action_create_room">"اتاق جدید"</string>
|
||||||
|
<string name="screen_create_room_add_people_title">"دعوت افراد"</string>
|
||||||
|
<string name="screen_create_room_error_creating_room">"هنگام ایجاد اتاق خطایی رخ داد"</string>
|
||||||
|
<string name="screen_create_room_private_option_description">"پیامهای این اتاق رمز شدهاند. رمزنگاری نمیتواند از این پس تغییر کند."</string>
|
||||||
|
<string name="screen_create_room_private_option_title">"اتاق خصوصی (فقط دعوت)"</string>
|
||||||
|
<string name="screen_create_room_public_option_description">"پیامها رمزنگاری نشده و هرکسی میتواند بخواندشان. میتوانید بعداً رمزنگاری را به کار بیندازید."</string>
|
||||||
|
<string name="screen_create_room_public_option_title">"اتاق عمومی (هرکسی)"</string>
|
||||||
|
<string name="screen_create_room_room_name_label">"نام اتاق"</string>
|
||||||
|
<string name="screen_create_room_title">"ایجاد اتاق"</string>
|
||||||
|
<string name="screen_create_room_topic_label">"موضوع (اختیاری)"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2024 New Vector Ltd.
|
* Copyright 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,7 +9,6 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,13 +22,9 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
setupAnvil()
|
||||||
generateDaggerFactories.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
anvil(projects.anvilcodegen)
|
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_deactivate_account_title">"Дэактываваць уліковы запіс"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -2,5 +2,13 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_deactivate_account_confirmation_dialog_content">"Palun kinnita uuesti, et soovid eemaldada oma konto kasutusest"</string>
|
<string name="screen_deactivate_account_confirmation_dialog_content">"Palun kinnita uuesti, et soovid eemaldada oma konto kasutusest"</string>
|
||||||
<string name="screen_deactivate_account_delete_all_messages">"Kustuta kõik minu sõnumid"</string>
|
<string name="screen_deactivate_account_delete_all_messages">"Kustuta kõik minu sõnumid"</string>
|
||||||
|
<string name="screen_deactivate_account_delete_all_messages_notice">"Hoiatus: tulevased kasutajad võivad näha poolikuid vestlusi."</string>
|
||||||
|
<string name="screen_deactivate_account_description">"Sinu konto kasutusest eemaldamine on %1$s ja sellega:"</string>
|
||||||
|
<string name="screen_deactivate_account_description_bold_part">"pöördumatu"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_1">"Sinu kasutajakonto %1$s (sa ei saa enam sellega võrku logida ning kasutajatunnust ei saa enam pruukida)."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_1_bold_part">"jäädavalt eemaldatakse kasutusest"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_2">"Sind logitakse välja kõikidest jututubadest."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_3">"Kustutatakse sinu andmed meie isikutuvastusserverist."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_4">"Sinu sõnumid on jätkuvalt nähtavad registreeritud kasutajatele, kuid kui otsustad sõnumid kustutada, siis nad nad pole nähtavad uutele ja registreerimata kasutajatele."</string>
|
||||||
<string name="screen_deactivate_account_title">"Eemalda konto kasutusest"</string>
|
<string name="screen_deactivate_account_title">"Eemalda konto kasutusest"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_deactivate_account_delete_all_messages">"حذف همهٔ پیامهایم"</string>
|
||||||
|
<string name="screen_deactivate_account_description_bold_part">"بازگشتناپذیر"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_1_bold_part">"از کار انداختن دایمی"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_2">"برداشتنتان از همهٔ اتاقهای گپ."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_3">"حذف اطّلاعات حسابتان از کارساز هویت."</string>
|
||||||
|
<string name="screen_deactivate_account_title">"غیرفعّالسازی حساب"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_deactivate_account_confirmation_dialog_content">"Conferma di voler disattivare il tuo account. Questa azione è irreversibile."</string>
|
||||||
|
<string name="screen_deactivate_account_delete_all_messages">"Elimina tutti i miei messaggi"</string>
|
||||||
|
<string name="screen_deactivate_account_delete_all_messages_notice">"Attenzione: gli utenti futuri potrebbero vedere conversazioni incomplete."</string>
|
||||||
|
<string name="screen_deactivate_account_description">"La disattivazione del tuo account è %1$s , quindi:"</string>
|
||||||
|
<string name="screen_deactivate_account_description_bold_part">"irreversibile"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_1">"%1$s il tuo account (non puoi riaccedere e il tuo ID non può essere riutilizzato)."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_1_bold_part">"Disattiva permanentemente"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_2">"Ti rimuove da tutte le stanze di chat."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_3">"Elimina le informazioni del tuo account dal nostro server di identità."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_4">"I tuoi messaggi saranno ancora visibili agli utenti registrati, ma non saranno disponibili per gli utenti nuovi o non registrati se decidi di eliminarli."</string>
|
||||||
|
<string name="screen_deactivate_account_title">"Disattivazione dell\'account"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_deactivate_account_confirmation_dialog_content">"Potwierdź dezaktywacje konta. Tej akcji nie można cofnąć."</string>
|
||||||
|
<string name="screen_deactivate_account_delete_all_messages">"Usuń wszystkie moje wiadomości"</string>
|
||||||
|
<string name="screen_deactivate_account_delete_all_messages_notice">"Ostrzeżenie: Przyszli użytkownicy mogą zobaczyć niekompletne rozmowy."</string>
|
||||||
|
<string name="screen_deactivate_account_description">"Dezaktywacja konta jest %1$s, zostanie:"</string>
|
||||||
|
<string name="screen_deactivate_account_description_bold_part">"nieodwracalna"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_1">"%1$s twoje konto (nie będziesz mógł się zalogować, a twoje ID przepadnie)."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_1_bold_part">"Permanentnie wyłączy"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_2">"Usunie Ciebie ze wszystkich pokoi rozmów."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_3">"Usunięte wszystkie dane konta z naszego serwera tożsamości."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_4">"Twoje wiadomości wciąż będą widoczne dla zarejestrowanych użytkowników, ale nie będą dostępne dla nowych lub niezarejestrowanych użytkowników, jeśli je usuniesz."</string>
|
||||||
|
<string name="screen_deactivate_account_title">"Dezaktywuj konto"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_deactivate_account_confirmation_dialog_content">"Confirme que pretende desativar a sua conta. Esta ação não pode ser desfeita."</string>
|
||||||
|
<string name="screen_deactivate_account_delete_all_messages">"Eliminar todas as minhas mensagens"</string>
|
||||||
|
<string name="screen_deactivate_account_delete_all_messages_notice">"Aviso: futuros usuários podem ver conversas incompletas."</string>
|
||||||
|
<string name="screen_deactivate_account_description">"A desativação da sua conta é %1$s, irá:"</string>
|
||||||
|
<string name="screen_deactivate_account_description_bold_part">"irreversível"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_1">"%1$s sua conta (não pode voltar a iniciar sessão e o seu ID não pode ser reutilizado)."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_1_bold_part">"Desativar permanentemente"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_2">"Removê-lo de todas as salas de chat."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_3">"Exclua as informações da sua conta do nosso servidor de identidade."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_4">"Suas mensagens ainda estarão visíveis para usuários registrados, mas não estarão disponíveis para usuários novos ou não registrados se você optar por excluí-las."</string>
|
||||||
|
<string name="screen_deactivate_account_title">"Desativar conta"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -5,7 +5,10 @@
|
||||||
<string name="screen_deactivate_account_delete_all_messages_notice">"Upozornenie: Budúcim používateľom sa môžu zobraziť neúplné konverzácie."</string>
|
<string name="screen_deactivate_account_delete_all_messages_notice">"Upozornenie: Budúcim používateľom sa môžu zobraziť neúplné konverzácie."</string>
|
||||||
<string name="screen_deactivate_account_description">"Deaktivácia vášho účtu znamená %1$s, že:"</string>
|
<string name="screen_deactivate_account_description">"Deaktivácia vášho účtu znamená %1$s, že:"</string>
|
||||||
<string name="screen_deactivate_account_description_bold_part">"nezvratný"</string>
|
<string name="screen_deactivate_account_description_bold_part">"nezvratný"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_1">"%1$s váš účet (nebudete sa môcť znova prihlásiť a vaše ID nebude možné znova použiť)."</string>
|
||||||
<string name="screen_deactivate_account_list_item_1_bold_part">"Natrvalo zakázať"</string>
|
<string name="screen_deactivate_account_list_item_1_bold_part">"Natrvalo zakázať"</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_2">"Odstrániť vás zo všetkých miestností."</string>
|
||||||
|
<string name="screen_deactivate_account_list_item_3">"Odstrániť informácie o vašom účte z nášho servera totožností."</string>
|
||||||
<string name="screen_deactivate_account_list_item_4">"Vaše správy budú stále viditeľné pre registrovaných používateľov, ale nebudú dostupné pre nových alebo neregistrovaných používateľov, ak sa ich rozhodnete odstrániť."</string>
|
<string name="screen_deactivate_account_list_item_4">"Vaše správy budú stále viditeľné pre registrovaných používateľov, ale nebudú dostupné pre nových alebo neregistrovaných používateľov, ak sa ich rozhodnete odstrániť."</string>
|
||||||
<string name="screen_deactivate_account_title">"Deaktivovať účet"</string>
|
<string name="screen_deactivate_account_title">"Deaktivovať účet"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,7 +9,6 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -15,13 +16,9 @@ android {
|
||||||
namespace = "io.element.android.features.ftue.impl"
|
namespace = "io.element.android.features.ftue.impl"
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
setupAnvil()
|
||||||
generateDaggerFactories.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
anvil(projects.anvilcodegen)
|
|
||||||
api(projects.features.ftue.api)
|
api(projects.features.ftue.api)
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,8 @@ import kotlinx.collections.immutable.persistentListOf
|
||||||
@Composable
|
@Composable
|
||||||
fun WelcomeView(
|
fun WelcomeView(
|
||||||
applicationName: String,
|
applicationName: String,
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onContinueClick: () -> Unit,
|
onContinueClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BackHandler(onBack = onContinueClick)
|
BackHandler(onBack = onContinueClick)
|
||||||
OnBoardingPage(
|
OnBoardingPage(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_notification_optin_subtitle">"میتوانید بعداً تنظیماتتان را تغییر دهید."</string>
|
||||||
|
<string name="screen_notification_optin_title">"اجازه به آگاهیها و از دست ندادن پیامها"</string>
|
||||||
|
<string name="screen_welcome_button">"بزن بریم!"</string>
|
||||||
|
<string name="screen_welcome_subtitle">"چیزهایی که باید بدانید:"</string>
|
||||||
|
<string name="screen_welcome_title">"به %1$s خوش آمدید!"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -15,18 +15,19 @@ import io.element.android.features.ftue.impl.state.DefaultFtueService
|
||||||
import io.element.android.features.ftue.impl.state.FtueStep
|
import io.element.android.features.ftue.impl.state.FtueStep
|
||||||
import io.element.android.features.lockscreen.api.LockScreenService
|
import io.element.android.features.lockscreen.api.LockScreenService
|
||||||
import io.element.android.features.lockscreen.test.FakeLockScreenService
|
import io.element.android.features.lockscreen.test.FakeLockScreenService
|
||||||
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||||
|
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
||||||
import io.element.android.libraries.permissions.impl.FakePermissionStateProvider
|
import io.element.android.libraries.permissions.impl.FakePermissionStateProvider
|
||||||
|
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
@ -36,8 +37,9 @@ class DefaultFtueServiceTest {
|
||||||
val sessionVerificationService = FakeSessionVerificationService().apply {
|
val sessionVerificationService = FakeSessionVerificationService().apply {
|
||||||
givenVerifiedStatus(SessionVerifiedStatus.Unknown)
|
givenVerifiedStatus(SessionVerifiedStatus.Unknown)
|
||||||
}
|
}
|
||||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
val service = createDefaultFtueService(
|
||||||
val service = createDefaultFtueService(coroutineScope, sessionVerificationService)
|
sessionVerificationService = sessionVerificationService,
|
||||||
|
)
|
||||||
|
|
||||||
service.state.test {
|
service.state.test {
|
||||||
// Verification state is unknown, we don't display the flow yet
|
// Verification state is unknown, we don't display the flow yet
|
||||||
|
|
@ -47,9 +49,6 @@ class DefaultFtueServiceTest {
|
||||||
sessionVerificationService.givenVerifiedStatus(SessionVerifiedStatus.NotVerified)
|
sessionVerificationService.givenVerifiedStatus(SessionVerifiedStatus.NotVerified)
|
||||||
assertThat(awaitItem()).isEqualTo(FtueState.Incomplete)
|
assertThat(awaitItem()).isEqualTo(FtueState.Incomplete)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
coroutineScope.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -58,10 +57,7 @@ class DefaultFtueServiceTest {
|
||||||
val sessionVerificationService = FakeSessionVerificationService()
|
val sessionVerificationService = FakeSessionVerificationService()
|
||||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = true)
|
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = true)
|
||||||
val lockScreenService = FakeLockScreenService()
|
val lockScreenService = FakeLockScreenService()
|
||||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
|
||||||
|
|
||||||
val service = createDefaultFtueService(
|
val service = createDefaultFtueService(
|
||||||
coroutineScope = coroutineScope,
|
|
||||||
sessionVerificationService = sessionVerificationService,
|
sessionVerificationService = sessionVerificationService,
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
permissionStateProvider = permissionStateProvider,
|
permissionStateProvider = permissionStateProvider,
|
||||||
|
|
@ -75,9 +71,6 @@ class DefaultFtueServiceTest {
|
||||||
service.updateState()
|
service.updateState()
|
||||||
|
|
||||||
assertThat(service.state.value).isEqualTo(FtueState.Complete)
|
assertThat(service.state.value).isEqualTo(FtueState.Complete)
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
coroutineScope.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -88,10 +81,7 @@ class DefaultFtueServiceTest {
|
||||||
val analyticsService = FakeAnalyticsService()
|
val analyticsService = FakeAnalyticsService()
|
||||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
|
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
|
||||||
val lockScreenService = FakeLockScreenService()
|
val lockScreenService = FakeLockScreenService()
|
||||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
|
||||||
|
|
||||||
val service = createDefaultFtueService(
|
val service = createDefaultFtueService(
|
||||||
coroutineScope = coroutineScope,
|
|
||||||
sessionVerificationService = sessionVerificationService,
|
sessionVerificationService = sessionVerificationService,
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
permissionStateProvider = permissionStateProvider,
|
permissionStateProvider = permissionStateProvider,
|
||||||
|
|
@ -126,20 +116,15 @@ class DefaultFtueServiceTest {
|
||||||
// Final state
|
// Final state
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
coroutineScope.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `if a check for a step is true, start from the next one`() = runTest {
|
fun `if a check for a step is true, start from the next one`() = runTest {
|
||||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
|
||||||
val sessionVerificationService = FakeSessionVerificationService()
|
val sessionVerificationService = FakeSessionVerificationService()
|
||||||
val analyticsService = FakeAnalyticsService()
|
val analyticsService = FakeAnalyticsService()
|
||||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
|
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
|
||||||
val lockScreenService = FakeLockScreenService()
|
val lockScreenService = FakeLockScreenService()
|
||||||
val service = createDefaultFtueService(
|
val service = createDefaultFtueService(
|
||||||
coroutineScope = coroutineScope,
|
|
||||||
sessionVerificationService = sessionVerificationService,
|
sessionVerificationService = sessionVerificationService,
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
permissionStateProvider = permissionStateProvider,
|
permissionStateProvider = permissionStateProvider,
|
||||||
|
|
@ -155,14 +140,10 @@ class DefaultFtueServiceTest {
|
||||||
|
|
||||||
analyticsService.setDidAskUserConsent()
|
analyticsService.setDidAskUserConsent()
|
||||||
assertThat(service.getNextStep(null)).isNull()
|
assertThat(service.getNextStep(null)).isNull()
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
coroutineScope.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `if version is older than 13 we don't display the notification opt in screen`() = runTest {
|
fun `if version is older than 13 we don't display the notification opt in screen`() = runTest {
|
||||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
|
||||||
val sessionVerificationService = FakeSessionVerificationService()
|
val sessionVerificationService = FakeSessionVerificationService()
|
||||||
val analyticsService = FakeAnalyticsService()
|
val analyticsService = FakeAnalyticsService()
|
||||||
val lockScreenService = FakeLockScreenService()
|
val lockScreenService = FakeLockScreenService()
|
||||||
|
|
@ -170,7 +151,6 @@ class DefaultFtueServiceTest {
|
||||||
val service = createDefaultFtueService(
|
val service = createDefaultFtueService(
|
||||||
sdkIntVersion = Build.VERSION_CODES.M,
|
sdkIntVersion = Build.VERSION_CODES.M,
|
||||||
sessionVerificationService = sessionVerificationService,
|
sessionVerificationService = sessionVerificationService,
|
||||||
coroutineScope = coroutineScope,
|
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
lockScreenService = lockScreenService,
|
lockScreenService = lockScreenService,
|
||||||
)
|
)
|
||||||
|
|
@ -182,14 +162,10 @@ class DefaultFtueServiceTest {
|
||||||
|
|
||||||
analyticsService.setDidAskUserConsent()
|
analyticsService.setDidAskUserConsent()
|
||||||
assertThat(service.getNextStep(null)).isNull()
|
assertThat(service.getNextStep(null)).isNull()
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
coroutineScope.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `reset do the expected actions S`() = runTest {
|
fun `reset do the expected actions S`() = runTest {
|
||||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
|
||||||
val resetAnalyticsLambda = lambdaRecorder<Unit> { }
|
val resetAnalyticsLambda = lambdaRecorder<Unit> { }
|
||||||
val analyticsService = FakeAnalyticsService(
|
val analyticsService = FakeAnalyticsService(
|
||||||
resetLambda = resetAnalyticsLambda
|
resetLambda = resetAnalyticsLambda
|
||||||
|
|
@ -199,7 +175,6 @@ class DefaultFtueServiceTest {
|
||||||
resetPermissionLambda = resetPermissionLambda
|
resetPermissionLambda = resetPermissionLambda
|
||||||
)
|
)
|
||||||
val service = createDefaultFtueService(
|
val service = createDefaultFtueService(
|
||||||
coroutineScope = coroutineScope,
|
|
||||||
sdkIntVersion = Build.VERSION_CODES.S,
|
sdkIntVersion = Build.VERSION_CODES.S,
|
||||||
permissionStateProvider = permissionStateProvider,
|
permissionStateProvider = permissionStateProvider,
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
|
|
@ -211,7 +186,6 @@ class DefaultFtueServiceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `reset do the expected actions TIRAMISU`() = runTest {
|
fun `reset do the expected actions TIRAMISU`() = runTest {
|
||||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
|
||||||
val resetLambda = lambdaRecorder<Unit> { }
|
val resetLambda = lambdaRecorder<Unit> { }
|
||||||
val analyticsService = FakeAnalyticsService(
|
val analyticsService = FakeAnalyticsService(
|
||||||
resetLambda = resetLambda
|
resetLambda = resetLambda
|
||||||
|
|
@ -221,7 +195,6 @@ class DefaultFtueServiceTest {
|
||||||
resetPermissionLambda = resetPermissionLambda
|
resetPermissionLambda = resetPermissionLambda
|
||||||
)
|
)
|
||||||
val service = createDefaultFtueService(
|
val service = createDefaultFtueService(
|
||||||
coroutineScope = coroutineScope,
|
|
||||||
sdkIntVersion = Build.VERSION_CODES.TIRAMISU,
|
sdkIntVersion = Build.VERSION_CODES.TIRAMISU,
|
||||||
permissionStateProvider = permissionStateProvider,
|
permissionStateProvider = permissionStateProvider,
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
|
|
@ -232,17 +205,16 @@ class DefaultFtueServiceTest {
|
||||||
.with(value("android.permission.POST_NOTIFICATIONS"))
|
.with(value("android.permission.POST_NOTIFICATIONS"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDefaultFtueService(
|
private fun TestScope.createDefaultFtueService(
|
||||||
coroutineScope: CoroutineScope,
|
sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(),
|
||||||
sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
|
|
||||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||||
permissionStateProvider: FakePermissionStateProvider = FakePermissionStateProvider(permissionGranted = false),
|
permissionStateProvider: PermissionStateProvider = FakePermissionStateProvider(permissionGranted = false),
|
||||||
lockScreenService: LockScreenService = FakeLockScreenService(),
|
lockScreenService: LockScreenService = FakeLockScreenService(),
|
||||||
sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(),
|
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||||
// First version where notification permission is required
|
// First version where notification permission is required
|
||||||
sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU,
|
sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU,
|
||||||
) = DefaultFtueService(
|
) = DefaultFtueService(
|
||||||
sessionCoroutineScope = coroutineScope,
|
sessionCoroutineScope = backgroundScope,
|
||||||
sessionVerificationService = sessionVerificationService,
|
sessionVerificationService = sessionVerificationService,
|
||||||
sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkIntVersion),
|
sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkIntVersion),
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,7 +9,6 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -15,13 +16,9 @@ android {
|
||||||
namespace = "io.element.android.features.invite.impl"
|
namespace = "io.element.android.features.invite.impl"
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
setupAnvil()
|
||||||
generateDaggerFactories.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
anvil(projects.anvilcodegen)
|
|
||||||
api(projects.features.invite.api)
|
api(projects.features.invite.api)
|
||||||
implementation(libs.androidx.datastore.preferences)
|
implementation(libs.androidx.datastore.preferences)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invites_decline_chat_message">"مطمئنید که میخواهید دعوت پیوستن به %1$s را رد کنید؟"</string>
|
||||||
|
<string name="screen_invites_decline_chat_title">"رد دعوت"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_message">"مطمئنید که میخواهید این گپ خصوصی با %1$s را رد کنید؟"</string>
|
||||||
|
<string name="screen_invites_decline_direct_chat_title">"رد گپ"</string>
|
||||||
|
<string name="screen_invites_empty_list">"بدون دعوت"</string>
|
||||||
|
<string name="screen_invites_invited_you">"%1$s (%2$s) دعوتتان کرد"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2024 New Vector Ltd.
|
* Copyright 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,7 +9,6 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -20,13 +21,9 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
setupAnvil()
|
||||||
generateDaggerFactories.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
anvil(projects.anvilcodegen)
|
|
||||||
api(projects.features.joinroom.api)
|
api(projects.features.joinroom.api)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_join_room_join_action">"پیوستن به اتاق"</string>
|
||||||
|
<string name="screen_join_room_knock_action">"در زدن برای پیوستن"</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_description">"%1$s هنوز از فضاها پشتیبانی نمیکند. میتوانید روی وب به فضاها دسترسی داشته باشید."</string>
|
||||||
|
<string name="screen_join_room_space_not_supported_title">"فضاها هنوز پشتیبانی نمیشوند"</string>
|
||||||
|
<string name="screen_join_room_subtitle_knock">"زدن روی این دکمه برای آگاه شدن مدیر اتاق. پس از تأیید میتوانید به گفتوگو بپیوندید."</string>
|
||||||
|
<string name="screen_join_room_subtitle_no_preview">"برای دیدن تاریخچهٔ پیام باید عضو این اتاق باشید."</string>
|
||||||
|
<string name="screen_join_room_title_knock">"میخواهید به اتاق بپیوندید؟"</string>
|
||||||
|
<string name="screen_join_room_title_no_preview">"پیشنمایش موجود نیست"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.leaveroom.api
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import io.element.android.libraries.architecture.Presenter
|
|
||||||
|
|
||||||
interface LeaveRoomPresenter : Presenter<LeaveRoomState> {
|
|
||||||
@Composable
|
|
||||||
override fun present(): LeaveRoomState
|
|
||||||
}
|
|
||||||
|
|
@ -57,9 +57,10 @@ fun aLeaveRoomState(
|
||||||
confirmation: LeaveRoomState.Confirmation = LeaveRoomState.Confirmation.Hidden,
|
confirmation: LeaveRoomState.Confirmation = LeaveRoomState.Confirmation.Hidden,
|
||||||
progress: LeaveRoomState.Progress = LeaveRoomState.Progress.Hidden,
|
progress: LeaveRoomState.Progress = LeaveRoomState.Progress.Hidden,
|
||||||
error: LeaveRoomState.Error = LeaveRoomState.Error.Hidden,
|
error: LeaveRoomState.Error = LeaveRoomState.Error.Hidden,
|
||||||
|
eventSink: (LeaveRoomEvent) -> Unit = {},
|
||||||
) = LeaveRoomState(
|
) = LeaveRoomState(
|
||||||
confirmation = confirmation,
|
confirmation = confirmation,
|
||||||
progress = progress,
|
progress = progress,
|
||||||
error = error,
|
error = error,
|
||||||
eventSink = {},
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2022-2024 New Vector Ltd.
|
* Copyright 2022-2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,20 +9,15 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.features.leaveroom.impl"
|
namespace = "io.element.android.features.leaveroom.impl"
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
setupAnvil()
|
||||||
generateDaggerFactories.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
anvil(projects.anvilcodegen)
|
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,14 @@ import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.Dm
|
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.Dm
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.Generic
|
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.Generic
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.LastUserInRoom
|
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.LastUserInRoom
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.PrivateRoom
|
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.PrivateRoom
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.di.SessionScope
|
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||||
|
|
@ -30,12 +28,11 @@ import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(SessionScope::class)
|
class LeaveRoomPresenter @Inject constructor(
|
||||||
class DefaultLeaveRoomPresenter @Inject constructor(
|
|
||||||
private val client: MatrixClient,
|
private val client: MatrixClient,
|
||||||
private val roomMembershipObserver: RoomMembershipObserver,
|
private val roomMembershipObserver: RoomMembershipObserver,
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
) : LeaveRoomPresenter {
|
) : Presenter<LeaveRoomState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): LeaveRoomState {
|
override fun present(): LeaveRoomState {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.leaveroom.impl.di
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||||
|
import io.element.android.features.leaveroom.impl.LeaveRoomPresenter
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
||||||
|
@ContributesTo(SessionScope::class)
|
||||||
|
@Module
|
||||||
|
interface LeaveRoomModule {
|
||||||
|
@Binds
|
||||||
|
fun bindLeaveRoomPresenter(presenter: LeaveRoomPresenter): Presenter<LeaveRoomState>
|
||||||
|
}
|
||||||
|
|
@ -27,13 +27,13 @@ import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class DefaultLeaveRoomPresenterTest {
|
class LeaveRoomPresenterTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state hides all dialogs`() = runTest {
|
fun `present - initial state hides all dialogs`() = runTest {
|
||||||
val presenter = createDefaultLeaveRoomPresenter()
|
val presenter = createLeaveRoomPresenter()
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -46,7 +46,7 @@ class DefaultLeaveRoomPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - show generic confirmation`() = runTest {
|
fun `present - show generic confirmation`() = runTest {
|
||||||
val presenter = createDefaultLeaveRoomPresenter(
|
val presenter = createLeaveRoomPresenter(
|
||||||
client = FakeMatrixClient().apply {
|
client = FakeMatrixClient().apply {
|
||||||
givenGetRoomResult(
|
givenGetRoomResult(
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
|
|
@ -66,7 +66,7 @@ class DefaultLeaveRoomPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - show private room confirmation`() = runTest {
|
fun `present - show private room confirmation`() = runTest {
|
||||||
val presenter = createDefaultLeaveRoomPresenter(
|
val presenter = createLeaveRoomPresenter(
|
||||||
client = FakeMatrixClient().apply {
|
client = FakeMatrixClient().apply {
|
||||||
givenGetRoomResult(
|
givenGetRoomResult(
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
|
|
@ -86,7 +86,7 @@ class DefaultLeaveRoomPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - show last user in room confirmation`() = runTest {
|
fun `present - show last user in room confirmation`() = runTest {
|
||||||
val presenter = createDefaultLeaveRoomPresenter(
|
val presenter = createLeaveRoomPresenter(
|
||||||
client = FakeMatrixClient().apply {
|
client = FakeMatrixClient().apply {
|
||||||
givenGetRoomResult(
|
givenGetRoomResult(
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
|
|
@ -106,7 +106,7 @@ class DefaultLeaveRoomPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - show DM confirmation`() = runTest {
|
fun `present - show DM confirmation`() = runTest {
|
||||||
val presenter = createDefaultLeaveRoomPresenter(
|
val presenter = createLeaveRoomPresenter(
|
||||||
client = FakeMatrixClient().apply {
|
client = FakeMatrixClient().apply {
|
||||||
givenGetRoomResult(
|
givenGetRoomResult(
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
|
|
@ -127,7 +127,7 @@ class DefaultLeaveRoomPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - leaving a room leaves the room`() = runTest {
|
fun `present - leaving a room leaves the room`() = runTest {
|
||||||
val roomMembershipObserver = RoomMembershipObserver()
|
val roomMembershipObserver = RoomMembershipObserver()
|
||||||
val presenter = createDefaultLeaveRoomPresenter(
|
val presenter = createLeaveRoomPresenter(
|
||||||
client = FakeMatrixClient().apply {
|
client = FakeMatrixClient().apply {
|
||||||
givenGetRoomResult(
|
givenGetRoomResult(
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
|
|
@ -151,7 +151,7 @@ class DefaultLeaveRoomPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - show error if leave room fails`() = runTest {
|
fun `present - show error if leave room fails`() = runTest {
|
||||||
val presenter = createDefaultLeaveRoomPresenter(
|
val presenter = createLeaveRoomPresenter(
|
||||||
client = FakeMatrixClient().apply {
|
client = FakeMatrixClient().apply {
|
||||||
givenGetRoomResult(
|
givenGetRoomResult(
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
|
|
@ -175,7 +175,7 @@ class DefaultLeaveRoomPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - show progress indicator while leaving a room`() = runTest {
|
fun `present - show progress indicator while leaving a room`() = runTest {
|
||||||
val presenter = createDefaultLeaveRoomPresenter(
|
val presenter = createLeaveRoomPresenter(
|
||||||
client = FakeMatrixClient().apply {
|
client = FakeMatrixClient().apply {
|
||||||
givenGetRoomResult(
|
givenGetRoomResult(
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
|
|
@ -199,7 +199,7 @@ class DefaultLeaveRoomPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - hide error hides the error`() = runTest {
|
fun `present - hide error hides the error`() = runTest {
|
||||||
val presenter = createDefaultLeaveRoomPresenter(
|
val presenter = createLeaveRoomPresenter(
|
||||||
client = FakeMatrixClient().apply {
|
client = FakeMatrixClient().apply {
|
||||||
givenGetRoomResult(
|
givenGetRoomResult(
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
|
|
@ -225,10 +225,10 @@ class DefaultLeaveRoomPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TestScope.createDefaultLeaveRoomPresenter(
|
private fun TestScope.createLeaveRoomPresenter(
|
||||||
client: MatrixClient = FakeMatrixClient(),
|
client: MatrixClient = FakeMatrixClient(),
|
||||||
roomMembershipObserver: RoomMembershipObserver = RoomMembershipObserver(),
|
roomMembershipObserver: RoomMembershipObserver = RoomMembershipObserver(),
|
||||||
): DefaultLeaveRoomPresenter = DefaultLeaveRoomPresenter(
|
): LeaveRoomPresenter = LeaveRoomPresenter(
|
||||||
client = client,
|
client = client,
|
||||||
roomMembershipObserver = roomMembershipObserver,
|
roomMembershipObserver = roomMembershipObserver,
|
||||||
dispatchers = testCoroutineDispatchers(false),
|
dispatchers = testCoroutineDispatchers(false),
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2022-2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("io.element.android-compose-library")
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "io.element.android.features.leaveroom.test"
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(projects.libraries.core)
|
|
||||||
implementation(projects.libraries.architecture)
|
|
||||||
implementation(projects.libraries.matrix.api)
|
|
||||||
api(projects.features.leaveroom.api)
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.leaveroom.fake
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
|
||||||
|
|
||||||
class FakeLeaveRoomPresenter : LeaveRoomPresenter {
|
|
||||||
val events = mutableListOf<LeaveRoomEvent>()
|
|
||||||
|
|
||||||
private fun handleEvent(event: LeaveRoomEvent) {
|
|
||||||
events += event
|
|
||||||
}
|
|
||||||
|
|
||||||
private var state = LeaveRoomState(
|
|
||||||
confirmation = LeaveRoomState.Confirmation.Hidden,
|
|
||||||
progress = LeaveRoomState.Progress.Hidden,
|
|
||||||
error = LeaveRoomState.Error.Hidden,
|
|
||||||
eventSink = ::handleEvent,
|
|
||||||
)
|
|
||||||
set(value) {
|
|
||||||
field = value.copy(eventSink = ::handleEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun givenState(state: LeaveRoomState) {
|
|
||||||
this.state = state
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun present(): LeaveRoomState = state
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2022-2024 New Vector Ltd.
|
* Copyright 2022-2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -16,13 +18,9 @@ android {
|
||||||
namespace = "io.element.android.features.licenses.impl"
|
namespace = "io.element.android.features.licenses.impl"
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
setupAnvil()
|
||||||
generateDaggerFactories.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
anvil(projects.anvilcodegen)
|
|
||||||
implementation(libs.serialization.json)
|
implementation(libs.serialization.json)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@ internal fun StaticMapPlaceholder(
|
||||||
contentDescription: String?,
|
contentDescription: String?,
|
||||||
width: Dp,
|
width: Dp,
|
||||||
height: Dp,
|
height: Dp,
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onLoadMapClick: () -> Unit,
|
onLoadMapClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2022-2024 New Vector Ltd.
|
* Copyright 2022-2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,7 +9,6 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
@ -19,9 +20,7 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
setupAnvil()
|
||||||
generateDaggerFactories.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.features.location.api)
|
api(projects.features.location.api)
|
||||||
|
|
@ -39,8 +38,6 @@ dependencies {
|
||||||
implementation(libs.accompanist.permission)
|
implementation(libs.accompanist.permission)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(libs.dagger)
|
implementation(libs.dagger)
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
anvil(projects.anvilcodegen)
|
|
||||||
|
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
testImplementation(libs.coroutines.test)
|
testImplementation(libs.coroutines.test)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,7 +9,6 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -15,13 +16,9 @@ android {
|
||||||
namespace = "io.element.android.features.lockscreen.impl"
|
namespace = "io.element.android.features.lockscreen.impl"
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
setupAnvil()
|
||||||
generateDaggerFactories.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
anvil(projects.anvilcodegen)
|
|
||||||
api(projects.features.lockscreen.api)
|
api(projects.features.lockscreen.api)
|
||||||
implementation(projects.appconfig)
|
implementation(projects.appconfig)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.features.lockscreen.impl.LockScreenConfig
|
import io.element.android.features.lockscreen.impl.LockScreenConfig
|
||||||
import io.element.android.features.lockscreen.impl.R
|
import io.element.android.features.lockscreen.impl.R
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@
|
||||||
|
|
||||||
package io.element.android.features.lockscreen.impl.unlock
|
package io.element.android.features.lockscreen.impl.unlock
|
||||||
|
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.lockscreen.impl.biometric.BiometricUnlock
|
import io.element.android.features.lockscreen.impl.biometric.BiometricUnlock
|
||||||
|
import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockError
|
||||||
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
@ -23,6 +25,9 @@ open class PinUnlockStateProvider : PreviewParameterProvider<PinUnlockState> {
|
||||||
aPinUnlockState(showBiometricUnlock = false),
|
aPinUnlockState(showBiometricUnlock = false),
|
||||||
aPinUnlockState(showSignOutPrompt = true, remainingAttempts = 0),
|
aPinUnlockState(showSignOutPrompt = true, remainingAttempts = 0),
|
||||||
aPinUnlockState(signOutAction = AsyncAction.Loading),
|
aPinUnlockState(signOutAction = AsyncAction.Loading),
|
||||||
|
aPinUnlockState(biometricUnlockResult = BiometricUnlock.AuthenticationResult.Failure(
|
||||||
|
BiometricUnlockError(BiometricPrompt.ERROR_LOCKOUT, "Biometric auth disabled")
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_app_lock_biometric_authentication">"هویتسنجی زیستی"</string>
|
||||||
|
<string name="screen_app_lock_biometric_unlock">"قفلگشایی زیستسنجی"</string>
|
||||||
|
<string name="screen_app_lock_biometric_unlock_title_android">"قفلگشایی با زیستسنجی"</string>
|
||||||
|
<string name="screen_app_lock_forgot_pin">"فراموشی پین؟"</string>
|
||||||
|
<string name="screen_app_lock_settings_change_pin">"تغییر کد پین"</string>
|
||||||
|
<string name="screen_app_lock_settings_enable_biometric_unlock">"احازه به قفل گشایی زیستسنجی"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin">"برداشتن پین"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin_alert_message">"مطمئنید که میخواهید پین را بردارید؟"</string>
|
||||||
|
<string name="screen_app_lock_settings_remove_pin_alert_title">"برداشتن پین؟"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"اجازه به %1$s"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_skip">"ترجیح میدهم از پین استفاده کنم"</string>
|
||||||
|
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"زمیانتان را ذخیره کرده و از %1$s برای قفلگشایی هربارهٔ کاره استفاده کنید"</string>
|
||||||
|
<string name="screen_app_lock_setup_choose_pin">"گزینش پین"</string>
|
||||||
|
<string name="screen_app_lock_setup_confirm_pin">"تأیید پین"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_forbidden_dialog_content">"به دلیل امنیتی نمیتوانید این پین را برگزینید"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_forbidden_dialog_title">"گزینشی پینی متفاوت"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"لطفاً یک پین را دو بار وارد کنید"</string>
|
||||||
|
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"پینها مطابق نیستند"</string>
|
||||||
|
<string name="screen_app_lock_signout_alert_message">"برای ادامه باید دوباره وارد شده و پینی جدید ایجاد کنید"</string>
|
||||||
|
<string name="screen_app_lock_signout_alert_title">"دارید خارج میشوید"</string>
|
||||||
|
<string name="screen_app_lock_use_biometric_android">"استفاده از زیستسنجی"</string>
|
||||||
|
<string name="screen_app_lock_use_pin_android">"استفاده از پین"</string>
|
||||||
|
<string name="screen_signout_in_progress_dialog_content">"خارج شدن…"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import extension.ComponentMergingStrategy
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2022-2024 New Vector Ltd.
|
* Copyright 2022-2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,7 +10,6 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
alias(libs.plugins.kotlin.serialization)
|
alias(libs.plugins.kotlin.serialization)
|
||||||
}
|
}
|
||||||
|
|
@ -22,14 +24,10 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
setupAnvil(componentMergingStrategy = ComponentMergingStrategy.KSP)
|
||||||
generateDaggerFactories.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.anvilannotations)
|
|
||||||
implementation(projects.appconfig)
|
implementation(projects.appconfig)
|
||||||
anvil(projects.anvilcodegen)
|
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,4 @@ data class AccountProvider(
|
||||||
val isPublic: Boolean = false,
|
val isPublic: Boolean = false,
|
||||||
val isMatrixOrg: Boolean = false,
|
val isMatrixOrg: Boolean = false,
|
||||||
val isValid: Boolean = false,
|
val isValid: Boolean = false,
|
||||||
val supportSlidingSync: Boolean = false,
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@ open class AccountProviderProvider : PreviewParameterProvider<AccountProvider> {
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
anAccountProvider(),
|
anAccountProvider(),
|
||||||
anAccountProvider().copy(subtitle = null),
|
anAccountProvider().copy(subtitle = null),
|
||||||
anAccountProvider().copy(subtitle = null, title = "no.sliding.sync", supportSlidingSync = false),
|
anAccountProvider().copy(subtitle = null, title = "invalid", isValid = false),
|
||||||
anAccountProvider().copy(subtitle = null, title = "invalid", isValid = false, supportSlidingSync = false),
|
|
||||||
anAccountProvider().copy(subtitle = null, title = "Other", isPublic = false, isMatrixOrg = false),
|
anAccountProvider().copy(subtitle = null, title = "Other", isPublic = false, isMatrixOrg = false),
|
||||||
// Add other state here
|
// Add other state here
|
||||||
)
|
)
|
||||||
|
|
@ -28,5 +27,4 @@ fun anAccountProvider() = AccountProvider(
|
||||||
isPublic = true,
|
isPublic = true,
|
||||||
isMatrixOrg = true,
|
isMatrixOrg = true,
|
||||||
isValid = true,
|
isValid = true,
|
||||||
supportSlidingSync = true,
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
@Composable
|
@Composable
|
||||||
fun AccountProviderView(
|
fun AccountProviderView(
|
||||||
item: AccountProvider,
|
item: AccountProvider,
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.login.impl.di
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import io.element.android.features.login.impl.changeserver.ChangeServerPresenter
|
||||||
|
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
|
||||||
|
@ContributesTo(AppScope::class)
|
||||||
|
@Module
|
||||||
|
interface LoginModule {
|
||||||
|
@Binds
|
||||||
|
fun bindChangeServerPresenter(presenter: ChangeServerPresenter): Presenter<ChangeServerState>
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,6 @@ package io.element.android.features.login.impl.di
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||||
import dagger.Subcomponent
|
|
||||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.di.SingleIn
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
|
@ -17,7 +16,7 @@ import io.element.android.libraries.di.SingleIn
|
||||||
@SingleIn(QrCodeLoginScope::class)
|
@SingleIn(QrCodeLoginScope::class)
|
||||||
@MergeSubcomponent(QrCodeLoginScope::class)
|
@MergeSubcomponent(QrCodeLoginScope::class)
|
||||||
interface QrCodeLoginComponent : NodeFactoriesBindings {
|
interface QrCodeLoginComponent : NodeFactoriesBindings {
|
||||||
@Subcomponent.Builder
|
@MergeSubcomponent.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
fun build(): QrCodeLoginComponent
|
fun build(): QrCodeLoginComponent
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,4 @@ data class HomeserverData(
|
||||||
val homeserverUrl: String,
|
val homeserverUrl: String,
|
||||||
// True if a wellknown file has been found and is valid. If false, it means that the [homeserverUrl] is valid
|
// True if a wellknown file has been found and is valid. If false, it means that the [homeserverUrl] is valid
|
||||||
val isWellknownValid: Boolean,
|
val isWellknownValid: Boolean,
|
||||||
// True if a wellknown file has been found and is valid and is claiming a sliding sync Url
|
|
||||||
val supportSlidingSync: Boolean,
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class HomeserverResolver @Inject constructor(
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
private val wellknownRequest: WellknownRequest,
|
private val wellknownRequest: WellknownRequest,
|
||||||
) {
|
) {
|
||||||
suspend fun resolve(userInput: String): Flow<List<HomeserverData>> = flow {
|
fun resolve(userInput: String): Flow<List<HomeserverData>> = flow {
|
||||||
val flowContext = currentCoroutineContext()
|
val flowContext = currentCoroutineContext()
|
||||||
val trimmedUserInput = userInput.trim()
|
val trimmedUserInput = userInput.trim()
|
||||||
if (trimmedUserInput.length < 4) return@flow
|
if (trimmedUserInput.length < 4) return@flow
|
||||||
|
|
@ -46,13 +46,11 @@ class HomeserverResolver @Inject constructor(
|
||||||
}
|
}
|
||||||
val isValid = wellKnown?.isValid().orFalse()
|
val isValid = wellKnown?.isValid().orFalse()
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
val supportSlidingSync = wellKnown?.supportSlidingSync().orFalse()
|
|
||||||
// Emit the list as soon as possible
|
// Emit the list as soon as possible
|
||||||
currentList.add(
|
currentList.add(
|
||||||
HomeserverData(
|
HomeserverData(
|
||||||
homeserverUrl = url,
|
homeserverUrl = url,
|
||||||
isWellknownValid = true,
|
isWellknownValid = true,
|
||||||
supportSlidingSync = supportSlidingSync
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
withContext(flowContext) {
|
withContext(flowContext) {
|
||||||
|
|
@ -68,7 +66,6 @@ class HomeserverResolver @Inject constructor(
|
||||||
HomeserverData(
|
HomeserverData(
|
||||||
homeserverUrl = trimmedUserInput,
|
homeserverUrl = trimmedUserInput,
|
||||||
isWellknownValid = false,
|
isWellknownValid = false,
|
||||||
supportSlidingSync = false,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue