Merge branch 'develop' into fix/thread-list-avatar
This commit is contained in:
@@ -44,19 +44,19 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
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
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Assemble debug Gplay Enterprise APK
|
||||
|
||||
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
|
||||
@@ -18,12 +18,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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Set up Python 3.12
|
||||
|
||||
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
- uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
name: Use JDK 21
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||
with:
|
||||
|
||||
@@ -38,19 +38,19 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.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@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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
|
||||
|
||||
@@ -32,11 +32,11 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
|
||||
@@ -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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Dependency analysis
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run shellcheck
|
||||
@@ -333,7 +333,7 @@ jobs:
|
||||
permissions:
|
||||
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
|
||||
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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
|
||||
|
||||
@@ -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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.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@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Record screenshots
|
||||
|
||||
@@ -34,16 +34,16 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
- name: Create APKs
|
||||
env:
|
||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||
|
||||
@@ -38,19 +38,19 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
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
|
||||
|
||||
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Set up Python 3.12
|
||||
|
||||
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python 3.12
|
||||
|
||||
@@ -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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
|
||||
|
||||
+30
@@ -1,3 +1,33 @@
|
||||
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
|
||||
=============================
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import extension.buildConfigFieldStr
|
||||
import extension.locales
|
||||
import extension.setupDependencyInjection
|
||||
import extension.testCommonDependencies
|
||||
import org.sonarqube.gradle.SonarResolverTask
|
||||
import java.util.Locale
|
||||
|
||||
plugins {
|
||||
@@ -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()
|
||||
|
||||
+1
-1
Submodule enterprise updated: 50aa7a40ff...efdf9d9a15
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: bug fixes and improvements.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
+2
-4
@@ -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
-3
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+2
-4
@@ -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()
|
||||
|
||||
+2
-4
@@ -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(
|
||||
|
||||
+2
-4
@@ -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>()
|
||||
|
||||
+2
-4
@@ -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)
|
||||
|
||||
+2
-4
@@ -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()
|
||||
|
||||
|
||||
+2
-4
@@ -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)
|
||||
|
||||
+2
-4
@@ -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
-4
@@ -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 {
|
||||
|
||||
-16
@@ -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(
|
||||
@@ -198,17 +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())
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -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
|
||||
}
|
||||
|
||||
-11
@@ -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
|
||||
@@ -54,7 +53,6 @@ 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
|
||||
@@ -87,7 +85,6 @@ class RoomListPresenter(
|
||||
private val fullScreenIntentPermissionsPresenter: Presenter<FullScreenIntentPermissionsState>,
|
||||
private val batteryOptimizationPresenter: Presenter<BatteryOptimizationState>,
|
||||
private val notificationCleaner: NotificationCleaner,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val seenInvitesStore: SeenInvitesStore,
|
||||
private val announcementService: AnnouncementService,
|
||||
private val coldStartWatcher: AnalyticsColdStartWatcher,
|
||||
@@ -157,7 +154,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +276,6 @@ class RoomListPresenter(
|
||||
isDm = event.roomSummary.isDm,
|
||||
isFavorite = event.roomSummary.isFavorite,
|
||||
hasNewContent = event.roomSummary.hasNewContent,
|
||||
displayClearRoomCacheAction = appPreferencesStore.isDeveloperModeEnabledFlow().first(),
|
||||
)
|
||||
contextMenuState.value = initialState
|
||||
|
||||
@@ -336,10 +331,4 @@ class RoomListPresenter(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.clearCacheOfRoom(roomId: RoomId) = launch {
|
||||
client.getRoom(roomId)?.use { room ->
|
||||
room.clearEventCacheStorage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -44,7 +44,6 @@ data class RoomListState(
|
||||
val isDm: Boolean,
|
||||
val isFavorite: Boolean,
|
||||
val hasNewContent: Boolean,
|
||||
val displayClearRoomCacheAction: Boolean,
|
||||
) : ContextMenu
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -31,5 +31,4 @@ internal fun aContextMenuShown(
|
||||
isDm = isDm,
|
||||
hasNewContent = hasNewContent,
|
||||
isFavorite = isFavorite,
|
||||
displayClearRoomCacheAction = false,
|
||||
)
|
||||
|
||||
+2
-4
@@ -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()
|
||||
|
||||
+2
-4
@@ -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>()
|
||||
|
||||
+2
-4
@@ -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>()
|
||||
|
||||
+2
-4
@@ -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>()
|
||||
|
||||
-35
@@ -63,9 +63,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
|
||||
@@ -230,7 +228,6 @@ class RoomListPresenterTest {
|
||||
isDm = false,
|
||||
isFavorite = false,
|
||||
hasNewContent = false,
|
||||
displayClearRoomCacheAction = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -247,35 +244,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,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -303,7 +271,6 @@ class RoomListPresenterTest {
|
||||
isDm = false,
|
||||
isFavorite = false,
|
||||
hasNewContent = false,
|
||||
displayClearRoomCacheAction = false,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -668,7 +635,6 @@ 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(),
|
||||
@@ -696,7 +662,6 @@ class RoomListPresenterTest {
|
||||
fullScreenIntentPermissionsPresenter = { aFullScreenIntentPermissionsState() },
|
||||
batteryOptimizationPresenter = { aBatteryOptimizationState() },
|
||||
notificationCleaner = notificationCleaner,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
seenInvitesStore = seenInvitesStore,
|
||||
announcementService = announcementService,
|
||||
coldStartWatcher = FakeAnalyticsColdStartWatcher(),
|
||||
|
||||
+2
-4
@@ -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 {
|
||||
|
||||
+2
-4
@@ -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(
|
||||
|
||||
+2
-4
@@ -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
@@ -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)
|
||||
|
||||
+2
-4
@@ -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)
|
||||
|
||||
+2
-4
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
+34
-3
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -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() {
|
||||
|
||||
+4
-3
@@ -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,
|
||||
}
|
||||
-1
@@ -8,6 +8,5 @@
|
||||
package io.element.android.features.linknewdevice.impl.screens.root
|
||||
|
||||
sealed interface LinkNewDeviceRootEvent {
|
||||
data object LinkMobileDevice : LinkNewDeviceRootEvent
|
||||
data object CloseDialog : LinkNewDeviceRootEvent
|
||||
}
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ class LinkNewDeviceRootNode(
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : Plugin {
|
||||
fun onDone()
|
||||
fun linkDesktopDevice()
|
||||
fun onUnlockDevice(type: LinkDeviceType)
|
||||
}
|
||||
|
||||
private val callback: Callback = callback()
|
||||
@@ -39,7 +39,7 @@ class LinkNewDeviceRootNode(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onBackClick = callback::onDone,
|
||||
onLinkDesktopDeviceClick = callback::linkDesktopDevice,
|
||||
onUnlockDevice = callback::onUnlockDevice,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -50,7 +50,13 @@ class LinkNewDeviceRootPresenter(
|
||||
LaunchedEffect(step) {
|
||||
when (val finalStep = step) {
|
||||
is LinkMobileStep.Uninitialized -> {
|
||||
qrCodeData = AsyncData.Uninitialized
|
||||
// Ignore this step when loading QrCode
|
||||
if (!qrCodeData.isLoading()) {
|
||||
qrCodeData = AsyncData.Uninitialized
|
||||
}
|
||||
}
|
||||
is LinkMobileStep.CreatingQrCode -> {
|
||||
qrCodeData = AsyncData.Loading()
|
||||
}
|
||||
is LinkMobileStep.QrReady -> {
|
||||
qrCodeData = AsyncData.Success(Unit)
|
||||
@@ -64,12 +70,6 @@ class LinkNewDeviceRootPresenter(
|
||||
|
||||
fun handleEvent(event: LinkNewDeviceRootEvent) {
|
||||
when (event) {
|
||||
LinkNewDeviceRootEvent.LinkMobileDevice -> coroutineScope.launch {
|
||||
qrCodeData = AsyncData.Loading()
|
||||
// Wait for the QrCode to be ready
|
||||
linkNewMobileHandler.reset()
|
||||
linkNewMobileHandler.createAndStartNewHandler()
|
||||
}
|
||||
LinkNewDeviceRootEvent.CloseDialog -> coroutineScope.launch {
|
||||
linkNewMobileHandler.reset()
|
||||
}
|
||||
|
||||
+34
-36
@@ -41,7 +41,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
fun LinkNewDeviceRootView(
|
||||
state: LinkNewDeviceRootState,
|
||||
onBackClick: () -> Unit,
|
||||
onLinkDesktopDeviceClick: () -> Unit,
|
||||
onUnlockDevice: (type: LinkDeviceType) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val (title, subtitle, iconStyle) = if (state.isSupported.dataOrNull() == false) {
|
||||
@@ -57,6 +57,7 @@ fun LinkNewDeviceRootView(
|
||||
BigIcon.Style.Default(CompoundIcons.Devices())
|
||||
)
|
||||
}
|
||||
|
||||
FlowStepPage(
|
||||
onBackClick = onBackClick,
|
||||
title = title,
|
||||
@@ -83,40 +84,37 @@ fun LinkNewDeviceRootView(
|
||||
}
|
||||
is AsyncData.Success -> {
|
||||
if (state.isSupported.data) {
|
||||
when (state.qrCodeData) {
|
||||
AsyncData.Uninitialized,
|
||||
is AsyncData.Failure -> {
|
||||
Button(
|
||||
onClick = { state.eventSink(LinkNewDeviceRootEvent.LinkMobileDevice) },
|
||||
text = stringResource(id = R.string.screen_link_new_device_root_mobile_device),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
leadingIcon = IconSource.Vector(CompoundIcons.Mobile()),
|
||||
)
|
||||
Button(
|
||||
onClick = onLinkDesktopDeviceClick,
|
||||
text = stringResource(id = R.string.screen_link_new_device_root_desktop_computer),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
leadingIcon = IconSource.Vector(CompoundIcons.Computer()),
|
||||
)
|
||||
}
|
||||
is AsyncData.Loading,
|
||||
is AsyncData.Success -> {
|
||||
Button(
|
||||
onClick = { state.eventSink(LinkNewDeviceRootEvent.LinkMobileDevice) },
|
||||
text = stringResource(id = R.string.screen_link_new_device_root_loading_qr_code),
|
||||
showProgress = true,
|
||||
enabled = false,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Button(
|
||||
onClick = onLinkDesktopDeviceClick,
|
||||
text = stringResource(id = R.string.screen_link_new_device_root_desktop_computer),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = false,
|
||||
leadingIcon = IconSource.Vector(CompoundIcons.Computer()),
|
||||
)
|
||||
}
|
||||
}
|
||||
val canClick = state.qrCodeData is AsyncData.Uninitialized
|
||||
val isLoading = state.qrCodeData is AsyncData.Loading || state.qrCodeData is AsyncData.Success
|
||||
Button(
|
||||
onClick = {
|
||||
if (canClick) {
|
||||
onUnlockDevice(LinkDeviceType.Mobile)
|
||||
}
|
||||
},
|
||||
text = stringResource(
|
||||
id = if (isLoading) {
|
||||
R.string.screen_link_new_device_root_loading_qr_code
|
||||
} else {
|
||||
R.string.screen_link_new_device_root_mobile_device
|
||||
}
|
||||
),
|
||||
showProgress = isLoading,
|
||||
enabled = !isLoading,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
leadingIcon = IconSource.Vector(CompoundIcons.Mobile()),
|
||||
)
|
||||
Button(
|
||||
onClick = {
|
||||
if (canClick) {
|
||||
onUnlockDevice(LinkDeviceType.Desktop)
|
||||
}
|
||||
},
|
||||
text = stringResource(id = R.string.screen_link_new_device_root_desktop_computer),
|
||||
enabled = !isLoading,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
leadingIcon = IconSource.Vector(CompoundIcons.Computer()),
|
||||
)
|
||||
} else {
|
||||
Button(
|
||||
onClick = onBackClick,
|
||||
@@ -147,6 +145,6 @@ internal fun LinkNewDeviceRootViewPreview(
|
||||
LinkNewDeviceRootView(
|
||||
state = state,
|
||||
onBackClick = { },
|
||||
onLinkDesktopDeviceClick = { },
|
||||
onUnlockDevice = { },
|
||||
)
|
||||
}
|
||||
|
||||
+2
@@ -13,6 +13,7 @@ import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.enterprise.test.FakeSessionEnterpriseService
|
||||
import io.element.android.features.linknewdevice.api.LinkNewDeviceEntryPoint
|
||||
import io.element.android.features.lockscreen.test.FakeDeviceUnlockEntryPoint
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.node.TestParentNode
|
||||
@@ -39,6 +40,7 @@ class DefaultLinkNewDeviceEntryPointTest {
|
||||
linkNewMobileHandler = LinkNewMobileHandler(client),
|
||||
linkNewDesktopHandler = LinkNewDesktopHandler(client),
|
||||
sessionEnterpriseService = FakeSessionEnterpriseService(),
|
||||
deviceUnlockEntryPoint = FakeDeviceUnlockEntryPoint(),
|
||||
)
|
||||
}
|
||||
val callback: LinkNewDeviceEntryPoint.Callback = object : LinkNewDeviceEntryPoint.Callback {
|
||||
|
||||
+2
-4
@@ -13,7 +13,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.linknewdevice.impl.R
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
@@ -21,11 +20,10 @@ 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.pressBackKey
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DesktopNoticeViewTest {
|
||||
class DesktopNoticeViewTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
|
||||
ensureCalledOnce { callback ->
|
||||
|
||||
+2
-4
@@ -13,17 +13,15 @@ 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.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.pressBackKey
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ErrorViewTest {
|
||||
class ErrorViewTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `on back pressed - calls the onCancel callback`() = runAndroidComposeUiTest {
|
||||
ensureCalledOnce { callback ->
|
||||
|
||||
+5
-2
@@ -88,6 +88,7 @@ class EnterNumberPresenterTest {
|
||||
navigator = navigator,
|
||||
linkNewMobileHandler = linkNewMobileHandler,
|
||||
).test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
linkMobileHandler.emitStep(
|
||||
LinkMobileStep.QrScanned(checkCodeSender)
|
||||
@@ -96,7 +97,7 @@ class EnterNumberPresenterTest {
|
||||
initialState.eventSink(EnterNumberEvent.UpdateNumber("88"))
|
||||
skipItems(1)
|
||||
initialState.eventSink(EnterNumberEvent.Continue)
|
||||
skipItems(1)
|
||||
skipItems(2)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.sendingCode.isLoading()).isTrue()
|
||||
advanceUntilIdle()
|
||||
@@ -130,6 +131,7 @@ class EnterNumberPresenterTest {
|
||||
LinkMobileStep.QrScanned(checkCodeSender)
|
||||
)
|
||||
runCurrent()
|
||||
skipItems(1)
|
||||
initialState.eventSink(EnterNumberEvent.UpdateNumber("88"))
|
||||
skipItems(1)
|
||||
initialState.eventSink(EnterNumberEvent.Continue)
|
||||
@@ -163,6 +165,7 @@ class EnterNumberPresenterTest {
|
||||
createPresenter(
|
||||
linkNewMobileHandler = linkNewMobileHandler,
|
||||
).test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
linkMobileHandler.emitStep(
|
||||
LinkMobileStep.QrScanned(checkCodeSender)
|
||||
@@ -171,7 +174,7 @@ class EnterNumberPresenterTest {
|
||||
initialState.eventSink(EnterNumberEvent.UpdateNumber("88"))
|
||||
skipItems(1)
|
||||
initialState.eventSink(EnterNumberEvent.Continue)
|
||||
skipItems(1)
|
||||
skipItems(2)
|
||||
val loadingState = awaitItem()
|
||||
assertThat(loadingState.sendingCode.isLoading()).isTrue()
|
||||
expectNoEvents()
|
||||
|
||||
+2
-4
@@ -15,7 +15,6 @@ import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.assertIsNotEnabled
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
@@ -23,11 +22,10 @@ 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.pressBackKey
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class EnterNumberViewTest {
|
||||
class EnterNumberViewTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
|
||||
ensureCalledOnce { callback ->
|
||||
|
||||
+2
-4
@@ -13,15 +13,13 @@ 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.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
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 ShowQrCodeViewTest {
|
||||
class ShowQrCodeViewTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
|
||||
ensureCalledOnce { callback ->
|
||||
|
||||
+34
-1
@@ -75,15 +75,48 @@ class LinkNewDeviceRootPresenterTest {
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
createLinkMobileHandlerResult = { Result.success(linkMobileHandler) }
|
||||
)
|
||||
val linkNewMobileHandler = LinkNewMobileHandler(matrixClient)
|
||||
createPresenter(
|
||||
matrixClient = matrixClient,
|
||||
linkNewMobileHandler = linkNewMobileHandler,
|
||||
).test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.isSupported.dataOrNull()).isTrue()
|
||||
initialState.eventSink(LinkNewDeviceRootEvent.LinkMobileDevice)
|
||||
linkNewMobileHandler.createAndStartNewHandler()
|
||||
skipItems(1)
|
||||
val loadingState = awaitItem()
|
||||
assertThat(loadingState.qrCodeData.isLoading()).isTrue()
|
||||
skipItems(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - close dialog resets qrCodeData`() = runTest {
|
||||
val fakeLinkMobileHandler = FakeLinkMobileHandler(startResult = {})
|
||||
val matrixClient = FakeMatrixClient(
|
||||
canLinkNewDeviceResult = { Result.success(true) },
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
createLinkMobileHandlerResult = { Result.success(fakeLinkMobileHandler) }
|
||||
)
|
||||
val linkNewMobileHandler = LinkNewMobileHandler(matrixClient)
|
||||
createPresenter(
|
||||
matrixClient = matrixClient,
|
||||
linkNewMobileHandler = linkNewMobileHandler,
|
||||
).test {
|
||||
skipItems(1)
|
||||
linkNewMobileHandler.onTooManyRotation()
|
||||
var errorState = awaitItem()
|
||||
while (!errorState.qrCodeData.isFailure()) {
|
||||
errorState = awaitItem()
|
||||
}
|
||||
assertThat(errorState.qrCodeData.isFailure()).isTrue()
|
||||
errorState.eventSink(LinkNewDeviceRootEvent.CloseDialog)
|
||||
var resetState = awaitItem()
|
||||
while (!resetState.qrCodeData.isUninitialized()) {
|
||||
resetState = awaitItem()
|
||||
}
|
||||
assertThat(resetState.qrCodeData.isUninitialized()).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-17
@@ -13,20 +13,20 @@ 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.linknewdevice.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.EnsureNeverCalledWithParam
|
||||
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.pressBackKey
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LinkNewDeviceRootViewTest {
|
||||
class LinkNewDeviceRootViewTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `on back pressed - calls the onRetry callback`() = runAndroidComposeUiTest {
|
||||
val eventRecorder = EventsRecorder<LinkNewDeviceRootEvent>(expectEvents = false)
|
||||
@@ -44,29 +44,31 @@ class LinkNewDeviceRootViewTest {
|
||||
@Test
|
||||
fun `link desktop button clicked - calls the expected callback`() = runAndroidComposeUiTest {
|
||||
val eventRecorder = EventsRecorder<LinkNewDeviceRootEvent>(expectEvents = false)
|
||||
ensureCalledOnce { callback ->
|
||||
ensureCalledOnceWithParam(LinkDeviceType.Desktop) { callback ->
|
||||
setLinkNewDeviceRootView(
|
||||
state = aLinkNewDeviceRootState(
|
||||
isSupported = AsyncData.Success(true),
|
||||
eventSink = eventRecorder,
|
||||
),
|
||||
onLinkDesktopDeviceClick = callback,
|
||||
onUnlockDevice = callback,
|
||||
)
|
||||
clickOn(R.string.screen_link_new_device_root_desktop_computer)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `link mobile button clicked - emits the expected event`() = runAndroidComposeUiTest {
|
||||
val eventRecorder = EventsRecorder<LinkNewDeviceRootEvent>()
|
||||
setLinkNewDeviceRootView(
|
||||
state = aLinkNewDeviceRootState(
|
||||
isSupported = AsyncData.Success(true),
|
||||
eventSink = eventRecorder,
|
||||
fun `link mobile button clicked - calls the expected callback`() = runAndroidComposeUiTest {
|
||||
val eventRecorder = EventsRecorder<LinkNewDeviceRootEvent>(expectEvents = false)
|
||||
ensureCalledOnceWithParam(LinkDeviceType.Mobile) { callback ->
|
||||
setLinkNewDeviceRootView(
|
||||
state = aLinkNewDeviceRootState(
|
||||
isSupported = AsyncData.Success(true),
|
||||
eventSink = eventRecorder,
|
||||
),
|
||||
onUnlockDevice = callback,
|
||||
)
|
||||
)
|
||||
clickOn(R.string.screen_link_new_device_root_mobile_device)
|
||||
eventRecorder.assertSingle(LinkNewDeviceRootEvent.LinkMobileDevice)
|
||||
clickOn(R.string.screen_link_new_device_root_mobile_device)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,13 +89,13 @@ class LinkNewDeviceRootViewTest {
|
||||
private fun AndroidComposeUiTest<ComponentActivity>.setLinkNewDeviceRootView(
|
||||
state: LinkNewDeviceRootState = aLinkNewDeviceRootState(),
|
||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||
onLinkDesktopDeviceClick: () -> Unit = EnsureNeverCalled(),
|
||||
onUnlockDevice: (type: LinkDeviceType) -> Unit = EnsureNeverCalledWithParam(),
|
||||
) {
|
||||
setContent {
|
||||
LinkNewDeviceRootView(
|
||||
state = state,
|
||||
onBackClick = onBackClick,
|
||||
onLinkDesktopDeviceClick = onLinkDesktopDeviceClick,
|
||||
onUnlockDevice = onUnlockDevice,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -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.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@@ -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.pressBackKey
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ScanQrCodeViewTest {
|
||||
class ScanQrCodeViewTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
|
||||
val eventRecorder = EventsRecorder<ScanQrCodeEvent>(expectEvents = false)
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_share_location_live_location_disclaimer_title">"Votre historique de localisation en direct sera enregistré dans le salon et visible par les membres après la fin de la session."</string>
|
||||
<string name="screen_share_location_live_location_duration_picker_title">"Choisissez la durée pendant laquelle vous partagerez votre position en direct."</string>
|
||||
<string name="screen_share_location_live_location_missing_permissions">"Vous n’êtes pas autorisé à partager votre position en direct dans ce salon."</string>
|
||||
</resources>
|
||||
|
||||
+2
-4
@@ -17,7 +17,6 @@ import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
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.location.impl.common.ui.LocationConstraintsDialogState
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@@ -26,11 +25,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 ShareLocationViewTest {
|
||||
class ShareLocationViewTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `test back action`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>(expectEvents = false)
|
||||
|
||||
+2
-4
@@ -19,7 +19,6 @@ import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
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.location.api.Location
|
||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
@@ -29,11 +28,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 ShowLocationViewTest {
|
||||
class ShowLocationViewTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `test back action`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<ShowLocationEvent>(expectEvents = false)
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.lockscreen.api
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
|
||||
/**
|
||||
* An entry point for features that want to lock the screen and require
|
||||
* the user to unlock it before they can interact with the app.
|
||||
* - if the system lock is available, it will be used to unlock the screen.
|
||||
* - if the system lock is not available, but app lock is available, it will be used to unlock the screen.
|
||||
* - if neither is available, the screen will be unlocked immediately.
|
||||
*
|
||||
* The Node provided by [createNode] has to be added as a PermanentChild.
|
||||
*/
|
||||
interface DeviceUnlockEntryPoint : FeatureEntryPoint {
|
||||
fun createNode(
|
||||
parentNode: Node,
|
||||
buildContext: BuildContext,
|
||||
): Node
|
||||
|
||||
fun requestUnlock(callback: Callback)
|
||||
|
||||
interface Callback {
|
||||
fun onCancel()
|
||||
fun onUnlock()
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ dependencies {
|
||||
implementation(libs.androidx.biometric)
|
||||
|
||||
testCommonDependencies(libs, true)
|
||||
testImplementation(projects.features.lockscreen.test)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.cryptography.test)
|
||||
testImplementation(projects.libraries.cryptography.impl)
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.lockscreen.impl
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.features.lockscreen.api.DeviceUnlockEntryPoint
|
||||
import io.element.android.features.lockscreen.impl.device.DeviceUnlockCallbackHolder
|
||||
import io.element.android.features.lockscreen.impl.device.DeviceUnlockNode
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultDeviceUnlockEntryPoint(
|
||||
private val deviceUnlockCallbackHolder: DeviceUnlockCallbackHolder,
|
||||
) : DeviceUnlockEntryPoint {
|
||||
override fun createNode(
|
||||
parentNode: Node,
|
||||
buildContext: BuildContext,
|
||||
): Node {
|
||||
return parentNode.createNode<DeviceUnlockNode>(
|
||||
buildContext = buildContext,
|
||||
)
|
||||
}
|
||||
|
||||
override fun requestUnlock(callback: DeviceUnlockEntryPoint.Callback) {
|
||||
deviceUnlockCallbackHolder.requestUnlock(callback)
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -59,6 +59,7 @@ class DefaultBiometricAuthentication(
|
||||
private var cryptoObject: CryptoObject? = null
|
||||
|
||||
override suspend fun setup() {
|
||||
if (cryptoObject != null) return
|
||||
try {
|
||||
val secretKey = ensureKey()
|
||||
val cipher = encryptionDecryptionService.createEncryptionCipher(secretKey)
|
||||
@@ -109,7 +110,9 @@ private class AuthenticationCallback(
|
||||
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
if (result.cryptoObject?.cipher.isValid()) {
|
||||
if (result.authenticationType == BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC &&
|
||||
result.cryptoObject?.cipher.isValid() ||
|
||||
result.authenticationType == BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL) {
|
||||
callbacks.forEach { it.onBiometricAuthenticationSuccess() }
|
||||
deferredAuthenticationResult.complete(BiometricAuthenticator.AuthenticationResult.Success)
|
||||
} else {
|
||||
|
||||
+11
@@ -21,6 +21,11 @@ interface BiometricAuthenticatorManager {
|
||||
*/
|
||||
val hasAvailableAuthenticator: Boolean
|
||||
|
||||
/**
|
||||
* If the device is secured for example with a pin, a pattern, a password, or biometric.
|
||||
*/
|
||||
val canUseDeviceUnlock: Boolean
|
||||
|
||||
fun addCallback(callback: BiometricAuthenticator.Callback)
|
||||
fun removeCallback(callback: BiometricAuthenticator.Callback)
|
||||
|
||||
@@ -35,6 +40,12 @@ interface BiometricAuthenticatorManager {
|
||||
@Composable
|
||||
fun rememberUnlockBiometricAuthenticator(): BiometricAuthenticator
|
||||
|
||||
/**
|
||||
* Remember a biometric authenticator ready for unlocking the app, using the device settings.
|
||||
*/
|
||||
@Composable
|
||||
fun rememberUnlockDeviceBiometricAuthenticator(): BiometricAuthenticator
|
||||
|
||||
/**
|
||||
* Remember a biometric authenticator ready for confirmation.
|
||||
*/
|
||||
|
||||
+50
-6
@@ -35,6 +35,7 @@ import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
@@ -69,6 +70,12 @@ class DefaultBiometricAuthenticatorManager(
|
||||
get() = lockScreenConfig.isStrongBiometricsEnabled &&
|
||||
biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS
|
||||
|
||||
/**
|
||||
* Returns true if a device credential method (i.e.: pattern, pin) can be used.
|
||||
*/
|
||||
private val canUseDeviceCredentialAuth: Boolean
|
||||
get() = biometricManager.canAuthenticate(BiometricManager.Authenticators.DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS
|
||||
|
||||
/**
|
||||
* Returns true if any biometric method (weak or strong) can be used.
|
||||
*/
|
||||
@@ -78,6 +85,9 @@ class DefaultBiometricAuthenticatorManager(
|
||||
override val isDeviceSecured: Boolean
|
||||
get() = keyguardManager.isDeviceSecure
|
||||
|
||||
override val canUseDeviceUnlock: Boolean
|
||||
get() = isDeviceSecured && (canUseWeakBiometricAuth || canUseStrongBiometricAuth || canUseDeviceCredentialAuth)
|
||||
|
||||
private val internalCallback = object : DefaultBiometricUnlockCallback() {
|
||||
override fun onBiometricSetupError() {
|
||||
coroutineScope.launch { disable() }
|
||||
@@ -99,6 +109,28 @@ class DefaultBiometricAuthenticatorManager(
|
||||
isAvailable = isAvailable,
|
||||
promptTitle = promptTitle,
|
||||
promptNegative = promptNegative,
|
||||
forDeviceUnlock = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun rememberUnlockDeviceBiometricAuthenticator(): BiometricAuthenticator {
|
||||
val isAvailableTrigger by remember {
|
||||
// Need to trigger the creation of BiometricAuthenticator twice, else the callback will not be ready.
|
||||
// (the issue already exists in [rememberUnlockBiometricAuthenticator])
|
||||
flowOf(false, true)
|
||||
}.collectAsState(initial = false)
|
||||
val lifecycleState by LocalLifecycleOwner.current.lifecycle.currentStateFlow.collectAsState()
|
||||
val isAvailable by remember(lifecycleState) {
|
||||
derivedStateOf { isAvailableTrigger && canUseDeviceUnlock }
|
||||
}
|
||||
val promptTitle = stringResource(id = R.string.screen_app_lock_biometric_unlock_title_android)
|
||||
val promptNegative = null
|
||||
return rememberBiometricAuthenticator(
|
||||
isAvailable = isAvailable,
|
||||
promptTitle = promptTitle,
|
||||
promptNegative = promptNegative,
|
||||
forDeviceUnlock = true,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -114,6 +146,7 @@ class DefaultBiometricAuthenticatorManager(
|
||||
isAvailable = isAvailable,
|
||||
promptTitle = promptTitle,
|
||||
promptNegative = promptNegative,
|
||||
forDeviceUnlock = false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -126,7 +159,8 @@ class DefaultBiometricAuthenticatorManager(
|
||||
private fun rememberBiometricAuthenticator(
|
||||
isAvailable: Boolean,
|
||||
promptTitle: String,
|
||||
promptNegative: String,
|
||||
promptNegative: String?,
|
||||
forDeviceUnlock: Boolean,
|
||||
): BiometricAuthenticator {
|
||||
val activity = LocalContext.current.findFragmentActivity()
|
||||
return remember(isAvailable) {
|
||||
@@ -136,11 +170,21 @@ class DefaultBiometricAuthenticatorManager(
|
||||
canUseWeakBiometricAuth -> BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||
else -> 0
|
||||
}
|
||||
val promptInfo = BiometricPrompt.PromptInfo.Builder().apply {
|
||||
setTitle(promptTitle)
|
||||
setNegativeButtonText(promptNegative)
|
||||
setAllowedAuthenticators(authenticators)
|
||||
}.build()
|
||||
val promptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(promptTitle)
|
||||
.apply {
|
||||
if (promptNegative != null) {
|
||||
setNegativeButtonText(promptNegative)
|
||||
}
|
||||
if (forDeviceUnlock) {
|
||||
setAllowedAuthenticators(
|
||||
authenticators or if (canUseDeviceCredentialAuth) BiometricManager.Authenticators.DEVICE_CREDENTIAL else 0
|
||||
)
|
||||
} else {
|
||||
setAllowedAuthenticators(authenticators)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
DefaultBiometricAuthentication(
|
||||
activity = activity,
|
||||
promptInfo = promptInfo,
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.lockscreen.impl.device
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.features.lockscreen.api.DeviceUnlockEntryPoint
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@Inject
|
||||
@SingleIn(AppScope::class)
|
||||
class DeviceUnlockCallbackHolder {
|
||||
private val _deviceUnlockCallback = MutableStateFlow<DeviceUnlockEntryPoint.Callback?>(null)
|
||||
val deviceUnlockCallback: StateFlow<DeviceUnlockEntryPoint.Callback?> = _deviceUnlockCallback
|
||||
|
||||
fun requestUnlock(callback: DeviceUnlockEntryPoint.Callback) {
|
||||
_deviceUnlockCallback.value = callback
|
||||
}
|
||||
|
||||
fun onDone() {
|
||||
_deviceUnlockCallback.value = null
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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.lockscreen.impl.device
|
||||
|
||||
sealed interface DeviceUnlockEvent {
|
||||
data object CancelPinCode : DeviceUnlockEvent
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.lockscreen.impl.device
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.features.lockscreen.impl.unlock.PinUnlockPresenter
|
||||
import io.element.android.features.lockscreen.impl.unlock.PinUnlockView
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
@AssistedInject
|
||||
class DeviceUnlockNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: DeviceUnlockPresenter,
|
||||
private val pinUnlockPresenterFactory: PinUnlockPresenter.Factory,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
if (state.showApplicationPinCode) {
|
||||
val pinUnlockPresenter = remember {
|
||||
pinUnlockPresenterFactory.create(forDeviceUnlock = true)
|
||||
}
|
||||
val pinState = pinUnlockPresenter.present()
|
||||
PinUnlockView(
|
||||
state = pinState,
|
||||
isInAppUnlock = true,
|
||||
onCancel = {
|
||||
state.eventSink(DeviceUnlockEvent.CancelPinCode)
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.lockscreen.impl.device
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticatorManager
|
||||
import io.element.android.features.lockscreen.impl.pin.PinCodeManager
|
||||
import io.element.android.features.lockscreen.impl.unlock.PinUnlockHelper
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
@Inject
|
||||
class DeviceUnlockPresenter(
|
||||
private val pinUnlockHelper: PinUnlockHelper,
|
||||
private val biometricAuthenticatorManager: BiometricAuthenticatorManager,
|
||||
private val deviceUnlockCallbackHolder: DeviceUnlockCallbackHolder,
|
||||
private val pinCodeManager: PinCodeManager,
|
||||
) : Presenter<DeviceUnlockState> {
|
||||
@Composable
|
||||
override fun present(): DeviceUnlockState {
|
||||
var showApplicationPinCode by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
val biometricUnlock = biometricAuthenticatorManager.rememberUnlockDeviceBiometricAuthenticator()
|
||||
val deviceUnlockCallback by deviceUnlockCallbackHolder.deviceUnlockCallback.collectAsState()
|
||||
val canUseDeviceUnlock = biometricAuthenticatorManager.canUseDeviceUnlock
|
||||
|
||||
fun setUnlock(isUnlock: Boolean) {
|
||||
deviceUnlockCallback?.let {
|
||||
if (isUnlock) {
|
||||
it.onUnlock()
|
||||
} else {
|
||||
it.onCancel()
|
||||
}
|
||||
}
|
||||
showApplicationPinCode = false
|
||||
deviceUnlockCallbackHolder.onDone()
|
||||
}
|
||||
|
||||
LaunchedEffect(biometricUnlock, canUseDeviceUnlock, deviceUnlockCallback) {
|
||||
if (deviceUnlockCallback != null) {
|
||||
if (canUseDeviceUnlock) {
|
||||
biometricUnlock.setup()
|
||||
biometricUnlock.authenticate()
|
||||
} else if (pinCodeManager.hasPinCode().first()) {
|
||||
showApplicationPinCode = true
|
||||
} else {
|
||||
// No security, unlock immediately
|
||||
setUnlock(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pinUnlockHelper.OnUnlockEffect { isUnlock ->
|
||||
setUnlock(isUnlock)
|
||||
}
|
||||
|
||||
fun handleEvent(event: DeviceUnlockEvent) {
|
||||
when (event) {
|
||||
DeviceUnlockEvent.CancelPinCode -> {
|
||||
showApplicationPinCode = false
|
||||
setUnlock(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DeviceUnlockState(
|
||||
showApplicationPinCode = showApplicationPinCode,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* 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.lockscreen.impl.device
|
||||
|
||||
data class DeviceUnlockState(
|
||||
val showApplicationPinCode: Boolean,
|
||||
val eventSink: (DeviceUnlockEvent) -> Unit,
|
||||
)
|
||||
+8
-1
@@ -103,8 +103,15 @@ class LockScreenSettingsFlowNode(
|
||||
override fun onUnlock() {
|
||||
backstack.newRoot(NavTarget.Settings)
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
// Should not happen in this context.
|
||||
}
|
||||
}
|
||||
createNode<PinUnlockNode>(buildContext, plugins = listOf(callback))
|
||||
val inputs = PinUnlockNode.Inputs(
|
||||
forDeviceUnlock = false,
|
||||
)
|
||||
createNode<PinUnlockNode>(buildContext, plugins = listOf(callback, inputs))
|
||||
}
|
||||
NavTarget.SetupPin -> {
|
||||
createNode<SetupPinNode>(buildContext)
|
||||
|
||||
+9
-3
@@ -24,17 +24,23 @@ class PinUnlockHelper(
|
||||
private val pinCodeManager: PinCodeManager
|
||||
) {
|
||||
@Composable
|
||||
fun OnUnlockEffect(onUnlock: () -> Unit) {
|
||||
fun OnUnlockEffect(onUnlock: (Boolean) -> Unit) {
|
||||
val latestOnUnlock by rememberUpdatedState(onUnlock)
|
||||
DisposableEffect(Unit) {
|
||||
val biometricUnlockCallback = object : DefaultBiometricUnlockCallback() {
|
||||
override fun onBiometricAuthenticationSuccess() {
|
||||
latestOnUnlock()
|
||||
latestOnUnlock(true)
|
||||
}
|
||||
|
||||
override fun onBiometricAuthenticationFailed(error: Exception?) {
|
||||
if (error != null) {
|
||||
latestOnUnlock(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
val pinCodeVerifiedCallback = object : DefaultPinCodeManagerCallback() {
|
||||
override fun onPinCodeVerified() {
|
||||
latestOnUnlock()
|
||||
latestOnUnlock(true)
|
||||
}
|
||||
}
|
||||
biometricAuthenticatorManager.addCallback(biometricUnlockCallback)
|
||||
|
||||
+14
-1
@@ -17,7 +17,9 @@ import com.bumble.appyx.core.plugin.Plugin
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.callback
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
@@ -25,13 +27,23 @@ import io.element.android.libraries.di.SessionScope
|
||||
class PinUnlockNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: PinUnlockPresenter,
|
||||
presenterFactory: PinUnlockPresenter.Factory,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : Plugin {
|
||||
fun onUnlock()
|
||||
|
||||
// For feature unlock
|
||||
fun onCancel()
|
||||
}
|
||||
|
||||
data class Inputs(
|
||||
val forDeviceUnlock: Boolean,
|
||||
) : NodeInputs
|
||||
|
||||
private val callback: Callback = callback()
|
||||
private val inputs: Inputs = inputs()
|
||||
|
||||
private val presenter: PinUnlockPresenter = presenterFactory.create(inputs.forDeviceUnlock)
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
@@ -46,6 +58,7 @@ class PinUnlockNode(
|
||||
// UnlockNode is only used for in-app unlock, so we can safely set isInAppUnlock to true.
|
||||
// It's set to false in PinUnlockActivity.
|
||||
isInAppUnlock = true,
|
||||
onCancel = callback::onCancel,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
+12
-3
@@ -16,7 +16,9 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedFactory
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticator
|
||||
import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticatorManager
|
||||
import io.element.android.features.lockscreen.impl.pin.PinCodeManager
|
||||
@@ -32,8 +34,9 @@ import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Inject
|
||||
@AssistedInject
|
||||
class PinUnlockPresenter(
|
||||
@Assisted private val forDeviceUnlock: Boolean,
|
||||
private val pinCodeManager: PinCodeManager,
|
||||
private val biometricAuthenticatorManager: BiometricAuthenticatorManager,
|
||||
private val logoutUseCase: LogoutUseCase,
|
||||
@@ -41,6 +44,11 @@ class PinUnlockPresenter(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val pinUnlockHelper: PinUnlockHelper,
|
||||
) : Presenter<PinUnlockState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(forDeviceUnlock: Boolean): PinUnlockPresenter
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): PinUnlockState {
|
||||
val pinEntryState = remember {
|
||||
@@ -98,7 +106,7 @@ class PinUnlockPresenter(
|
||||
}
|
||||
}
|
||||
pinUnlockHelper.OnUnlockEffect {
|
||||
isUnlocked.value = true
|
||||
isUnlocked.value = it
|
||||
}
|
||||
|
||||
fun handleEvent(event: PinUnlockEvent) {
|
||||
@@ -128,6 +136,7 @@ class PinUnlockPresenter(
|
||||
}
|
||||
}
|
||||
return PinUnlockState(
|
||||
canNavigateBack = forDeviceUnlock,
|
||||
pinEntry = pinEntry,
|
||||
showWrongPinTitle = showWrongPinTitle,
|
||||
remainingAttempts = remainingAttempts,
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
||||
data class PinUnlockState(
|
||||
val canNavigateBack: Boolean,
|
||||
val pinEntry: AsyncData<PinEntry>,
|
||||
val showWrongPinTitle: Boolean,
|
||||
val remainingAttempts: AsyncData<Int>,
|
||||
|
||||
+11
@@ -38,10 +38,20 @@ open class PinUnlockStateProvider : PreviewParameterProvider<PinUnlockState> {
|
||||
showWrongPinTitle = true,
|
||||
isUnlocked = true,
|
||||
),
|
||||
aPinUnlockState(canNavigateBack = true),
|
||||
)
|
||||
}
|
||||
|
||||
open class PinUnlockStateCompactProvider : PreviewParameterProvider<PinUnlockState> {
|
||||
override val values: Sequence<PinUnlockState>
|
||||
get() = sequenceOf(
|
||||
aPinUnlockState(),
|
||||
aPinUnlockState(canNavigateBack = true)
|
||||
)
|
||||
}
|
||||
|
||||
fun aPinUnlockState(
|
||||
canNavigateBack: Boolean = false,
|
||||
pinEntry: AsyncData<PinEntry> = AsyncData.Success(PinEntry.createEmpty(4)),
|
||||
remainingAttempts: AsyncData<Int> = AsyncData.Success(3),
|
||||
showWrongPinTitle: Boolean = false,
|
||||
@@ -51,6 +61,7 @@ fun aPinUnlockState(
|
||||
isUnlocked: Boolean = false,
|
||||
signOutAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
) = PinUnlockState(
|
||||
canNavigateBack = canNavigateBack,
|
||||
pinEntry = pinEntry,
|
||||
showWrongPinTitle = showWrongPinTitle,
|
||||
remainingAttempts = remainingAttempts,
|
||||
|
||||
+41
-14
@@ -36,6 +36,7 @@ import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
@@ -50,6 +51,7 @@ import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
@@ -65,6 +67,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
fun PinUnlockView(
|
||||
state: PinUnlockState,
|
||||
isInAppUnlock: Boolean,
|
||||
onCancel: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
OnLifecycleEvent { _, event ->
|
||||
@@ -74,7 +77,7 @@ fun PinUnlockView(
|
||||
}
|
||||
}
|
||||
Surface(modifier) {
|
||||
PinUnlockPage(state = state, isInAppUnlock = isInAppUnlock)
|
||||
PinUnlockPage(state = state, isInAppUnlock = isInAppUnlock, onCancel = onCancel)
|
||||
if (state.showSignOutPrompt) {
|
||||
SignOutPrompt(
|
||||
isCancellable = state.isSignOutPromptCancellable,
|
||||
@@ -105,13 +108,14 @@ fun PinUnlockView(
|
||||
private fun PinUnlockPage(
|
||||
state: PinUnlockState,
|
||||
isInAppUnlock: Boolean,
|
||||
onCancel: () -> Unit,
|
||||
) {
|
||||
BoxWithConstraints {
|
||||
val commonModifier = Modifier
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding()
|
||||
.imePadding()
|
||||
.padding(all = 20.dp)
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding()
|
||||
.imePadding()
|
||||
.padding(all = 20.dp)
|
||||
|
||||
val header = @Composable {
|
||||
PinUnlockHeader(
|
||||
@@ -147,8 +151,8 @@ private fun PinUnlockPage(
|
||||
state.eventSink(PinUnlockEvent.OnPinEntryChanged(it))
|
||||
},
|
||||
modifier = Modifier
|
||||
.focusRequester(focusRequester)
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@@ -177,6 +181,15 @@ private fun PinUnlockPage(
|
||||
modifier = commonModifier,
|
||||
)
|
||||
}
|
||||
if (state.canNavigateBack) {
|
||||
BackButton(
|
||||
onClick = onCancel,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.systemBarsPadding()
|
||||
.padding(8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,8 +230,8 @@ private fun PinUnlockCompactView(
|
||||
}
|
||||
BoxWithConstraints(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight(),
|
||||
.weight(1f)
|
||||
.fillMaxHeight(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
content()
|
||||
@@ -239,9 +252,9 @@ private fun PinUnlockExpandedView(
|
||||
header()
|
||||
BoxWithConstraints(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.padding(top = 40.dp),
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.padding(top = 40.dp),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
@@ -274,8 +287,8 @@ private fun PinDot(
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(14.dp)
|
||||
.background(backgroundColor, CircleShape)
|
||||
.size(14.dp)
|
||||
.background(backgroundColor, CircleShape)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -373,6 +386,7 @@ internal fun PinUnlockViewInAppPreview(@PreviewParameter(PinUnlockStateProvider:
|
||||
PinUnlockView(
|
||||
state = state,
|
||||
isInAppUnlock = true,
|
||||
onCancel = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -384,6 +398,19 @@ internal fun PinUnlockViewPreview(@PreviewParameter(PinUnlockStateProvider::clas
|
||||
PinUnlockView(
|
||||
state = state,
|
||||
isInAppUnlock = false,
|
||||
onCancel = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(heightDp = 480, widthDp = 800)
|
||||
internal fun PinUnlockViewCompactPreview(@PreviewParameter(PinUnlockStateCompactProvider::class) state: PinUnlockState) {
|
||||
ElementPreview {
|
||||
PinUnlockView(
|
||||
state = state,
|
||||
isInAppUnlock = false,
|
||||
onCancel = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -41,7 +41,7 @@ class PinUnlockActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@Inject lateinit var presenter: PinUnlockPresenter
|
||||
@Inject lateinit var presenterFactory: PinUnlockPresenter.Factory
|
||||
@Inject lateinit var lockScreenService: LockScreenService
|
||||
@Inject lateinit var appPreferencesStore: AppPreferencesStore
|
||||
@Inject lateinit var featureFlagService: FeatureFlagService
|
||||
@@ -52,6 +52,7 @@ class PinUnlockActivity : AppCompatActivity() {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
bindings<PinUnlockBindings>().inject(this)
|
||||
val presenter = presenterFactory.create(forDeviceUnlock = false)
|
||||
setContent {
|
||||
val colors by remember {
|
||||
enterpriseService.semanticColorsFlow(sessionId = null)
|
||||
@@ -67,6 +68,9 @@ class PinUnlockActivity : AppCompatActivity() {
|
||||
PinUnlockView(
|
||||
state = state,
|
||||
isInAppUnlock = false,
|
||||
onCancel = {
|
||||
// Should not happen
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -8,15 +8,13 @@
|
||||
|
||||
package io.element.android.features.lockscreen.impl
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DefaultLockScreenEntryPointIntentTest {
|
||||
class DefaultLockScreenEntryPointIntentTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `test pin unlock intent`() {
|
||||
val entryPoint = DefaultLockScreenEntryPoint()
|
||||
|
||||
+11
-3
@@ -8,10 +8,18 @@
|
||||
|
||||
package io.element.android.features.lockscreen.impl.biometric
|
||||
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
|
||||
class FakeBiometricAuthenticator(
|
||||
override val isActive: Boolean = false,
|
||||
private val authenticateLambda: suspend () -> BiometricAuthenticator.AuthenticationResult = { BiometricAuthenticator.AuthenticationResult.Success },
|
||||
private val setupLambda: () -> Unit = { },
|
||||
private val authenticateLambda: () -> BiometricAuthenticator.AuthenticationResult = { BiometricAuthenticator.AuthenticationResult.Success },
|
||||
) : BiometricAuthenticator {
|
||||
override suspend fun setup() = Unit
|
||||
override suspend fun authenticate() = authenticateLambda()
|
||||
override suspend fun setup() = simulateLongTask {
|
||||
setupLambda()
|
||||
}
|
||||
|
||||
override suspend fun authenticate() = simulateLongTask {
|
||||
authenticateLambda()
|
||||
}
|
||||
}
|
||||
|
||||
+11
-3
@@ -12,8 +12,9 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
|
||||
class FakeBiometricAuthenticatorManager(
|
||||
override var isDeviceSecured: Boolean = true,
|
||||
override var hasAvailableAuthenticator: Boolean = false,
|
||||
override val isDeviceSecured: Boolean = true,
|
||||
override val canUseDeviceUnlock: Boolean = true,
|
||||
override val hasAvailableAuthenticator: Boolean = false,
|
||||
private val createBiometricAuthenticator: () -> BiometricAuthenticator = { FakeBiometricAuthenticator() },
|
||||
private val disableLambda: suspend () -> Unit = { },
|
||||
) : BiometricAuthenticatorManager {
|
||||
@@ -28,7 +29,14 @@ class FakeBiometricAuthenticatorManager(
|
||||
@Composable
|
||||
override fun rememberUnlockBiometricAuthenticator(): BiometricAuthenticator {
|
||||
return remember {
|
||||
createBiometricAuthenticator()
|
||||
createBiometricAuthenticator()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun rememberUnlockDeviceBiometricAuthenticator(): BiometricAuthenticator {
|
||||
return remember {
|
||||
createBiometricAuthenticator()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.features.lockscreen.impl.device
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticator
|
||||
import io.element.android.features.lockscreen.impl.biometric.FakeBiometricAuthenticator
|
||||
import io.element.android.features.lockscreen.impl.biometric.FakeBiometricAuthenticatorManager
|
||||
import io.element.android.features.lockscreen.impl.fixtures.aPinCodeManager
|
||||
import io.element.android.features.lockscreen.impl.pin.PinCodeManager
|
||||
import io.element.android.features.lockscreen.impl.unlock.PinUnlockHelper
|
||||
import io.element.android.features.lockscreen.test.FakeDeviceUnlockEntryPointCallback
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DeviceUnlockPresenterTest {
|
||||
@Test
|
||||
fun `present - when unlock requested and device unlock available, use biometric authenticator`() = runTest {
|
||||
val setupLambda = lambdaRecorder<Unit> { }
|
||||
val authenticateLambda = lambdaRecorder<BiometricAuthenticator.AuthenticationResult> {
|
||||
BiometricAuthenticator.AuthenticationResult.Success
|
||||
}
|
||||
val fakeBiometricAuthenticator = FakeBiometricAuthenticator(
|
||||
setupLambda = setupLambda,
|
||||
authenticateLambda = authenticateLambda,
|
||||
)
|
||||
val biometricAuthenticatorManager = FakeBiometricAuthenticatorManager(
|
||||
canUseDeviceUnlock = true,
|
||||
createBiometricAuthenticator = { fakeBiometricAuthenticator },
|
||||
)
|
||||
val callbackHolder = DeviceUnlockCallbackHolder()
|
||||
val callback = FakeDeviceUnlockEntryPointCallback()
|
||||
|
||||
createDeviceUnlockPresenter(
|
||||
biometricAuthenticatorManager = biometricAuthenticatorManager,
|
||||
callbackHolder = callbackHolder,
|
||||
).test {
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.showApplicationPinCode).isFalse()
|
||||
}
|
||||
callbackHolder.requestUnlock(callback)
|
||||
advanceUntilIdle()
|
||||
setupLambda.assertions().isCalledOnce()
|
||||
authenticateLambda.assertions().isCalledOnce()
|
||||
skipItems(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when unlock requested and device unlock unavailable and app pin is configured, show app pin`() = runTest {
|
||||
val callbackHolder = DeviceUnlockCallbackHolder()
|
||||
val callback = FakeDeviceUnlockEntryPointCallback()
|
||||
val pinCodeManager = aPinCodeManager().apply {
|
||||
createPinCode("1234")
|
||||
}
|
||||
createDeviceUnlockPresenter(
|
||||
biometricAuthenticatorManager = FakeBiometricAuthenticatorManager(canUseDeviceUnlock = false),
|
||||
callbackHolder = callbackHolder,
|
||||
pinCodeManager = pinCodeManager,
|
||||
).test {
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.showApplicationPinCode).isFalse()
|
||||
}
|
||||
|
||||
callbackHolder.requestUnlock(callback)
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.showApplicationPinCode).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when unlock requested and no security, unlock immediately`() = runTest {
|
||||
val callbackHolder = DeviceUnlockCallbackHolder()
|
||||
val onUnlockedLambda = lambdaRecorder<Unit> { }
|
||||
val callback = FakeDeviceUnlockEntryPointCallback(
|
||||
onUnlockedLambda = onUnlockedLambda,
|
||||
)
|
||||
createDeviceUnlockPresenter(
|
||||
biometricAuthenticatorManager = FakeBiometricAuthenticatorManager(canUseDeviceUnlock = false),
|
||||
callbackHolder = callbackHolder,
|
||||
).test {
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.showApplicationPinCode).isFalse()
|
||||
}
|
||||
callbackHolder.requestUnlock(callback)
|
||||
skipItems(2)
|
||||
assertThat(callbackHolder.deviceUnlockCallback.value).isNull()
|
||||
onUnlockedLambda.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - CancelPinCode event cancels unlock request`() = runTest {
|
||||
val callbackHolder = DeviceUnlockCallbackHolder()
|
||||
val onCancelLambda = lambdaRecorder<Unit> { }
|
||||
val callback = FakeDeviceUnlockEntryPointCallback(
|
||||
onCancelLambda = onCancelLambda,
|
||||
)
|
||||
val pinCodeManager = aPinCodeManager().apply {
|
||||
createPinCode("1234")
|
||||
}
|
||||
createDeviceUnlockPresenter(
|
||||
biometricAuthenticatorManager = FakeBiometricAuthenticatorManager(canUseDeviceUnlock = false),
|
||||
callbackHolder = callbackHolder,
|
||||
pinCodeManager = pinCodeManager,
|
||||
).test {
|
||||
awaitItem()
|
||||
callbackHolder.requestUnlock(callback)
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.showApplicationPinCode).isTrue()
|
||||
state.eventSink(DeviceUnlockEvent.CancelPinCode)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.showApplicationPinCode).isFalse()
|
||||
}
|
||||
skipItems(1)
|
||||
onCancelLambda.assertions().isCalledOnce()
|
||||
assertThat(callbackHolder.deviceUnlockCallback.value).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDeviceUnlockPresenter(
|
||||
biometricAuthenticatorManager: FakeBiometricAuthenticatorManager = FakeBiometricAuthenticatorManager(),
|
||||
callbackHolder: DeviceUnlockCallbackHolder = DeviceUnlockCallbackHolder(),
|
||||
pinCodeManager: PinCodeManager = aPinCodeManager(),
|
||||
): DeviceUnlockPresenter {
|
||||
val pinUnlockHelper = PinUnlockHelper(
|
||||
biometricAuthenticatorManager = biometricAuthenticatorManager,
|
||||
pinCodeManager = pinCodeManager,
|
||||
)
|
||||
return DeviceUnlockPresenter(
|
||||
pinUnlockHelper = pinUnlockHelper,
|
||||
biometricAuthenticatorManager = biometricAuthenticatorManager,
|
||||
deviceUnlockCallbackHolder = callbackHolder,
|
||||
pinCodeManager = pinCodeManager,
|
||||
)
|
||||
}
|
||||
}
|
||||
+27
@@ -147,11 +147,37 @@ class PinUnlockPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - forDeviceUnlock is exposed in state`() = runTest {
|
||||
val presenter = createPinUnlockPresenter(forDeviceUnlock = true)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.canNavigateBack).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - pin entry changed event updates pin entry`() = runTest {
|
||||
val presenter = createPinUnlockPresenter()
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
state.eventSink(PinUnlockEvent.OnPinEntryChanged(halfCompletePin))
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
state.pinEntry.assertText(halfCompletePin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun AsyncData<PinEntry>.assertText(text: String) {
|
||||
dataOrNull()?.assertText(text)
|
||||
}
|
||||
|
||||
private suspend fun TestScope.createPinUnlockPresenter(
|
||||
forDeviceUnlock: Boolean = false,
|
||||
biometricAuthenticatorManager: BiometricAuthenticatorManager = FakeBiometricAuthenticatorManager(),
|
||||
callback: PinCodeManager.Callback = DefaultPinCodeManagerCallback(),
|
||||
logoutUseCase: FakeLogoutUseCase = FakeLogoutUseCase(logoutLambda = {}),
|
||||
@@ -162,6 +188,7 @@ class PinUnlockPresenterTest {
|
||||
createPinCode(completePin)
|
||||
}
|
||||
return PinUnlockPresenter(
|
||||
forDeviceUnlock = forDeviceUnlock,
|
||||
pinCodeManager = pinCodeManager,
|
||||
biometricAuthenticatorManager = biometricAuthenticatorManager,
|
||||
logoutUseCase = logoutUseCase,
|
||||
|
||||
+2
-4
@@ -28,12 +28,10 @@ import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class PinKeypadTest {
|
||||
class PinKeypadTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `clicking on a number emits the expected event`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PinKeypadModel>()
|
||||
|
||||
+22
@@ -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.lockscreen.test
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import io.element.android.features.lockscreen.api.DeviceUnlockEntryPoint
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeDeviceUnlockEntryPoint : DeviceUnlockEntryPoint {
|
||||
override fun createNode(
|
||||
parentNode: Node,
|
||||
buildContext: BuildContext,
|
||||
): Node = lambdaError()
|
||||
|
||||
override fun requestUnlock(callback: DeviceUnlockEntryPoint.Callback) = lambdaError()
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.lockscreen.test
|
||||
|
||||
import io.element.android.features.lockscreen.api.DeviceUnlockEntryPoint
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
|
||||
class FakeDeviceUnlockEntryPointCallback(
|
||||
private val onCancelLambda: () -> Unit = EnsureNeverCalled(),
|
||||
private val onUnlockedLambda: () -> Unit = EnsureNeverCalled(),
|
||||
) : DeviceUnlockEntryPoint.Callback {
|
||||
override fun onCancel() = onCancelLambda()
|
||||
override fun onUnlock() = onUnlockedLambda()
|
||||
}
|
||||
+2
-4
@@ -8,14 +8,12 @@
|
||||
|
||||
package io.element.android.features.login.impl
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.login.api.LoginParams
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DefaultLoginIntentResolverTest {
|
||||
class DefaultLoginIntentResolverTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `nominal case`() {
|
||||
val sut = DefaultLoginIntentResolver()
|
||||
|
||||
+2
-4
@@ -28,17 +28,15 @@ import io.element.android.libraries.matrix.test.auth.FakeHomeServerLoginCompatib
|
||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class DefaultElementClassicConnectionTest {
|
||||
class DefaultElementClassicConnectionTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `connection can be started Element Classic service can be bound`() = runTest {
|
||||
val connection = createDefaultElementClassicConnection(
|
||||
|
||||
+2
-4
@@ -8,7 +8,6 @@
|
||||
|
||||
package io.element.android.features.login.impl.qrcode
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.bumble.appyx.core.modality.AncestryInfo
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl
|
||||
@@ -22,16 +21,15 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginData
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class QrCodeLoginFlowNodeTest {
|
||||
class QrCodeLoginFlowNodeTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `backstack changes when confirmation steps are received`() = runTest {
|
||||
val qrCodeLoginManager = FakeQrCodeLoginManager()
|
||||
|
||||
+2
-4
@@ -16,7 +16,6 @@ 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.features.login.impl.accountprovider.anAccountProvider
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
||||
@@ -28,12 +27,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 org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ChooseAccountProviderViewTest {
|
||||
class ChooseAccountProviderViewTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest {
|
||||
val eventSink = EventsRecorder<ChooseAccountProviderEvents>(expectEvents = false)
|
||||
|
||||
+3
-5
@@ -10,7 +10,6 @@
|
||||
package io.element.android.features.login.impl.screens.classic
|
||||
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.login.impl.classic.ElementClassicConnection
|
||||
@@ -26,16 +25,15 @@ import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
// Use AndroidJUnit4 for the test with the Bitmap.
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ClassicFlowNodeHelperTest {
|
||||
// Use RobolectricTest for the test with the Bitmap.
|
||||
class ClassicFlowNodeHelperTest : RobolectricTest() {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user