mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 03:06:14 +00:00
Merge branch 'develop' into misc/reactions-pinned-messages
This commit is contained in:
@@ -52,6 +52,7 @@ Uncomment this markdown table below and edit the last line `|||`:
|
||||
|
||||
<!-- Depending on the Pull Request content, it can be acceptable if some of the following checkboxes stay unchecked. -->
|
||||
|
||||
- [ ] I am aware of the [etiquette](https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md#etiquette).
|
||||
- This PR was made with the help of AI:
|
||||
- [ ] Yes. In this case, please request a review by Copilot.
|
||||
- [ ] No.
|
||||
|
||||
@@ -34,6 +34,13 @@
|
||||
"/^org.jetbrains.kotlinx:kotlinx-datetime/",
|
||||
],
|
||||
},
|
||||
{
|
||||
// Keep Guava on the Android variant and ignore jre-only upgrades.
|
||||
"matchPackageNames": [
|
||||
"com.google.guava:guava",
|
||||
],
|
||||
"allowedVersions": "/-android$/",
|
||||
},
|
||||
{
|
||||
// Limit PostHog Android upgrade to one PR per month, the first day of the month
|
||||
"matchPackageNames": [
|
||||
|
||||
@@ -44,19 +44,19 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Assemble debug APKs
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
path: |
|
||||
app/build/outputs/apk/gplay/debug/*-universal-debug.apk
|
||||
app/build/outputs/apk/fdroid/debug/*-universal-debug.apk
|
||||
- uses: rnkdsh/action-upload-diawi@4e1421305be7cfc510d05f47850262eeaf345108 # v1.5.12
|
||||
- uses: rnkdsh/action-upload-diawi@76778fcd1f9f589efaaa62f63d5f03430bbb1306 # v1.5.13
|
||||
id: diawi
|
||||
# Do not fail the whole build if Diawi upload fails
|
||||
continue-on-error: true
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -56,12 +56,12 @@ jobs:
|
||||
- name: Clone submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Assemble debug Gplay Enterprise APK
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
# Skip in forks, it doesn't work even with the fallback token
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
- run: |
|
||||
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||
- name: Danger
|
||||
uses: danger/danger-js@67ed2c1f42fd2fc198cc3c14b43c8f83351f4fe9 # 13.0.5
|
||||
uses: danger/danger-js@58355dc2b274a87699c8b7deac01baba16d6f061 # 13.0.8
|
||||
with:
|
||||
args: "--dangerfile ./tools/danger/dangerfile.js"
|
||||
env:
|
||||
|
||||
@@ -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@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Set up Python 3.12
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
mkdir -p screenshots/en
|
||||
cp tests/uitests/src/test/snapshots/images/* screenshots/en
|
||||
- name: Deploy GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
|
||||
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./screenshots
|
||||
|
||||
@@ -13,10 +13,10 @@ jobs:
|
||||
# Skip in forks
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
- uses: actions/setup-java@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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.ref }}
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
- uses: actions/setup-java@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@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Assemble debug APK
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
concurrency:
|
||||
group: maestro-test
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
|
||||
@@ -32,11 +32,11 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@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@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: false
|
||||
|
||||
@@ -76,16 +76,16 @@ jobs:
|
||||
name: Dependency analysis
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Dependency analysis
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
steps:
|
||||
- name: Check membership
|
||||
if: github.event.pull_request.user.login != 'renovate[bot]'
|
||||
uses: tspascoal/get-user-teams-membership@57e9f42acd78f4d0f496b3be4368fc5f62696662 # v3
|
||||
uses: tspascoal/get-user-teams-membership@b2546c5affc730fd8e3d8483ae9ad3621938c2f9 # v4.0.2
|
||||
id: teams
|
||||
with:
|
||||
username: ${{ github.event.pull_request.user.login }}
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
name: Search for invalid screenshot files
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python 3.12
|
||||
@@ -65,16 +65,16 @@ jobs:
|
||||
name: Search for invalid dependencies
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Set up Python 3.12
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('check-konsist-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-konsist-develop-{0}', github.sha) || format('check-konsist-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -108,12 +108,12 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Run Konsist tests
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('check-compose-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-compose-develop-{0}', github.sha) || format('check-compose-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -149,12 +149,12 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Run compose tests
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('check-lint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-lint-develop-{0}', github.sha) || format('check-lint-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -183,12 +183,12 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Build Gplay Debug
|
||||
@@ -213,7 +213,7 @@ jobs:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('check-detekt-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-detekt-develop-{0}', github.sha) || format('check-detekt-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -228,12 +228,12 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Run Detekt
|
||||
@@ -254,7 +254,7 @@ jobs:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('check-ktlint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-ktlint-develop-{0}', github.sha) || format('check-ktlint-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -269,12 +269,12 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Run Ktlint check
|
||||
@@ -295,7 +295,7 @@ jobs:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('check-docs-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-docs-develop-{0}', github.sha) || format('check-docs-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -319,7 +319,7 @@ jobs:
|
||||
name: Check shell scripts
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run shellcheck
|
||||
@@ -333,10 +333,10 @@ jobs:
|
||||
permissions:
|
||||
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
||||
- uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
|
||||
|
||||
upload_reports:
|
||||
name: Project Check Suite
|
||||
@@ -344,7 +344,7 @@ jobs:
|
||||
needs: [konsist, lint, ktlint, detekt]
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -360,7 +360,7 @@ jobs:
|
||||
yarn add danger-plugin-lint-report --dev
|
||||
- name: Danger lint
|
||||
if: always()
|
||||
uses: danger/danger-js@67ed2c1f42fd2fc198cc3c14b43c8f83351f4fe9 # 13.0.5
|
||||
uses: danger/danger-js@58355dc2b274a87699c8b7deac01baba16d6f061 # 13.0.8
|
||||
with:
|
||||
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
|
||||
env:
|
||||
|
||||
@@ -17,6 +17,7 @@ jobs:
|
||||
permissions:
|
||||
# Need write permissions on PRs to remove the label "Record-Screenshots"
|
||||
pull-requests: write
|
||||
contents: write
|
||||
name: Record screenshots on branch ${{ github.event.pull_request.head.ref || github.ref_name }}
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'Record-Screenshots'
|
||||
@@ -53,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@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Record screenshots
|
||||
|
||||
@@ -34,16 +34,16 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
- name: Create app bundle
|
||||
env:
|
||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
group: ${{ format('build-release-main-enterprise-{0}', github.sha) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
@@ -82,12 +82,12 @@ jobs:
|
||||
- name: Clone submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
- name: Create Enterprise app bundle
|
||||
env:
|
||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||
@@ -122,16 +122,16 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
- name: Create APKs
|
||||
env:
|
||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||
|
||||
@@ -38,19 +38,19 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Build debug code and test fixtures
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
||||
with:
|
||||
only-labels: "X-Needs-Info"
|
||||
days-before-issue-stale: 30
|
||||
|
||||
@@ -13,16 +13,16 @@ jobs:
|
||||
# Skip in forks
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Set up Python 3.12
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
# No concurrency required, runs every time on a schedule.
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python 3.12
|
||||
|
||||
@@ -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@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
|
||||
# https://github.com/codecov/codecov-action
|
||||
- name: ☂️ Upload coverage reports to codecov
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
triage-new-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/91
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
if: >
|
||||
github.repository == 'element-hq/element-x-android'
|
||||
steps:
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/43
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
name: Move triaged needs info issues on board
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2
|
||||
id: addItem
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/91
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'Team: Element X Feature')
|
||||
steps:
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/73
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'Team: Verticals Feature')
|
||||
steps:
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/57
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'Team: QA') ||
|
||||
contains(github.event.issue.labels.*.name, 'X-Needs-Signoff')
|
||||
steps:
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/69
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'X-Needs-Signoff')
|
||||
steps:
|
||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
||||
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/89
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
Generated
+2
-1
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.3.20" />
|
||||
<option name="externalSystemId" value="Gradle" />
|
||||
<option name="version" value="2.4.0" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
@@ -7,7 +7,7 @@ appId: ${MAESTRO_APP_ID}
|
||||
- tapOn:
|
||||
text: ${MAESTRO_INVITEE1_MXID}
|
||||
index: 1
|
||||
- tapOn: "Send invite"
|
||||
- tapOn: "Continue"
|
||||
- takeScreenshot: build/maestro/330-createAndDeleteDM
|
||||
- tapOn: "maestroelement2"
|
||||
- scroll
|
||||
|
||||
@@ -24,8 +24,16 @@ appId: ${MAESTRO_APP_ID}
|
||||
text: ${MAESTRO_INVITEE2_MXID}
|
||||
index: 1
|
||||
- tapOn: "Invite"
|
||||
- runFlow:
|
||||
when:
|
||||
visible: 'Invite new contact to this room?'
|
||||
commands:
|
||||
- tapOn:
|
||||
id: "confirm_invite_unknown"
|
||||
# Close the keyboard if it's still open
|
||||
- tapOn: "Back"
|
||||
# Go back to the room details screen
|
||||
- tapOn: "Back"
|
||||
- tapOn: "aRoomName"
|
||||
- scrollUntilVisible:
|
||||
direction: DOWN
|
||||
element:
|
||||
|
||||
@@ -44,11 +44,14 @@ PRs must meet these rules.
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
- Use sentence-style commit/PR messages (no conventional commits).
|
||||
- Apply exactly **one** `PR-` label for changelog categorization.
|
||||
- PR title = changelog entry — make it descriptive; no "Fixes #…" prefixes.
|
||||
- Include screenshots or screen recordings for any UI changes.
|
||||
- Keep PRs focused; split changes over 1000 lines.
|
||||
- Sentence-style titles (no conventional commits).
|
||||
- Exactly one `pr-` label (see `.github/release.yml`).
|
||||
- Title = changelog entry — descriptive, no "Fixes #…".
|
||||
- Leave description template for the developer. Redirect them to the [contributing etiquette](CONTRIBUTING.md#etiquette).
|
||||
- Screenshots/videos for visual changes.
|
||||
- 500 additions max — split large changes.
|
||||
- Commits need a title and description; no tiny or massive commits.
|
||||
- No history rewrites.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+362
@@ -1,3 +1,365 @@
|
||||
Changes in Element X v26.06.4
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.06.4 -->
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Add well-known parsing and extension seam for custom recovery passphrase by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/6944
|
||||
### 🙌 Improvements
|
||||
* Add ability to forward a message to several rooms. by @bmarty in https://github.com/element-hq/element-x-android/pull/7013
|
||||
### 🐛 Bugfixes
|
||||
* `screen_custom_recovery_key_input_notice` is now a plurals. by @bmarty in https://github.com/element-hq/element-x-android/pull/7045
|
||||
* Replace `ContentResolver.delete` usage with a backwards-compatible version by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7053
|
||||
### Dependency upgrades
|
||||
* Update kotlin to v0.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7036
|
||||
* Update gradle/actions action to v6.1.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7037
|
||||
* Update jna monorepo to v5.19.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7026
|
||||
* Update coil to v3.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7043
|
||||
* Update metro to v1.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7048
|
||||
* Update dependency io.sentry:sentry-android to v8.43.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7047
|
||||
* Update dependencyAnalysis to v3.15.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7050
|
||||
* Compile with sdk37 and target API 37 by @bmarty in https://github.com/element-hq/element-x-android/pull/7041
|
||||
### Others
|
||||
* call: Remove deprecated web<->EX api calls, use the new ones by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/6628
|
||||
* Fix deprecation warning by @bmarty in https://github.com/element-hq/element-x-android/pull/7042
|
||||
* Let Divider use our theme color. by @bmarty in https://github.com/element-hq/element-x-android/pull/7054
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.06.3...v26.06.4
|
||||
|
||||
Changes in Element X v26.06.3
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.06.3 -->
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Click on avatar in moderation bottom sheet opens avatar preview by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6991
|
||||
### 🙌 Improvements
|
||||
* Change: hide UserLocationPuck when sharing from current device by @ganfra in https://github.com/element-hq/element-x-android/pull/6955
|
||||
* Replace "Edit" button with Crop icon. by @bmarty in https://github.com/element-hq/element-x-android/pull/6953
|
||||
* change(location): ensure permissions are always requested at least once by @ganfra in https://github.com/element-hq/element-x-android/pull/6996
|
||||
* Refactor: replace client-side event filtering for public rooms with SDK by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7025
|
||||
### 🐛 Bugfixes
|
||||
* Ensure the video preview can be played several times. by @bmarty in https://github.com/element-hq/element-x-android/pull/6964
|
||||
* Fix "Sent" checkmark disappearing when the message is read and "Share presence" is disabled by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6985
|
||||
* Make `BaseRoom.getDirectRoomMember` change when `roomInfo.isDm` changes by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6989
|
||||
* Fix invites not being sent during the room creation by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7018
|
||||
* Fix sharing media inside the app by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7016
|
||||
* Fix ANRs when calculating DB sizes for Sentry by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7017
|
||||
* Fix thread list not loading older threads by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7034
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6986
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7021
|
||||
### 🧱 Build
|
||||
* Try fixing flaky screenshots for location timeline items by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6954
|
||||
* `ScanQrCodeViewTest`: try fixing broken test in CI by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7027
|
||||
### 🚧 In development 🚧
|
||||
* Ensure that the application rotate the QrCode if one day the SDK emits the `.Expired` error. by @bmarty in https://github.com/element-hq/element-x-android/pull/6993
|
||||
### Dependency upgrades
|
||||
* Update dependency androidx.javascriptengine:javascriptengine to v1.1.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6785
|
||||
* Update dependency com.google.firebase:firebase-bom to v34.14.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6967
|
||||
* Update dependency com.github.matrix-org:matrix-analytics-events to v0.36.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6963
|
||||
* Update dependencyAnalysis to v3.14.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6892
|
||||
* Update dependency io.element.android:element-call-embedded to v0.20.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6988
|
||||
* Update dependency io.mockk:mockk to v1.14.11 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6977
|
||||
* Update zizmorcore/zizmor-action action to v0.5.6 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6845
|
||||
* deps(codecov) : bumps to v7.0.0 to unblock CI by @ganfra in https://github.com/element-hq/element-x-android/pull/7002
|
||||
* Update wysiwyg to v2.42.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7009
|
||||
* Update actions/checkout action to v6.0.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6997
|
||||
* fix(deps): update android.gradle.plugin to v9 (major) by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6021
|
||||
* Update dependencyAnalysis to v3.14.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6995
|
||||
* Update dependency io.sentry:sentry-android to v8.43.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6895
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.06.11 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7014
|
||||
* Update dependency org.maplibre.gl:android-sdk to v13.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7022
|
||||
* Update dependency com.google.firebase:firebase-bom to v34.14.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7029
|
||||
* Update okhttp monorepo to v5.4.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7031
|
||||
### Others
|
||||
* Fix some string mappings for link new device errors by @hughns in https://github.com/element-hq/element-x-android/pull/6675
|
||||
* Fallback to the default push gateway on error by @p1gp1g in https://github.com/element-hq/element-x-android/pull/5742
|
||||
* Fix some string mappings for link new device errors by @bmarty in https://github.com/element-hq/element-x-android/pull/6958
|
||||
* Add `/myroomnick` slash command to change your display name in the current room by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6865
|
||||
* Update release notes for `v26.05.1` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6990
|
||||
* Fix message drafts not being saved in Threads by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6984
|
||||
* build-rust-sdk: allow skipping git update for kotlin components by @richvdh in https://github.com/element-hq/element-x-android/pull/7012
|
||||
* Fix broken screenshot tests by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7038
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.06.2...v26.06.3
|
||||
|
||||
Changes in Element X v26.06.2
|
||||
=============================
|
||||
|
||||
Security fixes 🔐
|
||||
-----------------
|
||||
- Fixed an issue with OIDC callback URLs that could cause the app to crash [(GHSA-r4f9-46vw-v7g3)](https://github.com/element-hq/element-x-android/security/advisories/GHSA-r4f9-46vw-v7g3).
|
||||
|
||||
Changes in Element X v26.06.1
|
||||
=============================
|
||||
|
||||
The release of `v26.06.0` was cancelled because it contained some changes that would prevent some users with devices having no Vulkan support from being able to install the app. This release contains the same changes as `v26.06.0` but with the Vulkan requirement removed.
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Image edition before sending: crop and rotate. by @bmarty in https://github.com/element-hq/element-x-android/pull/6842
|
||||
* Use a raw key for the SDK stores for new sessions by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6835
|
||||
* Notification settings: pick custom sounds for messages and call ringtones by @bmarty in https://github.com/element-hq/element-x-android/pull/6897
|
||||
### 🙌 Improvements
|
||||
* Do not show membership/profile events in public rooms by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6360
|
||||
* Use `runBlocking` for the token refresh logic by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6863
|
||||
* Move empty day separator filtering to a timeline post-processor by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6866
|
||||
* Add better logs to track token update failures by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6859
|
||||
* Read map tiler custom style from matrix .well-known file by @bmarty in https://github.com/element-hq/element-x-android/pull/6886
|
||||
* change(permissions) : allow to change ShareLiveLocation permission by @ganfra in https://github.com/element-hq/element-x-android/pull/6890
|
||||
* Avoid SQLCipher key derivation by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6774
|
||||
* change: replace the maplibre-compose UserLocationState to a simpler one by @ganfra in https://github.com/element-hq/element-x-android/pull/6913
|
||||
* Change : location provider by @ganfra in https://github.com/element-hq/element-x-android/pull/6935
|
||||
* Add flip actions to image edition by @bmarty in https://github.com/element-hq/element-x-android/pull/6949
|
||||
* Remove the `FloatingDateBadge` feature flag by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6950
|
||||
### 🐛 Bugfixes
|
||||
* Fix media viewer flickering by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6800
|
||||
* Fix 'Conversation label cannot be empty' error by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6823
|
||||
* Release proximity wakelock on Element Call when call ends by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6825
|
||||
* Hide edit pencil from accessibility by @bmarty in https://github.com/element-hq/element-x-android/pull/6826
|
||||
* Don't compress images sent through the Files attachment picker by @cizra in https://github.com/element-hq/element-x-android/pull/6755
|
||||
* [a11y] Improve accessibility of video and audio player by @bmarty in https://github.com/element-hq/element-x-android/pull/6830
|
||||
* [a11y] Improve accessibility of screen headers. by @bmarty in https://github.com/element-hq/element-x-android/pull/6827
|
||||
* Add mark as read / unread in room details by @bmarty in https://github.com/element-hq/element-x-android/pull/6818
|
||||
* Fix formatting inconsistencies in latest event summaries by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6855
|
||||
* Fix public read receipts being sent by mistake by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6838
|
||||
* Fix app having a pink top bar in the recent app list when PIN lock is setup by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6903
|
||||
* Create log messages from `WebView` just once by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6923
|
||||
* Use secondary color icon for leading icons by @bmarty in https://github.com/element-hq/element-x-android/pull/6926
|
||||
* Use reverse ordering for `FilterEmptyDayPostProcessor` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6927
|
||||
* Make Vulkan a *not required* feature by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6961
|
||||
### 🗣 Translations
|
||||
* Sync Strings - new translations to Catalan by @ElementBot in https://github.com/element-hq/element-x-android/pull/6856
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6921
|
||||
### 🧱 Build
|
||||
* Remove DI-generated code from Kover reports by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6834
|
||||
* Disable cron jobs and private SSH key jobs in forks by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6821
|
||||
* Revert "Disable cron jobs and private SSH key jobs in forks" by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6905
|
||||
* Downgrade detekt compose rules by @bmarty in https://github.com/element-hq/element-x-android/pull/6846
|
||||
* Add Stefan's etiquette to the project. by @bmarty in https://github.com/element-hq/element-x-android/pull/6909
|
||||
### 🚧 In development 🚧
|
||||
* [Link new device] Rotate QrCode instead of showing an error by @bmarty in https://github.com/element-hq/element-x-android/pull/6817
|
||||
* Fix image resizing behavior. by @bmarty in https://github.com/element-hq/element-x-android/pull/6924
|
||||
### Dependency upgrades
|
||||
* Update dependency net.zetetic:sqlcipher-android to v4.16.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6819
|
||||
* Update dependencyAnalysis to v3.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6813
|
||||
* Update metro to v1.1.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6832
|
||||
* Update dependency io.element.android:element-call-embedded to v0.19.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6833
|
||||
* Update kotlin to v2.3.8 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6822
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.05.20 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6831
|
||||
* Update media3 to v1.10.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6816
|
||||
* Update peaceiris/actions-gh-pages action to v4.1.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6820
|
||||
* Update dependencyAnalysis to v3.12.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6840
|
||||
* Update codecov/codecov-action action to v6.0.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6864
|
||||
* Update dependency io.element.android:element-call-embedded to v0.20.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6876
|
||||
* Update roborazzi to v1.62.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6858
|
||||
* Update dependency androidx.compose:compose-bom to v2026.05.01 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6888
|
||||
* Update roborazzi to v1.63.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6893
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.05.26 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6885
|
||||
* Update dependency org.maplibre.compose:maplibre-compose to v0.13.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6891
|
||||
* Update actions/stale action to v10.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6904
|
||||
* Update tspascoal/get-user-teams-membership action to v4.0.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6900
|
||||
* Update dependency org.maplibre.gl:android-sdk to v13.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6930
|
||||
* Update dependency org.unifiedpush.android:connector to v3.3.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6937
|
||||
* Update kotlin to v2.3.9 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6946
|
||||
* Update dependency com.posthog:posthog-android to v3.47.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6920
|
||||
* Import compound token from release 10.2.1 https://github.com/element-hq/compound-design-tokens/releases/tag/v10.2.1 by @bmarty in https://github.com/element-hq/element-x-android/pull/6942
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.06.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6947
|
||||
* Update dependency com.github.matrix-org:matrix-analytics-events to v0.36.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6939
|
||||
### Others
|
||||
* Make the avatar in the room member moderation bottom sheet clickable by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6705
|
||||
* Do not hide your own media for "Show media in timeline" by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6898
|
||||
* Report problem: add an optional TextField to provide a GitHub issue number. by @bmarty in https://github.com/element-hq/element-x-android/pull/6911
|
||||
* Fix read receipts not shown for calls by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6889
|
||||
* Reorder room detail items by @bmarty in https://github.com/element-hq/element-x-android/pull/6929
|
||||
* [Media viewer] File preview improvement by @bmarty in https://github.com/element-hq/element-x-android/pull/6933
|
||||
* [Media bottom sheet] UI iteration by @bmarty in https://github.com/element-hq/element-x-android/pull/6938
|
||||
* Add unread count to the room unread indicator by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6887
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.05.2...v26.06.1
|
||||
|
||||
Changes in Element X v26.05.2
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.05.2 -->
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Remove SignInWithClassic FeatureFlag to enable the feature. by @bmarty in https://github.com/element-hq/element-x-android/pull/6698
|
||||
* Create a new room when inviting people in a DM by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6756
|
||||
* Remove LiveLocationSharing feature flag by @ganfra in https://github.com/element-hq/element-x-android/pull/6811
|
||||
### 🙌 Improvements
|
||||
* Disable biometric unlock when we disable pin code unlock by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6781
|
||||
### 🐛 Bugfixes
|
||||
* Fix room list duplicate-detection telemetry crashing before it can report by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/6791
|
||||
* Only load full media on media viewer when it's the visible item by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6794
|
||||
* Attempt to fix room list item duplicates at midnight by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6793
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6798
|
||||
### 🧱 Build
|
||||
* Fix Maestro again after changes to the invite flow by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6796
|
||||
* Renovate: Keep Guava on the Android variant and ignore jre-only upgrades by @bmarty in https://github.com/element-hq/element-x-android/pull/6776
|
||||
### Dependency upgrades
|
||||
* Update dependency androidx.compose:compose-bom to v2026.05.00 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6784
|
||||
* Update dependency io.sentry:sentry-android to v8.41.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6787
|
||||
* Update kotlin by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6790
|
||||
* Update camera to v1.6.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6783
|
||||
* Update dependency androidx.webkit:webkit to v1.16.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6786
|
||||
* Update dependency com.google.firebase:firebase-bom to v34.13.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6789
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.05.18 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6805
|
||||
### Others
|
||||
* Add MIDI playback by @cizra in https://github.com/element-hq/element-x-android/pull/6770
|
||||
* Show error message when using "Sign in with QR code" with a QR from a device that is also not signed in by @hughns in https://github.com/element-hq/element-x-android/pull/6802
|
||||
|
||||
## New Contributors
|
||||
* @jennaharris7 made their first contribution in https://github.com/element-hq/element-x-android/pull/6791
|
||||
* @cizra made their first contribution in https://github.com/element-hq/element-x-android/pull/6770
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.05.1...v26.05.2
|
||||
|
||||
Changes in Element X v26.05.1
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.05.1 -->
|
||||
|
||||
Security fixes 🔐
|
||||
-----------------
|
||||
|
||||
This release contains some important security fixes:
|
||||
|
||||
- Check the user ID in the `sender_device_keys` property of Olm-encrypted to-device events to prevent sender spoofing by homeserver owners. ([#6553](https://github.com/matrix-org/matrix-rust-sdk/pull/6553), High, [CVE-2026-45056](https://www.cve.org/CVERecord?id=CVE-2026-45056), [GHSA-wfq4-36m3-9g42](https://github.com/matrix-org/matrix-rust-sdk/security/advisories/GHSA-wfq4-36m3-9g42))
|
||||
- Reject invalid edits as candidates for timeline updates. ([#6454](https://github.com/matrix-org/matrix-rust-sdk/pull/6454), Moderate, [CVE-2026-45057](https://www.cve.org/CVERecord?id=CVE-2026-45057), [GHSA-h97m-27fx-42rx](https://github.com/matrix-org/matrix-rust-sdk/security/advisories/GHSA-h97m-27fx-42rx))
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Make Element Call screen work edge-to-edge by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6634
|
||||
### 🙌 Improvements
|
||||
* Stop removing the `logs` dir when clearing cache by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6765
|
||||
* Adapt to new DM definition changes in the SDK by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6748
|
||||
* feat: Update call started timeline item + declined support by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/6649
|
||||
### 🐛 Bugfixes
|
||||
* Improve pin code UX by @bmarty in https://github.com/element-hq/element-x-android/pull/6744
|
||||
* Use just the other user's avatar for DM details by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6738
|
||||
* Improve `FetchPushForegroundService`'s reliability by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6757
|
||||
* Prevent user from starting Live Location Sharing in thread by @bmarty in https://github.com/element-hq/element-x-android/pull/6767
|
||||
* Fix media playback from the timeline broken when exiting a thread by @bmarty in https://github.com/element-hq/element-x-android/pull/6771
|
||||
* Pin code: remove the key if there is no pin code by @bmarty in https://github.com/element-hq/element-x-android/pull/6780
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6761
|
||||
### 🚧 In development 🚧
|
||||
* Feature : share live location by @ganfra in https://github.com/element-hq/element-x-android/pull/6741
|
||||
### Dependency upgrades
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.05.7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6746
|
||||
* Update actions/add-to-project action to v2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6758
|
||||
* Update dependency io.github.sergio-sastre.ComposablePreviewScanner:android to v0.9.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6759
|
||||
* Update dependency io.element.android:element-call-embedded to v0.19.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6766
|
||||
* Update metro to v1 (major) by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6720
|
||||
* Update tspascoal/get-user-teams-membership action to v4.0.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6750
|
||||
* Update plugin sonarqube to v7.3.0.8198 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6743
|
||||
* Update plugin dependencycheck to v12.2.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6760
|
||||
* Update dependency com.google.guava:guava to v33.6.0-android by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6646
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.05.13 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6779
|
||||
### Others
|
||||
* Render media captions formatting in the media viewer by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6729
|
||||
* Reduce FeatureFlag `Knock` effect on room creation and room edition forms by @bmarty in https://github.com/element-hq/element-x-android/pull/6768
|
||||
* Use the right analytics span as a parent in `checkNetworkConnection` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6751
|
||||
* Add missing strings `theme.black` by @bmarty in https://github.com/element-hq/element-x-android/pull/6772
|
||||
* Map back button in web view to esc (revive fixed version of: https://github.com/element-hq/element-x-android/pull/6724) by @toger5 in https://github.com/element-hq/element-x-android/pull/6725
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.05.0...v26.05.1
|
||||
|
||||
Changes in Element X v26.05.0
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.05.0 -->
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Add flag for automatic back pagination feature by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6637
|
||||
* Promote "history sharing on invite" out of developer options by @richvdh in https://github.com/element-hq/element-x-android/pull/6647
|
||||
* Remove RoomDirectorySearch feature flag — always enable the feature by @Copilot in https://github.com/element-hq/element-x-android/pull/6736
|
||||
### 🙌 Improvements
|
||||
* Change native back button behavior in EC view (close settings in EC with os native back) by @toger5 in https://github.com/element-hq/element-x-android/pull/6642
|
||||
* Revert PR #6642 by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6724
|
||||
* Use 'Report a problem' string instead of 'Report bug' by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6735
|
||||
### 🐛 Bugfixes
|
||||
* Remove distributed tracing of the 'timeline loading' flow by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6644
|
||||
* Set max lines for 'in reply to' view conditionally by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6612
|
||||
* Mention pill cut off by @bmarty in https://github.com/element-hq/element-x-android/pull/6651
|
||||
* Ensure that bottom sheet can scroll by @bmarty in https://github.com/element-hq/element-x-android/pull/6661
|
||||
* Remove legacy `mx-reply` from `toPlainText` formatted event contents by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6683
|
||||
* Fix ANRs when receiving push notifications by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6696
|
||||
* Mitigate a deadlock when loading room timelines by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6674
|
||||
* Fix calls on Huawei devices: skip addWebMessageListener on Chromium < 119 by @manfrommedan in https://github.com/element-hq/element-x-android/pull/6640
|
||||
* Allow cancelling room loading in Home screen by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6723
|
||||
* Let our Json parser accept comments and trailing comma. by @bmarty in https://github.com/element-hq/element-x-android/pull/6700
|
||||
* Fix low width image message by @krbns in https://github.com/element-hq/element-x-android/pull/6692
|
||||
* Make icons in the Chat screen top bar 16dp by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6733
|
||||
* Fix back button sometimes not working after exiting a thread by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6732
|
||||
* Make send event state UI easier to click by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6739
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6658
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/6716
|
||||
### 🧱 Build
|
||||
* Fix record screenshots action permissions by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6679
|
||||
* Fix dependency error by @bmarty in https://github.com/element-hq/element-x-android/pull/6697
|
||||
### 🚧 In development 🚧
|
||||
* [Link new device] Add missing screen to render digits that the user has to type on the other device by @bmarty in https://github.com/element-hq/element-x-android/pull/6680
|
||||
### Dependency upgrades
|
||||
* Update dependency io.nlopez.compose.rules:detekt to v0.5.7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6594
|
||||
* Update zizmorcore/zizmor-action action to v0.5.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6630
|
||||
* Update dependency io.sentry:sentry-android to v8.38.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6597
|
||||
* fix(deps): update camera to v1.6.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6514
|
||||
* Update dependency io.sentry:sentry-android to v8.39.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6648
|
||||
* Update dependency io.element.android:element-call-embedded to v0.19.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6662
|
||||
* Update dependencyAnalysis to v3.9.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6657
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.04.27 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6666
|
||||
* Update dependency io.sentry:sentry-android to v8.40.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6691
|
||||
* Update dependency org.jsoup:jsoup to v1.22.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6660
|
||||
* Update kotlin by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6687
|
||||
* Update dependency androidx.compose:compose-bom to v2026.04.01 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6693
|
||||
* Update dependency io.nlopez.compose.rules:detekt to v0.5.8 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6711
|
||||
* Update dependency com.posthog:posthog-android to v3.43.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6704
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.05.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6718
|
||||
* Update roborazzi to v1.60.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6722
|
||||
* Update dependency net.zetetic:sqlcipher-android to v4.15.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6727
|
||||
* Update dependency org.maplibre.gl:android-sdk to v13.1.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6731
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.05.6 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6734
|
||||
* Update dependencyAnalysis to v3.10.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6742
|
||||
* Update tspascoal/get-user-teams-membership action to v4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6747
|
||||
### Others
|
||||
* devx: fix build sdk script options for macos by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/6636
|
||||
* PR:Fix mention pill cut off by @krbns in https://github.com/element-hq/element-x-android/pull/6622
|
||||
* Update media viewer UI by @bmarty in https://github.com/element-hq/element-x-android/pull/6643
|
||||
* Strip formatting from media captions in room summary by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6670
|
||||
* Update error mappings for Link new device flow by @hughns in https://github.com/element-hq/element-x-android/pull/6677
|
||||
* Rename `OIDC` components and variables to `OAuth` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6686
|
||||
* [Link new device] Add missing error case "already signed in" by @bmarty in https://github.com/element-hq/element-x-android/pull/6688
|
||||
* Improve detection of completion for Link new device flow by @hughns in https://github.com/element-hq/element-x-android/pull/6681
|
||||
* Remove external call support by @bmarty in https://github.com/element-hq/element-x-android/pull/6668
|
||||
* [a11y] Fix a set of issues by @bmarty in https://github.com/element-hq/element-x-android/pull/6650
|
||||
* Add clipping to RoomSummaryRow by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6654
|
||||
* Fix media viewer flickering and crashing by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6715
|
||||
* Rename verification methods by @bmarty in https://github.com/element-hq/element-x-android/pull/6726
|
||||
* Add a way to tweak MAS url. by @bmarty in https://github.com/element-hq/element-x-android/pull/6682
|
||||
* Fix 2 x Crash the app in Developer Options - Update AppDeveloperSettingsView.kt by @escix in https://github.com/element-hq/element-x-android/pull/6708
|
||||
* Introduce UI sample by @bmarty in https://github.com/element-hq/element-x-android/pull/6740
|
||||
|
||||
## New Contributors
|
||||
* @krbns made their first contribution in https://github.com/element-hq/element-x-android/pull/6622
|
||||
* @toger5 made their first contribution in https://github.com/element-hq/element-x-android/pull/6642
|
||||
* @manfrommedan made their first contribution in https://github.com/element-hq/element-x-android/pull/6640
|
||||
* @Copilot made their first contribution in https://github.com/element-hq/element-x-android/pull/6736
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.04.4...v26.05.0
|
||||
|
||||
Changes in Element X v26.04.4
|
||||
=============================
|
||||
|
||||
|
||||
+23
-2
@@ -6,7 +6,8 @@
|
||||
* [I want to help translating Element](#i-want-to-help-translating-element)
|
||||
* [I want to fix a bug](#i-want-to-fix-a-bug)
|
||||
* [I want to add a new feature or enhancement](#i-want-to-add-a-new-feature-or-enhancement)
|
||||
* [Developer onboarding](#developer-onboarding)
|
||||
* [Etiquette](#etiquette)
|
||||
* [Developer onboarding](#developer-onboarding)
|
||||
* [Submitting the PRs](#submitting-the-prs)
|
||||
* [Android Studio settings](#android-studio-settings)
|
||||
* [Compilation](#compilation)
|
||||
@@ -63,7 +64,27 @@ Once we know that you want to contribute and have confirmed that the new feature
|
||||
|
||||
Only once all of the above is met should you open a PR with your proposed changes.
|
||||
|
||||
## Developer onboarding
|
||||
### Etiquette
|
||||
|
||||
* As stated above all significant changes should be communicated through an issue before opening a PR
|
||||
* We are happy to receive contributions but features always require maintenance, so depending on the change we might not be willing to accept it
|
||||
* We are also fine with AI led contributions within reasonable bounds
|
||||
* You are completely responsible for the quality of the PR
|
||||
* If the PR doesn't show minimal effort (code does not compile, code does not work as expected, etc.) on your part it will be closed
|
||||
* Please write the description yourself, we don't have the bandwidth to read LLM essays. The code needs to speak for itself.
|
||||
* We use git for version control and GitHub for reviews, so in order to make everybody's life easier please:
|
||||
* Keep the existing pull request template
|
||||
* Don't submit large PRs, especially if not previously talked about. Anything above 200 lines is large (excluding generated code e.g. tests, translations, mocks)
|
||||
* Please don't open unfinished PRs and expect us to fill in the details
|
||||
* If you would like our opinion/direction on unfinished code please link your branch or idea in the ticket
|
||||
* Please limit the number of commits in a single PR. We are perfectly happy with splitting work across multiple sessions as long as they're logically independant and show promise of progress (ideally expressed through a ticket)
|
||||
* Each and every commit should stand on its own, clearly explaining what it does and why
|
||||
* Once a PR goes into review please don't rewrite the history unless agreed so with the reviewer.
|
||||
* Tweaks and fixes following review can be directly committed (to be interactively rebased later) or as fixups
|
||||
|
||||
*The reviewer's response time will generally match yours. Switching contexts is very hard so please act accordingly. You are responsible for making the reviewers job enjoyable!*
|
||||
|
||||
### Developer onboarding
|
||||
|
||||
For a detailed overview of the project, see [Developer Onboarding](./docs/_developer_onboarding.md).
|
||||
|
||||
|
||||
+11
-5
@@ -24,11 +24,11 @@ import extension.buildConfigFieldStr
|
||||
import extension.locales
|
||||
import extension.setupDependencyInjection
|
||||
import extension.testCommonDependencies
|
||||
import org.sonarqube.gradle.SonarResolverTask
|
||||
import java.util.Locale
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-application")
|
||||
alias(libs.plugins.kotlin.android)
|
||||
// When using precompiled plugins, we need to apply the firebase plugin like this
|
||||
id(libs.plugins.firebaseAppDistribution.get().pluginId)
|
||||
id("kotlin-parcelize")
|
||||
@@ -103,13 +103,13 @@ android {
|
||||
logger.warnInBox("Building ${defaultConfig.applicationId} ($baseAppName) [$buildType]")
|
||||
|
||||
buildTypes {
|
||||
val oidcRedirectSchemeBase = BuildTimeConfig.METADATA_HOST_REVERSED ?: "io.element.android"
|
||||
val oAuthRedirectSchemeBase = BuildTimeConfig.METADATA_HOST_REVERSED ?: "io.element.android"
|
||||
getByName("debug") {
|
||||
resValue("string", "app_name", "$baseAppName dbg")
|
||||
resValue(
|
||||
"string",
|
||||
"login_redirect_scheme",
|
||||
"$oidcRedirectSchemeBase.debug",
|
||||
"$oAuthRedirectSchemeBase.debug",
|
||||
)
|
||||
applicationIdSuffix = ".debug"
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
@@ -120,7 +120,7 @@ android {
|
||||
resValue(
|
||||
"string",
|
||||
"login_redirect_scheme",
|
||||
oidcRedirectSchemeBase,
|
||||
oAuthRedirectSchemeBase,
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
|
||||
@@ -157,7 +157,7 @@ android {
|
||||
resValue(
|
||||
"string",
|
||||
"login_redirect_scheme",
|
||||
"$oidcRedirectSchemeBase.nightly",
|
||||
"$oAuthRedirectSchemeBase.nightly",
|
||||
)
|
||||
matchingFallbacks += listOf("release")
|
||||
signingConfig = signingConfigs.getByName("nightly")
|
||||
@@ -189,6 +189,7 @@ android {
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
resValues = true
|
||||
}
|
||||
flavorDimensions += "store"
|
||||
productFlavors {
|
||||
@@ -245,6 +246,11 @@ androidComponents {
|
||||
configureLicensesTasks(reportingExtension)
|
||||
}
|
||||
|
||||
// Configure the SonarQube plugin to wait for the resource generation tasks to complete before running the analysis.
|
||||
tasks.withType<SonarResolverTask>().configureEach {
|
||||
dependsOn("generateGplayDebugResValues", "generateGplayDebugAndroidTestResValues")
|
||||
}
|
||||
|
||||
setupDependencyInjection()
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
android:scheme="elementx" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
Oidc redirection
|
||||
OAuth redirection
|
||||
-->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
+3
-3
@@ -10,14 +10,14 @@ package io.element.android.x.oidc
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.matrix.api.auth.OidcRedirectUrlProvider
|
||||
import io.element.android.libraries.matrix.api.auth.OAuthRedirectUrlProvider
|
||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
import io.element.android.x.R
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultOidcRedirectUrlProvider(
|
||||
class DefaultOAuthRedirectUrlProvider(
|
||||
private val stringProvider: StringProvider,
|
||||
) : OidcRedirectUrlProvider {
|
||||
) : OAuthRedirectUrlProvider {
|
||||
override fun provide() = buildString {
|
||||
append(stringProvider.getString(R.string.login_redirect_scheme))
|
||||
append(":/")
|
||||
@@ -11,7 +11,14 @@
|
||||
<item name="windowSplashScreenBackground">@color/splashscreen_bg_dark</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/transparent</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.ElementX</item>
|
||||
<item name="android:windowBackground">@color/splashscreen_bg_dark</item>
|
||||
<item name="android:statusBarColor">@color/splashscreen_bg_dark</item>
|
||||
<item name="android:navigationBarColor">@color/splashscreen_bg_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.ElementX" parent="Theme.Material3.Dark.NoActionBar" />
|
||||
<style name="Theme.ElementX" parent="Theme.Material3.Dark.NoActionBar">
|
||||
<item name="android:windowBackground">@color/splashscreen_bg_dark</item>
|
||||
<item name="android:statusBarColor">@color/splashscreen_bg_dark</item>
|
||||
<item name="android:navigationBarColor">@color/splashscreen_bg_dark</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -10,8 +10,14 @@
|
||||
<item name="windowSplashScreenBackground">@color/splashscreen_bg_light</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/transparent</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.ElementX</item>
|
||||
<item name="android:windowBackground">@color/splashscreen_bg_light</item>
|
||||
<item name="android:statusBarColor">@color/splashscreen_bg_light</item>
|
||||
<item name="android:navigationBarColor">@color/splashscreen_bg_light</item>
|
||||
</style>
|
||||
<style name="Theme.ElementX" parent="Theme.Material3.Light.NoActionBar">
|
||||
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
|
||||
<item name="android:windowBackground">@color/splashscreen_bg_light</item>
|
||||
<item name="android:statusBarColor">@color/splashscreen_bg_light</item>
|
||||
<item name="android:navigationBarColor">@color/splashscreen_bg_light</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
-->
|
||||
<paths>
|
||||
<cache-path name="cache" path="." />
|
||||
<files-path name="notification_sounds" path="notification_sounds/" />
|
||||
</paths>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="be"/>
|
||||
<locale android:name="bg"/>
|
||||
<locale android:name="ca"/>
|
||||
<locale android:name="cs"/>
|
||||
<locale android:name="cy"/>
|
||||
<locale android:name="da"/>
|
||||
|
||||
@@ -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" }
|
||||
|
||||
+2
-2
@@ -13,13 +13,13 @@ import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import io.element.android.x.R
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultOidcRedirectUrlProviderTest {
|
||||
class DefaultOAuthRedirectUrlProviderTest {
|
||||
@Test
|
||||
fun `test provide`() {
|
||||
val stringProvider = FakeStringProvider(
|
||||
defaultResult = "str"
|
||||
)
|
||||
val sut = DefaultOidcRedirectUrlProvider(
|
||||
val sut = DefaultOAuthRedirectUrlProvider(
|
||||
stringProvider = stringProvider,
|
||||
)
|
||||
val result = sut.provide()
|
||||
@@ -48,6 +48,8 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.coroutines.core)
|
||||
implementation(libs.androidx.annotationjvm)
|
||||
implementation(libs.androidx.corektx)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
}
|
||||
|
||||
@@ -33,13 +33,14 @@ dependencies {
|
||||
implementation(projects.libraries.deeplink.api)
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.oidc.api)
|
||||
implementation(projects.libraries.oauth.api)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.push.api)
|
||||
implementation(projects.libraries.pushproviders.api)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.matrixmedia.api)
|
||||
implementation(projects.libraries.sessionStorage.api)
|
||||
implementation(projects.libraries.uiCommon)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.features.login.api)
|
||||
@@ -59,7 +60,7 @@ dependencies {
|
||||
testImplementation(projects.features.login.test)
|
||||
testImplementation(projects.features.share.test)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.oidc.test)
|
||||
testImplementation(projects.libraries.oauth.test)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.libraries.pushproviders.test)
|
||||
|
||||
@@ -54,6 +54,7 @@ import io.element.android.features.ftue.api.state.FtueService
|
||||
import io.element.android.features.ftue.api.state.FtueState
|
||||
import io.element.android.features.home.api.HomeEntryPoint
|
||||
import io.element.android.features.linknewdevice.api.LinkNewDeviceEntryPoint
|
||||
import io.element.android.features.location.api.live.ActiveLiveLocationShareManager
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorContainer
|
||||
@@ -151,6 +152,7 @@ class LoggedInFlowNode(
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val analyticsRoomListStateWatcher: AnalyticsRoomListStateWatcher,
|
||||
private val createRoomEntryPoint: CreateRoomEntryPoint,
|
||||
private val activeLiveLocationShareManager: ActiveLiveLocationShareManager,
|
||||
) : BaseFlowNode<LoggedInFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Placeholder,
|
||||
@@ -211,6 +213,7 @@ class LoggedInFlowNode(
|
||||
super.onBuilt()
|
||||
lifecycleScope.launch {
|
||||
sessionEnterpriseService.init()
|
||||
activeLiveLocationShareManager.setup()
|
||||
}
|
||||
lifecycle.subscribe(
|
||||
onCreate = {
|
||||
@@ -219,7 +222,6 @@ class LoggedInFlowNode(
|
||||
loggedInFlowProcessor.observeEvents(sessionCoroutineScope)
|
||||
matrixClient.sessionVerificationService.setListener(verificationListener)
|
||||
mediaPreviewConfigMigration()
|
||||
|
||||
sessionCoroutineScope.launch {
|
||||
// Wait for the network to be connected before pre-fetching the max file upload size
|
||||
networkMonitor.connectivity.first { networkStatus -> networkStatus == NetworkStatus.Connected }
|
||||
@@ -380,9 +382,13 @@ class LoggedInFlowNode(
|
||||
}
|
||||
is NavTarget.Room -> {
|
||||
val joinedRoomCallback = object : JoinedRoomLoadedFlowNode.Callback {
|
||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) {
|
||||
override fun onDone() {
|
||||
backstack.pop()
|
||||
}
|
||||
|
||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean) {
|
||||
lifecycleScope.launch {
|
||||
attachRoom(roomIdOrAlias = roomId.toRoomIdOrAlias(), serverNames = serverNames, clearBackstack = false)
|
||||
attachRoom(roomIdOrAlias = roomId.toRoomIdOrAlias(), serverNames = serverNames, clearBackstack = clearBackStack)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@ import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.api.core.asEventId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.oidc.api.OidcAction
|
||||
import io.element.android.libraries.oidc.api.OidcActionFlow
|
||||
import io.element.android.libraries.oauth.api.OAuthAction
|
||||
import io.element.android.libraries.oauth.api.OAuthActionFlow
|
||||
import io.element.android.libraries.sessionstorage.api.LoggedInState
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.ui.common.nodes.emptyNode
|
||||
@@ -95,7 +95,7 @@ class RootFlowNode(
|
||||
private val signedOutEntryPoint: SignedOutEntryPoint,
|
||||
private val accountSelectEntryPoint: AccountSelectEntryPoint,
|
||||
private val intentResolver: IntentResolver,
|
||||
private val oidcActionFlow: OidcActionFlow,
|
||||
private val oAuthActionFlow: OAuthActionFlow,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val announcementService: AnnouncementService,
|
||||
private val analyticsService: AnalyticsService,
|
||||
@@ -392,7 +392,7 @@ class RootFlowNode(
|
||||
navigateTo(resolvedIntent.deeplinkData)
|
||||
}
|
||||
is ResolvedIntent.Login -> onLoginLink(resolvedIntent.params)
|
||||
is ResolvedIntent.Oidc -> onOidcAction(resolvedIntent.oidcAction)
|
||||
is ResolvedIntent.OAuth -> onOAuthAction(resolvedIntent.oAuthAction)
|
||||
is ResolvedIntent.Permalink -> navigateTo(resolvedIntent.permalinkData)
|
||||
is ResolvedIntent.IncomingShare -> onIncomingShare(resolvedIntent.shareIntentData)
|
||||
}
|
||||
@@ -529,8 +529,8 @@ class RootFlowNode(
|
||||
}
|
||||
}
|
||||
|
||||
private fun onOidcAction(oidcAction: OidcAction) {
|
||||
oidcActionFlow.post(oidcAction)
|
||||
private fun onOAuthAction(oAuthAction: OAuthAction) {
|
||||
oAuthActionFlow.post(oAuthAction)
|
||||
}
|
||||
|
||||
private suspend fun attachSession(sessionId: SessionId): LoggedInFlowNode {
|
||||
|
||||
@@ -89,16 +89,14 @@ class SyncOrchestrator(
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun observeStates() = coroutineScope.launch {
|
||||
Timber.tag(tag).d("start observing the app and network state")
|
||||
|
||||
val isAppActiveFlow = combine(
|
||||
val isAppActiveFlows = listOf(
|
||||
appForegroundStateService.isInForeground,
|
||||
appForegroundStateService.isInCall,
|
||||
appForegroundStateService.isSyncingNotificationEvent,
|
||||
appForegroundStateService.hasRingingCall,
|
||||
) { isInForeground, isInCall, isSyncingNotificationEvent, hasRingingCall ->
|
||||
isInForeground || isInCall || isSyncingNotificationEvent || hasRingingCall
|
||||
}
|
||||
|
||||
appForegroundStateService.isSharingLiveLocation
|
||||
)
|
||||
val isAppActiveFlow = combine(isAppActiveFlows) { actives -> actives.any { it } }
|
||||
combine(
|
||||
// small debounce to avoid spamming startSync when the state is changing quickly in case of error.
|
||||
syncService.syncState.debounce(100.milliseconds),
|
||||
|
||||
@@ -18,13 +18,13 @@ import io.element.android.libraries.deeplink.api.DeeplinkData
|
||||
import io.element.android.libraries.deeplink.api.DeeplinkParser
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import io.element.android.libraries.oidc.api.OidcAction
|
||||
import io.element.android.libraries.oidc.api.OidcIntentResolver
|
||||
import io.element.android.libraries.oauth.api.OAuthAction
|
||||
import io.element.android.libraries.oauth.api.OAuthIntentResolver
|
||||
import timber.log.Timber
|
||||
|
||||
sealed interface ResolvedIntent {
|
||||
data class Navigation(val deeplinkData: DeeplinkData) : ResolvedIntent
|
||||
data class Oidc(val oidcAction: OidcAction) : ResolvedIntent
|
||||
data class OAuth(val oAuthAction: OAuthAction) : ResolvedIntent
|
||||
data class Permalink(val permalinkData: PermalinkData) : ResolvedIntent
|
||||
data class Login(val params: LoginParams) : ResolvedIntent
|
||||
data class IncomingShare(val shareIntentData: ShareIntentData) : ResolvedIntent
|
||||
@@ -34,7 +34,7 @@ sealed interface ResolvedIntent {
|
||||
class IntentResolver(
|
||||
private val deeplinkParser: DeeplinkParser,
|
||||
private val loginIntentResolver: LoginIntentResolver,
|
||||
private val oidcIntentResolver: OidcIntentResolver,
|
||||
private val oAuthIntentResolver: OAuthIntentResolver,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
private val shareIntentHandler: ShareIntentHandler,
|
||||
) {
|
||||
@@ -45,9 +45,9 @@ class IntentResolver(
|
||||
val deepLinkData = deeplinkParser.getFromIntent(intent)
|
||||
if (deepLinkData != null) return ResolvedIntent.Navigation(deepLinkData)
|
||||
|
||||
// Coming during login using Oidc?
|
||||
val oidcAction = oidcIntentResolver.resolve(intent)
|
||||
if (oidcAction != null) return ResolvedIntent.Oidc(oidcAction)
|
||||
// Coming during login using OAuth?
|
||||
val oAuthAction = oAuthIntentResolver.resolve(intent)
|
||||
if (oAuthAction != null) return ResolvedIntent.OAuth(oAuthAction)
|
||||
|
||||
val actionViewData = intent
|
||||
.takeIf { it.action == Intent.ACTION_VIEW }
|
||||
|
||||
+7
-2
@@ -82,7 +82,8 @@ class JoinedRoomLoadedFlowNode(
|
||||
plugins = plugins,
|
||||
), DependencyInjectionGraphOwner {
|
||||
interface Callback : Plugin {
|
||||
fun navigateToRoom(roomId: RoomId, serverNames: List<String>)
|
||||
fun onDone()
|
||||
fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean = false)
|
||||
fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
|
||||
fun navigateToGlobalNotificationSettings()
|
||||
fun navigateToDeveloperSettings()
|
||||
@@ -142,6 +143,10 @@ class JoinedRoomLoadedFlowNode(
|
||||
|
||||
private fun createRoomDetailsNode(buildContext: BuildContext, initialTarget: RoomDetailsEntryPoint.InitialTarget): Node {
|
||||
val callback = object : RoomDetailsEntryPoint.Callback {
|
||||
override fun onDone() {
|
||||
callback.onDone()
|
||||
}
|
||||
|
||||
override fun navigateToGlobalNotificationSettings() {
|
||||
callback.navigateToGlobalNotificationSettings()
|
||||
}
|
||||
@@ -150,7 +155,7 @@ class JoinedRoomLoadedFlowNode(
|
||||
callback.navigateToDeveloperSettings()
|
||||
}
|
||||
|
||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) {
|
||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean) {
|
||||
callback.navigateToRoom(roomId, serverNames)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="banner_migrate_to_native_sliding_sync_action">"Tanca sessió i actualitza"</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s ja no admet el protocol antic. Tanca sessió i torna a entrar per continuar utilitzant l\'aplicació."</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"El servidor utilitzat ja no admet el protocol antic. Tanca sessió i torna-la a iniciar per continuar utilitzant l\'aplicació."</string>
|
||||
</resources>
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -26,16 +26,14 @@ 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_THREAD_ID
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
import io.element.android.libraries.oidc.api.OidcAction
|
||||
import io.element.android.libraries.oidc.test.FakeOidcIntentResolver
|
||||
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()
|
||||
@@ -170,9 +168,9 @@ class IntentResolverTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test resolve oidc`() {
|
||||
fun `test resolve OAuth`() {
|
||||
val sut = createIntentResolver(
|
||||
oidcIntentResolverResult = { OidcAction.GoBack() },
|
||||
oAuthIntentResolverResult = { OAuthAction.GoBack() },
|
||||
)
|
||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
@@ -180,8 +178,8 @@ class IntentResolverTest {
|
||||
}
|
||||
val result = sut.resolve(intent)
|
||||
assertThat(result).isEqualTo(
|
||||
ResolvedIntent.Oidc(
|
||||
oidcAction = OidcAction.GoBack()
|
||||
ResolvedIntent.OAuth(
|
||||
oAuthAction = OAuthAction.GoBack()
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -194,7 +192,7 @@ class IntentResolverTest {
|
||||
val sut = createIntentResolver(
|
||||
loginIntentResolverResult = { null },
|
||||
permalinkParserResult = { permalinkData },
|
||||
oidcIntentResolverResult = { null },
|
||||
oAuthIntentResolverResult = { null },
|
||||
)
|
||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
@@ -213,7 +211,7 @@ class IntentResolverTest {
|
||||
val sut = createIntentResolver(
|
||||
permalinkParserResult = { PermalinkData.FallbackLink(Uri.parse("https://matrix.org")) },
|
||||
loginIntentResolverResult = { null },
|
||||
oidcIntentResolverResult = { null },
|
||||
oAuthIntentResolverResult = { null },
|
||||
)
|
||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
@@ -230,7 +228,7 @@ class IntentResolverTest {
|
||||
)
|
||||
val sut = createIntentResolver(
|
||||
permalinkParserResult = { permalinkData },
|
||||
oidcIntentResolverResult = { null },
|
||||
oAuthIntentResolverResult = { null },
|
||||
)
|
||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||
action = Intent.ACTION_BATTERY_LOW
|
||||
@@ -244,7 +242,7 @@ class IntentResolverTest {
|
||||
fun `test incoming share simple`() {
|
||||
val shareIntentData = ShareIntentData.PlainText("Hello")
|
||||
val sut = createIntentResolver(
|
||||
oidcIntentResolverResult = { null },
|
||||
oAuthIntentResolverResult = { null },
|
||||
onIncomingShareIntent = { shareIntentData },
|
||||
)
|
||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||
@@ -260,7 +258,7 @@ class IntentResolverTest {
|
||||
val fileUri = "content://com.example.app/file1.jpg".toUri()
|
||||
val shareIntentData = ShareIntentData.Uris(text = "Hello", uris = listOf(UriToShare(fileUri, "image/jpg")))
|
||||
val sut = createIntentResolver(
|
||||
oidcIntentResolverResult = { null },
|
||||
oAuthIntentResolverResult = { null },
|
||||
onIncomingShareIntent = { shareIntentData },
|
||||
)
|
||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||
@@ -277,7 +275,7 @@ class IntentResolverTest {
|
||||
val sut = createIntentResolver(
|
||||
permalinkParserResult = { PermalinkData.FallbackLink(Uri.parse("https://matrix.org")) },
|
||||
loginIntentResolverResult = { null },
|
||||
oidcIntentResolverResult = { null },
|
||||
oAuthIntentResolverResult = { null },
|
||||
)
|
||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
@@ -292,7 +290,7 @@ class IntentResolverTest {
|
||||
val aLoginParams = LoginParams("accountProvider", null)
|
||||
val sut = createIntentResolver(
|
||||
loginIntentResolverResult = { aLoginParams },
|
||||
oidcIntentResolverResult = { null },
|
||||
oAuthIntentResolverResult = { null },
|
||||
)
|
||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
@@ -306,7 +304,7 @@ class IntentResolverTest {
|
||||
deeplinkParserResult: DeeplinkData? = null,
|
||||
permalinkParserResult: (String) -> PermalinkData = { lambdaError() },
|
||||
loginIntentResolverResult: (String) -> LoginParams? = { lambdaError() },
|
||||
oidcIntentResolverResult: (Intent) -> OidcAction? = { lambdaError() },
|
||||
oAuthIntentResolverResult: (Intent) -> OAuthAction? = { lambdaError() },
|
||||
onIncomingShareIntent: (Intent) -> ShareIntentData? = { null },
|
||||
): IntentResolver {
|
||||
return IntentResolver(
|
||||
@@ -314,8 +312,8 @@ class IntentResolverTest {
|
||||
loginIntentResolver = FakeLoginIntentResolver(
|
||||
parseResult = loginIntentResolverResult,
|
||||
),
|
||||
oidcIntentResolver = FakeOidcIntentResolver(
|
||||
resolveResult = oidcIntentResolverResult,
|
||||
oAuthIntentResolver = FakeOAuthIntentResolver(
|
||||
resolveResult = oAuthIntentResolverResult,
|
||||
),
|
||||
permalinkParser = FakePermalinkParser(
|
||||
result = permalinkParserResult
|
||||
|
||||
@@ -21,7 +21,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
||||
import io.element.android.libraries.matrix.api.oauth.AccountManagementAction
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
|
||||
+2
-1
@@ -13,7 +13,8 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeJoinedRoomLoadedFlowNodeCallback : JoinedRoomLoadedFlowNode.Callback {
|
||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>) = lambdaError()
|
||||
override fun onDone() = lambdaError()
|
||||
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean) = lambdaError()
|
||||
override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError()
|
||||
override fun navigateToGlobalNotificationSettings() = lambdaError()
|
||||
override fun navigateToDeveloperSettings() = lambdaError()
|
||||
|
||||
+3
-5
@@ -30,7 +30,7 @@ tasks.register<Delete>("clean").configure {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
|
||||
private val ktLintVersion = the<LibrariesForLibs>().versions.ktlint.get()
|
||||
private val catalog = the<LibrariesForLibs>()
|
||||
|
||||
allprojects {
|
||||
// Detekt
|
||||
@@ -46,7 +46,7 @@ allprojects {
|
||||
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
||||
}
|
||||
dependencies {
|
||||
detektPlugins("io.nlopez.compose.rules:detekt:0.5.7")
|
||||
detektPlugins(catalog.detekt.compose.rules)
|
||||
detektPlugins(project(":tests:detekt-rules"))
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ allprojects {
|
||||
|
||||
// See https://github.com/JLLeitschuh/ktlint-gradle#configuration
|
||||
configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
|
||||
version = ktLintVersion
|
||||
version = catalog.versions.ktlint.get()
|
||||
android = true
|
||||
ignoreFailures = false
|
||||
enableExperimentalRules = true
|
||||
@@ -106,8 +106,6 @@ allprojects {
|
||||
// Fix compilation warning for annotations
|
||||
// See https://youtrack.jetbrains.com/issue/KT-73255/Change-defaulting-rule-for-annotations for more details
|
||||
freeCompilerArgs.add("-Xannotation-default-target=first-only")
|
||||
// Opt-in to context receivers
|
||||
freeCompilerArgs.add("-Xcontext-parameters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
This file contains some rough notes about Oidc implementation, with some examples of actual data.
|
||||
This file contains some rough notes about OAuth implementation, with some examples of actual data.
|
||||
|
||||
[ios implementation](https://github.com/element-hq/element-x-ios/compare/develop...doug/oidc-temp)
|
||||
|
||||
@@ -25,7 +25,7 @@ tosUri = "https://element.io/user-terms-of-service",
|
||||
policyUri = "https://element.io/privacy"
|
||||
|
||||
|
||||
Example of OidcData (from presentUrl callback):
|
||||
Example of OAuthData (from presentUrl callback):
|
||||
url: https://auth-oidc.lab.element.dev/authorize?response_type=code&client_id=01GYCAGG3PA70CJ97ZVP0WFJY3&redirect_uri=io.element%3A%2Fcallback&scope=openid+urn%3Amatrix%3Aorg.matrix.msc2967.client%3Aapi%3A*+urn%3Amatrix%3Aorg.matrix.msc2967.client%3Adevice%3AYAgcPW4mcG&state=ex6mNJVFZ5jn9wL8&nonce=NZ93DOyIGQd9exPQ&code_challenge_method=S256&code_challenge=FFRcPALNSPCh-ZgpyTRFu_h8NZJVncfvihbfT9CyX8U&prompt=consent
|
||||
|
||||
Formatted url:
|
||||
@@ -43,8 +43,8 @@ https://auth-oidc.lab.element.dev/authorize?
|
||||
state: ex6mNJVFZ5jn9wL8
|
||||
|
||||
|
||||
Oidc client example: https://github.com/matrix-org/matrix-rust-sdk/blob/39ad8a46801fb4317a777ebf895822b3675b709c/examples/oidc_cli/src/main.rs
|
||||
Oidc sdk doc: https://github.com/matrix-org/matrix-rust-sdk/blob/39ad8a46801fb4317a777ebf895822b3675b709c/crates/matrix-sdk/src/oidc.rs
|
||||
OAuth client example: https://github.com/matrix-org/matrix-rust-sdk/blob/39ad8a46801fb4317a777ebf895822b3675b709c/examples/oidc_cli/src/main.rs
|
||||
OAuth sdk doc: https://github.com/matrix-org/matrix-rust-sdk/blob/39ad8a46801fb4317a777ebf895822b3675b709c/crates/matrix-sdk/src/oidc.rs
|
||||
|
||||
|
||||
Test server:
|
||||
+1
-1
Submodule enterprise updated: cdde60c158...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
|
||||
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: improvements in Element Call, room knocking and room directory are now available, improvements on DMs.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: bug fixes and improvements.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: added image editing before sending and an option for custom notification sounds, improved start times, several other bug fixes and improvements.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: fixed an issue with devices not supporting Vulkan, added image editing before sending and an option for custom notification sounds, improved start times, several other bug fixes and improvements.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: bug fixes and improvements.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: bug fixes and improvements.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_settings_help_us_improve">"Comparteix dades d\'ús anònimes per ajudar-nos a identificar problemes."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Pots llegir tots els nostres termes %1$s."</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"aquí"</string>
|
||||
<string name="screen_analytics_settings_share_data">"Comparteix dades analítiques"</string>
|
||||
</resources>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_settings_help_us_improve">"問題発見のため、匿名の使用データの共有にご協力ください。"</string>
|
||||
<string name="screen_analytics_settings_read_terms">"利用規約の全文を%1$sから確認することができます。"</string>
|
||||
<string name="screen_analytics_settings_help_us_improve">"改善のため、匿名の使用データの共有にご協力ください。"</string>
|
||||
<string name="screen_analytics_settings_read_terms">"規約の全文は%1$sから確認することができます。"</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"こちら"</string>
|
||||
<string name="screen_analytics_settings_share_data">"使用データを共有"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_settings_help_us_improve">"共享匿名使用数据以帮助我们排查问题。"</string>
|
||||
<string name="screen_analytics_settings_read_terms">"你可以阅读我们的所有条款 %1$s。"</string>
|
||||
<string name="screen_analytics_settings_read_terms">"你可以点击 %1$s 阅读我们的所有条款。"</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"此处"</string>
|
||||
<string name="screen_analytics_settings_share_data">"共享分析数据"</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"No registrarem ni elaborarem perfils de cap dada personal"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Comparteix dades d\'ús anònimes per ajudar-nos a identificar problemes."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Pots llegir tots els nostres termes %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"aquí"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Ho pots desactivar en qualsevol moment"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"No compartirem les teves dades amb tercers"</string>
|
||||
<string name="screen_analytics_prompt_title">"Ajuda\'ns a millorar %1$s"</string>
|
||||
</resources>
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"いかなる個人情報も記録, 分析されることはありません"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"問題発見のため、匿名の使用データの共有にご協力ください。"</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"利用規約の全文を%1$sから確認することができます。"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"改善のため、匿名の使用データの共有にご協力ください。"</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"規約の全文は%1$sから確認することができます。"</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"こちら"</string>
|
||||
<string name="screen_analytics_prompt_settings">"いつでも設定は変更できます"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"情報が第三者に共有されることはありません"</string>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"我们不会记录或分析任何个人数据"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"共享匿名使用数据以帮助我们排查问题。"</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"你可以阅读我们的所有条款 %1$s。"</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"你可以点击 %1$s 阅读我们的所有条款。"</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"此处"</string>
|
||||
<string name="screen_analytics_prompt_settings">"可以随时关闭此功能"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"我们不会与第三方共享你的数据"</string>
|
||||
|
||||
+3
-2
@@ -35,8 +35,9 @@ class DefaultAnnouncementStore(
|
||||
|
||||
override fun announcementStatusFlow(announcement: Announcement): Flow<AnnouncementStatus> {
|
||||
val key = announcement.toKey()
|
||||
// Announcement.Fullscreen.Space is disabled, consider it's shown
|
||||
// For NewNotificationSound, a migration will set it to Show on application upgrade (see AppMigration08)
|
||||
// Default-status convention: Shown for one-shot intro/feature announcements that should
|
||||
// NOT replay on first launch (Fullscreen.Space is disabled; NewNotificationSound is opted
|
||||
// in by AppMigration08).
|
||||
val defaultStatus = when (announcement) {
|
||||
Announcement.Fullscreen.Space -> AnnouncementStatus.Shown
|
||||
Announcement.NewNotificationSound -> AnnouncementStatus.Shown
|
||||
|
||||
+14
-17
@@ -6,12 +6,14 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalTestApi::class)
|
||||
|
||||
package io.element.android.features.announcement.impl.fullscreen
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import io.element.android.features.announcement.api.Announcement
|
||||
import io.element.android.features.announcement.impl.AnnouncementEvent
|
||||
import io.element.android.features.announcement.impl.AnnouncementState
|
||||
@@ -20,43 +22,38 @@ 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 org.junit.Rule
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class FullscreenAnnouncementViewTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
class FullscreenAnnouncementViewTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `clicking on back sends a AnnouncementEvent`() {
|
||||
fun `clicking on back sends a AnnouncementEvent`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<AnnouncementEvent>()
|
||||
rule.setFullscreenAnnouncementView(
|
||||
setFullscreenAnnouncementView(
|
||||
anAnnouncementState(
|
||||
announcement = Announcement.Fullscreen.Space,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.pressBackKey()
|
||||
pressBackKey()
|
||||
eventsRecorder.assertSingle(AnnouncementEvent.Continue(Announcement.Fullscreen.Space))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Continue sends a AnnouncementEvent`() {
|
||||
fun `clicking on Continue sends a AnnouncementEvent`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<AnnouncementEvent>()
|
||||
rule.setFullscreenAnnouncementView(
|
||||
setFullscreenAnnouncementView(
|
||||
anAnnouncementState(
|
||||
announcement = Announcement.Fullscreen.Space,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_continue)
|
||||
clickOn(CommonStrings.action_continue)
|
||||
eventsRecorder.assertSingle(AnnouncementEvent.Continue(Announcement.Fullscreen.Space))
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setFullscreenAnnouncementView(
|
||||
private fun AndroidComposeUiTest<ComponentActivity>.setFullscreenAnnouncementView(
|
||||
state: AnnouncementState,
|
||||
) {
|
||||
setContent {
|
||||
|
||||
+6
-19
@@ -14,22 +14,9 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
sealed interface CallType : NodeInputs, Parcelable {
|
||||
@Parcelize
|
||||
data class ExternalUrl(val url: String) : CallType {
|
||||
override fun toString(): String {
|
||||
return "ExternalUrl"
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class RoomCall(
|
||||
val sessionId: SessionId,
|
||||
val roomId: RoomId,
|
||||
val isAudioCall: Boolean
|
||||
) : CallType {
|
||||
override fun toString(): String {
|
||||
return "RoomCall(sessionId=$sessionId, roomId=$roomId, isAudioCall=$isAudioCall)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@Parcelize
|
||||
data class CallData(
|
||||
val sessionId: SessionId,
|
||||
val roomId: RoomId,
|
||||
val isAudioCall: Boolean
|
||||
) : NodeInputs, Parcelable
|
||||
+4
-4
@@ -17,13 +17,13 @@ import io.element.android.libraries.matrix.api.core.UserId
|
||||
interface ElementCallEntryPoint {
|
||||
/**
|
||||
* Start a call of the given type.
|
||||
* @param callType The type of call to start.
|
||||
* @param callData The data of call to start.
|
||||
*/
|
||||
fun startCall(callType: CallType)
|
||||
fun startCall(callData: CallData)
|
||||
|
||||
/**
|
||||
* Handle an incoming call.
|
||||
* @param callType The type of call.
|
||||
* @param callData The data of call.
|
||||
* @param eventId The event id of the event that started the call.
|
||||
* @param senderId The user id of the sender of the event that started the call.
|
||||
* @param roomName The name of the room the call is in.
|
||||
@@ -35,7 +35,7 @@ interface ElementCallEntryPoint {
|
||||
* @param textContent The text content of the notification. If null the default content from the system will be used.
|
||||
*/
|
||||
suspend fun handleIncomingCall(
|
||||
callType: CallType.RoomCall,
|
||||
callData: CallData,
|
||||
eventId: EventId,
|
||||
senderId: UserId,
|
||||
roomName: String?,
|
||||
|
||||
@@ -84,7 +84,6 @@ dependencies {
|
||||
implementation(projects.services.toolbox.api)
|
||||
implementation(libs.androidx.webkit)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.network.retrofit)
|
||||
implementation(libs.serialization.json)
|
||||
implementation(libs.element.call.embedded)
|
||||
api(projects.features.call.api)
|
||||
|
||||
@@ -30,44 +30,10 @@
|
||||
<activity
|
||||
android:name=".ui.ElementCallActivity"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
|
||||
android:exported="true"
|
||||
android:label="@string/element_call"
|
||||
android:launchMode="singleTask"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:taskAffinity="io.element.android.features.call">
|
||||
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
|
||||
<!-- Matching asset file: https://call.element.io/.well-known/assetlinks.json -->
|
||||
<data android:host="call.element.io" />
|
||||
</intent-filter>
|
||||
<!-- Custom scheme to handle urls from other domains in the format: element://call?url=https%3A%2F%2Felement.io -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="element" />
|
||||
<data android:host="call" />
|
||||
</intent-filter>
|
||||
<!-- Custom scheme to handle urls from other domains in the format: io.element.call:/?url=https%3A%2F%2Felement.io -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="io.element.call" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
android:taskAffinity="io.element.android.features.call" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.IncomingCallActivity"
|
||||
|
||||
+7
-7
@@ -11,7 +11,7 @@ package io.element.android.features.call.impl
|
||||
import android.content.Context
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.features.call.api.CallType
|
||||
import io.element.android.features.call.api.CallData
|
||||
import io.element.android.features.call.api.ElementCallEntryPoint
|
||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||
import io.element.android.features.call.impl.utils.ActiveCallManager
|
||||
@@ -30,12 +30,12 @@ class DefaultElementCallEntryPoint(
|
||||
const val REQUEST_CODE = 2255
|
||||
}
|
||||
|
||||
override fun startCall(callType: CallType) {
|
||||
context.startActivity(IntentProvider.createIntent(context, callType))
|
||||
override fun startCall(callData: CallData) {
|
||||
context.startActivity(IntentProvider.createIntent(context, callData))
|
||||
}
|
||||
|
||||
override suspend fun handleIncomingCall(
|
||||
callType: CallType.RoomCall,
|
||||
callData: CallData,
|
||||
eventId: EventId,
|
||||
senderId: UserId,
|
||||
roomName: String?,
|
||||
@@ -47,8 +47,8 @@ class DefaultElementCallEntryPoint(
|
||||
textContent: String?,
|
||||
) {
|
||||
val incomingCallNotificationData = CallNotificationData(
|
||||
sessionId = callType.sessionId,
|
||||
roomId = callType.roomId,
|
||||
sessionId = callData.sessionId,
|
||||
roomId = callData.roomId,
|
||||
eventId = eventId,
|
||||
senderId = senderId,
|
||||
roomName = roomName,
|
||||
@@ -58,7 +58,7 @@ class DefaultElementCallEntryPoint(
|
||||
expirationTimestamp = expirationTimestamp,
|
||||
notificationChannelId = notificationChannelId,
|
||||
textContent = textContent,
|
||||
audioOnly = callType.isAudioCall
|
||||
audioOnly = callData.isAudioCall,
|
||||
)
|
||||
activeCallManager.registerIncomingCall(notificationData = incomingCallNotificationData)
|
||||
}
|
||||
|
||||
+9
-2
@@ -18,7 +18,7 @@ import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.app.Person
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.appconfig.ElementCallConfig
|
||||
import io.element.android.features.call.api.CallType
|
||||
import io.element.android.features.call.api.CallData
|
||||
import io.element.android.features.call.impl.receivers.DeclineCallBroadcastReceiver
|
||||
import io.element.android.features.call.impl.ui.IncomingCallActivity
|
||||
import io.element.android.features.call.impl.utils.IntentProvider
|
||||
@@ -89,7 +89,14 @@ class RingingCallNotificationCreator(
|
||||
.setImportant(true)
|
||||
.build()
|
||||
|
||||
val answerIntent = IntentProvider.getPendingIntent(context, CallType.RoomCall(sessionId, roomId, isAudioCall = audioOnly))
|
||||
val answerIntent = IntentProvider.getPendingIntent(
|
||||
context,
|
||||
CallData(
|
||||
sessionId = sessionId,
|
||||
roomId = roomId,
|
||||
isAudioCall = audioOnly,
|
||||
),
|
||||
)
|
||||
val notificationData = CallNotificationData(
|
||||
sessionId = sessionId,
|
||||
roomId = roomId,
|
||||
|
||||
+4
-4
@@ -10,8 +10,8 @@ package io.element.android.features.call.impl.pip
|
||||
|
||||
import io.element.android.features.call.impl.utils.PipController
|
||||
|
||||
sealed interface PictureInPictureEvents {
|
||||
data class SetPipController(val pipController: PipController) : PictureInPictureEvents
|
||||
data object EnterPictureInPicture : PictureInPictureEvents
|
||||
data class OnPictureInPictureModeChanged(val isInPip: Boolean) : PictureInPictureEvents
|
||||
sealed interface PictureInPictureEvent {
|
||||
data class SetPipController(val pipController: PipController) : PictureInPictureEvent
|
||||
data object EnterPictureInPicture : PictureInPictureEvent
|
||||
data class OnPictureInPictureModeChanged(val isInPip: Boolean) : PictureInPictureEvent
|
||||
}
|
||||
+4
-4
@@ -36,17 +36,17 @@ class PictureInPicturePresenter(
|
||||
var isInPictureInPicture by remember { mutableStateOf(false) }
|
||||
var pipController by remember { mutableStateOf<PipController?>(null) }
|
||||
|
||||
fun handleEvent(event: PictureInPictureEvents) {
|
||||
fun handleEvent(event: PictureInPictureEvent) {
|
||||
when (event) {
|
||||
is PictureInPictureEvents.SetPipController -> {
|
||||
is PictureInPictureEvent.SetPipController -> {
|
||||
pipController = event.pipController
|
||||
}
|
||||
PictureInPictureEvents.EnterPictureInPicture -> {
|
||||
PictureInPictureEvent.EnterPictureInPicture -> {
|
||||
coroutineScope.launch {
|
||||
switchToPip(pipController)
|
||||
}
|
||||
}
|
||||
is PictureInPictureEvents.OnPictureInPictureModeChanged -> {
|
||||
is PictureInPictureEvent.OnPictureInPictureModeChanged -> {
|
||||
Timber.tag(loggerTag.value).d("onPictureInPictureModeChanged: ${event.isInPip}")
|
||||
isInPictureInPicture = event.isInPip
|
||||
if (event.isInPip) {
|
||||
|
||||
+1
-1
@@ -11,5 +11,5 @@ package io.element.android.features.call.impl.pip
|
||||
data class PictureInPictureState(
|
||||
val supportPip: Boolean,
|
||||
val isInPictureInPicture: Boolean,
|
||||
val eventSink: (PictureInPictureEvents) -> Unit,
|
||||
val eventSink: (PictureInPictureEvent) -> Unit,
|
||||
)
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ package io.element.android.features.call.impl.pip
|
||||
fun aPictureInPictureState(
|
||||
supportPip: Boolean = false,
|
||||
isInPictureInPicture: Boolean = false,
|
||||
eventSink: (PictureInPictureEvents) -> Unit = {},
|
||||
eventSink: (PictureInPictureEvent) -> Unit = {},
|
||||
): PictureInPictureState {
|
||||
return PictureInPictureState(
|
||||
supportPip = supportPip,
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.content.IntentCompat
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.call.api.CallType
|
||||
import io.element.android.features.call.api.CallData
|
||||
import io.element.android.features.call.impl.di.CallBindings
|
||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||
import io.element.android.features.call.impl.utils.ActiveCallManager
|
||||
@@ -42,7 +42,7 @@ class DeclineCallBroadcastReceiver : BroadcastReceiver() {
|
||||
context.bindings<CallBindings>().inject(this)
|
||||
appCoroutineScope.launch {
|
||||
activeCallManager.hangUpCall(
|
||||
callType = CallType.RoomCall(
|
||||
callData = CallData(
|
||||
sessionId = notificationData.sessionId,
|
||||
roomId = notificationData.roomId,
|
||||
isAudioCall = notificationData.audioOnly
|
||||
|
||||
+4
-2
@@ -9,6 +9,8 @@ package io.element.android.features.call.impl.ui
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||
import io.element.android.libraries.designsystem.preview.ROOM_NAME
|
||||
import io.element.android.libraries.designsystem.preview.USER_NAME_BOB
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
@@ -34,8 +36,8 @@ internal fun aCallNotificationData(
|
||||
roomId = RoomId("!1234:matrix.org"),
|
||||
eventId = EventId("\$asdadadsad:matrix.org"),
|
||||
senderId = UserId("@bob:matrix.org"),
|
||||
roomName = "A room",
|
||||
senderName = "Bob",
|
||||
roomName = ROOM_NAME,
|
||||
senderName = USER_NAME_BOB,
|
||||
avatarUrl = null,
|
||||
notificationChannelId = "incoming_call",
|
||||
timestamp = 0L,
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.call.impl.ui
|
||||
internal sealed interface CallScreenBackPressAction {
|
||||
data object DispatchEscapeToWebView : CallScreenBackPressAction
|
||||
data object EnterPictureInPicture : CallScreenBackPressAction
|
||||
}
|
||||
|
||||
internal object CallScreenBackPressPolicy {
|
||||
fun resolve(
|
||||
supportPip: Boolean,
|
||||
hasWebView: Boolean,
|
||||
fromNative: Boolean,
|
||||
): CallScreenBackPressAction? {
|
||||
return when {
|
||||
hasWebView && fromNative -> CallScreenBackPressAction.DispatchEscapeToWebView
|
||||
hasWebView && supportPip -> CallScreenBackPressAction.EnterPictureInPicture
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -10,8 +10,8 @@ package io.element.android.features.call.impl.ui
|
||||
|
||||
import io.element.android.features.call.impl.utils.WidgetMessageInterceptor
|
||||
|
||||
sealed interface CallScreenEvents {
|
||||
data object Hangup : CallScreenEvents
|
||||
data class SetupMessageChannels(val widgetMessageInterceptor: WidgetMessageInterceptor) : CallScreenEvents
|
||||
data class OnWebViewError(val description: String?) : CallScreenEvents
|
||||
sealed interface CallScreenEvent {
|
||||
data object Hangup : CallScreenEvent
|
||||
data class SetupMessageChannels(val widgetMessageInterceptor: WidgetMessageInterceptor) : CallScreenEvent
|
||||
data class OnWebViewError(val description: String?) : CallScreenEvent
|
||||
}
|
||||
+34
-56
@@ -23,7 +23,7 @@ import dev.zacsweers.metro.AssistedFactory
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.call.api.CallType
|
||||
import io.element.android.features.call.api.CallData
|
||||
import io.element.android.features.call.impl.data.WidgetMessage
|
||||
import io.element.android.features.call.impl.utils.ActiveCallManager
|
||||
import io.element.android.features.call.impl.utils.CallWidgetProvider
|
||||
@@ -52,7 +52,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@AssistedInject
|
||||
class CallScreenPresenter(
|
||||
@Assisted private val callType: CallType,
|
||||
@Assisted private val callData: CallData,
|
||||
@Assisted private val navigator: CallScreenNavigator,
|
||||
private val callWidgetProvider: CallWidgetProvider,
|
||||
userAgentProvider: UserAgentProvider,
|
||||
@@ -69,10 +69,9 @@ class CallScreenPresenter(
|
||||
) : Presenter<CallScreenState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(callType: CallType, navigator: CallScreenNavigator): CallScreenPresenter
|
||||
fun create(callData: CallData, navigator: CallScreenNavigator): CallScreenPresenter
|
||||
}
|
||||
|
||||
private val isInWidgetMode = callType is CallType.RoomCall
|
||||
private val userAgent = userAgentProvider.provide()
|
||||
|
||||
@Composable
|
||||
@@ -90,9 +89,9 @@ class CallScreenPresenter(
|
||||
DisposableEffect(Unit) {
|
||||
coroutineScope.launch {
|
||||
// Sets the call as joined
|
||||
activeCallManager.joinedCall(callType)
|
||||
activeCallManager.joinedCall(callData)
|
||||
fetchRoomCallUrl(
|
||||
inputs = callType,
|
||||
callData = callData,
|
||||
urlState = urlState,
|
||||
callWidgetDriver = callWidgetDriver,
|
||||
languageTag = languageTag,
|
||||
@@ -100,19 +99,10 @@ class CallScreenPresenter(
|
||||
)
|
||||
}
|
||||
onDispose {
|
||||
appCoroutineScope.launch { activeCallManager.hangUpCall(callType) }
|
||||
appCoroutineScope.launch { activeCallManager.hangUpCall(callData) }
|
||||
}
|
||||
}
|
||||
|
||||
when (callType) {
|
||||
is CallType.ExternalUrl -> {
|
||||
// No analytics yet for external calls
|
||||
}
|
||||
is CallType.RoomCall -> {
|
||||
screenTracker.TrackScreen(screen = MobileScreen.ScreenName.RoomCall)
|
||||
}
|
||||
}
|
||||
|
||||
screenTracker.TrackScreen(screen = MobileScreen.ScreenName.RoomCall)
|
||||
HandleMatrixClientSyncState()
|
||||
|
||||
callWidgetDriver.value?.let { driver ->
|
||||
@@ -149,25 +139,22 @@ class CallScreenPresenter(
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
if (callType is CallType.RoomCall) {
|
||||
// Note: For external calls isWidgetLoaded will always be false
|
||||
LaunchedEffect(Unit) {
|
||||
// Wait for the call to be joined, if it takes too long, we display an error
|
||||
delay(10.seconds)
|
||||
LaunchedEffect(Unit) {
|
||||
// Wait for the call to be joined, if it takes too long, we display an error
|
||||
delay(10.seconds)
|
||||
|
||||
if (!isWidgetLoaded) {
|
||||
Timber.w("The call took too long to load. Displaying an error before exiting.")
|
||||
if (!isWidgetLoaded) {
|
||||
Timber.w("The call took too long to load. Displaying an error before exiting.")
|
||||
|
||||
// This will display a simple 'Sorry, an error occurred' dialog and force the user to exit the call
|
||||
webViewError = ""
|
||||
}
|
||||
// This will display a simple 'Sorry, an error occurred' dialog and force the user to exit the call
|
||||
webViewError = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleEvent(event: CallScreenEvents) {
|
||||
fun handleEvent(event: CallScreenEvent) {
|
||||
when (event) {
|
||||
is CallScreenEvents.Hangup -> {
|
||||
is CallScreenEvent.Hangup -> {
|
||||
val widgetId = callWidgetDriver.value?.id
|
||||
val interceptor = messageInterceptor.value
|
||||
if (widgetId != null && interceptor != null && isWidgetLoaded) {
|
||||
@@ -187,10 +174,10 @@ class CallScreenPresenter(
|
||||
}
|
||||
}
|
||||
}
|
||||
is CallScreenEvents.SetupMessageChannels -> {
|
||||
is CallScreenEvent.SetupMessageChannels -> {
|
||||
messageInterceptor.value = event.widgetMessageInterceptor
|
||||
}
|
||||
is CallScreenEvents.OnWebViewError -> {
|
||||
is CallScreenEvent.OnWebViewError -> {
|
||||
if (!ignoreWebViewError) {
|
||||
webViewError = event.description.orEmpty()
|
||||
}
|
||||
@@ -204,37 +191,29 @@ class CallScreenPresenter(
|
||||
webViewError = webViewError,
|
||||
userAgent = userAgent,
|
||||
isCallActive = isWidgetLoaded,
|
||||
isInWidgetMode = isInWidgetMode,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun fetchRoomCallUrl(
|
||||
inputs: CallType,
|
||||
callData: CallData,
|
||||
urlState: MutableState<AsyncData<String>>,
|
||||
callWidgetDriver: MutableState<MatrixWidgetDriver?>,
|
||||
languageTag: String?,
|
||||
theme: String?,
|
||||
) {
|
||||
urlState.runCatchingUpdatingState {
|
||||
when (inputs) {
|
||||
is CallType.ExternalUrl -> {
|
||||
inputs.url
|
||||
}
|
||||
is CallType.RoomCall -> {
|
||||
val result = callWidgetProvider.getWidget(
|
||||
sessionId = inputs.sessionId,
|
||||
roomId = inputs.roomId,
|
||||
clientId = UUID.randomUUID().toString(),
|
||||
isAudioCall = inputs.isAudioCall,
|
||||
languageTag = languageTag,
|
||||
theme = theme,
|
||||
).getOrThrow()
|
||||
callWidgetDriver.value = result.driver
|
||||
Timber.d("Call widget driver initialized for sessionId: ${inputs.sessionId}, roomId: ${inputs.roomId}")
|
||||
result.url
|
||||
}
|
||||
}
|
||||
val result = callWidgetProvider.getWidget(
|
||||
sessionId = callData.sessionId,
|
||||
roomId = callData.roomId,
|
||||
clientId = UUID.randomUUID().toString(),
|
||||
isAudioCall = callData.isAudioCall,
|
||||
languageTag = languageTag,
|
||||
theme = theme,
|
||||
).getOrThrow()
|
||||
callWidgetDriver.value = result.driver
|
||||
Timber.d("Call widget driver initialized for sessionId: ${callData.sessionId}, roomId: ${callData.roomId}")
|
||||
result.url
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,12 +221,11 @@ class CallScreenPresenter(
|
||||
private fun HandleMatrixClientSyncState() {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
DisposableEffect(Unit) {
|
||||
val roomCallType = callType as? CallType.RoomCall ?: return@DisposableEffect onDispose {}
|
||||
val client = matrixClientsProvider.getOrNull(roomCallType.sessionId) ?: return@DisposableEffect onDispose {
|
||||
Timber.w("No MatrixClient found for sessionId, can't send call notification: ${roomCallType.sessionId}")
|
||||
val client = matrixClientsProvider.getOrNull(callData.sessionId) ?: return@DisposableEffect onDispose {
|
||||
Timber.w("No MatrixClient found for sessionId, can't send call notification: ${callData.sessionId}")
|
||||
}
|
||||
coroutineScope.launch {
|
||||
Timber.d("Observing sync state in-call for sessionId: ${roomCallType.sessionId}")
|
||||
Timber.d("Observing sync state in-call for sessionId: ${callData.sessionId}")
|
||||
client.syncService.syncState
|
||||
.collect { state ->
|
||||
if (state != SyncState.Running) {
|
||||
@@ -256,7 +234,7 @@ class CallScreenPresenter(
|
||||
}
|
||||
}
|
||||
onDispose {
|
||||
Timber.d("Stopped observing sync state in-call for sessionId: ${roomCallType.sessionId}")
|
||||
Timber.d("Stopped observing sync state in-call for sessionId: ${callData.sessionId}")
|
||||
// Make sure we mark the call as ended in the app state
|
||||
appForegroundStateService.updateIsInCallState(false)
|
||||
}
|
||||
|
||||
+1
-2
@@ -15,6 +15,5 @@ data class CallScreenState(
|
||||
val webViewError: String?,
|
||||
val userAgent: String,
|
||||
val isCallActive: Boolean,
|
||||
val isInWidgetMode: Boolean,
|
||||
val eventSink: (CallScreenEvents) -> Unit,
|
||||
val eventSink: (CallScreenEvent) -> Unit,
|
||||
)
|
||||
|
||||
+1
-3
@@ -26,15 +26,13 @@ internal fun aCallScreenState(
|
||||
webViewError: String? = null,
|
||||
userAgent: String = "",
|
||||
isCallActive: Boolean = true,
|
||||
isInWidgetMode: Boolean = false,
|
||||
eventSink: (CallScreenEvents) -> Unit = {},
|
||||
eventSink: (CallScreenEvent) -> Unit = {},
|
||||
): CallScreenState {
|
||||
return CallScreenState(
|
||||
urlState = urlState,
|
||||
webViewError = webViewError,
|
||||
userAgent = userAgent,
|
||||
isCallActive = isCallActive,
|
||||
isInWidgetMode = isInWidgetMode,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
}
|
||||
|
||||
+98
-89
@@ -17,9 +17,10 @@ import android.webkit.WebChromeClient
|
||||
import android.webkit.WebView
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -33,7 +34,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import io.element.android.features.call.impl.R
|
||||
import io.element.android.features.call.impl.pip.PictureInPictureEvents
|
||||
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
|
||||
import io.element.android.features.call.impl.utils.InvalidAudioDeviceReason
|
||||
@@ -45,7 +46,6 @@ import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import timber.log.Timber
|
||||
@@ -64,94 +64,93 @@ internal fun CallScreenView(
|
||||
requestPermissions: (Array<String>, RequestPermissionCallback) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun handleBack() {
|
||||
if (pipState.supportPip) {
|
||||
pipState.eventSink.invoke(PictureInPictureEvents.EnterPictureInPicture)
|
||||
} else {
|
||||
state.eventSink(CallScreenEvents.Hangup)
|
||||
var callWebView by remember { mutableStateOf<WebView?>(null) }
|
||||
|
||||
fun handleBack(fromNative: Boolean = false) {
|
||||
when (CallScreenBackPressPolicy.resolve(supportPip = pipState.supportPip, hasWebView = callWebView != null, fromNative)) {
|
||||
CallScreenBackPressAction.EnterPictureInPicture ->
|
||||
pipState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
|
||||
CallScreenBackPressAction.DispatchEscapeToWebView ->
|
||||
callWebView?.dispatchEscKeyEvent()
|
||||
null -> Timber.d("Back press with unsupported pip is a no-op")
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
) { padding ->
|
||||
BackHandler {
|
||||
handleBack()
|
||||
BackHandler {
|
||||
handleBack(fromNative = true)
|
||||
}
|
||||
if (state.webViewError != null) {
|
||||
ErrorDialog(
|
||||
content = buildString {
|
||||
append(stringResource(CommonStrings.error_unknown))
|
||||
state.webViewError.takeIf { it.isNotEmpty() }?.let { append("\n\n").append(it) }
|
||||
},
|
||||
onSubmit = { state.eventSink(CallScreenEvent.Hangup) },
|
||||
)
|
||||
} else {
|
||||
var webViewAudioManager by remember { mutableStateOf<WebViewAudioManager?>(null) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
var invalidAudioDeviceReason by remember { mutableStateOf<InvalidAudioDeviceReason?>(null) }
|
||||
invalidAudioDeviceReason?.let {
|
||||
InvalidAudioDeviceDialog(invalidAudioDeviceReason = it) {
|
||||
invalidAudioDeviceReason = null
|
||||
}
|
||||
}
|
||||
if (state.webViewError != null) {
|
||||
ErrorDialog(
|
||||
content = buildString {
|
||||
append(stringResource(CommonStrings.error_unknown))
|
||||
state.webViewError.takeIf { it.isNotEmpty() }?.let { append("\n\n").append(it) }
|
||||
},
|
||||
onSubmit = { state.eventSink(CallScreenEvents.Hangup) },
|
||||
)
|
||||
} else {
|
||||
var webViewAudioManager by remember { mutableStateOf<WebViewAudioManager?>(null) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
var invalidAudioDeviceReason by remember { mutableStateOf<InvalidAudioDeviceReason?>(null) }
|
||||
invalidAudioDeviceReason?.let {
|
||||
InvalidAudioDeviceDialog(invalidAudioDeviceReason = it) {
|
||||
invalidAudioDeviceReason = null
|
||||
}
|
||||
CallWebView(
|
||||
modifier = modifier.consumeWindowInsets(WindowInsets.systemBars).fillMaxSize(),
|
||||
url = state.urlState,
|
||||
userAgent = state.userAgent,
|
||||
onPermissionsRequest = { request ->
|
||||
val androidPermissions = mapWebkitPermissions(request.resources)
|
||||
val callback: RequestPermissionCallback = { request.grant(it) }
|
||||
requestPermissions(androidPermissions.toTypedArray(), callback)
|
||||
},
|
||||
onConsoleMessage = onConsoleMessage,
|
||||
onCreateWebView = { webView ->
|
||||
callWebView = webView
|
||||
webView.addBackHandler(onBackPressed = ::handleBack)
|
||||
val interceptor = WebViewWidgetMessageInterceptor(
|
||||
webView = webView,
|
||||
onUrlLoaded = { url ->
|
||||
webView.evaluateJavascript("controls.onBackButtonPressed = () => { backHandler.onBackPressed() }", null)
|
||||
if (webViewAudioManager?.isInCallMode?.get() == false) {
|
||||
Timber.d("URL $url is loaded, starting in-call audio mode")
|
||||
webViewAudioManager?.onCallStarted()
|
||||
} else {
|
||||
Timber.d("Can't start in-call audio mode since the app is already in it.")
|
||||
}
|
||||
},
|
||||
onError = { state.eventSink(CallScreenEvent.OnWebViewError(it)) },
|
||||
)
|
||||
webViewAudioManager = WebViewAudioManager(
|
||||
webView = webView,
|
||||
coroutineScope = coroutineScope,
|
||||
onInvalidAudioDeviceAdded = { invalidAudioDeviceReason = it },
|
||||
)
|
||||
state.eventSink(CallScreenEvent.SetupMessageChannels(interceptor))
|
||||
val pipController = WebViewPipController(webView)
|
||||
pipState.eventSink(PictureInPictureEvent.SetPipController(pipController))
|
||||
},
|
||||
onDestroyWebView = {
|
||||
callWebView = null
|
||||
// Reset audio mode
|
||||
webViewAudioManager?.onCallStopped()
|
||||
}
|
||||
|
||||
CallWebView(
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.fillMaxSize(),
|
||||
url = state.urlState,
|
||||
userAgent = state.userAgent,
|
||||
onPermissionsRequest = { request ->
|
||||
val androidPermissions = mapWebkitPermissions(request.resources)
|
||||
val callback: RequestPermissionCallback = { request.grant(it) }
|
||||
requestPermissions(androidPermissions.toTypedArray(), callback)
|
||||
},
|
||||
onConsoleMessage = onConsoleMessage,
|
||||
onCreateWebView = { webView ->
|
||||
webView.addBackHandler(onBackPressed = ::handleBack)
|
||||
val interceptor = WebViewWidgetMessageInterceptor(
|
||||
webView = webView,
|
||||
onUrlLoaded = { url ->
|
||||
webView.evaluateJavascript("controls.onBackButtonPressed = () => { backHandler.onBackPressed() }", null)
|
||||
if (webViewAudioManager?.isInCallMode?.get() == false) {
|
||||
Timber.d("URL $url is loaded, starting in-call audio mode")
|
||||
webViewAudioManager?.onCallStarted()
|
||||
} else {
|
||||
Timber.d("Can't start in-call audio mode since the app is already in it.")
|
||||
}
|
||||
},
|
||||
onError = { state.eventSink(CallScreenEvents.OnWebViewError(it)) },
|
||||
)
|
||||
webViewAudioManager = WebViewAudioManager(
|
||||
webView = webView,
|
||||
coroutineScope = coroutineScope,
|
||||
onInvalidAudioDeviceAdded = { invalidAudioDeviceReason = it },
|
||||
)
|
||||
state.eventSink(CallScreenEvents.SetupMessageChannels(interceptor))
|
||||
val pipController = WebViewPipController(webView)
|
||||
pipState.eventSink(PictureInPictureEvents.SetPipController(pipController))
|
||||
},
|
||||
onDestroyWebView = {
|
||||
// Reset audio mode
|
||||
webViewAudioManager?.onCallStopped()
|
||||
}
|
||||
)
|
||||
when (state.urlState) {
|
||||
AsyncData.Uninitialized,
|
||||
is AsyncData.Loading ->
|
||||
ProgressDialog(text = stringResource(id = CommonStrings.common_please_wait))
|
||||
is AsyncData.Failure -> {
|
||||
Timber.e(state.urlState.error, "WebView failed to load URL: ${state.urlState.error.message}")
|
||||
ErrorDialog(
|
||||
content = state.urlState.error.message.orEmpty(),
|
||||
onSubmit = { state.eventSink(CallScreenEvents.Hangup) },
|
||||
)
|
||||
}
|
||||
is AsyncData.Success -> Unit
|
||||
)
|
||||
when (state.urlState) {
|
||||
AsyncData.Uninitialized,
|
||||
is AsyncData.Loading ->
|
||||
ProgressDialog(text = stringResource(id = CommonStrings.common_please_wait))
|
||||
is AsyncData.Failure -> {
|
||||
Timber.e(state.urlState.error, "WebView failed to load URL: ${state.urlState.error.message}")
|
||||
ErrorDialog(
|
||||
content = state.urlState.error.message.orEmpty(),
|
||||
onSubmit = { state.eventSink(CallScreenEvent.Hangup) },
|
||||
)
|
||||
}
|
||||
is AsyncData.Success -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,15 +247,16 @@ private fun WebView.setup(
|
||||
|
||||
private fun WebView.addBackHandler(onBackPressed: () -> Unit) {
|
||||
addJavascriptInterface(
|
||||
object {
|
||||
@Suppress("unused")
|
||||
@JavascriptInterface
|
||||
fun onBackPressed() = onBackPressed()
|
||||
},
|
||||
JavascriptBackHandlerBridge(callback = onBackPressed),
|
||||
"backHandler"
|
||||
)
|
||||
}
|
||||
|
||||
private fun WebView.dispatchEscKeyEvent() {
|
||||
dispatchKeyEvent(android.view.KeyEvent(android.view.KeyEvent.ACTION_DOWN, android.view.KeyEvent.KEYCODE_ESCAPE))
|
||||
dispatchKeyEvent(android.view.KeyEvent(android.view.KeyEvent.ACTION_UP, android.view.KeyEvent.KEYCODE_ESCAPE))
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun CallScreenViewPreview(
|
||||
@@ -275,3 +275,12 @@ internal fun CallScreenViewPreview(
|
||||
internal fun InvalidAudioDeviceDialogPreview() = ElementPreview {
|
||||
InvalidAudioDeviceDialog(invalidAudioDeviceReason = InvalidAudioDeviceReason.BT_AUDIO_DEVICE_DISABLED) {}
|
||||
}
|
||||
|
||||
internal class JavascriptBackHandlerBridge(
|
||||
private val callback: () -> Unit,
|
||||
) {
|
||||
@JavascriptInterface
|
||||
fun onBackPressed() {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector 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.call.impl.ui
|
||||
|
||||
import io.element.android.features.call.api.CallType
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
fun CallType.getSessionId(): SessionId? {
|
||||
return when (this) {
|
||||
is CallType.ExternalUrl -> null
|
||||
is CallType.RoomCall -> sessionId
|
||||
}
|
||||
}
|
||||
+49
-31
@@ -32,19 +32,20 @@ import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.core.app.PictureInPictureModeChangedInfo
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.core.util.Consumer
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.compound.colors.SemanticColorsLightDark
|
||||
import io.element.android.features.call.api.CallType
|
||||
import io.element.android.features.call.api.CallType.ExternalUrl
|
||||
import io.element.android.features.call.api.CallData
|
||||
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
||||
import io.element.android.features.call.impl.di.CallBindings
|
||||
import io.element.android.features.call.impl.pip.PictureInPictureEvents
|
||||
import io.element.android.features.call.impl.pip.PictureInPictureEvent
|
||||
import io.element.android.features.call.impl.pip.PictureInPicturePresenter
|
||||
import io.element.android.features.call.impl.pip.PictureInPictureState
|
||||
import io.element.android.features.call.impl.pip.PipView
|
||||
import io.element.android.features.call.impl.services.CallForegroundService
|
||||
import io.element.android.features.call.impl.utils.CallIntentDataParser
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.libraries.androidutils.browser.ConsoleMessageLogger
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
@@ -54,6 +55,7 @@ import io.element.android.libraries.audio.api.AudioFocusRequester
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.designsystem.theme.ElementThemeApp
|
||||
import io.element.android.libraries.designsystem.utils.hasCompactHeightWindowSize
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import timber.log.Timber
|
||||
@@ -64,7 +66,6 @@ class ElementCallActivity :
|
||||
AppCompatActivity(),
|
||||
CallScreenNavigator,
|
||||
PipView {
|
||||
@Inject lateinit var callIntentDataParser: CallIntentDataParser
|
||||
@Inject lateinit var presenterFactory: CallScreenPresenter.Factory
|
||||
@Inject lateinit var appPreferencesStore: AppPreferencesStore
|
||||
@Inject lateinit var featureFlagService: FeatureFlagService
|
||||
@@ -80,9 +81,9 @@ class ElementCallActivity :
|
||||
|
||||
private val requestPermissionsLauncher = registerPermissionResultLauncher()
|
||||
|
||||
private val webViewTarget = mutableStateOf<CallType?>(null)
|
||||
private val webViewTarget = mutableStateOf<CallData?>(null)
|
||||
|
||||
private var eventSink: ((CallScreenEvents) -> Unit)? = null
|
||||
private var eventSink: ((CallScreenEvent) -> Unit)? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -98,7 +99,7 @@ class ElementCallActivity :
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
|
||||
}
|
||||
|
||||
setCallType(intent)
|
||||
setCallData(intent)
|
||||
// If presenter is not created at this point, it means we have no call to display, the Activity is finishing, so return early
|
||||
if (!::presenter.isInitialized) {
|
||||
return
|
||||
@@ -111,9 +112,30 @@ class ElementCallActivity :
|
||||
setContent {
|
||||
val pipState = pictureInPicturePresenter.present()
|
||||
ListenToAndroidEvents(pipState)
|
||||
val colors by remember(webViewTarget.value?.getSessionId()) {
|
||||
enterpriseService.semanticColorsFlow(sessionId = webViewTarget.value?.getSessionId())
|
||||
val colors by remember(webViewTarget.value?.sessionId) {
|
||||
enterpriseService.semanticColorsFlow(sessionId = webViewTarget.value?.sessionId)
|
||||
}.collectAsState(SemanticColorsLightDark.default)
|
||||
|
||||
// When the height is compact, hide the system bars by default to maximize the space for the call, using immersive mode
|
||||
val hasCompactHeight = hasCompactHeightWindowSize()
|
||||
DisposableEffect(hasCompactHeight, pipState.isInPictureInPicture) {
|
||||
if (hasCompactHeight && !pipState.isInPictureInPicture) {
|
||||
val window = this@ElementCallActivity.window ?: return@DisposableEffect onDispose {}
|
||||
val insetsController = WindowCompat.getInsetsController(window, window.decorView)
|
||||
val systemBarInsets = WindowInsetsCompat.Type.systemBars()
|
||||
insetsController.hide(systemBarInsets)
|
||||
|
||||
insetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
|
||||
onDispose {
|
||||
insetsController.show(systemBarInsets)
|
||||
insetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
|
||||
}
|
||||
} else {
|
||||
onDispose {}
|
||||
}
|
||||
}
|
||||
|
||||
ElementThemeApp(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
featureFlagService = featureFlagService,
|
||||
@@ -123,9 +145,8 @@ class ElementCallActivity :
|
||||
) {
|
||||
val state = presenter.present()
|
||||
eventSink = state.eventSink
|
||||
LaunchedEffect(state.isCallActive, state.isInWidgetMode) {
|
||||
// Note when not in WidgetMode, isCallActive will never be true, so consider the call is active
|
||||
if (state.isCallActive || !state.isInWidgetMode) {
|
||||
LaunchedEffect(state.isCallActive) {
|
||||
if (state.isCallActive) {
|
||||
setCallIsActive()
|
||||
}
|
||||
}
|
||||
@@ -163,7 +184,7 @@ class ElementCallActivity :
|
||||
if (requestPermissionCallback != null) {
|
||||
Timber.tag(loggerTag.value).w("Ignoring onUserLeaveHint event because user is asked to grant permissions")
|
||||
} else {
|
||||
pipEventSink(PictureInPictureEvents.EnterPictureInPicture)
|
||||
pipEventSink(PictureInPictureEvent.EnterPictureInPicture)
|
||||
}
|
||||
}
|
||||
addOnUserLeaveHintListener(listener)
|
||||
@@ -173,10 +194,10 @@ class ElementCallActivity :
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
val onPictureInPictureModeChangedListener = Consumer { _: PictureInPictureModeChangedInfo ->
|
||||
pipEventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(isInPictureInPictureMode))
|
||||
pipEventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(isInPictureInPictureMode))
|
||||
if (!isInPictureInPictureMode && !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||
Timber.tag(loggerTag.value).d("Exiting PiP mode: Hangup the call")
|
||||
eventSink?.invoke(CallScreenEvents.Hangup)
|
||||
eventSink?.invoke(CallScreenEvent.Hangup)
|
||||
}
|
||||
}
|
||||
addOnPictureInPictureModeChangedListener(onPictureInPictureModeChangedListener)
|
||||
@@ -188,7 +209,7 @@ class ElementCallActivity :
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setCallType(intent)
|
||||
setCallData(intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -207,25 +228,24 @@ class ElementCallActivity :
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun setCallType(intent: Intent?) {
|
||||
val callType = intent?.let {
|
||||
IntentCompat.getParcelableExtra(intent, DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, CallType::class.java)
|
||||
?: intent.dataString?.let(::parseUrl)?.let(::ExternalUrl)
|
||||
private fun setCallData(intent: Intent?) {
|
||||
val callData = intent?.let {
|
||||
IntentCompat.getParcelableExtra(intent, DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, CallData::class.java)
|
||||
}
|
||||
val currentCallType = webViewTarget.value
|
||||
if (currentCallType == null) {
|
||||
if (callType == null) {
|
||||
val currentCallData = webViewTarget.value
|
||||
if (currentCallData == null) {
|
||||
if (callData == null) {
|
||||
Timber.tag(loggerTag.value).d("Re-opened the activity but we have no url to load or a cached one, finish the activity")
|
||||
finish()
|
||||
} else {
|
||||
Timber.tag(loggerTag.value).d("Set the call type and create the presenter")
|
||||
webViewTarget.value = callType
|
||||
presenter = presenterFactory.create(callType, this)
|
||||
webViewTarget.value = callData
|
||||
presenter = presenterFactory.create(callData, this)
|
||||
}
|
||||
} else {
|
||||
if (callType == null) {
|
||||
if (callData == null) {
|
||||
Timber.tag(loggerTag.value).d("Coming back from notification, do nothing")
|
||||
} else if (callType != currentCallType) {
|
||||
} else if (callData != currentCallData) {
|
||||
Timber.tag(loggerTag.value).d("User starts another call, restart the Activity")
|
||||
setIntent(intent)
|
||||
recreate()
|
||||
@@ -236,8 +256,6 @@ class ElementCallActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseUrl(url: String?): String? = callIntentDataParser.parse(url)
|
||||
|
||||
private fun registerPermissionResultLauncher(): ActivityResultLauncher<Array<String>> {
|
||||
return registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
@@ -287,7 +305,7 @@ class ElementCallActivity :
|
||||
}
|
||||
|
||||
override fun hangUp() {
|
||||
eventSink?.invoke(CallScreenEvents.Hangup)
|
||||
eventSink?.invoke(CallScreenEvent.Hangup)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -19,7 +19,7 @@ import androidx.core.content.IntentCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.compound.colors.SemanticColorsLightDark
|
||||
import io.element.android.features.call.api.CallType
|
||||
import io.element.android.features.call.api.CallData
|
||||
import io.element.android.features.call.api.ElementCallEntryPoint
|
||||
import io.element.android.features.call.impl.di.CallBindings
|
||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||
@@ -118,10 +118,10 @@ class IncomingCallActivity : AppCompatActivity() {
|
||||
|
||||
private fun onAnswer(notificationData: CallNotificationData) {
|
||||
elementCallEntryPoint.startCall(
|
||||
CallType.RoomCall(
|
||||
notificationData.sessionId,
|
||||
notificationData.roomId,
|
||||
isAudioCall = notificationData.audioOnly
|
||||
CallData(
|
||||
sessionId = notificationData.sessionId,
|
||||
roomId = notificationData.roomId,
|
||||
isAudioCall = notificationData.audioOnly,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -129,7 +129,7 @@ class IncomingCallActivity : AppCompatActivity() {
|
||||
private fun onCancel() {
|
||||
val activeCall = activeCallManager.activeCall.value ?: return
|
||||
appCoroutineScope.launch {
|
||||
activeCallManager.hangUpCall(callType = activeCall.callType)
|
||||
activeCallManager.hangUpCall(callData = activeCall.callData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+24
-27
@@ -20,7 +20,7 @@ import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.appconfig.ElementCallConfig
|
||||
import io.element.android.features.call.api.CallType
|
||||
import io.element.android.features.call.api.CallData
|
||||
import io.element.android.features.call.api.CurrentCall
|
||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
|
||||
@@ -73,20 +73,20 @@ interface ActiveCallManager {
|
||||
|
||||
/**
|
||||
* Called to hang up the active call. It will hang up the call and remove any existing UI and the active call.
|
||||
* @param callType The type of call that the user hangs up, either an external url one or a room one.
|
||||
* @param callData The data about the call.
|
||||
* @param notificationData The data for the incoming call notification.
|
||||
*/
|
||||
suspend fun hangUpCall(
|
||||
callType: CallType,
|
||||
callData: CallData,
|
||||
notificationData: CallNotificationData? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* Called after the user joined a call. It will remove any existing UI and set the call state as [CallState.InCall].
|
||||
*
|
||||
* @param callType The type of call that the user joined, either an external url one or a room one.
|
||||
* @param callData The data about the call.
|
||||
*/
|
||||
suspend fun joinedCall(callType: CallType)
|
||||
suspend fun joinedCall(callData: CallData)
|
||||
}
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@@ -143,7 +143,7 @@ class DefaultActiveCallManager(
|
||||
return
|
||||
}
|
||||
activeCall.value = ActiveCall(
|
||||
callType = CallType.RoomCall(
|
||||
callData = CallData(
|
||||
sessionId = notificationData.sessionId,
|
||||
roomId = notificationData.roomId,
|
||||
isAudioCall = notificationData.audioOnly,
|
||||
@@ -198,17 +198,17 @@ class DefaultActiveCallManager(
|
||||
}
|
||||
|
||||
override suspend fun hangUpCall(
|
||||
callType: CallType,
|
||||
callData: CallData,
|
||||
notificationData: CallNotificationData?,
|
||||
) = mutex.withLock {
|
||||
Timber.tag(tag).d("Hang up call: $callType")
|
||||
Timber.tag(tag).d("Hang up call: $callData")
|
||||
cancelIncomingCallNotification()
|
||||
val currentActiveCall = activeCall.value ?: run {
|
||||
// activeCall.value can be null if the application has been killed while the call was ringing
|
||||
// Build a currentActiveCall with the provided parameters.
|
||||
notificationData?.let {
|
||||
ActiveCall(
|
||||
callType = callType,
|
||||
callData = callData,
|
||||
callState = CallState.Ringing(
|
||||
notificationData = notificationData,
|
||||
)
|
||||
@@ -219,8 +219,8 @@ class DefaultActiveCallManager(
|
||||
return@withLock
|
||||
}
|
||||
|
||||
if (currentActiveCall.callType != callType) {
|
||||
Timber.tag(tag).w("Call type $callType does not match the active call type, ignoring")
|
||||
if (currentActiveCall.callData != callData) {
|
||||
Timber.tag(tag).w("Call type $callData does not match the active call type, ignoring")
|
||||
return@withLock
|
||||
}
|
||||
if (currentActiveCall.callState is CallState.Ringing) {
|
||||
@@ -244,8 +244,8 @@ class DefaultActiveCallManager(
|
||||
activeCall.value = null
|
||||
}
|
||||
|
||||
override suspend fun joinedCall(callType: CallType) = mutex.withLock {
|
||||
Timber.tag(tag).d("Joined call: $callType")
|
||||
override suspend fun joinedCall(callData: CallData) = mutex.withLock {
|
||||
Timber.tag(tag).d("Joined call: $callData")
|
||||
cancelIncomingCallNotification()
|
||||
if (activeWakeLock?.isHeld == true) {
|
||||
Timber.tag(tag).d("Releasing partial wakelock after joining call")
|
||||
@@ -254,7 +254,7 @@ class DefaultActiveCallManager(
|
||||
timedOutCallJob?.cancel()
|
||||
|
||||
activeCall.value = ActiveCall(
|
||||
callType = callType,
|
||||
callData = callData,
|
||||
callState = CallState.InCall,
|
||||
)
|
||||
}
|
||||
@@ -307,15 +307,15 @@ class DefaultActiveCallManager(
|
||||
private fun observeRingingCall() {
|
||||
activeCall
|
||||
.filterNotNull()
|
||||
.filter { it.callState is CallState.Ringing && it.callType is CallType.RoomCall }
|
||||
.filter { it.callState is CallState.Ringing }
|
||||
.flatMapLatest { activeCall ->
|
||||
val callType = activeCall.callType as CallType.RoomCall
|
||||
val callData = activeCall.callData
|
||||
val ringingInfo = activeCall.callState as CallState.Ringing
|
||||
val client = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull() ?: run {
|
||||
val client = matrixClientProvider.getOrRestore(callData.sessionId).getOrNull() ?: run {
|
||||
Timber.tag(tag).d("Couldn't find session for incoming call: $activeCall")
|
||||
return@flatMapLatest flowOf()
|
||||
}
|
||||
val room = client.getRoom(callType.roomId) ?: run {
|
||||
val room = client.getRoom(callData.roomId) ?: run {
|
||||
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
|
||||
return@flatMapLatest flowOf()
|
||||
}
|
||||
@@ -346,17 +346,17 @@ class DefaultActiveCallManager(
|
||||
// has joined the call from another session.
|
||||
activeCall
|
||||
.filterNotNull()
|
||||
.filter { it.callState is CallState.Ringing && it.callType is CallType.RoomCall }
|
||||
.filter { it.callState is CallState.Ringing }
|
||||
.flatMapLatest { activeCall ->
|
||||
val callType = activeCall.callType as CallType.RoomCall
|
||||
val callData = activeCall.callData
|
||||
// Get a flow of updated `hasRoomCall` and `activeRoomCallParticipants` values for the room
|
||||
val room = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull()?.getRoom(callType.roomId) ?: run {
|
||||
val room = matrixClientProvider.getOrRestore(callData.sessionId).getOrNull()?.getRoom(callData.roomId) ?: run {
|
||||
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
|
||||
return@flatMapLatest flowOf()
|
||||
}
|
||||
room.roomInfoFlow.map {
|
||||
Timber.tag(tag).d("Has room call status changed for ringing call: ${it.hasRoomCall}")
|
||||
it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants)
|
||||
it.hasRoomCall to (callData.sessionId in it.activeRoomCallParticipants)
|
||||
}
|
||||
}
|
||||
// We only want to check if the room active call status changes
|
||||
@@ -388,10 +388,7 @@ class DefaultActiveCallManager(
|
||||
// Nothing to do
|
||||
}
|
||||
is CallState.InCall -> {
|
||||
when (val callType = value.callType) {
|
||||
is CallType.ExternalUrl -> defaultCurrentCallService.onCallStarted(CurrentCall.ExternalUrl(callType.url))
|
||||
is CallType.RoomCall -> defaultCurrentCallService.onCallStarted(CurrentCall.RoomCall(callType.roomId))
|
||||
}
|
||||
defaultCurrentCallService.onCallStarted(CurrentCall.RoomCall(value.callData.roomId))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,7 +401,7 @@ class DefaultActiveCallManager(
|
||||
* Represents an active call.
|
||||
*/
|
||||
data class ActiveCall(
|
||||
val callType: CallType,
|
||||
val callData: CallData,
|
||||
val callState: CallState,
|
||||
)
|
||||
|
||||
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector 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.call.impl.utils
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import dev.zacsweers.metro.Inject
|
||||
|
||||
@Inject
|
||||
class CallIntentDataParser {
|
||||
private val validHttpSchemes = sequenceOf("https")
|
||||
private val knownHosts = sequenceOf(
|
||||
"call.element.io",
|
||||
)
|
||||
|
||||
fun parse(data: String?): String? {
|
||||
val parsedUrl = data?.toUri() ?: return null
|
||||
val scheme = parsedUrl.scheme
|
||||
return when {
|
||||
scheme in validHttpSchemes -> parsedUrl
|
||||
scheme == "element" && parsedUrl.host == "call" -> {
|
||||
parsedUrl.getUrlParameter()
|
||||
}
|
||||
scheme == "io.element.call" && parsedUrl.host == null -> {
|
||||
parsedUrl.getUrlParameter()
|
||||
}
|
||||
// This should never be possible, but we still need to take into account the possibility
|
||||
else -> null
|
||||
}
|
||||
?.takeIf { it.host in knownHosts }
|
||||
?.withCustomParameters()
|
||||
}
|
||||
|
||||
private fun Uri.getUrlParameter(): Uri? {
|
||||
return getQueryParameter("url")
|
||||
?.let { urlParameter ->
|
||||
urlParameter.toUri().takeIf { uri ->
|
||||
uri.scheme in validHttpSchemes && !uri.host.isNullOrBlank()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the uri has the following parameters and value in the fragment:
|
||||
* - appPrompt=false
|
||||
* - confineToRoom=true
|
||||
* to ensure that the rendering will bo correct on the embedded Webview.
|
||||
*/
|
||||
private fun Uri.withCustomParameters(): String {
|
||||
val builder = buildUpon()
|
||||
// Remove the existing query parameters
|
||||
builder.clearQuery()
|
||||
queryParameterNames.forEach {
|
||||
if (it == APP_PROMPT_PARAMETER || it == CONFINE_TO_ROOM_PARAMETER) return@forEach
|
||||
builder.appendQueryParameter(it, getQueryParameter(it))
|
||||
}
|
||||
// Remove the existing fragment parameters, and build the new fragment
|
||||
val currentFragment = fragment ?: ""
|
||||
// Reset the current fragment
|
||||
builder.fragment("")
|
||||
val queryFragmentPosition = currentFragment.lastIndexOf("?")
|
||||
val newFragment = if (queryFragmentPosition == -1) {
|
||||
// No existing query, build it.
|
||||
"$currentFragment?$APP_PROMPT_PARAMETER=false&$CONFINE_TO_ROOM_PARAMETER=true"
|
||||
} else {
|
||||
buildString {
|
||||
append(currentFragment.substring(0, queryFragmentPosition + 1))
|
||||
val queryFragment = currentFragment.substring(queryFragmentPosition + 1)
|
||||
// Replace the existing parameters
|
||||
val newQueryFragment = queryFragment
|
||||
.replace("$APP_PROMPT_PARAMETER=true", "$APP_PROMPT_PARAMETER=false")
|
||||
.replace("$CONFINE_TO_ROOM_PARAMETER=false", "$CONFINE_TO_ROOM_PARAMETER=true")
|
||||
append(newQueryFragment)
|
||||
// Ensure the parameters are there
|
||||
if (!newQueryFragment.contains("$APP_PROMPT_PARAMETER=false")) {
|
||||
if (newQueryFragment.isNotEmpty()) {
|
||||
append("&")
|
||||
}
|
||||
append("$APP_PROMPT_PARAMETER=false")
|
||||
}
|
||||
if (!newQueryFragment.contains("$CONFINE_TO_ROOM_PARAMETER=true")) {
|
||||
append("&$CONFINE_TO_ROOM_PARAMETER=true")
|
||||
}
|
||||
}
|
||||
}
|
||||
// We do not want to encode the Fragment part, so append it manually
|
||||
return builder.build().toString() + "#" + newFragment
|
||||
}
|
||||
|
||||
private const val APP_PROMPT_PARAMETER = "appPrompt"
|
||||
private const val CONFINE_TO_ROOM_PARAMETER = "confineToRoom"
|
||||
-1
@@ -14,7 +14,6 @@ import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.services.appnavstate.api.ActiveRoomsHolder
|
||||
|
||||
+5
-5
@@ -12,21 +12,21 @@ import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import io.element.android.features.call.api.CallType
|
||||
import io.element.android.features.call.api.CallData
|
||||
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
||||
import io.element.android.features.call.impl.ui.ElementCallActivity
|
||||
|
||||
internal object IntentProvider {
|
||||
fun createIntent(context: Context, callType: CallType): Intent = Intent(context, ElementCallActivity::class.java).apply {
|
||||
putExtra(DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, callType)
|
||||
fun createIntent(context: Context, callData: CallData): Intent = Intent(context, ElementCallActivity::class.java).apply {
|
||||
putExtra(DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, callData)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_USER_ACTION)
|
||||
}
|
||||
|
||||
fun getPendingIntent(context: Context, callType: CallType): PendingIntent {
|
||||
fun getPendingIntent(context: Context, callData: CallData): PendingIntent {
|
||||
return PendingIntentCompat.getActivity(
|
||||
context,
|
||||
DefaultElementCallEntryPoint.REQUEST_CODE,
|
||||
createIntent(context, callType),
|
||||
createIntent(context, callData),
|
||||
PendingIntent.FLAG_CANCEL_CURRENT,
|
||||
false
|
||||
)!!
|
||||
|
||||
+9
-14
@@ -201,16 +201,12 @@ class WebViewAudioManager(
|
||||
return
|
||||
}
|
||||
|
||||
coroutineScope.launch {
|
||||
proximitySensorMutex.withLock {
|
||||
if (proximitySensorWakeLock?.isHeld == true) {
|
||||
proximitySensorWakeLock?.release()
|
||||
}
|
||||
}
|
||||
// Since this should run when the call is no longer running, it should be OK to not use the mutex here
|
||||
if (proximitySensorWakeLock?.isHeld == true) {
|
||||
proximitySensorWakeLock?.release()
|
||||
}
|
||||
|
||||
audioManager.mode = AudioManager.MODE_NORMAL
|
||||
|
||||
if (!hasRegisteredCallbacks) {
|
||||
Timber.w("Audio: tried to disable webview in-call audio mode without registering callbacks")
|
||||
return
|
||||
@@ -268,8 +264,8 @@ class WebViewAudioManager(
|
||||
private fun setWebViewAndroidNativeBridge() {
|
||||
Timber.d("Adding callback in controls.onAudioPlaybackStarted")
|
||||
webView.evaluateJavascript("controls.onAudioPlaybackStarted = () => { androidNativeBridge.onTrackReady(); };", null)
|
||||
Timber.d("Adding callback in controls.onOutputDeviceSelect")
|
||||
webView.evaluateJavascript("controls.onOutputDeviceSelect = (id) => { androidNativeBridge.setOutputDevice(id); };", null)
|
||||
Timber.d("Adding callback in controls.onAudioDeviceSelect")
|
||||
webView.evaluateJavascript("controls.onAudioDeviceSelect = (id) => { androidNativeBridge.setAudioDevice(id); };", null)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,8 +292,8 @@ class WebViewAudioManager(
|
||||
) {
|
||||
Timber.d("Updating available audio devices")
|
||||
val deviceList = json.encodeToString(devices)
|
||||
webView.evaluateJavascript("controls.setAvailableOutputDevices($deviceList);", {
|
||||
Timber.d("Audio: setAvailableOutputDevices result: $it")
|
||||
webView.evaluateJavascript("controls.setAvailableAudioDevices($deviceList);", {
|
||||
Timber.d("Audio: setAvailableAudioDevices result: $it")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -345,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")
|
||||
@@ -353,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
|
||||
}
|
||||
@@ -402,7 +397,7 @@ private class AndroidWebViewAudioBridge(
|
||||
private val onAudioPlaybackStarted: () -> Unit,
|
||||
) {
|
||||
@JavascriptInterface
|
||||
fun setOutputDevice(id: String) {
|
||||
fun setAudioDevice(id: String) {
|
||||
Timber.d("Audio device selected in webview, id: $id")
|
||||
onAudioDeviceSelected(id)
|
||||
}
|
||||
|
||||
+21
-14
@@ -140,26 +140,33 @@ class WebViewWidgetMessageInterceptor(
|
||||
}
|
||||
}
|
||||
|
||||
// Create a WebMessageListener, which will receive messages from the WebView and reply to them
|
||||
val webMessageListener = WebViewCompat.WebMessageListener { _, message, _, _, _ ->
|
||||
onMessageReceived(message.data)
|
||||
}
|
||||
// Always register JavascriptInterface as the baseline message channel.
|
||||
// This works on all WebView implementations including Huawei.
|
||||
webView.addJavascriptInterface(object {
|
||||
@JavascriptInterface
|
||||
fun postMessage(json: String?) {
|
||||
onMessageReceived(json)
|
||||
}
|
||||
}, LISTENER_NAME)
|
||||
|
||||
// Use WebMessageListener if supported, otherwise use JavascriptInterface
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
|
||||
// Additionally register WebMessageListener on WebViews that reliably support it.
|
||||
// Huawei WebView (Chromium < 119) reports WEB_MESSAGE_LISTENER as supported
|
||||
// but silently drops messages, so we only trust it on Chromium 119+.
|
||||
// See: https://github.com/element-hq/element-x-android/issues/6632
|
||||
val webViewVersionName = WebViewCompat.getCurrentWebViewPackage(webView.context)?.versionName.orEmpty()
|
||||
Timber.d("Using WebView version: $webViewVersionName")
|
||||
val webViewVersionCode = webViewVersionName.split(".").firstOrNull()?.toIntOrNull() ?: 0
|
||||
|
||||
if (webViewVersionCode >= 119 &&
|
||||
WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
|
||||
WebViewCompat.addWebMessageListener(
|
||||
webView,
|
||||
LISTENER_NAME,
|
||||
setOf("*"),
|
||||
webMessageListener
|
||||
)
|
||||
} else {
|
||||
webView.addJavascriptInterface(object {
|
||||
@JavascriptInterface
|
||||
fun postMessage(json: String?) {
|
||||
onMessageReceived(json)
|
||||
WebViewCompat.WebMessageListener { _, message, _, _, _ ->
|
||||
onMessageReceived(message.data)
|
||||
}
|
||||
}, LISTENER_NAME)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="call_foreground_service_channel_title_android">"Trucada en curs"</string>
|
||||
<string name="call_foreground_service_message_android">"Toca per tornar a la trucada"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Trucada en curs"</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Element Call entrant"</string>
|
||||
</resources>
|
||||
@@ -4,5 +4,5 @@
|
||||
<string name="call_foreground_service_message_android">"Kõne juurde naasmiseks klõpsa"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Kõne on pooleli"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call ei võimalda selles Androidi versioonis Bluetoothi heliseadmete kasutamist. Palun vali mõni muu heliseade."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Sissetulev Element Calli kõne"</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Saabuv Element Calli kõne"</string>
|
||||
</resources>
|
||||
|
||||
+5
-7
@@ -11,7 +11,7 @@ package io.element.android.features.call
|
||||
import android.content.Intent
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.call.api.CallType
|
||||
import io.element.android.features.call.api.CallData
|
||||
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||
import io.element.android.features.call.impl.ui.ElementCallActivity
|
||||
@@ -21,23 +21,21 @@ 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()
|
||||
entryPoint.startCall(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, isAudioCall = false))
|
||||
entryPoint.startCall(CallData(A_SESSION_ID, A_ROOM_ID, isAudioCall = false))
|
||||
|
||||
val expectedIntent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, ElementCallActivity::class.java)
|
||||
val intent = shadowOf(RuntimeEnvironment.getApplication()).nextStartedActivity
|
||||
@@ -53,7 +51,7 @@ class DefaultElementCallEntryPointTest {
|
||||
val entryPoint = createEntryPoint(activeCallManager = activeCallManager)
|
||||
|
||||
entryPoint.handleIncomingCall(
|
||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, isAudioCall = false),
|
||||
callData = CallData(A_SESSION_ID, A_ROOM_ID, isAudioCall = false),
|
||||
eventId = AN_EVENT_ID,
|
||||
senderId = A_USER_ID_2,
|
||||
roomName = "roomName",
|
||||
|
||||
+15
-27
@@ -8,11 +8,9 @@
|
||||
|
||||
package io.element.android.features.call.impl.pip
|
||||
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
@@ -20,9 +18,7 @@ class PictureInPicturePresenterTest {
|
||||
@Test
|
||||
fun `when pip is not supported, the state value supportPip is false`() = runTest {
|
||||
val presenter = createPictureInPicturePresenter(supportPip = false)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.supportPip).isFalse()
|
||||
}
|
||||
@@ -35,9 +31,7 @@ class PictureInPicturePresenterTest {
|
||||
supportPip = true,
|
||||
pipView = FakePipView(setPipParamsResult = { }),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.supportPip).isTrue()
|
||||
}
|
||||
@@ -53,18 +47,16 @@ class PictureInPicturePresenterTest {
|
||||
enterPipModeResult = enterPipModeResult,
|
||||
),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.isInPictureInPicture).isFalse()
|
||||
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
|
||||
initialState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
|
||||
enterPipModeResult.assertions().isCalledOnce()
|
||||
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(true))
|
||||
initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(true))
|
||||
val pipState = awaitItem()
|
||||
assertThat(pipState.isInPictureInPicture).isTrue()
|
||||
// User stops pip
|
||||
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(false))
|
||||
initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(false))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.isInPictureInPicture).isFalse()
|
||||
}
|
||||
@@ -80,12 +72,10 @@ class PictureInPicturePresenterTest {
|
||||
handUpResult = handUpResult
|
||||
),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(PictureInPictureEvents.SetPipController(FakePipController(canEnterPipResult = { false })))
|
||||
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
|
||||
initialState.eventSink(PictureInPictureEvent.SetPipController(FakePipController(canEnterPipResult = { false })))
|
||||
initialState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
|
||||
handUpResult.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
@@ -102,12 +92,10 @@ class PictureInPicturePresenterTest {
|
||||
enterPipModeResult = enterPipModeResult
|
||||
),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(
|
||||
PictureInPictureEvents.SetPipController(
|
||||
PictureInPictureEvent.SetPipController(
|
||||
FakePipController(
|
||||
canEnterPipResult = { true },
|
||||
enterPipResult = enterPipResult,
|
||||
@@ -115,16 +103,16 @@ class PictureInPicturePresenterTest {
|
||||
)
|
||||
)
|
||||
)
|
||||
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
|
||||
initialState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
|
||||
enterPipModeResult.assertions().isCalledOnce()
|
||||
enterPipResult.assertions().isNeverCalled()
|
||||
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(true))
|
||||
initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(true))
|
||||
val pipState = awaitItem()
|
||||
assertThat(pipState.isInPictureInPicture).isTrue()
|
||||
enterPipResult.assertions().isCalledOnce()
|
||||
// User stops pip
|
||||
exitPipResult.assertions().isNeverCalled()
|
||||
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(false))
|
||||
initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(false))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.isInPictureInPicture).isFalse()
|
||||
exitPipResult.assertions().isCalledOnce()
|
||||
|
||||
+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(
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector 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.call.ui
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.call.api.CallData
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import org.junit.Test
|
||||
|
||||
class CallDataTest {
|
||||
@Test
|
||||
fun `RoomCall stringification does not contain the URL`() {
|
||||
assertThat(CallData(A_SESSION_ID, A_ROOM_ID, false).toString())
|
||||
.isEqualTo("CallData(sessionId=$A_SESSION_ID, roomId=$A_ROOM_ID, isAudioCall=false)")
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.call.ui
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.call.impl.ui.CallScreenBackPressAction
|
||||
import io.element.android.features.call.impl.ui.CallScreenBackPressPolicy
|
||||
import org.junit.Test
|
||||
|
||||
class CallScreenBackPressPolicyTest {
|
||||
@Test
|
||||
fun `resolve returns dispatch escape when a web view is available and native button is pressed`() {
|
||||
val result = CallScreenBackPressPolicy.resolve(
|
||||
supportPip = false,
|
||||
hasWebView = true,
|
||||
fromNative = true,
|
||||
)
|
||||
|
||||
assertThat(result).isEqualTo(CallScreenBackPressAction.DispatchEscapeToWebView)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve dispatch escape when there is a web view and pip is supported on native button press`() {
|
||||
val result = CallScreenBackPressPolicy.resolve(
|
||||
supportPip = true,
|
||||
hasWebView = true,
|
||||
fromNative = true,
|
||||
)
|
||||
|
||||
assertThat(result).isEqualTo(CallScreenBackPressAction.DispatchEscapeToWebView)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve returns hangup when there is no web view and pip is not supported from native button`() {
|
||||
val result = CallScreenBackPressPolicy.resolve(
|
||||
supportPip = false,
|
||||
hasWebView = false,
|
||||
fromNative = true,
|
||||
)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve returns hangup when there is no web view even though pip is supported from native button`() {
|
||||
val result = CallScreenBackPressPolicy.resolve(
|
||||
supportPip = true,
|
||||
hasWebView = false,
|
||||
fromNative = true,
|
||||
)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve goes to pip if its not from native but from the webview`() {
|
||||
val result = CallScreenBackPressPolicy.resolve(
|
||||
supportPip = true,
|
||||
hasWebView = true,
|
||||
fromNative = false,
|
||||
)
|
||||
|
||||
assertThat(result).isEqualTo(CallScreenBackPressAction.EnterPictureInPicture)
|
||||
}
|
||||
@Test
|
||||
fun `resolve hangs up if its not from native but from the webview and pip is not supported`() {
|
||||
val result = CallScreenBackPressPolicy.resolve(
|
||||
supportPip = false,
|
||||
hasWebView = true,
|
||||
fromNative = false,
|
||||
)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invalid cases (event comes from webview but there is now webview) all result in hangup`() {
|
||||
val withPipSupport = CallScreenBackPressPolicy.resolve(
|
||||
supportPip = true,
|
||||
hasWebView = false,
|
||||
fromNative = false,
|
||||
)
|
||||
assertThat(withPipSupport).isNull()
|
||||
val withOutPipSupport = CallScreenBackPressPolicy.resolve(
|
||||
supportPip = false,
|
||||
hasWebView = false,
|
||||
fromNative = false,
|
||||
)
|
||||
assertThat(withOutPipSupport).isNull()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user