Merge branch 'develop' into feat/skin-tone-picker

This commit is contained in:
Benoit Marty
2026-07-07 15:21:27 +02:00
committed by GitHub
2205 changed files with 16549 additions and 7822 deletions
+4 -4
View File
@@ -44,19 +44,19 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# 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 }}
persist-credentials: false
- name: Use JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Assemble debug APKs
@@ -80,7 +80,7 @@ jobs:
path: |
app/build/outputs/apk/gplay/debug/*-universal-debug.apk
app/build/outputs/apk/fdroid/debug/*-universal-debug.apk
- uses: rnkdsh/action-upload-diawi@4e1421305be7cfc510d05f47850262eeaf345108 # v1.5.12
- uses: rnkdsh/action-upload-diawi@76778fcd1f9f589efaaa62f63d5f03430bbb1306 # v1.5.13
id: diawi
# Do not fail the whole build if Diawi upload fails
continue-on-error: true
+3 -3
View File
@@ -43,7 +43,7 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
@@ -56,12 +56,12 @@ jobs:
- name: Clone submodules
run: git submodule update --init --recursive
- name: Use JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Assemble debug Gplay Enterprise APK
+2 -2
View File
@@ -11,7 +11,7 @@ jobs:
# Skip in forks, it doesn't work even with the fallback token
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Add SSH private keys for submodule repositories
@@ -24,7 +24,7 @@ jobs:
- run: |
npm install --save-dev @babel/plugin-transform-flow-strip-types
- name: Danger
uses: danger/danger-js@67ed2c1f42fd2fc198cc3c14b43c8f83351f4fe9 # 13.0.5
uses: danger/danger-js@e9b58820039524318198d8059ec98b0c02b7875c # 13.0.10
with:
args: "--dangerfile ./tools/danger/dangerfile.js"
env:
+6 -5
View File
@@ -1,9 +1,10 @@
name: Generate GitHub Pages
on:
workflow_dispatch:
schedule:
# At 00:00 on every Tuesday UTC
- cron: '0 0 * * 2'
# Disabled for now, as we don't want to generate GitHub Pages on every push to main anymore
# schedule:
# # At 00:00 on every Tuesday UTC
# - cron: '0 0 * * 2'
permissions: {}
@@ -18,12 +19,12 @@ jobs:
- name: ⏬ Checkout with LFS
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
- name: Use JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Set up Python 3.12
+2 -2
View File
@@ -13,10 +13,10 @@ jobs:
# Skip in forks
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
name: Use JDK 21
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
with:
+4 -4
View File
@@ -38,19 +38,19 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
ref: ${{ github.ref }}
persist-credentials: false
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
name: Use JDK 21
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Assemble debug APK
@@ -78,7 +78,7 @@ jobs:
concurrency:
group: maestro-test
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
with:
# Ensure we are building the branch and not the branch after being merged on develop
+2 -2
View File
@@ -32,11 +32,11 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Use JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
+5 -5
View File
@@ -37,13 +37,13 @@ jobs:
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
- name: Use JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: false
@@ -76,16 +76,16 @@ jobs:
name: Dependency analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Use JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Dependency analysis
+26 -26
View File
@@ -33,7 +33,7 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Add SSH private keys for submodule repositories
@@ -51,7 +51,7 @@ jobs:
name: Search for invalid screenshot files
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up Python 3.12
@@ -65,16 +65,16 @@ jobs:
name: Search for invalid dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Use JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Set up Python 3.12
@@ -93,7 +93,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('check-konsist-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-konsist-develop-{0}', github.sha) || format('check-konsist-{0}', github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
@@ -108,12 +108,12 @@ jobs:
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 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Run Konsist tests
@@ -134,7 +134,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('check-compose-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-compose-develop-{0}', github.sha) || format('check-compose-{0}', github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
@@ -149,12 +149,12 @@ jobs:
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 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Run compose tests
@@ -168,7 +168,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('check-lint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-lint-develop-{0}', github.sha) || format('check-lint-{0}', github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
@@ -183,12 +183,12 @@ jobs:
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 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Build Gplay Debug
@@ -213,7 +213,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('check-detekt-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-detekt-develop-{0}', github.sha) || format('check-detekt-{0}', github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
@@ -228,12 +228,12 @@ jobs:
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 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Run Detekt
@@ -254,7 +254,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('check-ktlint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-ktlint-develop-{0}', github.sha) || format('check-ktlint-{0}', github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
@@ -269,12 +269,12 @@ jobs:
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 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Run Ktlint check
@@ -295,7 +295,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('check-docs-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-docs-develop-{0}', github.sha) || format('check-docs-{0}', github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
@@ -319,7 +319,7 @@ jobs:
name: Check shell scripts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Run shellcheck
@@ -333,10 +333,10 @@ jobs:
permissions:
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
- uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
upload_reports:
name: Project Check Suite
@@ -344,7 +344,7 @@ jobs:
needs: [konsist, lint, ktlint, detekt]
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
@@ -360,7 +360,7 @@ jobs:
yarn add danger-plugin-lint-report --dev
- name: Danger lint
if: always()
uses: danger/danger-js@67ed2c1f42fd2fc198cc3c14b43c8f83351f4fe9 # 13.0.5
uses: danger/danger-js@e9b58820039524318198d8059ec98b0c02b7875c # 13.0.10
with:
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
env:
+2 -2
View File
@@ -54,13 +54,13 @@ jobs:
with:
persist-credentials: false
- name: ☕️ Use JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
# Add gradle cache, this should speed up the process
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Record screenshots
+9 -9
View File
@@ -34,16 +34,16 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Use JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
- name: Create app bundle
env:
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
@@ -71,7 +71,7 @@ jobs:
group: ${{ format('build-release-main-enterprise-{0}', github.sha) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Add SSH private keys for submodule repositories
@@ -82,12 +82,12 @@ jobs:
- name: Clone submodules
run: git submodule update --init --recursive
- name: Use JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
- name: Create Enterprise app bundle
env:
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
@@ -122,16 +122,16 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Use JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
- name: Create APKs
env:
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
+3 -3
View File
@@ -38,19 +38,19 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# 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 }}
persist-credentials: false
- name: Use JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Build debug code and test fixtures
+3 -3
View File
@@ -13,16 +13,16 @@ jobs:
# Skip in forks
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Use JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Set up Python 3.12
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up Python 3.12
+3 -3
View File
@@ -63,12 +63,12 @@ jobs:
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 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
@@ -108,7 +108,7 @@ jobs:
# https://github.com/codecov/codecov-action
- name: ☂️ Upload coverage reports to codecov
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
+1 -1
View File
@@ -2,6 +2,6 @@
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="externalSystemId" value="Gradle" />
<option name="version" value="2.3.21" />
<option name="version" value="2.4.0" />
</component>
</project>
+243
View File
@@ -1,3 +1,238 @@
Changes in Element X v26.07.0
=============================
<!-- Release notes generated using configuration in .github/release.yml at v26.07.0 -->
## What's Changed
### ✨ Features
* Add read all messages feature by @hghgrtut in https://github.com/element-hq/element-x-android/pull/7049
### 🐛 Bugfixes
* Detect RTL text in events and adjust the text rendering by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6994
### 🗣 Translations
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7071
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7115
### 🧱 Build
* Fix Sonar with AGP 9 (some workarounds) by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7055
* Disable periodically generating the screenshot gallery by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7110
* Try a different workaround for flaky `ScanQrCodeViewTest` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7109
### 🚧 In development 🚧
* Protect link new device by @bmarty in https://github.com/element-hq/element-x-android/pull/6992
### Dependency upgrades
* Update metro to v1.2.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7057
* Update gradle/actions action to v6.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7063
* Update roborazzi to v1.64.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7068
* Update rnkdsh/action-upload-diawi action to v1.5.13 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7067
* Update dependency io.element.android:element-call-embedded to v0.20.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7075
* Update dependency com.github.matrix-org:matrix-analytics-events to v0.37.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7079
* Update actions/setup-java action to v5.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7088
* Update dependency org.matrix.rustcomponents:sdk-android to v26.06.18 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7052
* Update firebaseAppDistribution to v5.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7096
* Update dependency com.google.firebase:firebase-bom to v34.15.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7089
* Update dependency com.google.crypto.tink:tink-android to v1.22.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7105
* Update actions/checkout action to v7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7098
* Update lifecycle to v2.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7097
* Update danger/danger-js action to v13.0.8 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7104
* Update dependency org.matrix.rustcomponents:sdk-android to v26.06.25 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7107
* Update dependency androidx.compose:compose-bom to v2026.06.00 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7095
* Update dependency io.element.android:element-call-embedded to v0.20.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7117
* Update zizmorcore/zizmor-action action to v0.5.7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7116
* Update dependency org.matrix.rustcomponents:sdk-android to v26.06.30 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7127
* Update dependency com.posthog:posthog-android to v3.51.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7128
### Others
* Fix small code smell by @bmarty in https://github.com/element-hq/element-x-android/pull/7058
* Hide message notification content when the app is locked by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6902
## New Contributors
* @hghgrtut made their first contribution in https://github.com/element-hq/element-x-android/pull/7049
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.06.4...v26.07.0
Changes in Element X v26.06.4
=============================
<!-- Release notes generated using configuration in .github/release.yml at v26.06.4 -->
## What's Changed
### ✨ Features
* Add well-known parsing and extension seam for custom recovery passphrase by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/6944
### 🙌 Improvements
* Add ability to forward a message to several rooms. by @bmarty in https://github.com/element-hq/element-x-android/pull/7013
### 🐛 Bugfixes
* `screen_custom_recovery_key_input_notice` is now a plurals. by @bmarty in https://github.com/element-hq/element-x-android/pull/7045
* Replace `ContentResolver.delete` usage with a backwards-compatible version by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7053
### Dependency upgrades
* Update kotlin to v0.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7036
* Update gradle/actions action to v6.1.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7037
* Update jna monorepo to v5.19.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7026
* Update coil to v3.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7043
* Update metro to v1.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7048
* Update dependency io.sentry:sentry-android to v8.43.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7047
* Update dependencyAnalysis to v3.15.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7050
* Compile with sdk37 and target API 37 by @bmarty in https://github.com/element-hq/element-x-android/pull/7041
### Others
* call: Remove deprecated web<->EX api calls, use the new ones by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/6628
* Fix deprecation warning by @bmarty in https://github.com/element-hq/element-x-android/pull/7042
* Let Divider use our theme color. by @bmarty in https://github.com/element-hq/element-x-android/pull/7054
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.06.3...v26.06.4
Changes in Element X v26.06.3
=============================
<!-- Release notes generated using configuration in .github/release.yml at v26.06.3 -->
## What's Changed
### ✨ Features
* Click on avatar in moderation bottom sheet opens avatar preview by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6991
### 🙌 Improvements
* Change: hide UserLocationPuck when sharing from current device by @ganfra in https://github.com/element-hq/element-x-android/pull/6955
* Replace "Edit" button with Crop icon. by @bmarty in https://github.com/element-hq/element-x-android/pull/6953
* change(location): ensure permissions are always requested at least once by @ganfra in https://github.com/element-hq/element-x-android/pull/6996
* Refactor: replace client-side event filtering for public rooms with SDK by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7025
### 🐛 Bugfixes
* Ensure the video preview can be played several times. by @bmarty in https://github.com/element-hq/element-x-android/pull/6964
* Fix "Sent" checkmark disappearing when the message is read and "Share presence" is disabled by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6985
* Make `BaseRoom.getDirectRoomMember` change when `roomInfo.isDm` changes by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6989
* Fix invites not being sent during the room creation by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7018
* Fix sharing media inside the app by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7016
* Fix ANRs when calculating DB sizes for Sentry by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7017
* Fix thread list not loading older threads by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7034
### 🗣 Translations
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6986
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7021
### 🧱 Build
* Try fixing flaky screenshots for location timeline items by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6954
* `ScanQrCodeViewTest`: try fixing broken test in CI by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7027
### 🚧 In development 🚧
* Ensure that the application rotate the QrCode if one day the SDK emits the `.Expired` error. by @bmarty in https://github.com/element-hq/element-x-android/pull/6993
### Dependency upgrades
* Update dependency androidx.javascriptengine:javascriptengine to v1.1.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6785
* Update dependency com.google.firebase:firebase-bom to v34.14.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6967
* Update dependency com.github.matrix-org:matrix-analytics-events to v0.36.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6963
* Update dependencyAnalysis to v3.14.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6892
* Update dependency io.element.android:element-call-embedded to v0.20.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6988
* Update dependency io.mockk:mockk to v1.14.11 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6977
* Update zizmorcore/zizmor-action action to v0.5.6 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6845
* deps(codecov) : bumps to v7.0.0 to unblock CI by @ganfra in https://github.com/element-hq/element-x-android/pull/7002
* Update wysiwyg to v2.42.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7009
* Update actions/checkout action to v6.0.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6997
* fix(deps): update android.gradle.plugin to v9 (major) by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6021
* Update dependencyAnalysis to v3.14.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6995
* Update dependency io.sentry:sentry-android to v8.43.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6895
* Update dependency org.matrix.rustcomponents:sdk-android to v26.06.11 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7014
* Update dependency org.maplibre.gl:android-sdk to v13.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7022
* Update dependency com.google.firebase:firebase-bom to v34.14.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7029
* Update okhttp monorepo to v5.4.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7031
### Others
* Fix some string mappings for link new device errors by @hughns in https://github.com/element-hq/element-x-android/pull/6675
* Fallback to the default push gateway on error by @p1gp1g in https://github.com/element-hq/element-x-android/pull/5742
* Fix some string mappings for link new device errors by @bmarty in https://github.com/element-hq/element-x-android/pull/6958
* Add `/myroomnick` slash command to change your display name in the current room by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6865
* Update release notes for `v26.05.1` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6990
* Fix message drafts not being saved in Threads by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6984
* build-rust-sdk: allow skipping git update for kotlin components by @richvdh in https://github.com/element-hq/element-x-android/pull/7012
* Fix broken screenshot tests by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7038
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.06.2...v26.06.3
Changes in Element X v26.06.2
=============================
Security fixes 🔐
-----------------
- Fixed an issue with OIDC callback URLs that could cause the app to crash [(GHSA-r4f9-46vw-v7g3)](https://github.com/element-hq/element-x-android/security/advisories/GHSA-r4f9-46vw-v7g3).
Changes in Element X v26.06.1
=============================
The release of `v26.06.0` was cancelled because it contained some changes that would prevent some users with devices having no Vulkan support from being able to install the app. This release contains the same changes as `v26.06.0` but with the Vulkan requirement removed.
## What's Changed
### ✨ Features
* Image edition before sending: crop and rotate. by @bmarty in https://github.com/element-hq/element-x-android/pull/6842
* Use a raw key for the SDK stores for new sessions by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6835
* Notification settings: pick custom sounds for messages and call ringtones by @bmarty in https://github.com/element-hq/element-x-android/pull/6897
### 🙌 Improvements
* Do not show membership/profile events in public rooms by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6360
* Use `runBlocking` for the token refresh logic by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6863
* Move empty day separator filtering to a timeline post-processor by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6866
* Add better logs to track token update failures by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6859
* Read map tiler custom style from matrix .well-known file by @bmarty in https://github.com/element-hq/element-x-android/pull/6886
* change(permissions) : allow to change ShareLiveLocation permission by @ganfra in https://github.com/element-hq/element-x-android/pull/6890
* Avoid SQLCipher key derivation by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6774
* change: replace the maplibre-compose UserLocationState to a simpler one by @ganfra in https://github.com/element-hq/element-x-android/pull/6913
* Change : location provider by @ganfra in https://github.com/element-hq/element-x-android/pull/6935
* Add flip actions to image edition by @bmarty in https://github.com/element-hq/element-x-android/pull/6949
* Remove the `FloatingDateBadge` feature flag by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6950
### 🐛 Bugfixes
* Fix media viewer flickering by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6800
* Fix 'Conversation label cannot be empty' error by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6823
* Release proximity wakelock on Element Call when call ends by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6825
* Hide edit pencil from accessibility by @bmarty in https://github.com/element-hq/element-x-android/pull/6826
* Don't compress images sent through the Files attachment picker by @cizra in https://github.com/element-hq/element-x-android/pull/6755
* [a11y] Improve accessibility of video and audio player by @bmarty in https://github.com/element-hq/element-x-android/pull/6830
* [a11y] Improve accessibility of screen headers. by @bmarty in https://github.com/element-hq/element-x-android/pull/6827
* Add mark as read / unread in room details by @bmarty in https://github.com/element-hq/element-x-android/pull/6818
* Fix formatting inconsistencies in latest event summaries by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6855
* Fix public read receipts being sent by mistake by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6838
* Fix app having a pink top bar in the recent app list when PIN lock is setup by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6903
* Create log messages from `WebView` just once by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6923
* Use secondary color icon for leading icons by @bmarty in https://github.com/element-hq/element-x-android/pull/6926
* Use reverse ordering for `FilterEmptyDayPostProcessor` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6927
* Make Vulkan a *not required* feature by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6961
### 🗣 Translations
* Sync Strings - new translations to Catalan by @ElementBot in https://github.com/element-hq/element-x-android/pull/6856
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6921
### 🧱 Build
* Remove DI-generated code from Kover reports by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6834
* Disable cron jobs and private SSH key jobs in forks by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6821
* Revert "Disable cron jobs and private SSH key jobs in forks" by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6905
* Downgrade detekt compose rules by @bmarty in https://github.com/element-hq/element-x-android/pull/6846
* Add Stefan's etiquette to the project. by @bmarty in https://github.com/element-hq/element-x-android/pull/6909
### 🚧 In development 🚧
* [Link new device] Rotate QrCode instead of showing an error by @bmarty in https://github.com/element-hq/element-x-android/pull/6817
* Fix image resizing behavior. by @bmarty in https://github.com/element-hq/element-x-android/pull/6924
### Dependency upgrades
* Update dependency net.zetetic:sqlcipher-android to v4.16.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6819
* Update dependencyAnalysis to v3.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6813
* Update metro to v1.1.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6832
* Update dependency io.element.android:element-call-embedded to v0.19.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6833
* Update kotlin to v2.3.8 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6822
* Update dependency org.matrix.rustcomponents:sdk-android to v26.05.20 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6831
* Update media3 to v1.10.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6816
* Update peaceiris/actions-gh-pages action to v4.1.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6820
* Update dependencyAnalysis to v3.12.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6840
* Update codecov/codecov-action action to v6.0.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6864
* Update dependency io.element.android:element-call-embedded to v0.20.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6876
* Update roborazzi to v1.62.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6858
* Update dependency androidx.compose:compose-bom to v2026.05.01 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6888
* Update roborazzi to v1.63.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6893
* Update dependency org.matrix.rustcomponents:sdk-android to v26.05.26 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6885
* Update dependency org.maplibre.compose:maplibre-compose to v0.13.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6891
* Update actions/stale action to v10.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6904
* Update tspascoal/get-user-teams-membership action to v4.0.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6900
* Update dependency org.maplibre.gl:android-sdk to v13.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6930
* Update dependency org.unifiedpush.android:connector to v3.3.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6937
* Update kotlin to v2.3.9 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6946
* Update dependency com.posthog:posthog-android to v3.47.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6920
* Import compound token from release 10.2.1 https://github.com/element-hq/compound-design-tokens/releases/tag/v10.2.1 by @bmarty in https://github.com/element-hq/element-x-android/pull/6942
* Update dependency org.matrix.rustcomponents:sdk-android to v26.06.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6947
* Update dependency com.github.matrix-org:matrix-analytics-events to v0.36.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6939
### Others
* Make the avatar in the room member moderation bottom sheet clickable by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6705
* Do not hide your own media for "Show media in timeline" by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6898
* Report problem: add an optional TextField to provide a GitHub issue number. by @bmarty in https://github.com/element-hq/element-x-android/pull/6911
* Fix read receipts not shown for calls by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6889
* Reorder room detail items by @bmarty in https://github.com/element-hq/element-x-android/pull/6929
* [Media viewer] File preview improvement by @bmarty in https://github.com/element-hq/element-x-android/pull/6933
* [Media bottom sheet] UI iteration by @bmarty in https://github.com/element-hq/element-x-android/pull/6938
* Add unread count to the room unread indicator by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6887
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.05.2...v26.06.1
Changes in Element X v26.05.2
=============================
@@ -42,6 +277,14 @@ Changes in Element X v26.05.1
<!-- Release notes generated using configuration in .github/release.yml at v26.05.1 -->
Security fixes 🔐
-----------------
This release contains some important security fixes:
- Check the user ID in the `sender_device_keys` property of Olm-encrypted to-device events to prevent sender spoofing by homeserver owners. ([#6553](https://github.com/matrix-org/matrix-rust-sdk/pull/6553), High, [CVE-2026-45056](https://www.cve.org/CVERecord?id=CVE-2026-45056), [GHSA-wfq4-36m3-9g42](https://github.com/matrix-org/matrix-rust-sdk/security/advisories/GHSA-wfq4-36m3-9g42))
- Reject invalid edits as candidates for timeline updates. ([#6454](https://github.com/matrix-org/matrix-rust-sdk/pull/6454), Moderate, [CVE-2026-45057](https://www.cve.org/CVERecord?id=CVE-2026-45057), [GHSA-h97m-27fx-42rx](https://github.com/matrix-org/matrix-rust-sdk/security/advisories/GHSA-h97m-27fx-42rx))
## What's Changed
### ✨ Features
* Make Element Call screen work edge-to-edge by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6634
+7 -1
View File
@@ -24,11 +24,11 @@ import extension.buildConfigFieldStr
import extension.locales
import extension.setupDependencyInjection
import extension.testCommonDependencies
import org.sonarqube.gradle.SonarResolverTask
import java.util.Locale
plugins {
id("io.element.android-compose-application")
alias(libs.plugins.kotlin.android)
// When using precompiled plugins, we need to apply the firebase plugin like this
id(libs.plugins.firebaseAppDistribution.get().pluginId)
id("kotlin-parcelize")
@@ -189,6 +189,7 @@ android {
buildFeatures {
buildConfig = true
resValues = true
}
flavorDimensions += "store"
productFlavors {
@@ -245,6 +246,11 @@ androidComponents {
configureLicensesTasks(reportingExtension)
}
// Configure the SonarQube plugin to wait for the resource generation tasks to complete before running the analysis.
tasks.withType<SonarResolverTask>().configureEach {
dependsOn("generateGplayDebugResValues", "generateGplayDebugAndroidTestResValues")
}
setupDependencyInjection()
dependencies {
@@ -24,14 +24,12 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_THREAD_ID
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.robolectric.RobolectricTest
import io.element.android.x.MainActivity
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
@RunWith(RobolectricTestRunner::class)
class DefaultIntentProviderTest {
class DefaultIntentProviderTest : RobolectricTest() {
@Test
fun `test getViewRoomIntent with data`() {
val deepLinkCreator = lambdaRecorder<SessionId, RoomId?, ThreadId?, EventId?, String> { _, _, _, _ -> "deepLinkCreatorResult" }
@@ -45,15 +45,13 @@ import io.element.android.services.analytics.test.watchers.FakeAnalyticsSendMess
import io.element.android.services.appnavstate.api.ActiveRoomsHolder
import io.element.android.services.appnavstate.test.FakeActiveRoomsHolder
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
import io.element.android.tests.testutils.robolectric.RobolectricTest
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class JoinedRoomLoadedFlowNodeTest {
class JoinedRoomLoadedFlowNodeTest : RobolectricTest() {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@@ -29,13 +29,11 @@ import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.oauth.api.OAuthAction
import io.element.android.libraries.oauth.test.FakeOAuthIntentResolver
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
@RunWith(RobolectricTestRunner::class)
class IntentResolverTest {
class IntentResolverTest : RobolectricTest() {
@Test
fun `resolve launcher intent should return null`() {
val sut = createIntentResolver()
-2
View File
@@ -106,8 +106,6 @@ allprojects {
// Fix compilation warning for annotations
// See https://youtrack.jetbrains.com/issue/KT-73255/Change-defaulting-rule-for-annotations for more details
freeCompilerArgs.add("-Xannotation-default-target=first-only")
// Opt-in to context receivers
freeCompilerArgs.add("-Xcontext-parameters")
}
}
}
@@ -0,0 +1,2 @@
Main changes in this version: added image editing before sending and an option for custom notification sounds, improved start times, several other bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases
@@ -0,0 +1,2 @@
Main changes in this version: fixed an issue with devices not supporting Vulkan, added image editing before sending and an option for custom notification sounds, improved start times, several other bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases
@@ -0,0 +1,2 @@
Main changes in this version: bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases
@@ -0,0 +1,2 @@
Main changes in this version: bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases
@@ -0,0 +1,2 @@
Main changes in this version: bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases
@@ -31,12 +31,12 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.analytics.api.AnalyticsOptInEvents
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
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.visuallist.VisualList
import io.element.android.libraries.designsystem.components.visuallist.VisualListItemData
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
@@ -129,22 +129,21 @@ private fun AnalyticsOptInContent() {
verticalBias = -0.4f
)
) {
InfoListOrganism(
VisualList(
items = persistentListOf(
InfoListItem(
VisualListItemData(
message = stringResource(id = R.string.screen_analytics_prompt_data_usage),
iconVector = CompoundIcons.CheckCircle(),
),
InfoListItem(
VisualListItemData(
message = stringResource(id = R.string.screen_analytics_prompt_third_party_sharing),
iconVector = CompoundIcons.CheckCircle(),
),
InfoListItem(
VisualListItemData(
message = stringResource(id = R.string.screen_analytics_prompt_settings),
iconVector = CompoundIcons.CheckCircle(),
),
),
textStyle = ElementTheme.typography.fontBodyLgMedium,
iconTint = ElementTheme.colors.iconSuccessPrimary,
)
}
@@ -36,10 +36,10 @@ import io.element.android.features.announcement.impl.AnnouncementState
import io.element.android.features.announcement.impl.AnnouncementStateProvider
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.components.visuallist.VisualList
import io.element.android.libraries.designsystem.components.visuallist.VisualListItemData
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
@@ -134,11 +134,9 @@ private fun FullscreenAnnouncementContent(
Column(
modifier = modifier.fillMaxSize(),
) {
InfoListOrganism(
VisualList(
modifier = Modifier.fillMaxWidth(),
items = announcement.items(),
textStyle = ElementTheme.typography.fontBodyLgMedium,
iconTint = ElementTheme.colors.iconSecondary,
iconSize = 24.dp
)
announcement.notice()?.let { notice ->
@@ -186,25 +184,25 @@ private fun Announcement.Fullscreen.icon() = when (this) {
}
@Composable
private fun Announcement.Fullscreen.items(): ImmutableList<InfoListItem> = when (this) {
private fun Announcement.Fullscreen.items(): ImmutableList<VisualListItemData> = when (this) {
Announcement.Fullscreen.Space -> persistentListOf(
InfoListItem(
VisualListItemData(
message = "View spaces you\'ve created or joined",
iconVector = CompoundIcons.VisibilityOn(),
),
InfoListItem(
VisualListItemData(
message = "Accept or decline invites to spaces",
iconVector = CompoundIcons.Email(),
),
InfoListItem(
VisualListItemData(
message = "Discover any rooms you can join in your spaces",
iconVector = CompoundIcons.Search(),
),
InfoListItem(
VisualListItemData(
message = "Join public spaces",
iconVector = CompoundIcons.Explore(),
),
InfoListItem(
VisualListItemData(
message = "Leave any spaces youve joined",
iconVector = CompoundIcons.Leave(),
),
@@ -14,7 +14,6 @@ import androidx.activity.ComponentActivity
import androidx.compose.ui.test.AndroidComposeUiTest
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.announcement.api.Announcement
import io.element.android.features.announcement.impl.AnnouncementEvent
import io.element.android.features.announcement.impl.AnnouncementState
@@ -23,11 +22,10 @@ import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.pressBackKey
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class FullscreenAnnouncementViewTest {
class FullscreenAnnouncementViewTest : RobolectricTest() {
@Test
fun `clicking on back sends a AnnouncementEvent`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<AnnouncementEvent>()
-1
View File
@@ -84,7 +84,6 @@ dependencies {
implementation(projects.services.toolbox.api)
implementation(libs.androidx.webkit)
implementation(libs.coil.compose)
implementation(libs.network.retrofit)
implementation(libs.serialization.json)
implementation(libs.element.call.embedded)
api(projects.features.call.api)
@@ -207,7 +207,6 @@ class WebViewAudioManager(
}
audioManager.mode = AudioManager.MODE_NORMAL
if (!hasRegisteredCallbacks) {
Timber.w("Audio: tried to disable webview in-call audio mode without registering callbacks")
return
@@ -265,8 +264,8 @@ class WebViewAudioManager(
private fun setWebViewAndroidNativeBridge() {
Timber.d("Adding callback in controls.onAudioPlaybackStarted")
webView.evaluateJavascript("controls.onAudioPlaybackStarted = () => { androidNativeBridge.onTrackReady(); };", null)
Timber.d("Adding callback in controls.onOutputDeviceSelect")
webView.evaluateJavascript("controls.onOutputDeviceSelect = (id) => { androidNativeBridge.setOutputDevice(id); };", null)
Timber.d("Adding callback in controls.onAudioDeviceSelect")
webView.evaluateJavascript("controls.onAudioDeviceSelect = (id) => { androidNativeBridge.setAudioDevice(id); };", null)
}
/**
@@ -293,8 +292,8 @@ class WebViewAudioManager(
) {
Timber.d("Updating available audio devices")
val deviceList = json.encodeToString(devices)
webView.evaluateJavascript("controls.setAvailableOutputDevices($deviceList);", {
Timber.d("Audio: setAvailableOutputDevices result: $it")
webView.evaluateJavascript("controls.setAvailableAudioDevices($deviceList);", {
Timber.d("Audio: setAvailableAudioDevices result: $it")
})
}
@@ -342,6 +341,7 @@ class WebViewAudioManager(
} else {
// On Android 11 and lower, we don't have the concept of communication devices
// We have to call the right methods based on the device type
@Suppress("DEPRECATION")
if (device != null) {
if (device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO && disableBluetoothAudioDevices) {
Timber.w("Bluetooth audio devices are disabled on this Android version")
@@ -350,11 +350,9 @@ class WebViewAudioManager(
return
}
setAudioEnabled(true)
@Suppress("DEPRECATION")
isSpeakerphoneOn = device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
isBluetoothScoOn = device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
} else {
@Suppress("DEPRECATION")
isSpeakerphoneOn = false
isBluetoothScoOn = false
}
@@ -399,7 +397,7 @@ private class AndroidWebViewAudioBridge(
private val onAudioPlaybackStarted: () -> Unit,
) {
@JavascriptInterface
fun setOutputDevice(id: String) {
fun setAudioDevice(id: String) {
Timber.d("Audio device selected in webview, id: $id")
onAudioDeviceSelected(id)
}
@@ -1,6 +1,6 @@
<?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_channel_title_android">"正在进行的通话"</string>
<string name="call_foreground_service_message_android">"点击以返回通话"</string>
<string name="call_foreground_service_title_android">"☎️ 通话中"</string>
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call 不支持在此 Android 版本中使用蓝牙音频设备。请选择其它音频设备。"</string>
@@ -21,19 +21,17 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.robolectric.RobolectricTest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.Shadows.shadowOf
import kotlin.time.Duration.Companion.seconds
@RunWith(RobolectricTestRunner::class)
class DefaultElementCallEntryPointTest {
class DefaultElementCallEntryPointTest : RobolectricTest() {
@Test
fun `startCall - starts ElementCallActivity setup with the needed extras`() = runTest {
val entryPoint = createEntryPoint()
@@ -23,13 +23,11 @@ import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.matrix.ui.media.test.FakeImageLoaderHolder
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.robolectric.RobolectricTest
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class RingingCallNotificationCreatorTest {
class RingingCallNotificationCreatorTest : RobolectricTest() {
@Test
fun `createNotification - with no associated MatrixClient does nothing`() = runTest {
val notificationCreator = createRingingCallNotificationCreator(
@@ -15,7 +15,6 @@ import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.test.AndroidComposeUiTest
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.call.impl.pip.PictureInPictureEvent
import io.element.android.features.call.impl.pip.PictureInPictureState
import io.element.android.features.call.impl.pip.aPictureInPictureState
@@ -26,9 +25,9 @@ import io.element.android.features.call.impl.ui.JavascriptBackHandlerBridge
import io.element.android.features.call.impl.ui.aCallScreenState
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.pressBackKey
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import org.robolectric.annotation.Implementation
import org.robolectric.annotation.Implements
@@ -36,8 +35,7 @@ import org.robolectric.annotation.Resetter
import org.robolectric.shadows.ShadowWebView
@OptIn(ExperimentalTestApi::class)
@RunWith(AndroidJUnit4::class)
class CallScreenViewTest {
class CallScreenViewTest : RobolectricTest() {
@Test
fun `pressing back key triggers hangup when no web view is available and pip is unsupported`() = runAndroidComposeUiTest {
val callEvents = EventsRecorder<CallScreenEvent>()
@@ -45,6 +45,7 @@ import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.plantTestTimber
import io.element.android.tests.testutils.robolectric.RobolectricTest
import io.mockk.coVerify
import io.mockk.mockk
import io.mockk.verify
@@ -54,12 +55,9 @@ import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
@RunWith(RobolectricTestRunner::class)
class DefaultActiveCallManagerTest {
class DefaultActiveCallManagerTest : RobolectricTest() {
private val notificationId = NotificationIdProvider.getForegroundServiceNotificationId(ForegroundServiceType.INCOMING_CALL)
@OptIn(ExperimentalCoroutinesApi::class)
@@ -13,6 +13,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -36,6 +39,13 @@ fun AddPeopleView(
modifier: Modifier = Modifier,
invitePeopleView: @Composable () -> Unit,
) {
val currentOnFinish by rememberUpdatedState(onFinish)
LaunchedEffect(state.sendInvitesAction, state.createRoomFromDmAction) {
if (state.sendInvitesAction.isSuccess() || state.createRoomFromDmAction.isSuccess()) {
currentOnFinish()
}
}
HeaderFooterPage(
modifier = modifier,
contentPadding = PaddingValues(0.dp),
@@ -47,7 +57,6 @@ fun AddPeopleView(
text = stringResource(CommonStrings.action_finish),
onClick = {
state.eventSink(InvitePeopleEvents.SendInvites)
onFinish()
},
enabled = state.canInvite,
modifier = Modifier
@@ -11,7 +11,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.SheetValue
import androidx.compose.material3.rememberBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -85,9 +86,9 @@ internal fun SelectParentSpaceOptions(
)
if (displaySelectSpaceBottomSheet) {
val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true,
confirmValueChange = { true },
val sheetState = rememberBottomSheetState(
initialValue = SheetValue.Hidden,
enabledValues = setOf(SheetValue.Hidden, SheetValue.Expanded),
)
ModalBottomSheet(
sheetState = sheetState,
@@ -3,9 +3,15 @@
<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_name_placeholder">"Дадаць імя…"</string>
<string name="screen_create_room_new_room_title">"Новы пакой"</string>
<string name="screen_create_room_new_space_title">"Новая прастора"</string>
<string name="screen_create_room_private_option_description">"Толькі запрошаныя людзі могуць атрымаць доступ да гэтага пакоя. Усе паведамленні абаронены end-to-end шыфраваннем."</string>
<string name="screen_create_room_public_option_description">"Любы можа знайсці гэты пакой.
Вы можаце змяніць гэта ў любы час у наладах пакоя."</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Папрасіце далучыцца"</string>
<string name="screen_create_room_room_address_section_title">"Адрас"</string>
<string name="screen_create_room_space_selection_sheet_title">"Дадаць у прастору"</string>
<string name="screen_create_room_topic_label">"Тэма (неабавязкова)"</string>
<string name="screen_create_room_topic_placeholder">"Дадаць апісанне…"</string>
</resources>
@@ -31,6 +31,6 @@
<string name="screen_create_room_space_selection_no_space_option">"Не додавати до простору"</string>
<string name="screen_create_room_space_selection_no_space_title">"Головна"</string>
<string name="screen_create_room_space_selection_sheet_title">"Додати до простору"</string>
<string name="screen_create_room_topic_label">"Тема (необов\'язково)"</string>
<string name="screen_create_room_topic_label">"Тематика обговорень (необовязково)"</string>
<string name="screen_create_room_topic_placeholder">"Додати опис…"</string>
</resources>
@@ -56,6 +56,7 @@ import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.robolectric.RobolectricTest
import io.element.android.tests.testutils.test
import io.mockk.mockk
import kotlinx.collections.immutable.persistentMapOf
@@ -66,8 +67,6 @@ import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.io.File
import java.util.Optional
@@ -75,8 +74,7 @@ private const val AN_URI_FROM_CAMERA = "content://uri_from_camera"
private const val AN_URI_FROM_CAMERA_2 = "content://uri_from_camera_2"
private const val AN_URI_FROM_GALLERY = "content://uri_from_gallery"
@RunWith(RobolectricTestRunner::class)
class ConfigureRoomPresenterTest {
class ConfigureRoomPresenterTest : RobolectricTest() {
@get:Rule
val warmUpRule = WarmUpRule()
@@ -8,7 +8,6 @@
package io.element.android.features.logout.impl
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -49,17 +48,18 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.deactivation.impl.R
import io.element.android.features.logout.impl.ui.AccountDeactivationActionDialog
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.components.list.SwitchListItem
import io.element.android.libraries.designsystem.components.visuallist.VisualList
import io.element.android.libraries.designsystem.components.visuallist.VisualListItemData
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.PasswordVisibilityToggle
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextField
@@ -175,9 +175,9 @@ private fun Content(
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary,
)
InfoListOrganism(
VisualList(
items = persistentListOf(
InfoListItem(
VisualListItemData(
message = buildAnnotatedStringWithStyledPart(
R.string.screen_deactivate_account_list_item_1,
R.string.screen_deactivate_account_list_item_1_bold_part,
@@ -194,7 +194,7 @@ private fun Content(
)
},
),
InfoListItem(
VisualListItemData(
message = stringResource(R.string.screen_deactivate_account_list_item_2),
iconComposable = {
Icon(
@@ -205,7 +205,7 @@ private fun Content(
)
},
),
InfoListItem(
VisualListItemData(
message = stringResource(R.string.screen_deactivate_account_list_item_3),
iconComposable = {
Icon(
@@ -216,7 +216,7 @@ private fun Content(
)
},
),
InfoListItem(
VisualListItemData(
message = stringResource(R.string.screen_deactivate_account_list_item_4),
iconComposable = {
Icon(
@@ -280,14 +280,10 @@ private fun Content(
placeholder = stringResource(CommonStrings.common_password),
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
val image =
if (passwordVisible) CompoundIcons.VisibilityOn() else CompoundIcons.VisibilityOff()
val description =
if (passwordVisible) stringResource(CommonStrings.a11y_hide_password) else stringResource(CommonStrings.a11y_show_password)
Box(modifier = Modifier.clickable { passwordVisible = !passwordVisible }) {
Icon(imageVector = image, description)
}
PasswordVisibilityToggle(
visible = passwordVisible,
onToggle = { passwordVisible = !passwordVisible },
)
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
@@ -3,11 +3,12 @@
<string name="screen_deactivate_account_confirmation_dialog_content">"계정을 비활성화하시겠습니까? 이 작업은 되돌릴 수 없습니다."</string>
<string name="screen_deactivate_account_delete_all_messages">"모든 내 메시지 삭제"</string>
<string name="screen_deactivate_account_delete_all_messages_notice">"경고: 향후 사용자는 불완전한 대화 내용을 볼 수 있습니다."</string>
<string name="screen_deactivate_account_description">"계정을 비활성화하는 것은 %1$s 이며, 다음과 같은 조치를 취합니다:"</string>
<string name="screen_deactivate_account_description">"계정을 삭제하면 %1$s, 다음과 같이 처리됩니다:"</string>
<string name="screen_deactivate_account_description_bold_part">"불가역적"</string>
<string name="screen_deactivate_account_list_item_1">"%1$s 귀하의 계정 (로그인할 수 없으며, 귀하의 ID는 재사용할 수 없습니다)."</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_list_item_4">"메시지는 등록된 사용자에게는 계속 표시되지만, 삭제하면 신규 또는 미등록 사용자는 볼 수 없게 됩니다."</string>
<string name="screen_deactivate_account_title">"계정 삭제"</string>
</resources>
@@ -9,6 +9,6 @@
<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_list_item_4">"Ваши сообщения по-прежнему будут видны зарегистрированным пользователям, но не будут доступны новым или незарегистрированным пользователям, если вы решите удалить их."</string>
<string name="screen_deactivate_account_list_item_4">"Ваши сообщения по-прежнему будут видны зарегистрированным пользователям, но не будут доступны новым или незарегистрированным пользователям, если Вы решите удалить их."</string>
<string name="screen_deactivate_account_title">"Удалить аккаунт"</string>
</resources>
@@ -1,14 +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">"Будь ласка, підтвердіть, що ви хочете деактивувати свій обліковий запис. Ця дія не може бути скасована."</string>
<string name="screen_deactivate_account_confirmation_dialog_content">"Підтвердьте, що хочете видалити свій обліковий запис. Цю дію неможливо скасувати."</string>
<string name="screen_deactivate_account_delete_all_messages">"Видалити всі мої повідомлення"</string>
<string name="screen_deactivate_account_delete_all_messages_notice">"Попередження: майбутні користувачі можуть бачити неповні розмови."</string>
<string name="screen_deactivate_account_description">"Деактивація вашого облікового запису%1$s , це буде:"</string>
<string name="screen_deactivate_account_description">"Видалення вашого облікового запису %1$s призведе до:"</string>
<string name="screen_deactivate_account_description_bold_part">"незворотні"</string>
<string name="screen_deactivate_account_list_item_1">"%1$sваш обліковий запис (ви не можете знову увійти, а ваш ідентифікатор не може бути використаний повторно)."</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_2">"Вилучити вас з усіх бесід."</string>
<string name="screen_deactivate_account_list_item_3">"Видаліть інформацію свого облікового запису з нашого сервера ідентифікації."</string>
<string name="screen_deactivate_account_list_item_4">"Ваші повідомлення залишатимуться видимими для зареєстрованих користувачів, але недоступними для нових або незареєстрованих користувачів, якщо ви вирішите їх видалити."</string>
<string name="screen_deactivate_account_title">"Відключити обліковий запис"</string>
<string name="screen_deactivate_account_title">"Видалити обліковий запис"</string>
</resources>
@@ -16,7 +16,6 @@ import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.deactivation.impl.R
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.test.AN_EXCEPTION
@@ -29,12 +28,11 @@ import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.pressTag
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
class AccountDeactivationViewTest {
class AccountDeactivationViewTest : RobolectricTest() {
@Test
fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>(expectEvents = false)
@@ -74,7 +74,10 @@ class ForwardMessagesNode(
return roomSelectEntryPoint.createNode(
parentNode = this,
buildContext = buildContext,
params = RoomSelectEntryPoint.Params(mode = RoomSelectMode.Forward),
params = RoomSelectEntryPoint.Params(
mode = RoomSelectMode.Forward,
maxNumberOfRooms = RoomSelectEntryPoint.DEFAULT_MAX_NUMBER_OF_ROOMS,
),
callback = callback,
)
}
@@ -14,7 +14,6 @@ import androidx.activity.ComponentActivity
import androidx.compose.ui.test.AndroidComposeUiTest
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.test.AN_EXCEPTION
@@ -24,11 +23,10 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.pressTag
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ForwardMessagesViewTest {
class ForwardMessagesViewTest : RobolectricTest() {
@Test
fun `cancel error emits the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<ForwardMessagesEvents>()
@@ -2,7 +2,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_identity_confirmation_cannot_confirm">"Tidak dapat mengonfirmasi?"</string>
<string name="screen_identity_confirmation_create_new_recovery_key">"Buat kunci pemulihan baru"</string>
<string name="screen_identity_confirmation_subtitle">"Verifikasi perangkat ini untuk menyiapkan perpesanan aman."</string>
<string name="screen_identity_confirmation_subtitle">"Pilih cara verifikasi untuk mengatur pengiriman pesan yang aman."</string>
<string name="screen_identity_confirmation_title">"Konfirmasi bahwa ini Anda"</string>
<string name="screen_identity_confirmation_use_another_device">"Gunakan perangkat lain"</string>
<string name="screen_identity_confirmation_use_recovery_key">"Gunakan kunci pemulihan"</string>
@@ -6,7 +6,7 @@
<string name="screen_identity_confirmation_title">"Подтвердите личность"</string>
<string name="screen_identity_confirmation_use_another_device">"Использовать другое устройство"</string>
<string name="screen_identity_confirmation_use_recovery_key">"Использовать ключ восстановления"</string>
<string name="screen_identity_confirmed_subtitle">"Теперь вы можете безопасно читать и отправлять сообщения, и все, с кем вы общаетесь в чате, также могут доверять этому устройству."</string>
<string name="screen_identity_confirmed_subtitle">"Теперь Вы можете безопасно читать и отправлять сообщения, и все, с кем Вы общаетесь в чате, также могут доверять этому устройству."</string>
<string name="screen_identity_confirmed_title">"Устройство проверено"</string>
<string name="screen_identity_use_another_device">"Использовать другое устройство"</string>
<string name="screen_identity_waiting_on_other_device">"Ожидание другого устройства…"</string>
@@ -2,12 +2,12 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_identity_confirmation_cannot_confirm">"Не можете підтвердити?"</string>
<string name="screen_identity_confirmation_create_new_recovery_key">"Створити новий ключ відновлення"</string>
<string name="screen_identity_confirmation_subtitle">"Верифікуйте цей пристрій, щоб налаштувати безпечний обмін повідомленнями."</string>
<string name="screen_identity_confirmation_title">"Підтвердьте, що це ви"</string>
<string name="screen_identity_confirmation_subtitle">"Виберіть спосіб звіряння, щоб налаштувати безпечний обмін повідомленнями."</string>
<string name="screen_identity_confirmation_title">"Підтвердьте свою цифрову ідентичність"</string>
<string name="screen_identity_confirmation_use_another_device">"Використовуйте інший пристрій"</string>
<string name="screen_identity_confirmation_use_recovery_key">"Використовуйте ключ відновлення"</string>
<string name="screen_identity_confirmed_subtitle">"Тепер ви можете безпечно читати або надсилати повідомлення, і кожен, з ким ви спілкуєтесь, також може довіряти цьому пристрою."</string>
<string name="screen_identity_confirmed_title">"Пристрій перевірено"</string>
<string name="screen_identity_confirmed_title">"Пристрій звірено"</string>
<string name="screen_identity_use_another_device">"Використовуйте інший пристрій"</string>
<string name="screen_identity_waiting_on_other_device">"Чекає на інше пристрій…"</string>
<string name="screen_notification_optin_subtitle">"Ви можете змінити свої налаштування пізніше."</string>
@@ -14,19 +14,17 @@ import androidx.activity.ComponentActivity
import androidx.compose.ui.test.AndroidComposeUiTest
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.ftue.impl.R
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
class ChooseSessionVerificationModeViewTest {
class ChooseSessionVerificationModeViewTest : RobolectricTest() {
@Config(qualifiers = "h1024dp")
@Test
fun `clicking on learn more invokes the expected callback`() = runAndroidComposeUiTest {
+1
View File
@@ -57,6 +57,7 @@ dependencies {
implementation(libs.androidx.datastore.preferences)
implementation(libs.haze)
implementation(libs.haze.materials)
implementation(projects.features.preferences.impl)
implementation(projects.features.reportroom.api)
implementation(projects.features.rolesandpermissions.api)
implementation(projects.libraries.previewutils)
@@ -279,6 +279,7 @@ private fun RoomsViewList(
hideInviteAvatars = hideInvitesAvatars,
isInviteSeen = room.displayType == RoomSummaryDisplayType.INVITE &&
state.seenRoomInvites.contains(room.roomId),
showUnreadCount = state.showUnreadCount,
onClick = onRoomClick,
eventSink = eventSink,
)
@@ -76,8 +76,9 @@ internal fun RoomSummaryRow(
hideInviteAvatars: Boolean,
isInviteSeen: Boolean,
onClick: (RoomListRoomSummary) -> Unit,
eventSink: (RoomListEvent) -> Unit,
modifier: Modifier = Modifier,
showUnreadCount: Boolean = false,
eventSink: (RoomListEvent) -> Unit,
) {
Box(modifier = modifier) {
when (room.displayType) {
@@ -127,7 +128,7 @@ internal fun RoomSummaryRow(
timestamp = room.timestamp,
isHighlighted = room.isHighlighted
)
MessagePreviewAndIndicatorRow(room = room)
MessagePreviewAndIndicatorRow(room = room, showUnreadCount = showUnreadCount)
}
}
RoomSummaryDisplayType.KNOCKED -> {
@@ -273,6 +274,7 @@ private fun InviteSubtitle(
@Composable
private fun MessagePreviewAndIndicatorRow(
room: RoomListRoomSummary,
showUnreadCount: Boolean,
modifier: Modifier = Modifier,
) {
Row(
@@ -360,8 +362,18 @@ private fun MessagePreviewAndIndicatorRow(
}
if (room.hasNewContent) {
val contentDescription = stringResource(CommonStrings.a11y_notifications_new_messages)
val count = if (showUnreadCount) {
if (room.userDefinedNotificationMode == RoomNotificationMode.MUTE) {
room.numberOfUnreadMessages
} else {
room.numberOfUnreadNotifications
}
} else {
null
}
UnreadIndicatorAtom(
color = tint,
count = count,
contentDescription = contentDescription,
)
}
@@ -36,6 +36,7 @@ open class RoomListContentStateProvider : PreviewParameterProvider<RoomListConte
internal fun aRoomsContentState(
securityBannerState: SecurityBannerState = SecurityBannerState.None,
showNewNotificationSoundBanner: Boolean = false,
showUnreadCount: Boolean = false,
summaries: ImmutableList<RoomListRoomSummary> = aRoomListRoomSummaryList(),
fullScreenIntentPermissionsState: FullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(),
batteryOptimizationState: BatteryOptimizationState = aBatteryOptimizationState(),
@@ -43,6 +44,7 @@ internal fun aRoomsContentState(
) = RoomListContentState.Rooms(
securityBannerState = securityBannerState,
showNewNotificationSoundBanner = showNewNotificationSoundBanner,
showUnreadCount = showUnreadCount,
fullScreenIntentPermissionsState = fullScreenIntentPermissionsState,
batteryOptimizationState = batteryOptimizationState,
summaries = summaries,
@@ -69,10 +69,6 @@ fun RoomListContextMenu(
onFavoriteChange = { isFavorite ->
eventSink(RoomListEvent.SetRoomIsFavorite(contextMenu.roomId, isFavorite))
},
onClearCacheRoomClick = {
eventSink(RoomListEvent.HideContextMenu)
eventSink(RoomListEvent.ClearCacheOfRoom(contextMenu.roomId))
},
onReportRoomClick = {
eventSink(RoomListEvent.HideContextMenu)
onReportRoomClick(contextMenu.roomId)
@@ -90,7 +86,6 @@ private fun RoomListModalBottomSheetContent(
onFavoriteChange: (isFavorite: Boolean) -> Unit,
onRoomMarkReadClick: () -> Unit,
onRoomMarkUnreadClick: () -> Unit,
onClearCacheRoomClick: () -> Unit,
onReportRoomClick: () -> Unit,
) {
Column(
@@ -111,7 +106,7 @@ private fun RoomListModalBottomSheetContent(
ListItem(
headlineContent = {
Text(
text = stringResource(id = R.string.screen_roomlist_mark_as_read),
text = stringResource(id = CommonStrings.action_mark_as_read),
style = MaterialTheme.typography.bodyLarge,
)
},
@@ -119,7 +114,6 @@ private fun RoomListModalBottomSheetContent(
leadingContent = ListItemContent.Icon(
iconSource = IconSource.Vector(CompoundIcons.MarkAsRead())
),
style = ListItemStyle.Primary,
)
} else {
ListItem(
@@ -133,7 +127,6 @@ private fun RoomListModalBottomSheetContent(
leadingContent = ListItemContent.Icon(
iconSource = IconSource.Vector(CompoundIcons.MarkAsUnread())
),
style = ListItemStyle.Primary,
)
}
val (textResId, icon) = if (contextMenu.isFavorite) {
@@ -159,7 +152,6 @@ private fun RoomListModalBottomSheetContent(
onClick = {
onFavoriteChange(!contextMenu.isFavorite)
},
style = ListItemStyle.Primary,
)
ListItem(
headlineContent = {
@@ -174,7 +166,6 @@ private fun RoomListModalBottomSheetContent(
CompoundIcons.Settings(),
)
),
style = ListItemStyle.Primary,
)
if (canReportRoom) {
ListItem(
@@ -202,18 +193,6 @@ private fun RoomListModalBottomSheetContent(
),
style = ListItemStyle.Destructive,
)
if (contextMenu.displayClearRoomCacheAction) {
ListItem(
headlineContent = {
Text(text = "Clear cache for this room")
},
modifier = Modifier.clickable { onClearCacheRoomClick() },
leadingContent = ListItemContent.Icon(
iconSource = IconSource.Vector(CompoundIcons.Delete())
),
style = ListItemStyle.Primary,
)
}
}
}
@@ -221,7 +200,7 @@ private fun RoomListModalBottomSheetContent(
@Composable
internal fun RoomListContextMenuPreview(
@PreviewParameter(RoomListStateContextMenuShownProvider::class) contextMenu: RoomListState.ContextMenu.Shown
) = ElementPreview {
) = ElementPreview(fillMaxSize = true) {
RoomListContextMenu(
contextMenu = contextMenu,
canReportRoom = true,
@@ -30,5 +30,4 @@ sealed interface RoomListEvent {
data class MarkAsRead(val roomId: RoomId) : ContextMenuEvent
data class MarkAsUnread(val roomId: RoomId) : ContextMenuEvent
data class SetRoomIsFavorite(val roomId: RoomId, val isFavorite: Boolean) : ContextMenuEvent
data class ClearCacheOfRoom(val roomId: RoomId) : ContextMenuEvent
}
@@ -28,7 +28,6 @@ import im.vector.app.features.analytics.plan.Interaction
import io.element.android.features.announcement.api.Announcement
import io.element.android.features.announcement.api.AnnouncementService
import io.element.android.features.home.impl.datasource.RoomListDataSource
import io.element.android.features.home.impl.filters.RoomListFilter.Rooms
import io.element.android.features.home.impl.filters.RoomListFiltersState
import io.element.android.features.home.impl.filters.into
import io.element.android.features.home.impl.search.RoomListSearchEvent
@@ -42,20 +41,19 @@ import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteE
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.preferences.impl.tasks.MarkRoomAsRead
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.roomlist.RoomList
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.push.api.battery.BatteryOptimizationState
import io.element.android.libraries.push.api.notifications.NotificationCleaner
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.api.watchers.AnalyticsColdStartWatcher
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
@@ -65,7 +63,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -79,17 +76,16 @@ class RoomListPresenter(
private val roomListDataSource: RoomListDataSource,
private val filtersPresenter: Presenter<RoomListFiltersState>,
private val searchPresenter: Presenter<RoomListSearchState>,
private val sessionPreferencesStore: SessionPreferencesStore,
private val analyticsService: AnalyticsService,
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
private val fullScreenIntentPermissionsPresenter: Presenter<FullScreenIntentPermissionsState>,
private val batteryOptimizationPresenter: Presenter<BatteryOptimizationState>,
private val notificationCleaner: NotificationCleaner,
private val appPreferencesStore: AppPreferencesStore,
private val markRoomAsRead: MarkRoomAsRead,
private val seenInvitesStore: SeenInvitesStore,
private val announcementService: AnnouncementService,
private val coldStartWatcher: AnalyticsColdStartWatcher,
private val spaceFiltersPresenter: Presenter<SpaceFiltersState>,
private val featureFlagService: FeatureFlagService,
) : Presenter<RoomListState> {
private val encryptionService = client.encryptionService
@@ -154,7 +150,6 @@ class RoomListPresenter(
}
is RoomListEvent.ShowDeclineInviteMenu -> declineInviteMenu.value = RoomListState.DeclineInviteMenu.Shown(event.roomSummary)
RoomListEvent.HideDeclineInviteMenu -> declineInviteMenu.value = RoomListState.DeclineInviteMenu.Hidden
is RoomListEvent.ClearCacheOfRoom -> coroutineScope.clearCacheOfRoom(event.roomId)
}
}
@@ -165,13 +160,17 @@ class RoomListPresenter(
roomListDataSource.updateFilter(allFilters)
}
val canReportRoom by produceState(false) { value = client.canReportRoom() }
val showUnreadCount by produceState(false) {
value = featureFlagService.isFeatureEnabled(FeatureFlags.UnreadIndicatorCount)
}
val contentState = roomListContentState(
securityBannerDismissed,
showNewNotificationSoundBanner,
showUnreadCount,
)
val canReportRoom by produceState(false) { value = client.canReportRoom() }
return RoomListState(
contextMenu = contextMenu.value,
declineInviteMenu = declineInviteMenu.value,
@@ -226,6 +225,7 @@ class RoomListPresenter(
private fun roomListContentState(
securityBannerDismissed: Boolean,
showNewNotificationSoundBanner: Boolean,
showUnreadCount: Boolean,
): RoomListContentState {
val roomSummaries by produceState(initialValue = AsyncData.Loading()) {
roomListDataSource.roomSummariesFlow.collect { value = AsyncData.Success(it) }
@@ -254,6 +254,7 @@ class RoomListPresenter(
RoomListContentState.Rooms(
securityBannerState = securityBannerState,
showNewNotificationSoundBanner = showNewNotificationSoundBanner,
showUnreadCount = showUnreadCount,
fullScreenIntentPermissionsState = fullScreenIntentPermissionsPresenter.present(),
batteryOptimizationState = batteryOptimizationPresenter.present(),
summaries = roomSummaries.dataOrNull().orEmpty().toImmutableList(),
@@ -271,7 +272,6 @@ class RoomListPresenter(
isDm = event.roomSummary.isDm,
isFavorite = event.roomSummary.isFavorite,
hasNewContent = event.roomSummary.hasNewContent,
displayClearRoomCacheAction = appPreferencesStore.isDeveloperModeEnabledFlow().first(),
)
contextMenuState.value = initialState
@@ -304,19 +304,10 @@ class RoomListPresenter(
}
private fun CoroutineScope.markAsRead(roomId: RoomId) = launch {
notificationCleaner.clearMessagesForRoom(client.sessionId, roomId)
client.getRoom(roomId)?.use { room ->
room.setUnreadFlag(isUnread = false)
val receiptType = if (sessionPreferencesStore.isSendPublicReadReceiptsEnabled().first()) {
ReceiptType.READ
} else {
ReceiptType.READ_PRIVATE
markRoomAsRead(roomId)
.onSuccess {
analyticsService.captureInteraction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle)
}
room.markAsRead(receiptType)
.onSuccess {
analyticsService.captureInteraction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle)
}
}
}
private fun CoroutineScope.markAsUnread(roomId: RoomId) = launch {
@@ -327,10 +318,4 @@ class RoomListPresenter(
}
}
}
private fun CoroutineScope.clearCacheOfRoom(roomId: RoomId) = launch {
client.getRoom(roomId)?.use { room ->
room.clearEventCacheStorage()
}
}
}
@@ -44,7 +44,6 @@ data class RoomListState(
val isDm: Boolean,
val isFavorite: Boolean,
val hasNewContent: Boolean,
val displayClearRoomCacheAction: Boolean,
) : ContextMenu
}
@@ -72,6 +71,7 @@ sealed interface RoomListContentState {
val fullScreenIntentPermissionsState: FullScreenIntentPermissionsState,
val batteryOptimizationState: BatteryOptimizationState,
val showNewNotificationSoundBanner: Boolean,
val showUnreadCount: Boolean,
val summaries: ImmutableList<RoomListRoomSummary>,
val seenRoomInvites: ImmutableSet<RoomId>,
) : RoomListContentState
@@ -31,5 +31,4 @@ internal fun aContextMenuShown(
isDm = isDm,
hasNewContent = hasNewContent,
isFavorite = isFavorite,
displayClearRoomCacheAction = false,
)
@@ -33,6 +33,8 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.tokens.generated.CompoundIcons
@@ -112,10 +114,12 @@ private fun RoomListSearchContent(
title = {
// The stateSaver will keep the selection state when returning to this UI
val focusRequester = remember { FocusRequester() }
val searchLabel = stringResource(CommonStrings.action_search)
FilledTextField(
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
.focusRequester(focusRequester)
.semantics { contentDescription = searchLabel },
state = state.query,
lineLimits = TextFieldLineLimits.SingleLine,
colors = TextFieldDefaults.colors(
@@ -132,7 +136,7 @@ private fun RoomListSearchContent(
IconButton(onClick = { state.eventSink(RoomListSearchEvent.ClearQuery) }) {
Icon(
imageVector = CompoundIcons.Close(),
contentDescription = stringResource(CommonStrings.action_cancel)
contentDescription = stringResource(CommonStrings.a11y_clear_search_field)
)
}
}
@@ -24,7 +24,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetValue
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.rememberBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -57,8 +57,9 @@ fun SpaceFiltersView(
modifier: Modifier = Modifier
) {
val isSelecting by rememberUpdatedState(state is SpaceFiltersState.Selecting)
val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true,
val sheetState = rememberBottomSheetState(
initialValue = SheetValue.Hidden,
enabledValues = setOf(SheetValue.Hidden, SheetValue.Expanded),
confirmValueChange = { sheetValueTarget ->
// This ensures the hide animation is not cancelled
when (sheetValueTarget) {
@@ -187,6 +188,6 @@ private fun SpaceFilterItem(
@PreviewsDayNight
@Composable
internal fun SpaceFiltersViewPreview(@PreviewParameter(SpaceFiltersStateProvider::class) state: SpaceFiltersState) = ElementPreview {
internal fun SpaceFiltersViewPreview(@PreviewParameter(SpaceFiltersStateProvider::class) state: SpaceFiltersState) = ElementPreview(fillMaxSize = true) {
SpaceFiltersView(state = state)
}
@@ -4,8 +4,8 @@
<string name="banner_battery_optimization_submit_android">"Nonaktifkan optimasi"</string>
<string name="banner_battery_optimization_title_android">"Notifikasi tidak masuk?"</string>
<string name="banner_set_up_recovery_content">"Buat kunci pemulihan baru yang dapat digunakan untuk memulihkan riwayat pesan terenkripsi Anda jika Anda kehilangan akses ke perangkat Anda."</string>
<string name="banner_set_up_recovery_submit">"Siapkan pemulihan"</string>
<string name="banner_set_up_recovery_title">"Siapkan pemulihan"</string>
<string name="banner_set_up_recovery_submit">"Siapkan Kunci pemulihan"</string>
<string name="banner_set_up_recovery_title">"Cadangkan obrolan Anda"</string>
<string name="confirm_recovery_key_banner_message">"Konfirmasikan kunci pemulihan Anda untuk mempertahankan akses ke penyimpanan kunci dan riwayat pesan Anda."</string>
<string name="confirm_recovery_key_banner_primary_button_title">"Masukkan kunci pemulihan Anda"</string>
<string name="confirm_recovery_key_banner_secondary_button_title">"Lupa kunci pemulihan Anda?"</string>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="banner_battery_optimization_content_android">"すべての通知を確実に受信するために、このアプリのバッテリー最適化を無効にしてください。"</string>
<string name="banner_battery_optimization_submit_android">"最適化を無効にする"</string>
<string name="banner_battery_optimization_submit_android">"無効にする"</string>
<string name="banner_battery_optimization_title_android">"通知が届いていませんか?"</string>
<string name="banner_new_sound_message">"通知音が更新され、より明確で速く、そして邪魔にならなくなりました。"</string>
<string name="banner_new_sound_title">"サウンドを刷新しました"</string>
@@ -38,7 +38,7 @@
<string name="screen_roomlist_filter_low_priority_empty_state_title">"低い優先度のチャットはまだありません"</string>
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"フィルターを解除して他のチャットを表示できます"</string>
<string name="screen_roomlist_filter_mixed_empty_state_title">"この選択中にチャットがありません"</string>
<string name="screen_roomlist_filter_people">"人々"</string>
<string name="screen_roomlist_filter_people">"ユーザー"</string>
<string name="screen_roomlist_filter_people_empty_state_title">"まだダイレクトメッセージは届いていません"</string>
<string name="screen_roomlist_filter_rooms">"ルーム"</string>
<string name="screen_roomlist_filter_rooms_empty_state_title">"まだルームに参加していません"</string>
@@ -6,7 +6,7 @@
<string name="banner_new_sound_message">"O seu ping de notificação foi atualizado—mais suave, mais rápido, e menos disruptivo."</string>
<string name="banner_new_sound_title">"Recarregamos seus sons"</string>
<string name="banner_set_up_recovery_content">"Recupere sua identidade criptográfica e o histórico de mensagens com uma chave de recuperação caso você perda todos os dispositivos existentes."</string>
<string name="banner_set_up_recovery_submit">"Configurar a recuperação"</string>
<string name="banner_set_up_recovery_submit">"Receber chave de recuperação"</string>
<string name="banner_set_up_recovery_title">"Configure a recuperação para proteger sua conta"</string>
<string name="confirm_recovery_key_banner_message">"Confirme sua chave de recuperação para manter o acesso ao seu armazenamento de chaves e histórico de mensagens."</string>
<string name="confirm_recovery_key_banner_primary_button_title">"Digite sua chave de recuperação"</string>
@@ -21,7 +21,7 @@
<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>
<string name="screen_invites_invited_you">"%1$s (%2$s) пригласил(а) Вас"</string>
<string name="screen_migration_message">"Это единоразовая процедура, спасибо, что подождали."</string>
<string name="screen_migration_title">"Настройка Вашего аккаунта."</string>
<string name="screen_roomlist_a11y_create_message">"Создать новую беседу или комнату"</string>
@@ -31,20 +31,20 @@
<string name="screen_roomlist_filter_favourites">"Избранные"</string>
<string name="screen_roomlist_filter_favourites_empty_state_subtitle">"Добавить чат в избранное можно в настройках чата.
На данный момент вы можете убрать фильтры, чтобы увидеть другие ваши чаты."</string>
<string name="screen_roomlist_filter_favourites_empty_state_title">"У вас пока нет избранных чатов"</string>
<string name="screen_roomlist_filter_favourites_empty_state_title">"У Вас пока нет избранных чатов"</string>
<string name="screen_roomlist_filter_invites">"Приглашения"</string>
<string name="screen_roomlist_filter_invites_empty_state_title">"У вас нет приглашений."</string>
<string name="screen_roomlist_filter_invites_empty_state_title">"У Вас нет приглашений."</string>
<string name="screen_roomlist_filter_low_priority">"Низкий приоритет"</string>
<string name="screen_roomlist_filter_low_priority_empty_state_title">"У вас пока нет чатов с низким приоритетом."</string>
<string name="screen_roomlist_filter_low_priority_empty_state_title">"У Вас пока нет чатов с низким приоритетом."</string>
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Вы можете убрать фильтры, чтобы увидеть другие ваши чаты."</string>
<string name="screen_roomlist_filter_mixed_empty_state_title">"У вас нет чатов, соответствующих фильтрам"</string>
<string name="screen_roomlist_filter_mixed_empty_state_title">"У Вас нет чатов, соответствующих фильтрам"</string>
<string name="screen_roomlist_filter_people">"Пользователи"</string>
<string name="screen_roomlist_filter_people_empty_state_title">"У вас пока нет личных сообщений"</string>
<string name="screen_roomlist_filter_people_empty_state_title">"У Вас пока нет личных сообщений"</string>
<string name="screen_roomlist_filter_rooms">"Комнаты"</string>
<string name="screen_roomlist_filter_rooms_empty_state_title">"Вас пока нет ни в одной комнате"</string>
<string name="screen_roomlist_filter_unreads">"Новые"</string>
<string name="screen_roomlist_filter_unreads_empty_state_title">"Поздравляем!
У вас нет непрочитанных сообщений!"</string>
У Вас нет непрочитанных сообщений!"</string>
<string name="screen_roomlist_knock_event_sent_description">"Запрос на присоединение отправлен"</string>
<string name="screen_roomlist_main_space_title">"Чаты"</string>
<string name="screen_roomlist_mark_as_read">"Пометить как прочитанное"</string>
@@ -6,7 +6,7 @@
<string name="banner_new_sound_message">"Vaše oznámenia boli aktualizované sú prehľadnejšie, rýchlejšie a menej rušivé."</string>
<string name="banner_new_sound_title">"Obnovili sme vaše zvuky"</string>
<string name="banner_set_up_recovery_content">"Vytvorte nový kľúč na obnovenie, ktorý môžete použiť na obnovenie vašej histórie šifrovaných správ v prípade straty prístupu k vašim zariadeniam."</string>
<string name="banner_set_up_recovery_submit">"Nastaviť obnovenie"</string>
<string name="banner_set_up_recovery_submit">"Získať kľúč na obnovenie"</string>
<string name="banner_set_up_recovery_title">"Nastaviť obnovenie"</string>
<string name="confirm_recovery_key_banner_message">"Potvrďte svoj kľúč na obnovenie, aby ste zachovali prístup k úložisku kľúčov a histórii správ."</string>
<string name="confirm_recovery_key_banner_primary_button_title">"Zadajte kľúč na obnovenie"</string>
@@ -5,15 +5,15 @@
<string name="banner_battery_optimization_title_android">"Не надходять сповіщення?"</string>
<string name="banner_new_sound_message">"Ваш сигнал сповіщень оновлено — він став чіткішим, швидшим і не таким надокучливим."</string>
<string name="banner_new_sound_title">"Ми оновили ваші звуки"</string>
<string name="banner_set_up_recovery_content">"Відновіть свою криптографічну ідентичність та історію повідомлень за допомогою ключа відновлення, якщо ви втратили всі наявні пристрої."</string>
<string name="banner_set_up_recovery_submit">"Налаштувати відновлення"</string>
<string name="banner_set_up_recovery_title">"Налаштуйте відновлення для захисту свого облікового запису"</string>
<string name="banner_set_up_recovery_content">"Ваші бесіди автоматично резервуються з використанням наскрізного шифрування. Щоб відновити цю резервну копію та зберегти свою цифрову ідентичність у разі втрати доступу до всіх своїх пристроїв, вам знадобиться ключ відновлення."</string>
<string name="banner_set_up_recovery_submit">"Отримати ключ відновлення"</string>
<string name="banner_set_up_recovery_title">"Створити резервну копію своїх бесід"</string>
<string name="confirm_recovery_key_banner_message">"Підтвердіть свій ключ відновлення, щоб мати доступ до сховища ключів та історії повідомлень."</string>
<string name="confirm_recovery_key_banner_primary_button_title">"Введіть ключ відновлення"</string>
<string name="confirm_recovery_key_banner_secondary_button_title">"Забули ключ відновлення?"</string>
<string name="confirm_recovery_key_banner_title">"Ваше сховище ключів не синхронізовано"</string>
<string name="full_screen_intent_banner_message">"Щоб ніколи не пропустити важливий виклик, змініть налаштування, щоб увімкнути повноекранні сповіщення, коли телефон заблоковано."</string>
<string name="full_screen_intent_banner_title">"Покращуйте досвід дзвінків"</string>
<string name="full_screen_intent_banner_title">"Покращуйте якість викликів"</string>
<string name="screen_home_tab_chats">"Бесіди"</string>
<string name="screen_home_tab_spaces">"Простори"</string>
<string name="screen_invites_decline_chat_message">"Ви впевнені, що хочете відхилити запрошення приєднатися до %1$s?"</string>
@@ -51,6 +51,6 @@
<string name="screen_roomlist_mark_as_unread">"Позначити непрочитаним"</string>
<string name="screen_roomlist_tombstoned_room_description">"Цю кімнату оновлено"</string>
<string name="screen_roomlist_your_spaces">"Ваші простори"</string>
<string name="session_verification_banner_message">"Схоже, ви використовуєте новий пристрій. Щоб отримати доступ до зашифрованих повідомлень, підтвердьте особу за допомогою іншого пристрою."</string>
<string name="session_verification_banner_message">"Здається, ви використовуєте новий пристрій. Звірте його за допомогою іншого пристрою, щоб отримати доступ до своїх зашифрованих повідомлень."</string>
<string name="session_verification_banner_title">"Підтвердьте, що це ви"</string>
</resources>
@@ -4,7 +4,7 @@
<string name="banner_battery_optimization_submit_android">"禁用优化"</string>
<string name="banner_battery_optimization_title_android">"通知未送达?"</string>
<string name="banner_new_sound_message">"通知提示音已升级:更清晰、更快速、干扰更少。"</string>
<string name="banner_new_sound_title">"我们已更新你的声音"</string>
<string name="banner_new_sound_title">"我们已更新提示音"</string>
<string name="banner_set_up_recovery_content">"你的聊天已被端到端加密自动备份。如果你无法访问所有设备,则需要使用恢复密钥并保留数字身份。"</string>
<string name="banner_set_up_recovery_submit">"获取恢复密钥"</string>
<string name="banner_set_up_recovery_title">"备份聊天"</string>
@@ -8,7 +8,6 @@
package io.element.android.features.home.impl
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.bumble.appyx.core.modality.BuildContext
import com.google.common.truth.Truth.assertThat
import io.element.android.features.home.api.HomeEntryPoint
@@ -18,12 +17,11 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.node.TestParentNode
import io.element.android.tests.testutils.robolectric.RobolectricTest
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class DefaultHomeEntryPointTest {
class DefaultHomeEntryPointTest : RobolectricTest() {
@Test
fun `test node builder`() = runTest {
val entryPoint = DefaultHomeEntryPoint()
@@ -13,18 +13,16 @@ package io.element.android.features.home.impl.filters
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.home.impl.R
import io.element.android.features.home.impl.filters.selection.FilterSelectionState
import io.element.android.libraries.testtags.TestTags
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.pressTag
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class RoomListFiltersViewTest {
class RoomListFiltersViewTest : RobolectricTest() {
@Test
fun `clicking on filters generates expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<RoomListFiltersEvent>()
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.home.impl.roomlist
import io.element.android.features.preferences.impl.tasks.MarkRoomAsRead
import io.element.android.libraries.matrix.api.core.RoomId
class FakeMarkRoomAsRead(
private val invokeLambda: suspend (RoomId) -> Result<Unit> = { Result.success(Unit) },
) : MarkRoomAsRead {
val invokedRoomIds = mutableListOf<RoomId>()
override suspend fun invoke(roomId: RoomId): Result<Unit> {
invokedRoomIds.add(roomId)
return invokeLambda(roomId)
}
}
@@ -14,7 +14,6 @@ import androidx.activity.ComponentActivity
import androidx.compose.ui.test.AndroidComposeUiTest
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.home.impl.R
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.ui.strings.CommonStrings
@@ -22,12 +21,11 @@ import io.element.android.tests.testutils.EnsureCalledOnceWithParam
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.robolectric.RobolectricTest
import io.element.android.tests.testutils.setSafeContent
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class RoomListContextMenuTest {
class RoomListContextMenuTest : RobolectricTest() {
@Test
fun `clicking on Mark as read generates expected Events`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<RoomListEvent>()
@@ -36,7 +34,7 @@ class RoomListContextMenuTest {
contextMenu = contextMenu,
eventSink = eventsRecorder,
)
clickOn(R.string.screen_roomlist_mark_as_read)
clickOn(CommonStrings.action_mark_as_read)
eventsRecorder.assertList(
listOf(
RoomListEvent.HideContextMenu,
@@ -12,19 +12,17 @@ package io.element.android.features.home.impl.roomlist
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.home.impl.model.aRoomListRoomSummary
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureCalledOnceWithParam
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.robolectric.RobolectricTest
import io.element.android.tests.testutils.setSafeContent
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class RoomListDeclineInviteMenuTest {
class RoomListDeclineInviteMenuTest : RobolectricTest() {
@Test
fun `clicking on decline emits the expected Events`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<RoomListEvent>()
@@ -30,12 +30,15 @@ import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInvit
import io.element.android.features.invite.test.InMemorySeenInvitesStore
import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.preferences.impl.tasks.MarkRoomAsRead
import io.element.android.features.rageshake.test.logs.FakeAnnouncementService
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.dateformatter.api.DateFormatter
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter
import io.element.android.libraries.eventformatter.test.FakeRoomLatestEventFormatter
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
@@ -61,9 +64,7 @@ import io.element.android.libraries.matrix.test.roomlist.FakeDynamicRoomList
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.libraries.matrix.test.sync.FakeSyncService
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
import io.element.android.libraries.push.api.battery.aBatteryOptimizationState
import io.element.android.libraries.push.api.notifications.NotificationCleaner
@@ -228,7 +229,6 @@ class RoomListPresenterTest {
isDm = false,
isFavorite = false,
hasNewContent = false,
displayClearRoomCacheAction = false,
)
)
}
@@ -245,35 +245,6 @@ class RoomListPresenterTest {
isDm = false,
isFavorite = true,
hasNewContent = false,
displayClearRoomCacheAction = false,
)
)
}
}
}
@Test
fun `present - show context menu with view source on`() = runTest {
val presenter = createRoomListPresenter(
appPreferencesStore = InMemoryAppPreferencesStore(
isDeveloperModeEnabled = true,
)
)
presenter.test {
val initialState = awaitItem()
val summary = createRoomListRoomSummary()
initialState.eventSink(RoomListEvent.ShowContextMenu(summary))
awaitItem().also { state ->
assertThat(state.contextMenu)
.isEqualTo(
RoomListState.ContextMenu.Shown(
roomId = summary.roomId,
roomName = summary.name,
isDm = false,
isFavorite = false,
// true here.
hasNewContent = false,
displayClearRoomCacheAction = true,
)
)
}
@@ -301,7 +272,6 @@ class RoomListPresenterTest {
isDm = false,
isFavorite = false,
hasNewContent = false,
displayClearRoomCacheAction = false,
)
)
@@ -458,11 +428,17 @@ class RoomListPresenterTest {
val notificationCleaner = FakeNotificationCleaner(
clearMessagesForRoomLambda = clearMessagesForRoomLambda,
)
val markRoomAsRead = createTestMarkRoomAsRead(
client = matrixClient,
notificationCleaner = notificationCleaner,
sessionPreferencesStore = sessionPreferencesStore,
)
val presenter = createRoomListPresenter(
client = matrixClient,
sessionPreferencesStore = sessionPreferencesStore,
analyticsService = analyticsService,
notificationCleaner = notificationCleaner,
markRoomAsRead = markRoomAsRead,
)
presenter.test {
val initialState = awaitItem()
@@ -654,6 +630,24 @@ class RoomListPresenterTest {
}
}
private fun createTestMarkRoomAsRead(
client: MatrixClient,
notificationCleaner: NotificationCleaner,
sessionPreferencesStore: SessionPreferencesStore,
): MarkRoomAsRead = FakeMarkRoomAsRead { roomId ->
notificationCleaner.clearMessagesForRoom(client.sessionId, roomId)
val room = client.getRoom(roomId) ?: return@FakeMarkRoomAsRead Result.failure(IllegalStateException("Room not found"))
room.use {
it.setUnreadFlag(isUnread = false)
val receiptType = if (sessionPreferencesStore.isSendPublicReadReceiptsEnabled().first()) {
ReceiptType.READ
} else {
ReceiptType.READ_PRIVATE
}
it.markAsRead(receiptType)
}
}
private fun TestScope.createRoomListPresenter(
client: MatrixClient = FakeMatrixClient(),
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
@@ -666,9 +660,10 @@ class RoomListPresenterTest {
spaceFiltersPresenter: Presenter<SpaceFiltersState> = Presenter { aDisabledSpaceFiltersState() },
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() },
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(),
announcementService: AnnouncementService = FakeAnnouncementService(),
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
markRoomAsRead: MarkRoomAsRead? = null,
) = RoomListPresenter(
client = client,
leaveRoomPresenter = { leaveRoomState },
@@ -685,17 +680,20 @@ class RoomListPresenterTest {
analyticsService = FakeAnalyticsService(),
),
searchPresenter = searchPresenter,
sessionPreferencesStore = sessionPreferencesStore,
filtersPresenter = filtersPresenter,
spaceFiltersPresenter = spaceFiltersPresenter,
analyticsService = analyticsService,
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
fullScreenIntentPermissionsPresenter = { aFullScreenIntentPermissionsState() },
batteryOptimizationPresenter = { aBatteryOptimizationState() },
notificationCleaner = notificationCleaner,
appPreferencesStore = appPreferencesStore,
markRoomAsRead = markRoomAsRead ?: createTestMarkRoomAsRead(
client = client,
notificationCleaner = notificationCleaner,
sessionPreferencesStore = sessionPreferencesStore,
),
seenInvitesStore = seenInvitesStore,
announcementService = announcementService,
coldStartWatcher = FakeAnalyticsColdStartWatcher(),
featureFlagService = featureFlagService,
)
}
@@ -19,7 +19,6 @@ import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.home.impl.HomeView
import io.element.android.features.home.impl.R
import io.element.android.features.home.impl.aHomeState
@@ -34,13 +33,12 @@ import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.robolectric.RobolectricTest
import io.element.android.tests.testutils.setSafeContent
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
class RoomListViewTest {
class RoomListViewTest : RobolectricTest() {
@Config(qualifiers = "h1024dp")
@Test
fun `displaying the view automatically sends a couple of UpdateVisibleRangeEvents`() = runAndroidComposeUiTest {
@@ -15,14 +15,12 @@ import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SpaceFiltersViewTest {
class SpaceFiltersViewTest : RobolectricTest() {
@Test
fun `clicking on a filter with alias shows display name and alias`() = runAndroidComposeUiTest {
val filter = aSpaceServiceFilter(
@@ -10,7 +10,7 @@
<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>
<string name="screen_invites_invited_you">"%1$s (%2$s) пригласил(а) Вас"</string>
<string name="screen_join_room_decline_and_block_alert_confirmation">"Да, отклонить и заблокировать"</string>
<string name="screen_join_room_decline_and_block_alert_message">"Вы действительно хотите отклонить приглашение в комнату? Также %1$s больше не сможет связаться с вами или приглашать в комнаты."</string>
<string name="screen_join_room_decline_and_block_alert_title">"Отклонить приглашение и заблокировать"</string>
@@ -16,7 +16,6 @@ import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.invite.impl.R
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
@@ -24,11 +23,10 @@ import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class DeclineAndBlockViewTest {
class DeclineAndBlockViewTest : RobolectricTest() {
@Test
fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>(expectEvents = false)
@@ -2,4 +2,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_invite_users_already_a_member">"이미 회원"</string>
<string name="screen_invite_users_already_invited">"이미 초대됨"</string>
<string name="screen_invite_users_confirm_dialog_subtitle_multiple_users">"이 사용자들과의 대화 내역이 없습니다. 계속하려면 먼저 이 방으로의 초대를 확인해 주세요."</string>
<string name="screen_invite_users_confirm_dialog_subtitle_one_user">"이 사용자와의 대화 내역이 없습니다. 계속하려면 먼저 이 방으로의 초대를 확인해 주세요."</string>
<string name="screen_invite_users_confirm_dialog_title_mutiple_users">"이 방에 새 사용자를 초대할까요?"</string>
<string name="screen_invite_users_confirm_dialog_title_one_user">"이 방에 새 사용자를 초대할까요?"</string>
</resources>
@@ -3,7 +3,7 @@
<string name="screen_invite_users_already_a_member">"Уже участник"</string>
<string name="screen_invite_users_already_invited">"Уже приглашен(а)"</string>
<string name="screen_invite_users_confirm_dialog_subtitle_multiple_users">"У тебя пока нет чатов с этими контактами. Подтверди приглашение в эту комнату, прежде чем продолжить."</string>
<string name="screen_invite_users_confirm_dialog_subtitle_one_user">"У вас пока нет чатов с этим контактом. Подтверди приглашение в эту комнату, прежде чем продолжить."</string>
<string name="screen_invite_users_confirm_dialog_subtitle_one_user">"У Вас пока нет чатов с этим контактом. Подтвердите, что это действительно тот контакт, которого Вы хотите пригласить, прежде чем продолжить."</string>
<string name="screen_invite_users_confirm_dialog_title_mutiple_users">"Пригласить новых участников в эту комнату?"</string>
<string name="screen_invite_users_confirm_dialog_title_one_user">"Пригласить нового участника в эту комнату?"</string>
</resources>
@@ -2,8 +2,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_invite_users_already_a_member">"Уже учасник"</string>
<string name="screen_invite_users_already_invited">"Уже запрошені"</string>
<string name="screen_invite_users_confirm_dialog_subtitle_multiple_users">"Наразі у вас немає чатів із цими контактами. Підтвердьте запрошення їх до цієї кімнати, перш ніж продовжувати."</string>
<string name="screen_invite_users_confirm_dialog_subtitle_one_user">"Наразі у вас немає чатів із цим контактом. Підтвердьте запрошення до цієї кімнати, перш ніж продовжувати."</string>
<string name="screen_invite_users_confirm_dialog_subtitle_multiple_users">"Наразі у вас немає бесід з цими контактами. Підтвердьте запрошення їх до цієї кімнати, перш ніж продовжувати."</string>
<string name="screen_invite_users_confirm_dialog_subtitle_one_user">"Наразі у вас немає бесід із цим контактом. Підтвердьте запрошення до цієї кімнати, перш ніж продовжувати."</string>
<string name="screen_invite_users_confirm_dialog_title_mutiple_users">"Запросити нових контактів до цієї кімнати?"</string>
<string name="screen_invite_users_confirm_dialog_title_one_user">"Запросити нового контакта до цієї кімнати?"</string>
</resources>
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_join_room_cancel_knock_alert_confirmation">"Так, адмяніць"</string>
<string name="screen_join_room_decline_and_block_button_title">"Адхіліць і заблакіраваць"</string>
<string name="screen_join_room_join_action">"Далучыцца"</string>
<string name="screen_join_room_knock_action">"Націсніце, каб далучыцца"</string>
@@ -15,6 +15,10 @@
<string name="screen_join_room_fail_reason">"Ruangan ini hanya untuk undangan atau mungkin ada pembatasan akses pada tingkat space."</string>
<string name="screen_join_room_forget_action">"Lupa"</string>
<string name="screen_join_room_invite_required_message">"Anda memerlukan undangan untuk bergabung dalam ruangan ini"</string>
<string name="screen_join_room_invited_by">
"Diundang oleh "
<userName/>
</string>
<string name="screen_join_room_join_action">"Gabung"</string>
<string name="screen_join_room_join_restricted_message">"Anda mungkin perlu diundang atau menjadi anggota space untuk bergabung."</string>
<string name="screen_join_room_knock_action">"Ketuk untuk bergabung"</string>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_join_room_ban_by_message">"%1$s заблокировал(а) вас в комнате."</string>
<string name="screen_join_room_ban_by_message">"%1$s заблокировал(а) Вас в комнате."</string>
<string name="screen_join_room_ban_message">"Вы были заблокированы в комнате"</string>
<string name="screen_join_room_ban_reason">"Причина: %1$s."</string>
<string name="screen_join_room_cancel_knock_action">"Отменить запрос"</string>
@@ -27,7 +27,7 @@
<string name="screen_join_room_loading_alert_title">"Не удалось показать предпросмотр комнаты"</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_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>
@@ -14,7 +14,6 @@ import androidx.activity.ComponentActivity
import androidx.compose.ui.test.AndroidComposeUiTest
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.invite.api.InviteData
import io.element.android.features.invite.test.anInviteData
import io.element.android.libraries.architecture.AsyncAction
@@ -29,11 +28,10 @@ import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class JoinRoomViewTest {
class JoinRoomViewTest : RobolectricTest() {
@Test
fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_decline_alert_title">"Адхіліць доступ"</string>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Паглядзець усе"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Прыняць"</string>
</resources>
@@ -4,10 +4,10 @@
<string name="screen_knock_requests_list_accept_all_alert_description">"Вы действительно хотите принять все заявки на присоединение?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Принять все запросы"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Принять все"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Мы не смогли принять все запросы. Попробовать еще раз?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Мы не смогли принять все запросы. Попробовать ещё раз?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Не удалось принять все запросы"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Принять все заявки на присоединение"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Мы не смогли принять этот запрос. Хотите попробовать еще раз?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Мы не смогли принять этот запрос. Хотите попробовать ещё раз?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Не удалось принять запрос"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Принятие заявки на присоединение"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Да, отклонить и запретить"</string>
@@ -18,7 +18,7 @@
<string name="screen_knock_requests_list_decline_alert_description">"Вы уверены, что хотите отклонить запрос %1$s на присоединение к этой комнате?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Отклонить доступ"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Отклонить и запретить"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Мы не смогли отклонить этот запрос. Попробовать еще раз?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Мы не смогли отклонить этот запрос. Попробовать ещё раз?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Не удалось отклонить запрос"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Отклонение заявки на присоединение"</string>
<string name="screen_knock_requests_list_empty_state_description">"Вы сможете увидеть запрос, когда кто-то попросит присоединиться к комнате."</string>
@@ -16,7 +16,6 @@ import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.knockrequests.impl.R
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
import io.element.android.libraries.ui.strings.CommonStrings
@@ -24,11 +23,10 @@ import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class KnockRequestsBannerViewTest {
class KnockRequestsBannerViewTest : RobolectricTest() {
@Test
fun `clicking on view on single request invoke the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>(expectEvents = false)
@@ -14,7 +14,6 @@ import androidx.activity.ComponentActivity
import androidx.compose.ui.test.AndroidComposeUiTest
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.knockrequests.impl.R
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
import io.element.android.libraries.architecture.AsyncAction
@@ -25,12 +24,11 @@ import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.robolectric.RobolectricTest
import kotlinx.collections.immutable.persistentListOf
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class KnockRequestsListViewTest {
class KnockRequestsListViewTest : RobolectricTest() {
@Test
fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>(expectEvents = false)
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="leave_conversation_alert_subtitle">"Czy na pewno chcesz opuścić tę konwersację? Konwersacja nie jest publiczna i nie będziesz mógł dołączyć ponownie bez zaproszenia."</string>
<string name="leave_room_alert_empty_subtitle">"Jesteś pewien, że chcesz opuścić ten pokój? Jesteś tu jedyną osobą. Jeśli wyjdziesz, nikt nie będzie mógł dołączyć, w tym Ty."</string>
<string name="leave_room_alert_empty_subtitle">"Czy na pewno chcesz opuścić ten pokój? Jesteś tu jedyną osobą. Jeśli wyjdziesz, nikt nie będzie mógł dołączyć, w tym Ty."</string>
<string name="leave_room_alert_private_subtitle">"Czy na pewno chcesz opuścić ten pokój? Ten pokój nie jest publiczny i nie będziesz mógł do niego wrócić bez zaproszenia."</string>
<string name="leave_room_alert_select_new_owner_action">"Wybierz właścicieli"</string>
<string name="leave_room_alert_select_new_owner_subtitle">"Jesteś jedynym właścicielem tego pokoju. Musisz przenieść własność na kogoś innego, zanim go opuścisz."</string>
<string name="leave_room_alert_select_new_owner_title">"Przenieś własność"</string>
<string name="leave_room_alert_subtitle">"Jesteś pewien, że chcesz wyjść z tego pokoju?"</string>
<string name="leave_room_alert_subtitle">"Czy na pewno chcesz wyjść z tego pokoju?"</string>
</resources>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="leave_conversation_alert_subtitle">"Вы уверены, что хотите покинуть беседу? Эта беседа не является общедоступной, и вы не сможете присоединиться к ней без приглашения."</string>
<string name="leave_room_alert_empty_subtitle">"Вы уверены, что хотите покинуть эту комнату? Здесь есть только вы. Если вы покинете комнату, никто не сможет присоединиться к ней в будущем, включая вас."</string>
<string name="leave_conversation_alert_subtitle">"Вы уверены, что хотите покинуть беседу? Эта беседа не является общедоступной, и Вы не сможете снова присоединиться к ней без приглашения."</string>
<string name="leave_room_alert_empty_subtitle">"Вы уверены, что хотите покинуть эту комнату? Здесь есть только Вы. Если Вы покинете комнату, никто не сможет присоединиться к ней в будущем, включая Вас."</string>
<string name="leave_room_alert_private_subtitle">"Вы уверены, что хотите покинуть эту комнату? Эта комната не является общедоступной, и Вы не сможете присоединиться к ней без приглашения."</string>
<string name="leave_room_alert_select_new_owner_action">"Назначить владельцев"</string>
<string name="leave_room_alert_select_new_owner_subtitle">"Вы единственный владелец этой комнаты. Перед тем, как её покинуть, необходимо передать владение кому-нибудь другому."</string>
@@ -30,6 +30,7 @@ dependencies {
// TODO Cleanup
implementation(projects.appconfig)
implementation(projects.features.enterprise.api)
implementation(projects.features.lockscreen.api)
implementation(projects.features.rageshake.api)
implementation(projects.libraries.core)
implementation(projects.libraries.androidutils)
@@ -53,6 +54,7 @@ dependencies {
testCommonDependencies(libs, true)
testImplementation(projects.features.linknewdevice.test)
testImplementation(projects.features.lockscreen.test)
testImplementation(projects.features.enterprise.test)
testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.matrix.test)
@@ -13,8 +13,10 @@ import androidx.activity.compose.LocalActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.composable.PermanentChild
import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.navigation.model.permanent.PermanentNavModel
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.navmodel.backstack.BackStack
@@ -34,8 +36,10 @@ import io.element.android.features.linknewdevice.impl.screens.error.ErrorNode
import io.element.android.features.linknewdevice.impl.screens.error.ErrorScreenType
import io.element.android.features.linknewdevice.impl.screens.number.EnterNumberNode
import io.element.android.features.linknewdevice.impl.screens.qrcode.ShowQrCodeNode
import io.element.android.features.linknewdevice.impl.screens.root.LinkDeviceType
import io.element.android.features.linknewdevice.impl.screens.root.LinkNewDeviceRootNode
import io.element.android.features.linknewdevice.impl.screens.scan.ScanQrCodeNode
import io.element.android.features.lockscreen.api.DeviceUnlockEntryPoint
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode
@@ -67,11 +71,16 @@ class LinkNewDeviceFlowNode(
private val linkNewMobileHandler: LinkNewMobileHandler,
private val linkNewDesktopHandler: LinkNewDesktopHandler,
private val sessionEnterpriseService: SessionEnterpriseService,
private val deviceUnlockEntryPoint: DeviceUnlockEntryPoint,
) : BaseFlowNode<LinkNewDeviceFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.Root,
savedStateMap = buildContext.savedStateMap,
),
permanentNavModel = PermanentNavModel(
navTargets = setOf(NavTarget.LockScreen),
savedStateMap = buildContext.savedStateMap,
),
buildContext = buildContext,
plugins = plugins,
) {
@@ -124,6 +133,9 @@ class LinkNewDeviceFlowNode(
@Parcelize
data object DesktopScanQrCode : NavTarget
@Parcelize
data object LockScreen : NavTarget
@Parcelize
data class Error(
val errorScreenType: ErrorScreenType,
@@ -137,6 +149,9 @@ class LinkNewDeviceFlowNode(
Timber.tag(tag.value).d("step: ${linkMobileStep::class.java.simpleName}")
when (linkMobileStep) {
LinkMobileStep.Uninitialized -> Unit
LinkMobileStep.CreatingQrCode -> {
// This step is handled in LinkNewDeviceRootPresenter
}
LinkMobileStep.Done -> {
callback.onDone()
}
@@ -224,13 +239,28 @@ class LinkNewDeviceFlowNode(
callback.onDone()
}
override fun linkDesktopDevice() {
linkNewDesktopHandler.reset()
backstack.push(NavTarget.DesktopNotice)
override fun onUnlockDevice(type: LinkDeviceType) {
val callback = object : DeviceUnlockEntryPoint.Callback {
override fun onCancel() = Unit
override fun onUnlock() = when (type) {
LinkDeviceType.Mobile -> {
linkNewMobileHandler.reset()
linkNewMobileHandler.createAndStartNewHandler()
}
LinkDeviceType.Desktop -> {
linkNewDesktopHandler.reset()
backstack.push(NavTarget.DesktopNotice)
}
}
}
deviceUnlockEntryPoint.requestUnlock(callback)
}
}
createNode<LinkNewDeviceRootNode>(buildContext, listOf(callback))
}
is NavTarget.LockScreen -> {
deviceUnlockEntryPoint.createNode(this, buildContext)
}
NavTarget.DesktopNotice -> {
val callback = object : DesktopNoticeNode.Callback {
override fun navigateBack() {
@@ -324,5 +354,6 @@ class LinkNewDeviceFlowNode(
}
}
BackstackView()
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LockScreen)
}
}
@@ -43,12 +43,15 @@ class LinkNewMobileHandler(
val stepFlow: StateFlow<LinkMobileStep>
get() = linkMobileStepFlow.asStateFlow()
fun createAndStartNewHandler() {
fun createAndStartNewHandler(forRotating: Boolean = false) {
Timber.tag(loggerTag.value).d("createAndStartNewHandler()")
currentJob?.cancel()
handler = matrixClient.createLinkMobileHandler().getOrNull()
handler?.let { h ->
currentJob = sessionScope.launch {
if (!forRotating) {
linkMobileStepFlow.emit(LinkMobileStep.CreatingQrCode)
}
h.linkMobileStep
.onEach {
linkMobileStepFlow.emit(it)
@@ -68,7 +71,7 @@ class LinkNewMobileHandler(
}
fun rotateQrCode() {
createAndStartNewHandler()
createAndStartNewHandler(forRotating = true)
}
fun onTooManyRotation() {
@@ -73,10 +73,10 @@ fun ErrorView(
@Composable
private fun titleText(errorScreenType: ErrorScreenType, appName: String) = when (errorScreenType) {
ErrorScreenType.Cancelled -> stringResource(R.string.screen_qr_code_login_error_cancelled_title)
ErrorScreenType.Cancelled -> stringResource(R.string.screen_link_new_device_error_request_cancelled_title)
ErrorScreenType.Declined -> stringResource(R.string.screen_qr_code_login_error_declined_title)
ErrorScreenType.Expired -> stringResource(R.string.screen_qr_code_login_error_expired_title)
ErrorScreenType.ProtocolNotSupported -> stringResource(R.string.screen_qr_code_login_error_linking_not_suported_title)
ErrorScreenType.Expired -> stringResource(R.string.screen_link_new_device_error_request_timeout_title)
ErrorScreenType.ProtocolNotSupported -> stringResource(R.string.screen_link_new_device_error_not_supported_title)
ErrorScreenType.InsecureChannelDetected -> stringResource(id = R.string.screen_qr_code_login_connection_note_secure_state_title)
ErrorScreenType.Mismatch2Digits -> stringResource(id = R.string.screen_link_new_device_wrong_number_title)
ErrorScreenType.SlidingSyncNotAvailable -> stringResource(id = R.string.screen_qr_code_login_error_sliding_sync_not_supported_title, appName)
@@ -86,10 +86,10 @@ private fun titleText(errorScreenType: ErrorScreenType, appName: String) = when
@Composable
private fun subtitleText(errorScreenType: ErrorScreenType, appName: String) = when (errorScreenType) {
ErrorScreenType.Cancelled -> stringResource(R.string.screen_qr_code_login_error_cancelled_subtitle)
ErrorScreenType.Cancelled -> stringResource(R.string.screen_link_new_device_error_request_cancelled_subtitle)
ErrorScreenType.Declined -> stringResource(R.string.screen_qr_code_login_error_declined_subtitle)
ErrorScreenType.Expired -> stringResource(R.string.screen_qr_code_login_error_expired_subtitle)
ErrorScreenType.ProtocolNotSupported -> stringResource(R.string.screen_qr_code_login_error_linking_not_suported_subtitle, appName)
ErrorScreenType.Expired -> stringResource(R.string.screen_link_new_device_error_request_timeout_subtitle)
ErrorScreenType.ProtocolNotSupported -> stringResource(R.string.screen_link_new_device_error_not_supported_subtitle)
ErrorScreenType.Mismatch2Digits -> stringResource(id = R.string.screen_link_new_device_wrong_number_subtitle)
ErrorScreenType.InsecureChannelDetected -> stringResource(id = R.string.screen_qr_code_login_connection_note_secure_state_description)
ErrorScreenType.SlidingSyncNotAvailable -> stringResource(id = R.string.screen_qr_code_login_error_sliding_sync_not_supported_subtitle, appName)
@@ -5,8 +5,9 @@
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.certificates
package io.element.android.features.linknewdevice.impl.screens.root
interface UserCertificatesProvider {
fun provides(): List<ByteArray>
enum class LinkDeviceType {
Mobile,
Desktop,
}

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