Merge branch 'release/25.05.0'
This commit is contained in:
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
retention-days: 5
|
||||
overwrite: true
|
||||
if-no-files-found: error
|
||||
- uses: rnkdsh/action-upload-diawi@v1.5.8
|
||||
- uses: rnkdsh/action-upload-diawi@605adbad0db6c000eee26adfd8fc128d7df8f7ab # v1.5.9
|
||||
id: diawi
|
||||
# Do not fail the whole build if Diawi upload fails
|
||||
continue-on-error: true
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
file: app/build/outputs/apk/gplay/debug/app-gplay-arm64-v8a-debug.apk
|
||||
- name: Add or update PR comment with QR Code to download APK.
|
||||
if: ${{ matrix.variant == 'debug' && github.event_name == 'pull_request' && steps.diawi.conclusion == 'success' }}
|
||||
uses: NejcZdovc/comment-pr@v2
|
||||
uses: NejcZdovc/comment-pr@a423635d183a8259308e80593c96fecf31539c26 # v2.1.0
|
||||
with:
|
||||
message: |
|
||||
:iphone: Scan the QR code below to install the build (arm64 only) for this PR.
|
||||
|
||||
2
.github/workflows/build_enterprise.yml
vendored
2
.github/workflows/build_enterprise.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@v0.9.1
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
- name: Clone submodules
|
||||
|
||||
4
.github/workflows/danger.yml
vendored
4
.github/workflows/danger.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@v0.9.1
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
- name: Clone submodules
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
- run: |
|
||||
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||
- name: Danger
|
||||
uses: danger/danger-js@13.0.4
|
||||
uses: danger/danger-js@bdccecb77e0144055fbaea9224f10cf8b1229b68 # 13.0.4
|
||||
with:
|
||||
args: "--dangerfile ./tools/danger/dangerfile.js"
|
||||
env:
|
||||
|
||||
4
.github/workflows/generate_github_pages.yml
vendored
4
.github/workflows/generate_github_pages.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
steps:
|
||||
- name: ⏬ Checkout with LFS
|
||||
uses: nschloe/action-cached-lfs-checkout@v1.2.3
|
||||
uses: nschloe/action-cached-lfs-checkout@f46300cd8952454b9f0a21a3d133d4bd5684cfc2 # v1.2.3
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
mkdir -p screenshots/en
|
||||
cp tests/uitests/src/test/snapshots/images/* screenshots/en
|
||||
- name: Deploy GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./screenshots
|
||||
|
||||
2
.github/workflows/gradle-wrapper-update.yml
vendored
2
.github/workflows/gradle-wrapper-update.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Update Gradle Wrapper
|
||||
uses: gradle-update/update-gradle-wrapper-action@v2
|
||||
uses: gradle-update/update-gradle-wrapper-action@512b1875f3b6270828abfe77b247d5895a2da1e5 # v2.1.0
|
||||
with:
|
||||
repo-token: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
||||
target-branch: develop
|
||||
|
||||
2
.github/workflows/nightlyReports.yml
vendored
2
.github/workflows/nightlyReports.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
if: ${{ github.repository == 'element-hq/element-x-android' }}
|
||||
steps:
|
||||
- name: ⏬ Checkout with LFS
|
||||
uses: nschloe/action-cached-lfs-checkout@v1.2.3
|
||||
uses: nschloe/action-cached-lfs-checkout@f46300cd8952454b9f0a21a3d133d4bd5684cfc2 # v1.2.3
|
||||
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
|
||||
14
.github/workflows/quality.yml
vendored
14
.github/workflows/quality.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@v0.9.1
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@v0.9.1
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@v0.9.1
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@v0.9.1
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
@@ -201,7 +201,7 @@ jobs:
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@v0.9.1
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
@@ -241,7 +241,7 @@ jobs:
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@v0.9.1
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
@@ -294,7 +294,7 @@ jobs:
|
||||
yarn add danger-plugin-lint-report --dev
|
||||
- name: Danger lint
|
||||
if: always()
|
||||
uses: danger/danger-js@13.0.4
|
||||
uses: danger/danger-js@bdccecb77e0144055fbaea9224f10cf8b1229b68 # 13.0.4
|
||||
with:
|
||||
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
|
||||
env:
|
||||
|
||||
6
.github/workflows/recordScreenshots.yml
vendored
6
.github/workflows/recordScreenshots.yml
vendored
@@ -19,18 +19,18 @@ jobs:
|
||||
steps:
|
||||
- name: Remove Record-Screenshots label
|
||||
if: github.event.label.name == 'Record-Screenshots'
|
||||
uses: actions-ecosystem/action-remove-labels@v1
|
||||
uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1.3.0
|
||||
with:
|
||||
labels: Record-Screenshots
|
||||
- name: ⏬ Checkout with LFS (PR)
|
||||
if: github.event.label.name == 'Record-Screenshots'
|
||||
uses: nschloe/action-cached-lfs-checkout@v1.2.3
|
||||
uses: nschloe/action-cached-lfs-checkout@f46300cd8952454b9f0a21a3d133d4bd5684cfc2 # v1.2.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
|
||||
- name: ⏬ Checkout with LFS (Branch)
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: nschloe/action-cached-lfs-checkout@v1.2.3
|
||||
uses: nschloe/action-cached-lfs-checkout@f46300cd8952454b9f0a21a3d133d4bd5684cfc2 # v1.2.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: ☕️ Use JDK 21
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@v0.9.1
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
|
||||
2
.github/workflows/sync-localazy.yml
vendored
2
.github/workflows/sync-localazy.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
./tools/localazy/importSupportedLocalesFromLocalazy.py
|
||||
./tools/test/generateAllScreenshots.py
|
||||
- name: Create Pull Request for Strings
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
with:
|
||||
token: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
||||
commit-message: Sync Strings from Localazy
|
||||
|
||||
2
.github/workflows/sync-sas-strings.yml
vendored
2
.github/workflows/sync-sas-strings.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Run SAS String script
|
||||
run: ./tools/sas/import_sas_strings.py
|
||||
- name: Create Pull Request for SAS Strings
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
with:
|
||||
commit-message: Sync SAS Strings
|
||||
title: Sync SAS Strings
|
||||
|
||||
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
@@ -33,13 +33,13 @@ jobs:
|
||||
sudo swapon /mnt/swapfile
|
||||
sudo swapon --show
|
||||
- name: ⏬ Checkout with LFS
|
||||
uses: nschloe/action-cached-lfs-checkout@v1.2.3
|
||||
uses: nschloe/action-cached-lfs-checkout@f46300cd8952454b9f0a21a3d133d4bd5684cfc2 # v1.2.3
|
||||
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 }}
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@v0.9.1
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
|
||||
# https://github.com/codecov/codecov-action
|
||||
- name: ☂️ Upload coverage reports to codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
2
.github/workflows/triage-incoming.yml
vendored
2
.github/workflows/triage-incoming.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
triage-new-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/91
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
14
.github/workflows/triage-labelled.yml
vendored
14
.github/workflows/triage-labelled.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
if: >
|
||||
github.repository == 'element-hq/element-x-android'
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/43
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
name: Move triaged needs info issues on board
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@v1.0.2
|
||||
id: addItem
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/91
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
labeled: X-Needs-Info
|
||||
- name: Print itemId
|
||||
run: echo ${{ steps.addItem.outputs.itemId }}
|
||||
- uses: kalgurn/update-project-item-status@main
|
||||
- uses: kalgurn/update-project-item-status@31e54df46a2cdaef4f85c31ac839fbcd2fd7c3a2 # 0.0.3
|
||||
if: ${{ steps.addItem.outputs.itemId }}
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/91
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'Team: Element X Feature')
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/73
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'Team: Verticals Feature')
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/57
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
@@ -66,7 +66,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@main
|
||||
- uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/69
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'X-Needs-Signoff')
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
- uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/element-hq/projects/89
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
2
.github/workflows/validate-lfs.yml
vendored
2
.github/workflows/validate-lfs.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Validate
|
||||
steps:
|
||||
- uses: nschloe/action-cached-lfs-checkout@v1.2.3
|
||||
- uses: nschloe/action-cached-lfs-checkout@f46300cd8952454b9f0a21a3d133d4bd5684cfc2 # v1.2.3
|
||||
|
||||
- run: |
|
||||
./tools/git/validate_lfs.sh
|
||||
|
||||
41
CHANGES.md
41
CHANGES.md
@@ -1,3 +1,44 @@
|
||||
Changes in Element X v25.04.3
|
||||
=============================
|
||||
|
||||
### 🙌 Improvements
|
||||
* Use PreferenceDropdown for appearance by @ganfra in https://github.com/element-hq/element-x-android/pull/4581
|
||||
### 🐛 Bugfixes
|
||||
* Use in-call volume and mode for EC by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4481
|
||||
* Send SVG images as files by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4595
|
||||
* Fetch the initial ignored user list manually when subscribing by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4598
|
||||
* Fix audio output selection for Element Call by @bmarty in https://github.com/element-hq/element-x-android/pull/4602
|
||||
* [a11y] Make more items focusable by @bmarty in https://github.com/element-hq/element-x-android/pull/4605
|
||||
* Fix ringing calls not stopping when the other user cancels the call by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4613
|
||||
* Ensure that pinning an event makes the pinned messages banner appear by @bmarty in https://github.com/element-hq/element-x-android/pull/4606
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/4590
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/4612
|
||||
### 📄 Documentation
|
||||
* Improve onboarding docs: by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4578
|
||||
### Dependency upgrades
|
||||
* Upgrade Rust bindings to `v25.04.11` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4580
|
||||
* fix(deps): update dependency androidx.sqlite:sqlite-ktx to v2.5.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4568
|
||||
* fix(deps): update dependency app.cash.molecule:molecule-runtime to v2.1.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4585
|
||||
* fix(deps): update core to v1.16.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4564
|
||||
* Upate datastore to 1.1.4 by @bmarty in https://github.com/element-hq/element-x-android/pull/4551
|
||||
* fix(deps): update media3 to v1.6.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4592
|
||||
* chore(deps): update danger/danger-js action to v13 by @renovate in https://github.com/element-hq/element-x-android/pull/4596
|
||||
* fix(deps): update dependency io.element.android:emojibase-bindings to v1.4.2 by @renovate in https://github.com/element-hq/element-x-android/pull/4591
|
||||
* fix(deps): update dagger to v2.56.2 by @renovate in https://github.com/element-hq/element-x-android/pull/4603
|
||||
* fix(deps): update dependency io.sentry:sentry-android to v8.8.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4557
|
||||
* fix(deps): update dependency androidx.compose:compose-bom to v2025.04.00 - autoclosed by @renovate in https://github.com/element-hq/element-x-android/pull/4565
|
||||
* fix(deps): update dependency com.posthog:posthog-android to v3.14.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4616
|
||||
* fix(deps): update android.gradle.plugin to v8.9.2 by @renovate in https://github.com/element-hq/element-x-android/pull/4615
|
||||
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.4.22 by @renovate in https://github.com/element-hq/element-x-android/pull/4622
|
||||
### Others
|
||||
* Improve accessibility of the timeline by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4579
|
||||
* Push: improve Push history screen, log and stored data by @bmarty in https://github.com/element-hq/element-x-android/pull/4601
|
||||
* Push gateway config by @bmarty in https://github.com/element-hq/element-x-android/pull/4608
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.04.2...v25.04.3
|
||||
|
||||
Changes in Element X v25.04.2
|
||||
=============================
|
||||
|
||||
|
||||
@@ -106,14 +106,25 @@ android {
|
||||
logger.warnInBox("Building ${defaultConfig.applicationId} ($baseAppName)")
|
||||
|
||||
buildTypes {
|
||||
val oidcRedirectSchemeBase = BuildTimeConfig.METADATA_HOST_REVERSED ?: "io.element.android"
|
||||
getByName("debug") {
|
||||
resValue("string", "app_name", "$baseAppName dbg")
|
||||
resValue(
|
||||
"string",
|
||||
"login_redirect_scheme",
|
||||
"$oidcRedirectSchemeBase.debug",
|
||||
)
|
||||
applicationIdSuffix = ".debug"
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
|
||||
getByName("release") {
|
||||
resValue("string", "app_name", baseAppName)
|
||||
resValue(
|
||||
"string",
|
||||
"login_redirect_scheme",
|
||||
oidcRedirectSchemeBase,
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
|
||||
postprocessing {
|
||||
@@ -131,6 +142,11 @@ android {
|
||||
applicationIdSuffix = ".nightly"
|
||||
versionNameSuffix = "-nightly"
|
||||
resValue("string", "app_name", "$baseAppName nightly")
|
||||
resValue(
|
||||
"string",
|
||||
"login_redirect_scheme",
|
||||
"$oidcRedirectSchemeBase.nightly",
|
||||
)
|
||||
matchingFallbacks += listOf("release")
|
||||
signingConfig = signingConfigs.getByName("nightly")
|
||||
|
||||
@@ -284,6 +300,7 @@ dependencies {
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
|
||||
koverDependencies()
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="io.element" />
|
||||
<data android:scheme="@string/login_redirect_scheme" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
Element web links
|
||||
|
||||
@@ -10,14 +10,17 @@ package io.element.android.x.di
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.appnav.di.RoomComponentFactory
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultRoomComponentFactory @Inject constructor(
|
||||
private val roomComponentBuilder: RoomComponent.Builder
|
||||
) : RoomComponentFactory {
|
||||
override fun create(room: MatrixRoom): Any {
|
||||
return roomComponentBuilder.room(room).build()
|
||||
override fun create(room: JoinedRoom): Any {
|
||||
return roomComponentBuilder
|
||||
.joinedRoom(room)
|
||||
.baseRoom(room)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
|
||||
@SingleIn(RoomScope::class)
|
||||
@MergeSubcomponent(RoomScope::class)
|
||||
@@ -22,7 +23,11 @@ interface RoomComponent : NodeFactoriesBindings {
|
||||
@MergeSubcomponent.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
fun room(room: MatrixRoom): Builder
|
||||
fun joinedRoom(room: JoinedRoom): Builder
|
||||
|
||||
@BindsInstance
|
||||
fun baseRoom(room: BaseRoom): Builder
|
||||
|
||||
fun build(): RoomComponent
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.x.oidc
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.auth.OidcRedirectUrlProvider
|
||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
import io.element.android.x.R
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultOidcRedirectUrlProvider @Inject constructor(
|
||||
private val stringProvider: StringProvider,
|
||||
) : OidcRedirectUrlProvider {
|
||||
override fun provide() = buildString {
|
||||
append(stringProvider.getString(R.string.login_redirect_scheme))
|
||||
append(":/")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.x.oidc
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import io.element.android.x.R
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultOidcRedirectUrlProviderTest {
|
||||
@Test
|
||||
fun `test provide`() {
|
||||
val stringProvider = FakeStringProvider(
|
||||
defaultResult = "str"
|
||||
)
|
||||
val sut = DefaultOidcRedirectUrlProvider(
|
||||
stringProvider = stringProvider,
|
||||
)
|
||||
val result = sut.provide()
|
||||
assertThat(result).isEqualTo("str:/")
|
||||
assertThat(stringProvider.lastResIdParam).isEqualTo(R.string.login_redirect_scheme)
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,7 @@ package io.element.android.appconfig
|
||||
object MatrixConfiguration {
|
||||
const val MATRIX_TO_PERMALINK_BASE_URL: String = "https://matrix.to/#/"
|
||||
val clientPermalinkBaseUrl: String? = null
|
||||
|
||||
// TODO remove this when report is fixed
|
||||
const val CAN_REPORT_ROOM = false
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
package io.element.android.appnav.di
|
||||
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
|
||||
interface RoomComponentFactory {
|
||||
fun create(room: MatrixRoom): Any
|
||||
fun create(room: JoinedRoom): Any
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -42,7 +43,9 @@ class SendQueues @Inject constructor(
|
||||
) { syncState, _ -> syncState }
|
||||
.debounce(SEND_QUEUES_RETRY_DELAY_MILLIS)
|
||||
.onEach { syncState ->
|
||||
Timber.tag("SendQueues").d("Sync state changed: $syncState")
|
||||
if (syncState == SyncState.Running) {
|
||||
Timber.tag("SendQueues").d("Enabling send queues again")
|
||||
matrixClient.setAllSendQueuesEnabled(enabled = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.appnav.room.joined.JoinedRoomFlowNode
|
||||
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
||||
import io.element.android.appnav.room.joined.LoadingRoomNodeView
|
||||
import io.element.android.appnav.room.joined.LoadingRoomState
|
||||
import io.element.android.features.joinroom.api.JoinRoomEntryPoint
|
||||
import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
@@ -49,6 +48,7 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomState
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
@@ -36,6 +36,8 @@ import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomState
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomStateFlowFactory
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@@ -35,7 +35,7 @@ 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.UserId
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -72,7 +72,7 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
|
||||
}
|
||||
|
||||
data class Inputs(
|
||||
val room: MatrixRoom,
|
||||
val room: JoinedRoom,
|
||||
val initialElement: RoomNavigationTarget,
|
||||
) : NodeInputs
|
||||
|
||||
@@ -95,6 +95,7 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
|
||||
},
|
||||
onDestroy = {
|
||||
Timber.v("OnDestroy")
|
||||
inputs.room.destroy()
|
||||
appNavigationStateService.onLeavingRoom(id)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -31,6 +31,8 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomState
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomStateProvider
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="banner_migrate_to_native_sliding_sync_action">"Allgofnodi ac Uwchraddio"</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"Nid yw %1$s bellach yn cefnogi\'r hen brotocol. Allgofnodwch a mewngofnodi\'n ôl i barhau i ddefnyddio\'r ap."</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Nid yw eich gweinydd cartref yn cefnogi\'r hen brotocol mwyach. Allgofnodwch a mewngofnodwch yn ôl i barhau i ddefnyddio\'r ap."</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Nid yw eich gweinydd cartref yn cefnogi\'r hen brotocol mwyach. Allgofnodwch a mewngofnodi yn ôl i barhau i ddefnyddio\'r ap."</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="banner_migrate_to_native_sliding_sync_action">"Αποσύνδεση & Αναβάθμιση"</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"Το %1$s δεν υποστηρίζει πλέον το παλιό πρωτόκολλο. Αποσυνδεθείτε και συνδεθείτε ξανά για να συνεχίσετε να χρησιμοποιείτε την εφαρμογή."</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Ο οικιακός διακομιστής σου δεν υποστηρίζει πλέον το παλιό πρωτόκολλο. Αποσυνδέσου και συνδέσου ξανά για να συνεχίσεις να χρησιμοποιείς την εφαρμογή."</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="banner_migrate_to_native_sliding_sync_action">"登出并升级"</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s 不再支持旧协议。请注销并重新登录以继续使用该应用程序。"</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"您的服务器不再支持旧协议。请登出并重新登录以继续使用此应用。"</string>
|
||||
</resources>
|
||||
|
||||
@@ -23,16 +23,17 @@ import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
||||
import io.element.android.features.messages.api.MessagesEntryPoint
|
||||
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
|
||||
import io.element.android.libraries.architecture.childNode
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class JoinRoomLoadedFlowNodeTest {
|
||||
class JoinBaseRoomLoadedFlowNodeTest {
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
@@ -68,7 +69,7 @@ class JoinRoomLoadedFlowNodeTest {
|
||||
}
|
||||
|
||||
private class FakeRoomComponentFactory : RoomComponentFactory {
|
||||
override fun create(room: MatrixRoom): Any {
|
||||
override fun create(room: JoinedRoom): Any {
|
||||
return Unit
|
||||
}
|
||||
}
|
||||
@@ -114,9 +115,7 @@ class JoinRoomLoadedFlowNodeTest {
|
||||
@Test
|
||||
fun `given a room flow node when initialized then it loads messages entry point`() = runTest {
|
||||
// GIVEN
|
||||
val room = FakeMatrixRoom(
|
||||
updateMembersResult = { }
|
||||
)
|
||||
val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {}))
|
||||
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
|
||||
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages())
|
||||
val roomFlowNode = createJoinedRoomLoadedFlowNode(
|
||||
@@ -137,9 +136,7 @@ class JoinRoomLoadedFlowNodeTest {
|
||||
@Test
|
||||
fun `given a room flow node when callback on room details is triggered then it loads room details entry point`() = runTest {
|
||||
// GIVEN
|
||||
val room = FakeMatrixRoom(
|
||||
updateMembersResult = { }
|
||||
)
|
||||
val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {}))
|
||||
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
|
||||
val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint()
|
||||
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages())
|
||||
@@ -20,10 +20,11 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
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.auth.FakeOidcRedirectUrlProvider
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
import io.element.android.libraries.oidc.api.OidcAction
|
||||
import io.element.android.libraries.oidc.impl.DefaultOidcIntentResolver
|
||||
import io.element.android.libraries.oidc.impl.OidcUrlParser
|
||||
import io.element.android.libraries.oidc.impl.DefaultOidcUrlParser
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
@@ -119,7 +120,7 @@ class IntentResolverTest {
|
||||
val sut = createIntentResolver()
|
||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
data = "io.element:/callback?error=access_denied&state=IFF1UETGye2ZA8pO".toUri()
|
||||
data = "io.element.android:/?error=access_denied&state=IFF1UETGye2ZA8pO".toUri()
|
||||
}
|
||||
val result = sut.resolve(intent)
|
||||
assertThat(result).isEqualTo(
|
||||
@@ -134,13 +135,13 @@ class IntentResolverTest {
|
||||
val sut = createIntentResolver()
|
||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
data = "io.element:/callback?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB".toUri()
|
||||
data = "io.element.android:/?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB".toUri()
|
||||
}
|
||||
val result = sut.resolve(intent)
|
||||
assertThat(result).isEqualTo(
|
||||
ResolvedIntent.Oidc(
|
||||
oidcAction = OidcAction.Success(
|
||||
url = "io.element:/callback?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB"
|
||||
url = "io.element.android:/?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB"
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -151,7 +152,7 @@ class IntentResolverTest {
|
||||
val sut = createIntentResolver()
|
||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
data = "io.element:/callback/invalid".toUri()
|
||||
data = "io.element.android:/invalid".toUri()
|
||||
}
|
||||
assertThrows(IllegalStateException::class.java) {
|
||||
sut.resolve(intent)
|
||||
@@ -246,7 +247,9 @@ class IntentResolverTest {
|
||||
return IntentResolver(
|
||||
deeplinkParser = DeeplinkParser(),
|
||||
oidcIntentResolver = DefaultOidcIntentResolver(
|
||||
oidcUrlParser = OidcUrlParser()
|
||||
oidcUrlParser = DefaultOidcUrlParser(
|
||||
oidcRedirectUrlProvider = FakeOidcRedirectUrlProvider(),
|
||||
)
|
||||
),
|
||||
permalinkParser = FakePermalinkParser(
|
||||
result = permalinkParserResult
|
||||
|
||||
@@ -10,7 +10,7 @@ package io.element.android.appnav.loggedin
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||
import io.element.android.tests.testutils.lambda.assert
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
@@ -35,8 +35,8 @@ class SendQueuesTest {
|
||||
matrixClient.sendQueueDisabledFlow = sendQueueDisabledFlow
|
||||
matrixClient.setAllSendQueuesEnabledLambda = setAllSendQueuesEnabledLambda
|
||||
val setRoomSendQueueEnabledLambda = lambdaRecorder { _: Boolean -> }
|
||||
val room = FakeMatrixRoom(
|
||||
setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda
|
||||
val room = FakeJoinedRoom(
|
||||
setSendQueueEnabledResult = setRoomSendQueueEnabledLambda
|
||||
)
|
||||
matrixClient.givenGetRoomResult(room.roomId, room)
|
||||
sut.launchIn(backgroundScope)
|
||||
@@ -61,8 +61,8 @@ class SendQueuesTest {
|
||||
matrixClient.setAllSendQueuesEnabledLambda = setAllSendQueuesEnabledLambda
|
||||
syncService.emitSyncState(SyncState.Offline)
|
||||
val setRoomSendQueueEnabledLambda = lambdaRecorder { _: Boolean -> }
|
||||
val room = FakeMatrixRoom(
|
||||
setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda
|
||||
val room = FakeJoinedRoom(
|
||||
setSendQueueEnabledResult = setRoomSendQueueEnabledLambda
|
||||
)
|
||||
matrixClient.givenGetRoomResult(room.roomId, room)
|
||||
|
||||
|
||||
@@ -9,21 +9,22 @@ package io.element.android.appnav.room
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appnav.room.joined.LoadingRoomState
|
||||
import io.element.android.appnav.room.joined.LoadingRoomStateFlowFactory
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||
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.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomState
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomStateFlowFactory
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class LoadingRoomStateFlowFactoryTest {
|
||||
class LoadingBaseRoomStateFlowFactoryTest {
|
||||
@Test
|
||||
fun `flow should emit Loading and then Loaded when there is a room in cache`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID, roomId = A_ROOM_ID)
|
||||
val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(sessionId = A_SESSION_ID, roomId = A_ROOM_ID))
|
||||
val matrixClient = FakeMatrixClient(A_SESSION_ID).apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
@@ -38,7 +39,7 @@ class LoadingRoomStateFlowFactoryTest {
|
||||
|
||||
@Test
|
||||
fun `flow should emit Loading and then Loaded when there is a room in cache after SS is loaded`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID, roomId = A_ROOM_ID)
|
||||
val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(sessionId = A_SESSION_ID, roomId = A_ROOM_ID))
|
||||
val roomListService = FakeRoomListService()
|
||||
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
|
||||
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
|
||||
@@ -11,7 +11,7 @@ Server list: https://github.com/element-hq/oidc-playground
|
||||
Metadata iOS: (from https://github.com/element-hq/element-x-ios/blob/5f9d07377cebc4f21d9668b1a25f6e3bb22f64a1/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift#L28)
|
||||
|
||||
clientName: InfoPlistReader.main.bundleDisplayName,
|
||||
redirectUri: "io.element:/callback",
|
||||
redirectUri: "io.element.android:/",
|
||||
clientUri: "https://element.io",
|
||||
tosUri: "https://element.io/user-terms-of-service",
|
||||
policyUri: "https://element.io/privacy"
|
||||
@@ -19,7 +19,7 @@ policyUri: "https://element.io/privacy"
|
||||
|
||||
Android:
|
||||
clientName = "Element",
|
||||
redirectUri = "io.element:/callback",
|
||||
redirectUri = "io.element.android:/",
|
||||
clientUri = "https://element.io",
|
||||
tosUri = "https://element.io/user-terms-of-service",
|
||||
policyUri = "https://element.io/privacy"
|
||||
|
||||
2
fastlane/metadata/android/en-US/changelogs/202505000.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202505000.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: bug fixes and improvements.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
@@ -1,4 +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">"Rhannu data defnydd dienw i\'n helpu i nodi problemau."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Gallwch ddarllen ein holl amodau %1$s."</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"yma"</string>
|
||||
<string name="screen_analytics_settings_share_data">"Rhannu data dadansoddeg"</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">"Teile anonyme Nutzungsdaten, um uns bei der Identifizierung von Problemen zu helfen."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Weitere Informationen findest du %1$s ."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Weitere Informationen findest du %1$s."</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"hier"</string>
|
||||
<string name="screen_analytics_settings_share_data">"Analysedaten teilen"</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">"Partekatu erabilerari buruzko datu anonimoak arazoak identifikatzen laguntzeko."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Gure baldintza guztiak irakur ditzakezu %1$s ."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Gure baldintza guztiak irakur ditzakezu %1$s."</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"hemen"</string>
|
||||
<string name="screen_analytics_settings_share_data">"Partekatu analisi-datuak"</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">"Dalinkitės anoniminiais naudojimo duomenimis ir padėkite mums nustatyti problemas."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Galite perskaityti visas mūsų sąlygas %1$s ."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Galite perskaityti visas mūsų sąlygas %1$s."</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"čia"</string>
|
||||
<string name="screen_analytics_settings_share_data">"Dalytis analitiniais duomenimis"</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">"Compartilhe dados de uso anônimos para nos ajudar a identificar problemas."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Você pode ler todos os nossos termos %1$s ."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Você pode ler todos os nossos termos %1$s."</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"aqui"</string>
|
||||
<string name="screen_analytics_settings_share_data">"Compartilhar dados de utilização"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +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">"Fyddwn ni ddim yn cofnodi nac yn proffilio unrhyw ddata personol"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Rhannu data defnydd dienw i\'n helpu i nodi problemau."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Gallwch ddarllen ein holl amodau %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"yma"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Gallwch ddiffodd hwn unrhyw bryd"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Fyddwn ni ddim yn rhannu eich data gyda thrydydd parti"</string>
|
||||
<string name="screen_analytics_prompt_title">"Helpwch i wella %1$s"</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"Wir zeichnen keine persönlichen Daten auf und erstellen keine Profile."</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Teile anonyme Nutzungsdaten, um uns bei der Identifizierung von Problemen zu helfen."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Weitere Informationen findest du %1$s ."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Weitere Informationen findest du %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"hier"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Du kannst diese Funktion jederzeit deaktivieren"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Wir geben deine Daten nicht an Dritte weiter"</string>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Partekatu erabilerari buruzko datu anonimoak arazoak identifikatzen laguntzeko."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Gure baldintza guztiak irakur ditzakezu %1$s ."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Gure baldintza guztiak irakur ditzakezu %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"hemen"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Edozein unetan desaktibatu dezakezu"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Ez ditugu zure datuak hirugarrenekin partekatuko"</string>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"Mes nekaupsime ir neprofiliuosime jokių asmens duomenų"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Dalinkitės anoniminiais naudojimo duomenimis ir padėkite mums nustatyti problemas."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Galite perskaityti visas mūsų sąlygas %1$s ."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Galite perskaityti visas mūsų sąlygas %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"čia"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Tai galite bet kada išjungti"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Mes nesidalinsime Jūsų duomenimis su trečiosiomis šalimis"</string>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"Não registraremos nem criaremos perfil baseado em qualquer dado pessoal"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Compartilhe dados de uso anônimos para nos ajudar a identificar problemas."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Você pode ler todos os nossos termos %1$s ."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Você pode ler todos os nossos termos %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"aqui"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Você pode desativar isso a qualquer momento"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Não compartilharemos seus dados com terceiros"</string>
|
||||
|
||||
@@ -241,7 +241,7 @@ class CallScreenPresenter @AssistedInject constructor(
|
||||
|
||||
private suspend fun MatrixClient.notifyCallStartIfNeeded(roomId: RoomId) {
|
||||
if (!notifiedCallStart) {
|
||||
getRoom(roomId)?.sendCallNotificationIfNeeded()
|
||||
getJoinedRoom(roomId)?.use { it.sendCallNotificationIfNeeded() }
|
||||
?.onSuccess { notifiedCallStart = true }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ package io.element.android.features.call.impl.ui
|
||||
import android.Manifest
|
||||
import android.app.PictureInPictureParams
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioFocusRequest
|
||||
import android.media.AudioManager
|
||||
@@ -78,7 +77,6 @@ class ElementCallActivity :
|
||||
|
||||
private val requestPermissionsLauncher = registerPermissionResultLauncher()
|
||||
|
||||
private var isDarkMode = false
|
||||
private val webViewTarget = mutableStateOf<CallType?>(null)
|
||||
|
||||
private var eventSink: ((CallScreenEvents) -> Unit)? = null
|
||||
@@ -102,10 +100,6 @@ class ElementCallActivity :
|
||||
return
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
updateUiMode(resources.configuration)
|
||||
}
|
||||
|
||||
pictureInPicturePresenter.setPipView(this)
|
||||
|
||||
audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
|
||||
@@ -174,11 +168,6 @@ class ElementCallActivity :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
updateUiMode(newConfig)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setCallType(intent)
|
||||
@@ -283,19 +272,6 @@ class ElementCallActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateUiMode(configuration: Configuration) {
|
||||
val prevDarkMode = isDarkMode
|
||||
val currentNightMode = configuration.uiMode and Configuration.UI_MODE_NIGHT_YES
|
||||
isDarkMode = currentNightMode != 0
|
||||
if (prevDarkMode != isDarkMode) {
|
||||
if (isDarkMode) {
|
||||
window.setBackgroundDrawableResource(android.R.drawable.screen_background_dark)
|
||||
} else {
|
||||
window.setBackgroundDrawableResource(android.R.drawable.screen_background_light)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun setPipParams() {
|
||||
setPictureInPictureParams(getPictureInPictureParams())
|
||||
|
||||
@@ -13,6 +13,8 @@ import android.os.PowerManager
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import coil3.SingletonImageLoader
|
||||
import coil3.annotation.DelicateCoilApi
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.appconfig.ElementCallConfig
|
||||
import io.element.android.features.call.api.CallType
|
||||
@@ -23,6 +25,8 @@ import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
|
||||
import io.element.android.libraries.push.api.notifications.ForegroundServiceType
|
||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||
import io.element.android.libraries.push.api.notifications.OnMissedCallNotificationHandler
|
||||
@@ -89,6 +93,7 @@ class DefaultActiveCallManager @Inject constructor(
|
||||
private val matrixClientProvider: MatrixClientProvider,
|
||||
private val defaultCurrentCallService: DefaultCurrentCallService,
|
||||
private val appForegroundStateService: AppForegroundStateService,
|
||||
private val imageLoaderHolder: ImageLoaderHolder,
|
||||
) : ActiveCallManager {
|
||||
private val tag = "DefaultActiveCallManager"
|
||||
private var timedOutCallJob: Job? = null
|
||||
@@ -125,6 +130,7 @@ class DefaultActiveCallManager @Inject constructor(
|
||||
)
|
||||
|
||||
timedOutCallJob = coroutineScope.launch {
|
||||
setUpCoil(notificationData.sessionId)
|
||||
showIncomingCallNotification(notificationData)
|
||||
|
||||
// Wait for the ringing call to time out
|
||||
@@ -140,6 +146,13 @@ class DefaultActiveCallManager @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoilApi::class)
|
||||
private suspend fun setUpCoil(sessionId: SessionId) {
|
||||
val matrixClient = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return
|
||||
// Ensure that the image loader is set, else the IncomingCallActivity will not be able to render the caller avatar
|
||||
SingletonImageLoader.setUnsafe(imageLoaderHolder.get(matrixClient))
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the incoming call timed out. It will remove the active call and remove any associated UI, adding a 'missed call' notification.
|
||||
*/
|
||||
|
||||
@@ -33,7 +33,7 @@ class DefaultCallWidgetProvider @Inject constructor(
|
||||
theme: String?,
|
||||
): Result<CallWidgetProvider.GetWidgetResult> = runCatching {
|
||||
val matrixClient = matrixClientsProvider.getOrRestore(sessionId).getOrThrow()
|
||||
val room = matrixClient.getRoom(roomId) ?: error("Room not found")
|
||||
val room = matrixClient.getJoinedRoom(roomId) ?: error("Room not found")
|
||||
|
||||
val customBaseUrl = appPreferencesStore.getCustomElementCallBaseUrlFlow().firstOrNull()
|
||||
val baseUrl = customBaseUrl ?: EMBEDDED_CALL_WIDGET_BASE_URL
|
||||
@@ -47,8 +47,11 @@ class DefaultCallWidgetProvider @Inject constructor(
|
||||
theme = theme,
|
||||
).getOrThrow()
|
||||
|
||||
val driver = room.getWidgetDriver(widgetSettings).getOrThrow()
|
||||
room.destroy()
|
||||
|
||||
CallWidgetProvider.GetWidgetResult(
|
||||
driver = room.getWidgetDriver(widgetSettings).getOrThrow(),
|
||||
driver = driver,
|
||||
url = callUrl,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,4 +3,5 @@
|
||||
<string name="call_foreground_service_channel_title_android">"Galwad cyfredol"</string>
|
||||
<string name="call_foreground_service_message_android">"Tapio i ddychwelyd i\'r alwad"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Galwad ar y gweill"</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Galwad Element"</string>
|
||||
</resources>
|
||||
|
||||
@@ -26,7 +26,7 @@ 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.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
|
||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
||||
@@ -84,7 +84,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||
fun `present - with CallType RoomCall sets call as active, loads URL, runs WidgetDriver and notifies the other clients a call started`() = runTest {
|
||||
val sendCallNotificationIfNeededLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||
val syncService = FakeSyncService(SyncState.Running)
|
||||
val fakeRoom = FakeMatrixRoom(sendCallNotificationIfNeededResult = sendCallNotificationIfNeededLambda)
|
||||
val fakeRoom = FakeJoinedRoom(sendCallNotificationIfNeededResult = sendCallNotificationIfNeededLambda)
|
||||
val client = FakeMatrixClient(syncService = syncService).apply {
|
||||
givenGetRoomResult(A_ROOM_ID, fakeRoom)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.push.api.notifications.ForegroundServiceType
|
||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||
@@ -227,7 +227,7 @@ class DefaultActiveCallManagerTest {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `observeRingingCalls - will cancel the active ringing call if the call is cancelled`() = runTest {
|
||||
val room = FakeMatrixRoom().apply {
|
||||
val room = FakeBaseRoom().apply {
|
||||
givenRoomInfo(aRoomInfo())
|
||||
}
|
||||
val client = FakeMatrixClient().apply {
|
||||
@@ -254,7 +254,7 @@ class DefaultActiveCallManagerTest {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `observeRingingCalls - will do nothing if either the session or the room are not found`() = runTest {
|
||||
val room = FakeMatrixRoom().apply {
|
||||
val room = FakeBaseRoom().apply {
|
||||
givenRoomInfo(aRoomInfo())
|
||||
}
|
||||
val client = FakeMatrixClient().apply {
|
||||
@@ -325,5 +325,6 @@ class DefaultActiveCallManagerTest {
|
||||
matrixClientProvider = matrixClientProvider,
|
||||
defaultCurrentCallService = DefaultCurrentCallService(),
|
||||
appForegroundStateService = FakeAppForegroundStateService(),
|
||||
imageLoaderHolder = FakeImageLoaderHolder(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ 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.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.widget.FakeCallWidgetSettingsProvider
|
||||
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
@@ -41,7 +41,7 @@ class DefaultCallWidgetProviderTest {
|
||||
|
||||
@Test
|
||||
fun `getWidget - fails if it can't generate the URL for the widget`() = runTest {
|
||||
val room = FakeMatrixRoom(
|
||||
val room = FakeJoinedRoom(
|
||||
generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.failure(Exception("Can't generate URL for widget")) }
|
||||
)
|
||||
val client = FakeMatrixClient().apply {
|
||||
@@ -53,7 +53,7 @@ class DefaultCallWidgetProviderTest {
|
||||
|
||||
@Test
|
||||
fun `getWidget - fails if it can't get the widget driver`() = runTest {
|
||||
val room = FakeMatrixRoom(
|
||||
val room = FakeJoinedRoom(
|
||||
generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") },
|
||||
getWidgetDriverResult = { Result.failure(Exception("Can't get a widget driver")) }
|
||||
)
|
||||
@@ -66,7 +66,7 @@ class DefaultCallWidgetProviderTest {
|
||||
|
||||
@Test
|
||||
fun `getWidget - returns a widget driver when all steps are successful`() = runTest {
|
||||
val room = FakeMatrixRoom(
|
||||
val room = FakeJoinedRoom(
|
||||
generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") },
|
||||
getWidgetDriverResult = { Result.success(FakeMatrixWidgetDriver()) },
|
||||
)
|
||||
@@ -79,7 +79,7 @@ class DefaultCallWidgetProviderTest {
|
||||
|
||||
@Test
|
||||
fun `getWidget - will use a custom base url if it exists`() = runTest {
|
||||
val room = FakeMatrixRoom(
|
||||
val room = FakeJoinedRoom(
|
||||
generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") },
|
||||
getWidgetDriverResult = { Result.success(FakeMatrixWidgetDriver()) },
|
||||
)
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
<string name="screen_create_room_action_create_room">"Новы пакой"</string>
|
||||
<string name="screen_create_room_add_people_title">"Запрасіць карыстальнікаў"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Пры стварэнні пакоя адбылася памылка"</string>
|
||||
<string name="screen_create_room_private_option_description">"Паведамленні ў гэтым пакоі зашыфраваны. Гэта шыфраванне нельга адключыць."</string>
|
||||
<string name="screen_create_room_private_option_title">"Прыватны пакой (толькі па запрашэнні)"</string>
|
||||
<string name="screen_create_room_public_option_description">"Паведамленні не зашыфраваны, і кожны можа іх прачытаць. Вы можаце ўключыць шыфраванне пазней."</string>
|
||||
<string name="screen_create_room_private_option_description">"Толькі запрошаныя людзі могуць атрымаць доступ да гэтага пакоя. Усе паведамленні абаронены end-to-end шыфраваннем."</string>
|
||||
<string name="screen_create_room_private_option_title">"Прыватны пакой"</string>
|
||||
<string name="screen_create_room_public_option_description">"Любы можа знайсці гэты пакой.
|
||||
Вы можаце змяніць гэта ў любы час у наладах пакоя."</string>
|
||||
<string name="screen_create_room_public_option_title">"Публічны пакой"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_title">"Хто заўгодна"</string>
|
||||
<string name="screen_create_room_room_access_section_header">"Доступ у пакой"</string>
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_create_room_action_create_room">"Ystafell newydd"</string>
|
||||
<string name="screen_create_room_add_people_title">"Gwahodd pobl"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Bu gwall wrth greu\'r ystafell"</string>
|
||||
<string name="screen_create_room_private_option_description">"Dim ond pobl wahoddwyd all gael mynediad i\'r ystafell hon. Mae pob neges wedi\'i hamgryptio o\'r dechrau i\'r diwedd."</string>
|
||||
<string name="screen_create_room_private_option_title">"Ystafell breifat"</string>
|
||||
<string name="screen_create_room_public_option_description">"Gall unrhyw un ddod o hyd i\'r ystafell hon.
|
||||
Gallwch newid hyn unrhyw bryd yng ngosodiadau ystafell."</string>
|
||||
<string name="screen_create_room_public_option_title">"Ystafell gyhoeddus"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_description">"Gall unrhyw un ymuno â\'r ystafell hon"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_title">"Unrhyw un"</string>
|
||||
<string name="screen_create_room_room_access_section_header">"Mynediad i\'r Ystafell"</string>
|
||||
@@ -11,4 +18,13 @@
|
||||
<string name="screen_create_room_room_name_label">"Enw\'r ystafell"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Gwelededd yr ystafell"</string>
|
||||
<string name="screen_create_room_title">"Creu ystafell"</string>
|
||||
<string name="screen_create_room_topic_label">"Pwnc (dewisol)"</string>
|
||||
<string name="screen_room_directory_search_title">"Cyfeiriadur ystafelloedd"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Digwyddodd gwall wrth geisio cychwyn sgwrs"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_action">"Ymuno â\'r ystafell yn ôl cyfeiriad"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Ddim yn gyfeiriad dilys"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Ewch i mewn…"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_found">"Cafwyd hyd i ystafell gyfatebol"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Heb ganfod yr ystafell"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"e.e. #enw-ystafell:matrix.org"</string>
|
||||
</resources>
|
||||
|
||||
@@ -14,10 +14,17 @@
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Οποιοσδήποτε μπορεί να ζητήσει να συμμετάσχει στο δωμάτιο, αλλά ένας διαχειριστής ή συντονιστής θα πρέπει να αποδεχθεί το αίτημα"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Αίτημα συμμετοχής"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Για να είναι ορατό αυτό το δωμάτιο στον κατάλογο των δημόσιων δωματίων, θα χρειαστείς μια διεύθυνση δωματίου."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Διεύθυνση δωματίου"</string>
|
||||
<string name="screen_create_room_room_name_label">"Όνομα δωματίου"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Ορατότητα δωματίου"</string>
|
||||
<string name="screen_create_room_title">"Δημιούργησε ένα δωμάτιο"</string>
|
||||
<string name="screen_create_room_topic_label">"Θέμα (προαιρετικό)"</string>
|
||||
<string name="screen_room_directory_search_title">"Κατάλογος δωματίων"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Παρουσιάστηκε σφάλμα κατά την προσπάθεια έναρξης μιας συνομιλίας"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_action">"Συμμετοχή σε δωμάτιο μέσω διεύθυνσης"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Μη έγκυρη διεύθυνση"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Εισάγετε…"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_found">"Βρέθηκε το αντίστοιχο δωμάτιο"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Το δωμάτιο δε βρέθηκε"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"π.χ. #όνομα-δωματίου:matrix.org"</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,12 +3,25 @@
|
||||
<string name="screen_create_room_action_create_room">"اتاق جدید"</string>
|
||||
<string name="screen_create_room_add_people_title">"دعوت افراد"</string>
|
||||
<string name="screen_create_room_error_creating_room">"هنگام ایجاد اتاق خطایی رخ داد"</string>
|
||||
<string name="screen_create_room_private_option_description">"پیامهای این اتاق رمز شدهاند. رمزنگاری نمیتواند از این پس تغییر کند."</string>
|
||||
<string name="screen_create_room_private_option_title">"اتاق خصوصی (فقط دعوت)"</string>
|
||||
<string name="screen_create_room_public_option_description">"پیامها رمزنگاری نشده و هرکسی میتواند بخواندشان. میتوانید بعداً رمزنگاری را به کار بیندازید."</string>
|
||||
<string name="screen_create_room_private_option_description">"تنها افراد دعوت شده میتوانند به این اتاق دسترسی داشته باشند. همهٔ پیامها رمزنگاری سرتاسری شدهاند."</string>
|
||||
<string name="screen_create_room_private_option_title">"اتاق خصوصی"</string>
|
||||
<string name="screen_create_room_public_option_description">"هرکسی میتواند اتاق را بیابد.
|
||||
میتوانید بعداً در تظیمات اتاق عوضش کنید."</string>
|
||||
<string name="screen_create_room_public_option_title">"اتاق عمومی"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_description">"هرکسی میتواند به این اتاق بپیوندد"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_title">"هرکسی"</string>
|
||||
<string name="screen_create_room_room_access_section_header">"دسترسی اتاق"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"درخواست دعوت"</string>
|
||||
<string name="screen_create_room_room_address_section_title">"نشانی اتاق"</string>
|
||||
<string name="screen_create_room_room_name_label">"نام اتاق"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"نمایانی اتاق"</string>
|
||||
<string name="screen_create_room_title">"ایجاد اتاق"</string>
|
||||
<string name="screen_create_room_topic_label">"موضوع (اختیاری)"</string>
|
||||
<string name="screen_room_directory_search_title">"فهرست اتاقها"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_action">"پیوستن به اتاق با نشانی"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"نشانی معتبری نیست"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_placeholder">"ورود…"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_found">"اتاق مطابق پیدا شد"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"اتاق پیدا نشد"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"نمونه: #room-name:matrix.org"</string>
|
||||
</resources>
|
||||
|
||||
@@ -21,4 +21,10 @@
|
||||
<string name="screen_create_room_topic_label">"主題(非必填)"</string>
|
||||
<string name="screen_room_directory_search_title">"聊天室目錄"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"嘗試開始聊天時發生錯誤"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_action">"按地址加入聊天室"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"不是有效的位址"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_placeholder">"輸入……"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_found">"找到相符的聊天室"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"找不到聊天室"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"例如 #room-name:matrix.org"</string>
|
||||
</resources>
|
||||
|
||||
@@ -66,7 +66,7 @@ private const val AN_URI_FROM_CAMERA_2 = "content://uri_from_camera_2"
|
||||
private const val AN_URI_FROM_GALLERY = "content://uri_from_gallery"
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class ConfigureRoomPresenterTest {
|
||||
class ConfigureBaseRoomPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@@ -21,7 +21,7 @@ import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class JoinRoomByAddressPresenterTest {
|
||||
class JoinBaseRoomByAddressPresenterTest {
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createJoinRoomByAddressPresenter()
|
||||
@@ -23,7 +23,7 @@ import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class JoinRoomByAddressViewTest {
|
||||
class JoinBaseRoomByAddressViewTest {
|
||||
@get:Rule
|
||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@@ -36,7 +36,7 @@ import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class CreateRoomRootPresenterTest {
|
||||
class CreateBaseRoomRootPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.junit.runner.RunWith
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CreateRoomRootViewTest {
|
||||
class CreateBaseRoomRootViewTest {
|
||||
@get:Rule
|
||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_deactivate_account_confirmation_dialog_content">"Cadarnhewch eich bod am gau\'r cyfrif. Does dom modd dadwneud y weithred hon."</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages">"Dileu fy holl negeseuon"</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages_notice">"Rhybudd: Mae\'n bosibl y bydd defnyddwyr y dyfodol yn gweld sgyrsiau anghyflawn."</string>
|
||||
<string name="screen_deactivate_account_description">"Bydd cau eich cyfrif yn %1$s yn:"</string>
|
||||
<string name="screen_deactivate_account_description_bold_part">"dim modd ei adfer"</string>
|
||||
<string name="screen_deactivate_account_list_item_1">"%1$s eich cyfrif (does dim modd i chi fewngofnodi eto, ac nid oes modd ailddefnyddio\'ch dull adnabod)."</string>
|
||||
<string name="screen_deactivate_account_list_item_1_bold_part">"Analluogi\'n barhaol"</string>
|
||||
<string name="screen_deactivate_account_list_item_2">"Eich tynnu o bob ystafell sgwrsio."</string>
|
||||
<string name="screen_deactivate_account_list_item_3">"Dileu manylion eich cyfrif o\'n gweinydd hunaniaeth."</string>
|
||||
<string name="screen_deactivate_account_list_item_4">"Bydd eich negeseuon yn dal i fod yn weladwy i ddefnyddwyr cofrestredig ond fyddan nhw ddim ar gael i ddefnyddwyr newydd neu anghofrestredig os byddwch yn dewis eu dileu."</string>
|
||||
<string name="screen_deactivate_account_title">"Cau cyfrif"</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_deactivate_account_description">"Desativar sua conta é %1$s, isso irá:"</string>
|
||||
<string name="screen_deactivate_account_list_item_2">"Te remover de todas as salas de conversa."</string>
|
||||
<string name="screen_deactivate_account_title">"Desativar conta"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_welcome_button">"Ffwrdd â ni!"</string>
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Methu cadarnhau?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Crëwch allwedd adfer newydd"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Dilyswch y ddyfais hon er mwyn gosod negeseuon diogel."</string>
|
||||
<string name="screen_identity_confirmation_title">"Cadarnhewch eich hunaniaeth"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Defnyddiwch ddyfais arall"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Defnyddiwch allwedd adfer"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Nawr gallwch chi ddarllen neu anfon negeseuon yn ddiogel, a gall unrhyw un rydych chi\'n sgwrsio â nhw ymddiried yn y ddyfais hon hefyd."</string>
|
||||
<string name="screen_identity_confirmed_title">"Dyfais wedi\'i dilysu"</string>
|
||||
<string name="screen_identity_use_another_device">"Defnyddiwch ddyfais arall"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Yn aros ar ddyfais arall…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Gallwch newid eich gosodiadau yn nes ymlaen."</string>
|
||||
<string name="screen_notification_optin_title">"Caniatáu hysbysiadau a pheidio byth â cholli neges"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Rhowch eich allwedd adfer"</string>
|
||||
<string name="screen_welcome_bullet_1">"Bydd galwadau, pleidleisiau, chwilio a mwy yn cael eu hychwanegu yn ddiweddarach eleni."</string>
|
||||
<string name="screen_welcome_bullet_2">"Nid yw hanes negeseuon ar gyfer ystafelloedd sydd wedi\'u hamgryptio ar gael eto."</string>
|
||||
<string name="screen_welcome_bullet_3">"Byddem wrth ein bodd yn clywed gennych, gadewch i ni wybod beth yw eich barn drwy\'r dudalen gosodiadau."</string>
|
||||
<string name="screen_welcome_button">"Ymlaen!"</string>
|
||||
<string name="screen_welcome_subtitle">"Dyma beth sydd angen i chi ei wybod:"</string>
|
||||
<string name="screen_welcome_title">"Croeso i %1$s!"</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -16,5 +17,7 @@ android {
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.services.analytics.api)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.invite.api
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class InviteData(
|
||||
val roomId: RoomId,
|
||||
val roomName: String,
|
||||
val isDm: Boolean,
|
||||
) : Parcelable
|
||||
|
||||
fun RoomPreviewInfo.toInviteData(): InviteData {
|
||||
return InviteData(
|
||||
roomId = roomId,
|
||||
roomName = name ?: roomId.value,
|
||||
isDm = false,
|
||||
)
|
||||
}
|
||||
|
||||
fun RoomInfo.toInviteData(): InviteData {
|
||||
return InviteData(
|
||||
roomId = id,
|
||||
roomName = name ?: id.value,
|
||||
isDm = isDm,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2024 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.invite.api.acceptdecline
|
||||
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
|
||||
interface AcceptDeclineInviteEvents {
|
||||
data class AcceptInvite(val invite: InviteData) : AcceptDeclineInviteEvents
|
||||
data class DeclineInvite(val invite: InviteData, val blockUser: Boolean, val shouldConfirm: Boolean) : AcceptDeclineInviteEvents
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.invite.api.response
|
||||
package io.element.android.features.invite.api.acceptdecline
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
@@ -5,12 +5,12 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.invite.api.response
|
||||
package io.element.android.features.invite.api.acceptdecline
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
open class AcceptDeclineInviteStateProvider : PreviewParameterProvider<AcceptDeclineInviteState> {
|
||||
override val values: Sequence<AcceptDeclineInviteState>
|
||||
@@ -18,27 +18,21 @@ open class AcceptDeclineInviteStateProvider : PreviewParameterProvider<AcceptDec
|
||||
anAcceptDeclineInviteState(),
|
||||
anAcceptDeclineInviteState(
|
||||
declineAction = ConfirmingDeclineInvite(
|
||||
InviteData(roomId = RoomId("!room:matrix.org"), isDm = true, roomName = "Alice", senderId = UserId("@alice:matrix.org")),
|
||||
InviteData(roomId = RoomId("!room:matrix.org"), isDm = true, roomName = "Alice"),
|
||||
blockUser = false,
|
||||
),
|
||||
),
|
||||
anAcceptDeclineInviteState(
|
||||
declineAction = ConfirmingDeclineInvite(
|
||||
InviteData(roomId = RoomId("!room:matrix.org"), isDm = false, roomName = "Some room", senderId = UserId("@alice:matrix.org")),
|
||||
blockUser = false,
|
||||
),
|
||||
),
|
||||
anAcceptDeclineInviteState(
|
||||
declineAction = ConfirmingDeclineInvite(
|
||||
InviteData(roomId = RoomId("!room:matrix.org"), isDm = true, roomName = "Alice", senderId = UserId("@alice:matrix.org")),
|
||||
InviteData(roomId = RoomId("!room:matrix.org"), isDm = true, roomName = "Alice"),
|
||||
blockUser = true,
|
||||
),
|
||||
),
|
||||
anAcceptDeclineInviteState(
|
||||
acceptAction = AsyncAction.Failure(Throwable("Whoops")),
|
||||
acceptAction = AsyncAction.Failure(Throwable("Error while accepting invite")),
|
||||
),
|
||||
anAcceptDeclineInviteState(
|
||||
declineAction = AsyncAction.Failure(Throwable("Whoops")),
|
||||
declineAction = AsyncAction.Failure(Throwable("Error while declining invite")),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.invite.api.response
|
||||
package io.element.android.features.invite.api.acceptdecline
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -15,8 +15,8 @@ interface AcceptDeclineInviteView {
|
||||
@Composable
|
||||
fun Render(
|
||||
state: AcceptDeclineInviteState,
|
||||
onAcceptInvite: (RoomId) -> Unit,
|
||||
onDeclineInvite: (RoomId) -> Unit,
|
||||
onAcceptInviteSuccess: (RoomId) -> Unit,
|
||||
onDeclineInviteSuccess: (RoomId) -> Unit,
|
||||
modifier: Modifier,
|
||||
)
|
||||
}
|
||||
@@ -5,11 +5,9 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.invite.api.response
|
||||
package io.element.android.features.invite.api.acceptdecline
|
||||
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
|
||||
data class ConfirmingDeclineInvite(
|
||||
val inviteData: InviteData,
|
||||
val blockUser: Boolean,
|
||||
) : AsyncAction.Confirming
|
||||
data class ConfirmingDeclineInvite(val inviteData: InviteData, val blockUser: Boolean) : AsyncAction.Confirming
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.invite.api.declineandblock
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
|
||||
interface DeclineInviteAndBlockEntryPoint : FeatureEntryPoint {
|
||||
fun createNode(parentNode: Node, buildContext: BuildContext, inviteData: InviteData): Node
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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.invite.api.response
|
||||
|
||||
interface AcceptDeclineInviteEvents {
|
||||
data class AcceptInvite(val invite: InviteData?) : AcceptDeclineInviteEvents
|
||||
data class DeclineInvite(val invite: InviteData?, val blockUser: Boolean = false) : AcceptDeclineInviteEvents
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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.invite.api.response
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
data class InviteData(
|
||||
val senderId: UserId,
|
||||
val roomId: RoomId,
|
||||
val roomName: String,
|
||||
val isDm: Boolean,
|
||||
)
|
||||
@@ -14,6 +14,11 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.invite.impl"
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupAnvil()
|
||||
@@ -36,9 +41,12 @@ dependencies {
|
||||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(libs.test.robolectric)
|
||||
testImplementation(projects.features.invite.test)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.invite.impl
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
||||
import javax.inject.Inject
|
||||
|
||||
interface AcceptInvite {
|
||||
suspend operator fun invoke(roomId: RoomId): Result<RoomId>
|
||||
}
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultAcceptInvite @Inject constructor(
|
||||
private val client: MatrixClient,
|
||||
private val joinRoom: JoinRoom,
|
||||
private val notificationCleaner: NotificationCleaner,
|
||||
private val seenInvitesStore: SeenInvitesStore,
|
||||
) : AcceptInvite {
|
||||
override suspend fun invoke(roomId: RoomId): Result<RoomId> {
|
||||
return joinRoom(
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
serverNames = emptyList(),
|
||||
trigger = JoinedRoom.Trigger.Invite,
|
||||
).onSuccess {
|
||||
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, roomId)
|
||||
seenInvitesStore.markAsUnSeen(roomId)
|
||||
}.map { roomId }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.invite.impl
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
||||
import javax.inject.Inject
|
||||
|
||||
interface DeclineInvite {
|
||||
suspend operator fun invoke(
|
||||
roomId: RoomId,
|
||||
blockUser: Boolean,
|
||||
reportRoom: Boolean,
|
||||
reportReason: String?
|
||||
): Result<RoomId>
|
||||
|
||||
sealed class Exception : kotlin.Exception() {
|
||||
data object RoomNotFound : Exception()
|
||||
data object DeclineInviteFailed : Exception()
|
||||
data object ReportRoomFailed : Exception()
|
||||
data object BlockUserFailed : Exception()
|
||||
}
|
||||
}
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultDeclineInvite @Inject constructor(
|
||||
private val client: MatrixClient,
|
||||
private val notificationCleaner: NotificationCleaner,
|
||||
private val seenInvitesStore: SeenInvitesStore,
|
||||
) : DeclineInvite {
|
||||
override suspend fun invoke(
|
||||
roomId: RoomId,
|
||||
blockUser: Boolean,
|
||||
reportRoom: Boolean,
|
||||
reportReason: String?
|
||||
): Result<RoomId> {
|
||||
val room = client.getRoom(roomId) ?: return Result.failure(DeclineInvite.Exception.RoomNotFound)
|
||||
room.use {
|
||||
room.leave()
|
||||
.onFailure { return Result.failure(DeclineInvite.Exception.DeclineInviteFailed) }
|
||||
.onSuccess {
|
||||
notificationCleaner.clearMembershipNotificationForRoom(
|
||||
sessionId = client.sessionId,
|
||||
roomId = roomId
|
||||
)
|
||||
seenInvitesStore.markAsUnSeen(roomId)
|
||||
}
|
||||
|
||||
if (blockUser) {
|
||||
val userIdToBlock = room.info().inviter?.userId
|
||||
if (userIdToBlock != null) {
|
||||
client
|
||||
.ignoreUser(userIdToBlock)
|
||||
.onFailure { return Result.failure(DeclineInvite.Exception.BlockUserFailed) }
|
||||
}
|
||||
}
|
||||
if (reportRoom) {
|
||||
room
|
||||
.reportRoom(reportReason)
|
||||
.onFailure { return Result.failure(DeclineInvite.Exception.ReportRoomFailed) }
|
||||
}
|
||||
}
|
||||
return Result.success(roomId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2024 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.invite.impl.acceptdecline
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.ConfirmingDeclineInvite
|
||||
import io.element.android.features.invite.impl.AcceptInvite
|
||||
import io.element.android.features.invite.impl.DeclineInvite
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class AcceptDeclineInvitePresenter @Inject constructor(
|
||||
private val acceptInvite: AcceptInvite,
|
||||
private val declineInvite: DeclineInvite,
|
||||
) : Presenter<AcceptDeclineInviteState> {
|
||||
@Composable
|
||||
override fun present(): AcceptDeclineInviteState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val acceptedAction: MutableState<AsyncAction<RoomId>> =
|
||||
remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
val declinedAction: MutableState<AsyncAction<RoomId>> =
|
||||
remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
|
||||
fun handleEvents(event: AcceptDeclineInviteEvents) {
|
||||
when (event) {
|
||||
is AcceptDeclineInviteEvents.AcceptInvite -> {
|
||||
localCoroutineScope.acceptInvite(event.invite.roomId, acceptedAction)
|
||||
}
|
||||
|
||||
is AcceptDeclineInviteEvents.DeclineInvite -> {
|
||||
val inviteData = event.invite
|
||||
if (event.shouldConfirm) {
|
||||
declinedAction.value = ConfirmingDeclineInvite(inviteData, event.blockUser)
|
||||
} else {
|
||||
localCoroutineScope.declineInvite(
|
||||
inviteData = inviteData,
|
||||
blockUser = event.blockUser,
|
||||
declinedAction = declinedAction,
|
||||
)
|
||||
}
|
||||
}
|
||||
is InternalAcceptDeclineInviteEvents.CancelDeclineInvite -> {
|
||||
declinedAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
|
||||
is InternalAcceptDeclineInviteEvents.DismissAcceptError -> {
|
||||
acceptedAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
|
||||
is InternalAcceptDeclineInviteEvents.DismissDeclineError -> {
|
||||
declinedAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AcceptDeclineInviteState(
|
||||
acceptAction = acceptedAction.value,
|
||||
declineAction = declinedAction.value,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.acceptInvite(
|
||||
roomId: RoomId,
|
||||
acceptedAction: MutableState<AsyncAction<RoomId>>,
|
||||
) = launch {
|
||||
acceptedAction.runUpdatingState {
|
||||
acceptInvite(roomId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.declineInvite(
|
||||
inviteData: InviteData,
|
||||
blockUser: Boolean,
|
||||
declinedAction: MutableState<AsyncAction<RoomId>>,
|
||||
) = launch {
|
||||
declinedAction.runUpdatingState {
|
||||
declineInvite(
|
||||
roomId = inviteData.roomId,
|
||||
blockUser = blockUser,
|
||||
reportRoom = false,
|
||||
reportReason = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,18 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.invite.impl.response
|
||||
package io.element.android.features.invite.impl.acceptdecline
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteStateProvider
|
||||
import io.element.android.features.invite.api.response.ConfirmingDeclineInvite
|
||||
import io.element.android.features.invite.api.response.InviteData
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteStateProvider
|
||||
import io.element.android.features.invite.api.acceptdecline.ConfirmingDeclineInvite
|
||||
import io.element.android.features.invite.impl.R
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
@@ -27,21 +28,21 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@Composable
|
||||
fun AcceptDeclineInviteView(
|
||||
state: AcceptDeclineInviteState,
|
||||
onAcceptInvite: (RoomId) -> Unit,
|
||||
onDeclineInvite: (RoomId) -> Unit,
|
||||
onAcceptInviteSuccess: (RoomId) -> Unit,
|
||||
onDeclineInviteSuccess: (RoomId) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
AsyncActionView(
|
||||
async = state.acceptAction,
|
||||
onSuccess = onAcceptInvite,
|
||||
onSuccess = onAcceptInviteSuccess,
|
||||
onErrorDismiss = {
|
||||
state.eventSink(InternalAcceptDeclineInviteEvents.DismissAcceptError)
|
||||
},
|
||||
)
|
||||
AsyncActionView(
|
||||
async = state.declineAction,
|
||||
onSuccess = onDeclineInvite,
|
||||
onSuccess = onDeclineInviteSuccess,
|
||||
onErrorDismiss = {
|
||||
state.eventSink(InternalAcceptDeclineInviteEvents.DismissDeclineError)
|
||||
},
|
||||
@@ -52,7 +53,13 @@ fun AcceptDeclineInviteView(
|
||||
invite = confirming.inviteData,
|
||||
blockUser = confirming.blockUser,
|
||||
onConfirmClick = {
|
||||
state.eventSink(InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite)
|
||||
state.eventSink(
|
||||
AcceptDeclineInviteEvents.DeclineInvite(
|
||||
confirming.inviteData,
|
||||
blockUser = confirming.blockUser,
|
||||
shouldConfirm = false
|
||||
)
|
||||
)
|
||||
},
|
||||
onDismissClick = {
|
||||
state.eventSink(InternalAcceptDeclineInviteEvents.CancelDeclineInvite)
|
||||
@@ -72,30 +79,21 @@ private fun DeclineConfirmationDialog(
|
||||
onDismissClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val senderId = invite.senderId.value
|
||||
val content = when {
|
||||
blockUser -> stringResource(R.string.screen_join_room_decline_and_block_alert_message, senderId)
|
||||
invite.isDm -> stringResource(R.string.screen_invites_decline_direct_chat_message, invite.roomName)
|
||||
else -> stringResource(R.string.screen_invites_decline_chat_message, invite.roomName)
|
||||
}
|
||||
val title = when {
|
||||
blockUser -> stringResource(R.string.screen_join_room_decline_and_block_alert_title)
|
||||
invite.isDm -> stringResource(R.string.screen_invites_decline_direct_chat_title)
|
||||
else -> stringResource(R.string.screen_invites_decline_chat_title)
|
||||
}
|
||||
val submitText = if (blockUser) {
|
||||
stringResource(R.string.screen_join_room_decline_and_block_alert_confirmation)
|
||||
} else {
|
||||
stringResource(CommonStrings.action_decline)
|
||||
}
|
||||
ConfirmationDialog(
|
||||
modifier = modifier,
|
||||
content = content,
|
||||
title = title,
|
||||
submitText = submitText,
|
||||
content = stringResource(R.string.screen_invites_decline_chat_message, invite.roomName),
|
||||
title = if (blockUser) {
|
||||
stringResource(R.string.screen_join_room_decline_and_block_alert_title)
|
||||
} else {
|
||||
stringResource(R.string.screen_invites_decline_chat_title)
|
||||
},
|
||||
submitText = if (blockUser) {
|
||||
stringResource(R.string.screen_join_room_decline_and_block_alert_confirmation)
|
||||
} else {
|
||||
stringResource(CommonStrings.action_decline)
|
||||
},
|
||||
cancelText = stringResource(CommonStrings.action_cancel),
|
||||
onSubmitClick = onConfirmClick,
|
||||
destructiveSubmit = blockUser,
|
||||
onDismiss = onDismissClick,
|
||||
)
|
||||
}
|
||||
@@ -106,7 +104,7 @@ internal fun AcceptDeclineInviteViewPreview(@PreviewParameter(AcceptDeclineInvit
|
||||
ElementPreview {
|
||||
AcceptDeclineInviteView(
|
||||
state = state,
|
||||
onAcceptInvite = {},
|
||||
onDeclineInvite = {},
|
||||
onAcceptInviteSuccess = {},
|
||||
onDeclineInviteSuccess = {},
|
||||
)
|
||||
}
|
||||
@@ -5,13 +5,13 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.invite.impl.response
|
||||
package io.element.android.features.invite.impl.acceptdecline
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteView
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteView
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import javax.inject.Inject
|
||||
@@ -21,14 +21,14 @@ class DefaultAcceptDeclineInviteView @Inject constructor() : AcceptDeclineInvite
|
||||
@Composable
|
||||
override fun Render(
|
||||
state: AcceptDeclineInviteState,
|
||||
onAcceptInvite: (RoomId) -> Unit,
|
||||
onDeclineInvite: (RoomId) -> Unit,
|
||||
onAcceptInviteSuccess: (RoomId) -> Unit,
|
||||
onDeclineInviteSuccess: (RoomId) -> Unit,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
AcceptDeclineInviteView(
|
||||
state = state,
|
||||
onAcceptInvite = onAcceptInvite,
|
||||
onDeclineInvite = onDeclineInvite,
|
||||
onAcceptInviteSuccess = onAcceptInviteSuccess,
|
||||
onDeclineInviteSuccess = onDeclineInviteSuccess,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
@@ -5,12 +5,11 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.invite.impl.response
|
||||
package io.element.android.features.invite.impl.acceptdecline
|
||||
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||
|
||||
sealed interface InternalAcceptDeclineInviteEvents : AcceptDeclineInviteEvents {
|
||||
data object ConfirmDeclineInvite : InternalAcceptDeclineInviteEvents
|
||||
data object CancelDeclineInvite : InternalAcceptDeclineInviteEvents
|
||||
data object DismissAcceptError : InternalAcceptDeclineInviteEvents
|
||||
data object DismissDeclineError : InternalAcceptDeclineInviteEvents
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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.invite.impl.declineandblock
|
||||
|
||||
sealed interface DeclineAndBlockEvents {
|
||||
data class UpdateReportReason(val reason: String) : DeclineAndBlockEvents
|
||||
data object ToggleReportRoom : DeclineAndBlockEvents
|
||||
data object ToggleBlockUser : DeclineAndBlockEvents
|
||||
data object Decline : DeclineAndBlockEvents
|
||||
data object ClearDeclineAction : DeclineAndBlockEvents
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.invite.impl.declineandblock
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class DeclineAndBlockNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
presenterFactory: DeclineAndBlockPresenter.Factory,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
data class Inputs(val inviteData: InviteData) : NodeInputs
|
||||
|
||||
private val inviteData = inputs<Inputs>().inviteData
|
||||
private val presenter = presenterFactory.create(inviteData)
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
DeclineAndBlockView(
|
||||
state = state,
|
||||
onBackClick = ::navigateUp,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.invite.impl.declineandblock
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.features.invite.impl.DeclineInvite
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class DeclineAndBlockPresenter @AssistedInject constructor(
|
||||
@Assisted private val inviteData: InviteData,
|
||||
private val declineInvite: DeclineInvite,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
) : Presenter<DeclineAndBlockState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(inviteData: InviteData): DeclineAndBlockPresenter
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): DeclineAndBlockState {
|
||||
var reportReason by rememberSaveable { mutableStateOf("") }
|
||||
var blockUser by rememberSaveable { mutableStateOf(true) }
|
||||
var reportRoom by rememberSaveable { mutableStateOf(false) }
|
||||
val declineAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
fun handleEvents(event: DeclineAndBlockEvents) {
|
||||
when (event) {
|
||||
DeclineAndBlockEvents.ClearDeclineAction -> declineAction.value = AsyncAction.Uninitialized
|
||||
DeclineAndBlockEvents.Decline -> coroutineScope.decline(reportReason, blockUser, reportRoom, declineAction)
|
||||
DeclineAndBlockEvents.ToggleBlockUser -> blockUser = !blockUser
|
||||
DeclineAndBlockEvents.ToggleReportRoom -> reportRoom = !reportRoom
|
||||
is DeclineAndBlockEvents.UpdateReportReason -> reportReason = event.reason
|
||||
}
|
||||
}
|
||||
|
||||
return DeclineAndBlockState(
|
||||
reportRoom = reportRoom,
|
||||
reportReason = reportReason,
|
||||
blockUser = blockUser,
|
||||
declineAction = declineAction.value,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.decline(
|
||||
reason: String,
|
||||
blockUser: Boolean,
|
||||
reportRoom: Boolean,
|
||||
action: MutableState<AsyncAction<Unit>>
|
||||
) = launch {
|
||||
action.value = AsyncAction.Loading
|
||||
declineInvite(
|
||||
roomId = inviteData.roomId,
|
||||
blockUser = blockUser,
|
||||
reportRoom = reportRoom,
|
||||
reportReason = reason
|
||||
).onSuccess {
|
||||
action.value = AsyncAction.Success(Unit)
|
||||
}.onFailure { error ->
|
||||
if (error is DeclineInvite.Exception.DeclineInviteFailed) {
|
||||
action.value = AsyncAction.Failure(error)
|
||||
} else {
|
||||
action.value = AsyncAction.Uninitialized
|
||||
snackbarDispatcher.post(SnackbarMessage(CommonStrings.error_unknown))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.invite.impl.declineandblock
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
|
||||
data class DeclineAndBlockState(
|
||||
val reportRoom: Boolean,
|
||||
val reportReason: String,
|
||||
val blockUser: Boolean,
|
||||
val declineAction: AsyncAction<Unit>,
|
||||
val eventSink: (DeclineAndBlockEvents) -> Unit
|
||||
) {
|
||||
val canDecline = blockUser || reportRoom && reportReason.isNotEmpty()
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.invite.impl.declineandblock
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
|
||||
open class DeclineAndBlockStateProvider : PreviewParameterProvider<DeclineAndBlockState> {
|
||||
override val values: Sequence<DeclineAndBlockState>
|
||||
get() = sequenceOf(
|
||||
aDeclineAndBlockState(),
|
||||
aDeclineAndBlockState(
|
||||
reportRoom = true,
|
||||
reportReason = "Inappropriate content",
|
||||
),
|
||||
aDeclineAndBlockState(
|
||||
blockUser = true,
|
||||
),
|
||||
aDeclineAndBlockState(
|
||||
declineAction = AsyncAction.Loading,
|
||||
),
|
||||
aDeclineAndBlockState(
|
||||
declineAction = AsyncAction.Failure(Exception("Failed to decline")),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun aDeclineAndBlockState(
|
||||
reportRoom: Boolean = false,
|
||||
reportReason: String = "",
|
||||
blockUser: Boolean = false,
|
||||
declineAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
eventSink: (DeclineAndBlockEvents) -> Unit = {},
|
||||
) = DeclineAndBlockState(
|
||||
reportRoom = reportRoom,
|
||||
reportReason = reportReason,
|
||||
blockUser = blockUser,
|
||||
declineAction = declineAction,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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.invite.impl.declineandblock
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.invite.impl.R
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DeclineAndBlockView(
|
||||
state: DeclineAndBlockState,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
val isDeclining = state.declineAction is AsyncAction.Loading
|
||||
AsyncActionView(
|
||||
async = state.declineAction,
|
||||
onSuccess = { onBackClick() },
|
||||
errorMessage = { stringResource(CommonStrings.error_unknown) },
|
||||
onRetry = { state.eventSink(DeclineAndBlockEvents.Decline) },
|
||||
onErrorDismiss = { state.eventSink(DeclineAndBlockEvents.ClearDeclineAction) }
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.screen_decline_and_block_title),
|
||||
style = ElementTheme.typography.aliasScreenTitle,
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
BackButton(onClick = onBackClick)
|
||||
}
|
||||
)
|
||||
},
|
||||
modifier = modifier
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.imePadding()
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(vertical = 16.dp)
|
||||
) {
|
||||
ListItem(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
headlineContent = {
|
||||
Text(text = stringResource(R.string.screen_decline_and_block_block_user_option_title))
|
||||
},
|
||||
supportingContent = {
|
||||
Text(text = stringResource(R.string.screen_decline_and_block_block_user_option_description))
|
||||
},
|
||||
onClick = {
|
||||
state.eventSink(DeclineAndBlockEvents.ToggleBlockUser)
|
||||
},
|
||||
trailingContent = ListItemContent.Switch(checked = state.blockUser)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
ListItem(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
headlineContent = {
|
||||
Text(text = stringResource(CommonStrings.action_report_room))
|
||||
},
|
||||
supportingContent = {
|
||||
Text(text = stringResource(R.string.screen_decline_and_block_report_user_option_description))
|
||||
},
|
||||
onClick = {
|
||||
state.eventSink(DeclineAndBlockEvents.ToggleReportRoom)
|
||||
},
|
||||
trailingContent = ListItemContent.Switch(checked = state.reportRoom)
|
||||
)
|
||||
|
||||
if (state.reportRoom) {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
TextField(
|
||||
value = state.reportReason,
|
||||
onValueChange = { state.eventSink(DeclineAndBlockEvents.UpdateReportReason(it)) },
|
||||
placeholder = stringResource(R.string.screen_decline_and_block_report_user_reason_placeholder),
|
||||
minLines = 3,
|
||||
enabled = !isDeclining,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.heightIn(min = 90.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_decline),
|
||||
destructive = true,
|
||||
showProgress = isDeclining,
|
||||
enabled = !isDeclining && state.canDecline,
|
||||
onClick = {
|
||||
focusManager.clearFocus(force = true)
|
||||
state.eventSink(DeclineAndBlockEvents.Decline)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun DeclineAndBlockViewPreview(
|
||||
@PreviewParameter(DeclineAndBlockStateProvider::class) state: DeclineAndBlockState
|
||||
) = ElementPreview {
|
||||
DeclineAndBlockView(
|
||||
state = state,
|
||||
onBackClick = {},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.invite.impl.declineandblock
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBlockEntryPoint
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultDeclineAndBlockEntryPoint @Inject constructor() : DeclineInviteAndBlockEntryPoint {
|
||||
override fun createNode(parentNode: Node, buildContext: BuildContext, inviteData: InviteData): Node {
|
||||
val inputs = DeclineAndBlockNode.Inputs(inviteData)
|
||||
return parentNode.createNode<DeclineAndBlockNode>(buildContext, plugins = listOf(inputs))
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,9 @@ import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.impl.SeenInvitesStoreFactory
|
||||
import io.element.android.features.invite.impl.response.AcceptDeclineInvitePresenter
|
||||
import io.element.android.features.invite.impl.acceptdecline.AcceptDeclineInvitePresenter
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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.invite.impl.response
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.ConfirmingDeclineInvite
|
||||
import io.element.android.features.invite.api.response.InviteData
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class AcceptDeclineInvitePresenter @Inject constructor(
|
||||
private val client: MatrixClient,
|
||||
private val joinRoom: JoinRoom,
|
||||
private val notificationCleaner: NotificationCleaner,
|
||||
private val seenInvitesStore: SeenInvitesStore,
|
||||
) : Presenter<AcceptDeclineInviteState> {
|
||||
@Composable
|
||||
override fun present(): AcceptDeclineInviteState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val acceptedAction: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
val declinedAction: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
|
||||
fun handleEvents(event: AcceptDeclineInviteEvents) {
|
||||
when (event) {
|
||||
is AcceptDeclineInviteEvents.AcceptInvite -> {
|
||||
val inviteData = event.invite
|
||||
if (inviteData == null) {
|
||||
acceptedAction.value = AsyncAction.Failure(InvalidDataException())
|
||||
} else {
|
||||
localCoroutineScope.acceptInvite(inviteData.roomId, acceptedAction)
|
||||
}
|
||||
}
|
||||
|
||||
is AcceptDeclineInviteEvents.DeclineInvite -> {
|
||||
val inviteData = event.invite
|
||||
if (inviteData == null) {
|
||||
declinedAction.value = AsyncAction.Failure(InvalidDataException())
|
||||
} else {
|
||||
declinedAction.value = ConfirmingDeclineInvite(inviteData, event.blockUser)
|
||||
}
|
||||
}
|
||||
|
||||
is InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite -> {
|
||||
when (val declinedActionValue = declinedAction.value) {
|
||||
is ConfirmingDeclineInvite -> {
|
||||
localCoroutineScope.declineInvite(
|
||||
inviteData = declinedActionValue.inviteData,
|
||||
declinedAction = declinedAction,
|
||||
blockUser = declinedActionValue.blockUser,
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
is InternalAcceptDeclineInviteEvents.CancelDeclineInvite -> {
|
||||
declinedAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
|
||||
is InternalAcceptDeclineInviteEvents.DismissAcceptError -> {
|
||||
acceptedAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
|
||||
is InternalAcceptDeclineInviteEvents.DismissDeclineError -> {
|
||||
declinedAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AcceptDeclineInviteState(
|
||||
acceptAction = acceptedAction.value,
|
||||
declineAction = declinedAction.value,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.acceptInvite(
|
||||
roomId: RoomId,
|
||||
acceptedAction: MutableState<AsyncAction<RoomId>>,
|
||||
) = launch {
|
||||
acceptedAction.runUpdatingState {
|
||||
joinRoom(
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
serverNames = emptyList(),
|
||||
trigger = JoinedRoom.Trigger.Invite,
|
||||
)
|
||||
.onSuccess {
|
||||
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, roomId)
|
||||
seenInvitesStore.markAsUnSeen(roomId)
|
||||
}
|
||||
.map { roomId }
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.declineInvite(
|
||||
inviteData: InviteData,
|
||||
blockUser: Boolean,
|
||||
declinedAction: MutableState<AsyncAction<RoomId>>,
|
||||
) = launch {
|
||||
suspend {
|
||||
client.getPendingRoom(inviteData.roomId)?.use {
|
||||
it.leave().getOrThrow()
|
||||
}
|
||||
if (blockUser) {
|
||||
client.ignoreUser(inviteData.senderId).getOrThrow()
|
||||
}
|
||||
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, inviteData.roomId)
|
||||
seenInvitesStore.markAsUnSeen(inviteData.roomId)
|
||||
inviteData.roomId
|
||||
}.runCatchingUpdatingState(declinedAction)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_decline_and_block_block_user_option_description">"Od tohoto uživatele neuvidíte žádné zprávy ani pozvánky do místnosti"</string>
|
||||
<string name="screen_decline_and_block_block_user_option_title">"Zablokovat uživatele"</string>
|
||||
<string name="screen_decline_and_block_report_user_option_description">"Nahlaste tuto místnost svému poskytovateli účtu."</string>
|
||||
<string name="screen_decline_and_block_report_user_reason_placeholder">"Popište důvod nahlášení…"</string>
|
||||
<string name="screen_decline_and_block_title">"Odmítnout a zablokovat"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Opravdu chcete odmítnout pozvánku do %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Odmítnout pozvání"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Opravdu chcete odmítnout tuto soukromou konverzaci s %1$s?"</string>
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_decline_and_block_block_user_option_description">"Fyddwch chi ddim yn gweld unrhyw negeseuon neu wahoddiadau ystafell gan y defnyddiwr hwn"</string>
|
||||
<string name="screen_decline_and_block_block_user_option_title">"Rhwystro defnyddiwr"</string>
|
||||
<string name="screen_decline_and_block_report_user_option_description">"Adrodd am yr ystafell hon i ddarparwr eich cyfrif."</string>
|
||||
<string name="screen_decline_and_block_report_user_reason_placeholder">"Disgrifiwch y rheswm dros adrodd…"</string>
|
||||
<string name="screen_decline_and_block_title">"Gwrthod a rhwystro"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Ydych chi\'n siŵr eich bod am wrthod y gwahoddiad i ymuno â %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Gwrthod y gwahoddiad"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Ydych chi\'n siŵr eich bod am wrthod y sgwrs breifat hon gyda %1$s?"</string>
|
||||
<string name="screen_invites_decline_direct_chat_title">"Gwrthod sgwrs"</string>
|
||||
<string name="screen_invites_empty_list">"Dim Gwahoddiadau"</string>
|
||||
<string name="screen_invites_invited_you">"Mae %1$s (%2$s) wedi eich gwahodd"</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_confirmation">"Iawn, gwrthod a rhwystro"</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_message">"Ydych chi\'n siŵr eich bod am wrthod y gwahoddiad i ymuno â\'r ystafell hon? Bydd hyn hefyd yn atal %1$s rhag cysylltu â chi neu eich gwahodd i ystafelloedd."</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_title">"Gwrthod gwahoddiad a rhwystro"</string>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_decline_and_block_block_user_option_description">"Sie werden keine Nachrichten oder Chatroomeinladungen von diesem Benutzer sehen."</string>
|
||||
<string name="screen_decline_and_block_block_user_option_title">"Benutzer blockieren"</string>
|
||||
<string name="screen_decline_and_block_report_user_option_description">"Melden Sie diesen Raum Ihrem Kontoanbieter."</string>
|
||||
<string name="screen_decline_and_block_report_user_reason_placeholder">"Beschreiben Sie den Grund für die Meldung…"</string>
|
||||
<string name="screen_decline_and_block_title">"Ablehnen und blockieren"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Möchtest du die Einladung zum Betreten von %1$s wirklich ablehnen?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Einladung ablehnen"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Bist du sicher, dass du diese Direktnachricht von %1$s ablehnen möchtest?"</string>
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_decline_and_block_block_user_option_description">"Δε θα δείτε μηνύματα ή προσκλήσεις δωματίου από αυτόν τον χρήστη"</string>
|
||||
<string name="screen_decline_and_block_block_user_option_title">"Αποκλεισμός χρήστη"</string>
|
||||
<string name="screen_decline_and_block_report_user_option_description">"Αναφέρετε αυτό το δωμάτιο στον πάροχο του λογαριασμού σας."</string>
|
||||
<string name="screen_decline_and_block_report_user_reason_placeholder">"Περιγράψτε τον λόγο αναφοράς…"</string>
|
||||
<string name="screen_decline_and_block_title">"Απόρριψη και αποκλεισμός"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Σίγουρα θες να απορρίψεις την πρόσκληση συμμετοχής στο %1$s;"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Απόρριψη πρόσκλησης"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Σίγουρα θες να απορρίψεις την ιδιωτική συνομιλία με τον χρήστη %1$s;"</string>
|
||||
<string name="screen_invites_decline_direct_chat_title">"Απόρριψη συνομιλίας"</string>
|
||||
<string name="screen_invites_empty_list">"Χωρίς προσκλήσεις"</string>
|
||||
<string name="screen_invites_invited_you">"%1$s (%2$s) σέ προσκάλεσε"</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_confirmation">"Ναι, απόρριψη και αποκλεισμός"</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_message">"Είστε βέβαιοι ότι θέλετε να απορρίψετε την πρόσκληση συμμετοχής σε αυτό το δωμάτιο; Αυτό θα εμποδίσει επίσης το χρήστη %1$s να επικοινωνήσει μαζί σας ή να σας προσκαλέσει σε δωμάτια."</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_title">"Απόρριψη πρόσκλησης και αποκλεισμός"</string>
|
||||
<string name="screen_join_room_decline_and_block_button_title">"Απόρριψη και αποκλεισμός"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_decline_and_block_block_user_option_description">"Sa ei näe enam selle kasutaja saadetud sõnumeid ja jututubade kutseid"</string>
|
||||
<string name="screen_decline_and_block_block_user_option_title">"Blokeeri kasutaja"</string>
|
||||
<string name="screen_decline_and_block_report_user_option_description">"Teata sellest jututoast oma teenusepakkujale."</string>
|
||||
<string name="screen_decline_and_block_report_user_reason_placeholder">"Kirjelda teatamise põhjust…"</string>
|
||||
<string name="screen_decline_and_block_title">"Keeldu ja blokeeri"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Kas sa oled kindel, et soovid keelduda liitumiskutsest: %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Lükka kutse tagasi"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Kas sa oled kindel, et soovid keelduda privaatsest vestlusest kasutajaga %1$s?"</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user