Merge branch 'release/0.6.5' into main
This commit is contained in:
commit
996147eaa6
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
|
||||
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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||
|
||||
jobs:
|
||||
debug:
|
||||
|
|
@ -30,11 +30,11 @@ jobs:
|
|||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
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
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
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
|
||||
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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
@ -38,11 +38,11 @@ jobs:
|
|||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
- name: Clone submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
|
|
|
|||
4
.github/workflows/generate_github_pages.yml
vendored
4
.github/workflows/generate_github_pages.yml
vendored
|
|
@ -13,11 +13,11 @@ jobs:
|
|||
steps:
|
||||
- name: ⏬ Checkout with LFS
|
||||
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
|
|
|
|||
6
.github/workflows/gradle-wrapper-update.yml
vendored
6
.github/workflows/gradle-wrapper-update.yml
vendored
|
|
@ -13,13 +13,13 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@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'
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Update Gradle Wrapper
|
||||
uses: gradle-update/update-gradle-wrapper-action@v1
|
||||
uses: gradle-update/update-gradle-wrapper-action@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
||||
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
|
||||
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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||
|
||||
jobs:
|
||||
build-apk:
|
||||
|
|
@ -33,11 +33,11 @@ jobs:
|
|||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
- 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'
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
|
|
|
|||
8
.github/workflows/nightly.yml
vendored
8
.github/workflows/nightly.yml
vendored
|
|
@ -7,8 +7,8 @@ on:
|
|||
- cron: "0 4 * * *"
|
||||
|
||||
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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||
|
||||
jobs:
|
||||
nightly:
|
||||
|
|
@ -17,11 +17,11 @@ jobs:
|
|||
if: ${{ github.repository == 'element-hq/element-x-android' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Build and upload Nightly application
|
||||
run: |
|
||||
./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
|
||||
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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||
|
||||
jobs:
|
||||
nightlyReports:
|
||||
|
|
@ -20,11 +20,11 @@ jobs:
|
|||
- name: ⏬ Checkout with LFS
|
||||
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
||||
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
|
@ -61,11 +61,11 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
|
|
|
|||
8
.github/workflows/nightly_enterprise.yml
vendored
8
.github/workflows/nightly_enterprise.yml
vendored
|
|
@ -7,8 +7,8 @@ on:
|
|||
- cron: "0 4 * * *"
|
||||
|
||||
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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||
|
||||
jobs:
|
||||
nightly:
|
||||
|
|
@ -23,11 +23,11 @@ jobs:
|
|||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
- name: Clone submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Build and upload Nightly application
|
||||
run: |
|
||||
./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
|
||||
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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||
|
||||
jobs:
|
||||
checkScript:
|
||||
|
|
@ -46,11 +46,11 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
|
|
@ -84,11 +84,11 @@ jobs:
|
|||
- name: Clone submodules
|
||||
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
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
|
|
@ -124,11 +124,11 @@ jobs:
|
|||
- name: Clone submodules
|
||||
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
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
|
|
@ -168,11 +168,11 @@ jobs:
|
|||
- name: Clone submodules
|
||||
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
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
|
|
@ -208,11 +208,11 @@ jobs:
|
|||
- name: Clone submodules
|
||||
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
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
|
|
@ -248,11 +248,11 @@ jobs:
|
|||
- name: Clone submodules
|
||||
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
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
|
|
|
|||
8
.github/workflows/recordScreenshots.yml
vendored
8
.github/workflows/recordScreenshots.yml
vendored
|
|
@ -7,7 +7,8 @@ on:
|
|||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
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:
|
||||
record:
|
||||
|
|
@ -32,11 +33,11 @@ jobs:
|
|||
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: ☕️ Use JDK 17
|
||||
- name: ☕️ Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
# Add gradle cache, this should speed up the process
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
|
@ -48,3 +49,4 @@ jobs:
|
|||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
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
|
||||
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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||
|
||||
jobs:
|
||||
gplay:
|
||||
|
|
@ -19,11 +19,11 @@ jobs:
|
|||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Create app bundle
|
||||
|
|
@ -55,11 +55,11 @@ jobs:
|
|||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
- name: Clone submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Create Enterprise app bundle
|
||||
|
|
@ -83,11 +83,11 @@ jobs:
|
|||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
- name: Create APKs
|
||||
|
|
|
|||
|
|
@ -51,10 +51,10 @@ if [[ -z ${REPO} ]]; then
|
|||
fi
|
||||
|
||||
echo "Deleting previous screenshots"
|
||||
./gradlew removeOldSnapshots --stacktrace --warn
|
||||
./gradlew removeOldSnapshots --stacktrace --warn $GRADLE_ARGS
|
||||
|
||||
echo "Record screenshots"
|
||||
./gradlew recordPaparazziDebug --stacktrace
|
||||
./gradlew recordPaparazziDebug --stacktrace $GRADLE_ARGS
|
||||
|
||||
echo "Committing changes"
|
||||
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
|
||||
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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --warn -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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --warn -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||
GROUP: ${{ format('sonar-{0}', github.ref) }}
|
||||
|
||||
jobs:
|
||||
|
|
@ -27,11 +27,11 @@ jobs:
|
|||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
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
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
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' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use JDK 17
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
|
|
|
|||
20
.github/workflows/tests.yml
vendored
20
.github/workflows/tests.yml
vendored
|
|
@ -9,8 +9,8 @@ on:
|
|||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
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
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
||||
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 --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
|
|
@ -46,24 +46,18 @@ jobs:
|
|||
- name: Clone submodules
|
||||
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
|
||||
- name: ☕️ Use JDK 17
|
||||
- name: ☕️ Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
|
||||
- name: ⚙️ Run unit tests for debug variant
|
||||
run: ./gradlew testDebugUnitTest $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: ⚙️ Check coverage for debug variant (includes unit & screenshot tests)
|
||||
run: ./gradlew testDebugUnitTest :tests:uitests:verifyPaparazziDebug :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyAll $CI_GRADLE_ARG_PROPERTIES
|
||||
|
||||
- name: 🚫 Upload kover failed coverage reports
|
||||
if: failure()
|
||||
|
|
@ -71,7 +65,7 @@ jobs:
|
|||
with:
|
||||
name: kover-error-report
|
||||
path: |
|
||||
app/build/reports/kover/verifyGplayDebug.err
|
||||
app/build/reports/kover
|
||||
|
||||
- name: ✅ Upload kover report (disabled)
|
||||
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
|
||||
.idea/navEditor.xml
|
||||
.idea/other.xml
|
||||
.idea/runConfigurations.xml
|
||||
.idea/tasks.xml
|
||||
.idea/workspace.xml
|
||||
.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)
|
||||
========================================
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kapt)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
@ -16,6 +15,6 @@ dependencies {
|
|||
implementation(libs.anvil.compiler.utils)
|
||||
implementation(libs.kotlinpoet)
|
||||
implementation(libs.dagger)
|
||||
compileOnly(libs.google.autoservice.annotations)
|
||||
kapt(libs.google.autoservice)
|
||||
implementation(libs.ksp.plugin)
|
||||
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.tasks.GenerateBuildConfig
|
||||
import extension.AssetCopyTask
|
||||
import extension.ComponentMergingStrategy
|
||||
import extension.GitBranchNameValueSource
|
||||
import extension.GitRevisionValueSource
|
||||
import extension.allEnterpriseImpl
|
||||
|
|
@ -19,14 +20,13 @@ import extension.allLibrariesImpl
|
|||
import extension.allServicesImpl
|
||||
import extension.koverDependencies
|
||||
import extension.locales
|
||||
import extension.setupAnvil
|
||||
import extension.setupKover
|
||||
import java.util.Locale
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-application")
|
||||
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
|
||||
id(libs.plugins.firebaseAppDistribution.get().pluginId)
|
||||
alias(libs.plugins.knit)
|
||||
|
|
@ -161,9 +161,6 @@ android {
|
|||
}
|
||||
}
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
|
|
@ -233,27 +230,34 @@ knit {
|
|||
}
|
||||
}
|
||||
|
||||
setupAnvil(
|
||||
generateDaggerCode = true,
|
||||
generateDaggerFactoriesUsingAnvil = false,
|
||||
componentMergingStrategy = ComponentMergingStrategy.KSP,
|
||||
)
|
||||
|
||||
dependencies {
|
||||
allLibrariesImpl()
|
||||
allServicesImpl()
|
||||
if (isEnterpriseBuild) {
|
||||
allEnterpriseImpl(rootDir, logger)
|
||||
allEnterpriseImpl(project)
|
||||
implementation(projects.appicon.enterprise)
|
||||
} else {
|
||||
implementation(projects.appicon.element)
|
||||
}
|
||||
allFeaturesImpl(rootDir, logger)
|
||||
allFeaturesImpl(project)
|
||||
implementation(projects.features.migration.api)
|
||||
implementation(projects.anvilannotations)
|
||||
implementation(projects.appnav)
|
||||
implementation(projects.appconfig)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
anvil(projects.anvilcodegen)
|
||||
implementation(projects.services.analytics.compose)
|
||||
|
||||
// Comment to not include firebase in the project
|
||||
"gplayImplementation"(projects.libraries.pushproviders.firebase)
|
||||
// Comment to not include unified push in the project
|
||||
implementation(projects.libraries.pushproviders.unifiedpush)
|
||||
if (ModulesConfig.pushProvidersConfig.includeFirebase) {
|
||||
"gplayImplementation"(projects.libraries.pushproviders.firebase)
|
||||
}
|
||||
if (ModulesConfig.pushProvidersConfig.includeUnifiedPush) {
|
||||
implementation(projects.libraries.pushproviders.unifiedpush)
|
||||
}
|
||||
|
||||
implementation(libs.appyx.core)
|
||||
implementation(libs.androidx.splash)
|
||||
|
|
@ -272,9 +276,6 @@ dependencies {
|
|||
|
||||
implementation(libs.matrix.emojibase.bindings)
|
||||
|
||||
implementation(libs.dagger)
|
||||
kapt(libs.dagger.compiler)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.test.robolectric)
|
||||
testImplementation(libs.coroutines.test)
|
||||
|
|
@ -297,14 +298,13 @@ tasks.withType<GenerateBuildConfig>().configureEach {
|
|||
licensee {
|
||||
allow("Apache-2.0")
|
||||
allow("MIT")
|
||||
allow("GPL-2.0-with-classpath-exception")
|
||||
allow("BSD-2-Clause")
|
||||
allowUrl("https://opensource.org/licenses/MIT")
|
||||
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://jsoup.org/license")
|
||||
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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ package io.element.android.x.di
|
|||
import android.content.Context
|
||||
import com.squareup.anvil.annotations.MergeComponent
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
|
|
@ -19,7 +18,7 @@ import io.element.android.libraries.di.SingleIn
|
|||
@SingleIn(AppScope::class)
|
||||
@MergeComponent(AppScope::class)
|
||||
interface AppComponent : NodeFactoriesBindings {
|
||||
@Component.Factory
|
||||
@MergeComponent.Factory
|
||||
interface Factory {
|
||||
fun create(
|
||||
@ApplicationContext @BindsInstance
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ package io.element.android.x.di
|
|||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
|
@ -20,7 +19,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
|
|||
@SingleIn(RoomScope::class)
|
||||
@MergeSubcomponent(RoomScope::class)
|
||||
interface RoomComponent : NodeFactoriesBindings {
|
||||
@Subcomponent.Builder
|
||||
@MergeSubcomponent.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
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.MergeSubcomponent
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
|
@ -20,7 +19,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
|||
@SingleIn(SessionScope::class)
|
||||
@MergeSubcomponent(SessionScope::class)
|
||||
interface SessionComponent : NodeFactoriesBindings {
|
||||
@Subcomponent.Builder
|
||||
@MergeSubcomponent.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
fun client(matrixClient: MatrixClient): Builder
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<locale android:name="en"/>
|
||||
<locale android:name="es"/>
|
||||
<locale android:name="et"/>
|
||||
<locale android:name="fa"/>
|
||||
<locale android:name="fr"/>
|
||||
<locale android:name="hu"/>
|
||||
<locale android:name="in"/>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
package io.element.android.appconfig
|
||||
|
||||
object SecureBackupConfig {
|
||||
const val LEARN_MORE_URL: String = "https://element.io/help#encryption5"
|
||||
object LearnMoreConfig {
|
||||
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")
|
||||
|
||||
import extension.allFeaturesApi
|
||||
import extension.setupAnvil
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
alias(libs.plugins.kapt)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
|
|
@ -20,13 +19,10 @@ android {
|
|||
namespace = "io.element.android.appnav"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
implementation(libs.dagger)
|
||||
kapt(libs.dagger.compiler)
|
||||
setupAnvil()
|
||||
|
||||
allFeaturesApi(rootDir, logger)
|
||||
dependencies {
|
||||
allFeaturesApi(project)
|
||||
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.androidutils)
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import im.vector.app.features.analytics.plan.SuperProperties
|
||||
import io.element.android.features.rageshake.api.crash.CrashDetectionPresenter
|
||||
import io.element.android.features.rageshake.api.detection.RageshakeDetectionPresenter
|
||||
import io.element.android.features.rageshake.api.crash.CrashDetectionState
|
||||
import io.element.android.features.rageshake.api.detection.RageshakeDetectionState
|
||||
import io.element.android.features.share.api.ShareService
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.SdkMetadata
|
||||
|
|
@ -22,8 +22,8 @@ import io.element.android.services.apperror.api.AppErrorStateService
|
|||
import javax.inject.Inject
|
||||
|
||||
class RootPresenter @Inject constructor(
|
||||
private val crashDetectionPresenter: CrashDetectionPresenter,
|
||||
private val rageshakeDetectionPresenter: RageshakeDetectionPresenter,
|
||||
private val crashDetectionPresenter: Presenter<CrashDetectionState>,
|
||||
private val rageshakeDetectionPresenter: Presenter<RageshakeDetectionState>,
|
||||
private val appErrorStateService: AppErrorStateService,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val shareService: ShareService,
|
||||
|
|
|
|||
|
|
@ -1,4 +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>
|
||||
|
|
|
|||
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 com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appnav.root.RootPresenter
|
||||
import io.element.android.features.rageshake.impl.crash.DefaultCrashDetectionPresenter
|
||||
import io.element.android.features.rageshake.impl.detection.DefaultRageshakeDetectionPresenter
|
||||
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.rageshake.api.crash.aCrashDetectionState
|
||||
import io.element.android.features.rageshake.api.detection.aRageshakeDetectionState
|
||||
import io.element.android.features.share.api.ShareService
|
||||
import io.element.android.features.share.test.FakeShareService
|
||||
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.apperror.api.AppErrorState
|
||||
import io.element.android.services.apperror.api.AppErrorStateService
|
||||
|
|
@ -44,7 +38,6 @@ class RootPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.crashDetectionState.crashDetected).isFalse()
|
||||
}
|
||||
|
|
@ -61,7 +54,7 @@ class RootPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(2)
|
||||
skipItems(1)
|
||||
lambda.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
|
@ -76,8 +69,6 @@ class RootPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.errorState).isInstanceOf(AppErrorState.Error::class.java)
|
||||
val initialErrorState = initialState.errorState as AppErrorState.Error
|
||||
|
|
@ -93,25 +84,9 @@ class RootPresenterTest {
|
|||
appErrorService: AppErrorStateService = DefaultAppErrorStateService(),
|
||||
shareService: ShareService = FakeShareService {},
|
||||
): 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(
|
||||
crashDetectionPresenter = crashDetectionPresenter,
|
||||
rageshakeDetectionPresenter = rageshakeDetectionPresenter,
|
||||
crashDetectionPresenter = { aCrashDetectionState() },
|
||||
rageshakeDetectionPresenter = { aRageshakeDetectionState() },
|
||||
appErrorStateService = appErrorService,
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
shareService = shareService,
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ allprojects {
|
|||
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
||||
}
|
||||
dependencies {
|
||||
detektPlugins("io.nlopez.compose.rules:detekt:0.4.12")
|
||||
detektPlugins("io.nlopez.compose.rules:detekt:0.4.15")
|
||||
}
|
||||
|
||||
// KtLint
|
||||
|
|
@ -71,8 +71,9 @@ allprojects {
|
|||
// To have XML report for Danger
|
||||
reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE)
|
||||
}
|
||||
val generatedPath = "${layout.buildDirectory.asFile.get()}/generated/"
|
||||
filter {
|
||||
exclude { element -> element.file.path.contains("${layout.buildDirectory.asFile.get()}/generated/") }
|
||||
exclude { element -> element.file.path.contains(generatedPath) }
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
*
|
||||
|
|
@ -7,7 +9,6 @@
|
|||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
|
|
@ -15,13 +16,9 @@ android {
|
|||
namespace = "io.element.android.features.analytics.impl"
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.core)
|
||||
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.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
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.background.OnboardingBackground
|
||||
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.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
|
@ -104,14 +104,9 @@ private fun AnalyticsOptInHeader(
|
|||
bold = true,
|
||||
tagAndLink = LINK_TAG to AnalyticsConfig.POLICY_LINK,
|
||||
)
|
||||
ClickableText(
|
||||
text = text,
|
||||
onClick = {
|
||||
text
|
||||
.getStringAnnotations(LINK_TAG, it, it)
|
||||
.firstOrNull()
|
||||
?.let { _ -> onClickTerms() }
|
||||
},
|
||||
ClickableLinkText(
|
||||
annotatedString = text,
|
||||
onClick = { onClickTerms() },
|
||||
modifier = Modifier
|
||||
.padding(8.dp),
|
||||
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.collectAsState
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.appconfig.AnalyticsConfig
|
||||
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.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultAnalyticsPreferencesPresenter @Inject constructor(
|
||||
class AnalyticsPreferencesPresenter @Inject constructor(
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val buildMeta: BuildMeta,
|
||||
) : AnalyticsPreferencesPresenter {
|
||||
) : Presenter<AnalyticsPreferencesState> {
|
||||
@Composable
|
||||
override fun present(): AnalyticsPreferencesState {
|
||||
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
|
||||
fun `present - initial state available`() = runTest {
|
||||
val presenter = DefaultAnalyticsPreferencesPresenter(
|
||||
val presenter = AnalyticsPreferencesPresenter(
|
||||
FakeAnalyticsService(isEnabled = true, didAskUserConsent = true),
|
||||
aBuildMeta()
|
||||
)
|
||||
|
|
@ -41,7 +41,7 @@ class AnalyticsPreferencesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - initial state not available`() = runTest {
|
||||
val presenter = DefaultAnalyticsPreferencesPresenter(
|
||||
val presenter = AnalyticsPreferencesPresenter(
|
||||
FakeAnalyticsService(isEnabled = false, didAskUserConsent = false),
|
||||
aBuildMeta()
|
||||
)
|
||||
|
|
@ -55,7 +55,7 @@ class AnalyticsPreferencesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - enable and disable`() = runTest {
|
||||
val presenter = DefaultAnalyticsPreferencesPresenter(
|
||||
val presenter = AnalyticsPreferencesPresenter(
|
||||
FakeAnalyticsService(isEnabled = true, didAskUserConsent = true),
|
||||
aBuildMeta()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
|
|
@ -7,20 +9,15 @@
|
|||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.cachecleaner.impl"
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
api(projects.features.cachecleaner.api)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
|
|
@ -7,7 +9,6 @@
|
|||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
id("kotlin-parcelize")
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
|
@ -24,13 +25,10 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
implementation(projects.appconfig)
|
||||
implementation(projects.anvilannotations)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.designsystem)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,6 @@ import io.element.android.features.call.impl.utils.WidgetMessageInterceptor
|
|||
|
||||
sealed interface CallScreenEvents {
|
||||
data object Hangup : CallScreenEvents
|
||||
data class SetupMessageChannels(val widgetMessageInterceptor: WidgetMessageInterceptor) :
|
||||
CallScreenEvents
|
||||
data class SetupMessageChannels(val widgetMessageInterceptor: WidgetMessageInterceptor) : CallScreenEvents
|
||||
data class OnWebViewError(val description: String?) : CallScreenEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ class CallScreenPresenter @AssistedInject constructor(
|
|||
val callWidgetDriver = remember { mutableStateOf<MatrixWidgetDriver?>(null) }
|
||||
val messageInterceptor = remember { mutableStateOf<WidgetMessageInterceptor?>(null) }
|
||||
var isJoinedCall by rememberSaveable { mutableStateOf(false) }
|
||||
var ignoreWebViewError by rememberSaveable { mutableStateOf(false) }
|
||||
var webViewError by remember { mutableStateOf<String?>(null) }
|
||||
val languageTag = languageTagProvider.provideLanguageTag()
|
||||
val theme = if (ElementTheme.isLightTheme) "light" else "dark"
|
||||
DisposableEffect(Unit) {
|
||||
|
|
@ -125,6 +127,8 @@ class CallScreenPresenter @AssistedInject constructor(
|
|||
LaunchedEffect(Unit) {
|
||||
interceptor.interceptedMessages
|
||||
.onEach {
|
||||
// We are receiving messages from the WebView, consider that the application is loaded
|
||||
ignoreWebViewError = true
|
||||
// Relay message to Widget Driver
|
||||
callWidgetDriver.value?.send(it)
|
||||
|
||||
|
|
@ -163,11 +167,18 @@ class CallScreenPresenter @AssistedInject constructor(
|
|||
is CallScreenEvents.SetupMessageChannels -> {
|
||||
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(
|
||||
urlState = urlState.value,
|
||||
webViewError = webViewError,
|
||||
userAgent = userAgent,
|
||||
isInWidgetMode = isInWidgetMode,
|
||||
eventSink = { handleEvents(it) },
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import io.element.android.libraries.architecture.AsyncData
|
|||
|
||||
data class CallScreenState(
|
||||
val urlState: AsyncData<String>,
|
||||
val webViewError: String?,
|
||||
val userAgent: String,
|
||||
val isInWidgetMode: Boolean,
|
||||
val eventSink: (CallScreenEvents) -> Unit,
|
||||
|
|
|
|||
|
|
@ -16,17 +16,20 @@ open class CallScreenStateProvider : PreviewParameterProvider<CallScreenState> {
|
|||
aCallScreenState(),
|
||||
aCallScreenState(urlState = AsyncData.Loading()),
|
||||
aCallScreenState(urlState = AsyncData.Failure(Exception("An error occurred"))),
|
||||
aCallScreenState(webViewError = "Error details from WebView"),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun aCallScreenState(
|
||||
urlState: AsyncData<String> = AsyncData.Success("https://call.element.io/some-actual-call?with=parameters"),
|
||||
webViewError: String? = null,
|
||||
userAgent: String = "",
|
||||
isInWidgetMode: Boolean = false,
|
||||
eventSink: (CallScreenEvents) -> Unit = {},
|
||||
): CallScreenState {
|
||||
return CallScreenState(
|
||||
urlState = urlState,
|
||||
webViewError = webViewError,
|
||||
userAgent = userAgent,
|
||||
isInWidgetMode = isInWidgetMode,
|
||||
eventSink = eventSink,
|
||||
|
|
|
|||
|
|
@ -85,35 +85,48 @@ internal fun CallScreenView(
|
|||
BackHandler {
|
||||
handleBack()
|
||||
}
|
||||
CallWebView(
|
||||
modifier = Modifier
|
||||
if (state.webViewError != null) {
|
||||
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)
|
||||
.consumeWindowInsets(padding)
|
||||
.fillMaxSize(),
|
||||
url = state.urlState,
|
||||
userAgent = state.userAgent,
|
||||
onPermissionsRequest = { request ->
|
||||
val androidPermissions = mapWebkitPermissions(request.resources)
|
||||
val callback: RequestPermissionCallback = { request.grant(it) }
|
||||
requestPermissions(androidPermissions.toTypedArray(), callback)
|
||||
},
|
||||
onWebViewCreate = { webView ->
|
||||
val interceptor = WebViewWidgetMessageInterceptor(webView)
|
||||
state.eventSink(CallScreenEvents.SetupMessageChannels(interceptor))
|
||||
val pipController = WebViewPipController(webView)
|
||||
pipState.eventSink(PictureInPictureEvents.SetPipController(pipController))
|
||||
url = state.urlState,
|
||||
userAgent = state.userAgent,
|
||||
onPermissionsRequest = { request ->
|
||||
val androidPermissions = mapWebkitPermissions(request.resources)
|
||||
val callback: RequestPermissionCallback = { request.grant(it) }
|
||||
requestPermissions(androidPermissions.toTypedArray(), callback)
|
||||
},
|
||||
onWebViewCreate = { webView ->
|
||||
val interceptor = WebViewWidgetMessageInterceptor(
|
||||
webView = webView,
|
||||
onError = { state.eventSink(CallScreenEvents.OnWebViewError(it)) },
|
||||
)
|
||||
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
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.net.http.SslError
|
||||
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.WebViewClient
|
||||
import androidx.webkit.WebViewCompat
|
||||
import androidx.webkit.WebViewFeature
|
||||
import io.element.android.features.call.impl.BuildConfig
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import timber.log.Timber
|
||||
|
||||
class WebViewWidgetMessageInterceptor(
|
||||
private val webView: WebView,
|
||||
private val onError: (String?) -> Unit,
|
||||
) : WidgetMessageInterceptor {
|
||||
companion object {
|
||||
// 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"
|
||||
|| !message.data.response && message.data.api == "fromWidget") {
|
||||
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);
|
||||
} 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(),
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io"))
|
||||
assertThat(initialState.webViewError).isNull()
|
||||
assertThat(initialState.isInWidgetMode).isFalse()
|
||||
analyticsLambda.assertions().isNeverCalled()
|
||||
joinedCallLambda.assertions().isCalledOnce()
|
||||
|
|
@ -270,6 +271,48 @@ class CallScreenPresenterTest {
|
|||
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(
|
||||
callType: CallType,
|
||||
navigator: CallScreenNavigator = FakeCallScreenNavigator(),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import extension.ComponentMergingStrategy
|
||||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
* Copyright 2022-2024 New Vector Ltd.
|
||||
*
|
||||
|
|
@ -7,7 +10,6 @@
|
|||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
|
|
@ -21,13 +23,9 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
setupAnvil(componentMergingStrategy = ComponentMergingStrategy.KSP)
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
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.MergeSubcomponent
|
||||
import dagger.Subcomponent
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
|
|
@ -17,7 +16,7 @@ import io.element.android.libraries.di.SingleIn
|
|||
@SingleIn(CreateRoomScope::class)
|
||||
@MergeSubcomponent(CreateRoomScope::class)
|
||||
interface CreateRoomComponent : NodeFactoriesBindings {
|
||||
@Subcomponent.Builder
|
||||
@MergeSubcomponent.Builder
|
||||
interface Builder {
|
||||
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.
|
||||
*
|
||||
|
|
@ -7,7 +9,6 @@
|
|||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
|
|
@ -21,13 +22,9 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.core)
|
||||
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">
|
||||
<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_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>
|
||||
</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_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_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_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_title">"Deaktivovať účet"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
|
|
@ -7,7 +9,6 @@
|
|||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
|
|
@ -15,13 +16,9 @@ android {
|
|||
namespace = "io.element.android.features.ftue.impl"
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
api(projects.features.ftue.api)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.core)
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
@Composable
|
||||
fun WelcomeView(
|
||||
applicationName: String,
|
||||
modifier: Modifier = Modifier,
|
||||
onContinueClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BackHandler(onBack = onContinueClick)
|
||||
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.lockscreen.api.LockScreenService
|
||||
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.test.verification.FakeSessionVerificationService
|
||||
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
||||
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.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -36,8 +37,9 @@ class DefaultFtueServiceTest {
|
|||
val sessionVerificationService = FakeSessionVerificationService().apply {
|
||||
givenVerifiedStatus(SessionVerifiedStatus.Unknown)
|
||||
}
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val service = createDefaultFtueService(coroutineScope, sessionVerificationService)
|
||||
val service = createDefaultFtueService(
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
)
|
||||
|
||||
service.state.test {
|
||||
// Verification state is unknown, we don't display the flow yet
|
||||
|
|
@ -47,9 +49,6 @@ class DefaultFtueServiceTest {
|
|||
sessionVerificationService.givenVerifiedStatus(SessionVerifiedStatus.NotVerified)
|
||||
assertThat(awaitItem()).isEqualTo(FtueState.Incomplete)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -58,10 +57,7 @@ class DefaultFtueServiceTest {
|
|||
val sessionVerificationService = FakeSessionVerificationService()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = true)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
|
|
@ -75,9 +71,6 @@ class DefaultFtueServiceTest {
|
|||
service.updateState()
|
||||
|
||||
assertThat(service.state.value).isEqualTo(FtueState.Complete)
|
||||
|
||||
// Cleanup
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -88,10 +81,7 @@ class DefaultFtueServiceTest {
|
|||
val analyticsService = FakeAnalyticsService()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
|
|
@ -126,20 +116,15 @@ class DefaultFtueServiceTest {
|
|||
// Final state
|
||||
null,
|
||||
)
|
||||
|
||||
// Cleanup
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if a check for a step is true, start from the next one`() = runTest {
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val sessionVerificationService = FakeSessionVerificationService()
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
|
|
@ -155,14 +140,10 @@ class DefaultFtueServiceTest {
|
|||
|
||||
analyticsService.setDidAskUserConsent()
|
||||
assertThat(service.getNextStep(null)).isNull()
|
||||
|
||||
// Cleanup
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
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 analyticsService = FakeAnalyticsService()
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
|
|
@ -170,7 +151,6 @@ class DefaultFtueServiceTest {
|
|||
val service = createDefaultFtueService(
|
||||
sdkIntVersion = Build.VERSION_CODES.M,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
coroutineScope = coroutineScope,
|
||||
analyticsService = analyticsService,
|
||||
lockScreenService = lockScreenService,
|
||||
)
|
||||
|
|
@ -182,14 +162,10 @@ class DefaultFtueServiceTest {
|
|||
|
||||
analyticsService.setDidAskUserConsent()
|
||||
assertThat(service.getNextStep(null)).isNull()
|
||||
|
||||
// Cleanup
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reset do the expected actions S`() = runTest {
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val resetAnalyticsLambda = lambdaRecorder<Unit> { }
|
||||
val analyticsService = FakeAnalyticsService(
|
||||
resetLambda = resetAnalyticsLambda
|
||||
|
|
@ -199,7 +175,6 @@ class DefaultFtueServiceTest {
|
|||
resetPermissionLambda = resetPermissionLambda
|
||||
)
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sdkIntVersion = Build.VERSION_CODES.S,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
analyticsService = analyticsService,
|
||||
|
|
@ -211,7 +186,6 @@ class DefaultFtueServiceTest {
|
|||
|
||||
@Test
|
||||
fun `reset do the expected actions TIRAMISU`() = runTest {
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val resetLambda = lambdaRecorder<Unit> { }
|
||||
val analyticsService = FakeAnalyticsService(
|
||||
resetLambda = resetLambda
|
||||
|
|
@ -221,7 +195,6 @@ class DefaultFtueServiceTest {
|
|||
resetPermissionLambda = resetPermissionLambda
|
||||
)
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sdkIntVersion = Build.VERSION_CODES.TIRAMISU,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
analyticsService = analyticsService,
|
||||
|
|
@ -232,17 +205,16 @@ class DefaultFtueServiceTest {
|
|||
.with(value("android.permission.POST_NOTIFICATIONS"))
|
||||
}
|
||||
|
||||
private fun createDefaultFtueService(
|
||||
coroutineScope: CoroutineScope,
|
||||
sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
|
||||
private fun TestScope.createDefaultFtueService(
|
||||
sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(),
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
permissionStateProvider: FakePermissionStateProvider = FakePermissionStateProvider(permissionGranted = false),
|
||||
permissionStateProvider: PermissionStateProvider = FakePermissionStateProvider(permissionGranted = false),
|
||||
lockScreenService: LockScreenService = FakeLockScreenService(),
|
||||
sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
// First version where notification permission is required
|
||||
sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU,
|
||||
) = DefaultFtueService(
|
||||
sessionCoroutineScope = coroutineScope,
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkIntVersion),
|
||||
analyticsService = analyticsService,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
|
|
@ -7,7 +9,6 @@
|
|||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
|
|
@ -15,13 +16,9 @@ android {
|
|||
namespace = "io.element.android.features.invite.impl"
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
api(projects.features.invite.api)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
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.
|
||||
*
|
||||
|
|
@ -7,7 +9,6 @@
|
|||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
|
|
@ -20,13 +21,9 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
api(projects.features.joinroom.api)
|
||||
implementation(projects.libraries.core)
|
||||
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,
|
||||
progress: LeaveRoomState.Progress = LeaveRoomState.Progress.Hidden,
|
||||
error: LeaveRoomState.Error = LeaveRoomState.Error.Hidden,
|
||||
eventSink: (LeaveRoomEvent) -> Unit = {},
|
||||
) = LeaveRoomState(
|
||||
confirmation = confirmation,
|
||||
progress = progress,
|
||||
error = error,
|
||||
eventSink = {},
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
* Copyright 2022-2024 New Vector Ltd.
|
||||
*
|
||||
|
|
@ -7,20 +9,15 @@
|
|||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.leaveroom.impl"
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
|
|
|
|||
|
|
@ -12,16 +12,14 @@ import androidx.compose.runtime.MutableState
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.LeaveRoomPresenter
|
||||
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.Generic
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.LastUserInRoom
|
||||
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.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
|
|
@ -30,12 +28,11 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultLeaveRoomPresenter @Inject constructor(
|
||||
class LeaveRoomPresenter @Inject constructor(
|
||||
private val client: MatrixClient,
|
||||
private val roomMembershipObserver: RoomMembershipObserver,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) : LeaveRoomPresenter {
|
||||
) : Presenter<LeaveRoomState> {
|
||||
@Composable
|
||||
override fun present(): LeaveRoomState {
|
||||
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.Test
|
||||
|
||||
class DefaultLeaveRoomPresenterTest {
|
||||
class LeaveRoomPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `present - initial state hides all dialogs`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter()
|
||||
val presenter = createLeaveRoomPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -46,7 +46,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - show generic confirmation`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -66,7 +66,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - show private room confirmation`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -86,7 +86,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - show last user in room confirmation`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -106,7 +106,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - show DM confirmation`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -127,7 +127,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
@Test
|
||||
fun `present - leaving a room leaves the room`() = runTest {
|
||||
val roomMembershipObserver = RoomMembershipObserver()
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -151,7 +151,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - show error if leave room fails`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -175,7 +175,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - show progress indicator while leaving a room`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -199,7 +199,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - hide error hides the error`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -225,10 +225,10 @@ class DefaultLeaveRoomPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createDefaultLeaveRoomPresenter(
|
||||
private fun TestScope.createLeaveRoomPresenter(
|
||||
client: MatrixClient = FakeMatrixClient(),
|
||||
roomMembershipObserver: RoomMembershipObserver = RoomMembershipObserver(),
|
||||
): DefaultLeaveRoomPresenter = DefaultLeaveRoomPresenter(
|
||||
): LeaveRoomPresenter = LeaveRoomPresenter(
|
||||
client = client,
|
||||
roomMembershipObserver = roomMembershipObserver,
|
||||
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.
|
||||
*
|
||||
|
|
@ -16,13 +18,9 @@ android {
|
|||
namespace = "io.element.android.features.licenses.impl"
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
implementation(libs.serialization.json)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.designsystem)
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ internal fun StaticMapPlaceholder(
|
|||
contentDescription: String?,
|
||||
width: Dp,
|
||||
height: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
onLoadMapClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
* Copyright 2022-2024 New Vector Ltd.
|
||||
*
|
||||
|
|
@ -7,7 +9,6 @@
|
|||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -19,9 +20,7 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
api(projects.features.location.api)
|
||||
|
|
@ -39,8 +38,6 @@ dependencies {
|
|||
implementation(libs.accompanist.permission)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(libs.dagger)
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
|
|
@ -7,7 +9,6 @@
|
|||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
|
|
@ -15,13 +16,9 @@ android {
|
|||
namespace = "io.element.android.features.lockscreen.impl"
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
api(projects.features.lockscreen.api)
|
||||
implementation(projects.appconfig)
|
||||
implementation(projects.libraries.core)
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ import androidx.compose.runtime.derivedStateOf
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.lockscreen.impl.LockScreenConfig
|
||||
import io.element.android.features.lockscreen.impl.R
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@
|
|||
|
||||
package io.element.android.features.lockscreen.impl.unlock
|
||||
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
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.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
|
@ -23,6 +25,9 @@ open class PinUnlockStateProvider : PreviewParameterProvider<PinUnlockState> {
|
|||
aPinUnlockState(showBiometricUnlock = false),
|
||||
aPinUnlockState(showSignOutPrompt = true, remainingAttempts = 0),
|
||||
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.
|
||||
*
|
||||
|
|
@ -7,7 +10,6 @@
|
|||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
id("kotlin-parcelize")
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
|
@ -22,14 +24,10 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
setupAnvil(componentMergingStrategy = ComponentMergingStrategy.KSP)
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
implementation(projects.appconfig)
|
||||
anvil(projects.anvilcodegen)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.architecture)
|
||||
|
|
|
|||
|
|
@ -14,5 +14,4 @@ data class AccountProvider(
|
|||
val isPublic: Boolean = false,
|
||||
val isMatrixOrg: Boolean = false,
|
||||
val isValid: Boolean = false,
|
||||
val supportSlidingSync: Boolean = false,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ open class AccountProviderProvider : PreviewParameterProvider<AccountProvider> {
|
|||
get() = sequenceOf(
|
||||
anAccountProvider(),
|
||||
anAccountProvider().copy(subtitle = null),
|
||||
anAccountProvider().copy(subtitle = null, title = "no.sliding.sync", supportSlidingSync = false),
|
||||
anAccountProvider().copy(subtitle = null, title = "invalid", isValid = false, supportSlidingSync = false),
|
||||
anAccountProvider().copy(subtitle = null, title = "invalid", isValid = false),
|
||||
anAccountProvider().copy(subtitle = null, title = "Other", isPublic = false, isMatrixOrg = false),
|
||||
// Add other state here
|
||||
)
|
||||
|
|
@ -28,5 +27,4 @@ fun anAccountProvider() = AccountProvider(
|
|||
isPublic = true,
|
||||
isMatrixOrg = true,
|
||||
isValid = true,
|
||||
supportSlidingSync = true,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
|||
@Composable
|
||||
fun AccountProviderView(
|
||||
item: AccountProvider,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
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.MergeSubcomponent
|
||||
import dagger.Subcomponent
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
|
|
@ -17,7 +16,7 @@ import io.element.android.libraries.di.SingleIn
|
|||
@SingleIn(QrCodeLoginScope::class)
|
||||
@MergeSubcomponent(QrCodeLoginScope::class)
|
||||
interface QrCodeLoginComponent : NodeFactoriesBindings {
|
||||
@Subcomponent.Builder
|
||||
@MergeSubcomponent.Builder
|
||||
interface Builder {
|
||||
fun build(): QrCodeLoginComponent
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,4 @@ data class HomeserverData(
|
|||
val homeserverUrl: String,
|
||||
// True if a wellknown file has been found and is valid. If false, it means that the [homeserverUrl] is valid
|
||||
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 wellknownRequest: WellknownRequest,
|
||||
) {
|
||||
suspend fun resolve(userInput: String): Flow<List<HomeserverData>> = flow {
|
||||
fun resolve(userInput: String): Flow<List<HomeserverData>> = flow {
|
||||
val flowContext = currentCoroutineContext()
|
||||
val trimmedUserInput = userInput.trim()
|
||||
if (trimmedUserInput.length < 4) return@flow
|
||||
|
|
@ -46,13 +46,11 @@ class HomeserverResolver @Inject constructor(
|
|||
}
|
||||
val isValid = wellKnown?.isValid().orFalse()
|
||||
if (isValid) {
|
||||
val supportSlidingSync = wellKnown?.supportSlidingSync().orFalse()
|
||||
// Emit the list as soon as possible
|
||||
currentList.add(
|
||||
HomeserverData(
|
||||
homeserverUrl = url,
|
||||
isWellknownValid = true,
|
||||
supportSlidingSync = supportSlidingSync
|
||||
)
|
||||
)
|
||||
withContext(flowContext) {
|
||||
|
|
@ -68,7 +66,6 @@ class HomeserverResolver @Inject constructor(
|
|||
HomeserverData(
|
||||
homeserverUrl = trimmedUserInput,
|
||||
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