mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a907501c08 | |||
| 7f43998fa5 | |||
| edb3520c56 | |||
| 0c78553641 | |||
| 081904345a | |||
| ce180ef25b | |||
| c7800abb0f | |||
| 1aeb4655bc | |||
| 2db4c9847f | |||
| 621df1dedf | |||
| ac264ea08c | |||
| c0c0b48470 | |||
| fa08091528 | |||
| 399adf2276 | |||
| 67fb6744fe | |||
| 3e1010ba07 | |||
| 1eb530e727 | |||
| 0cd363b1c3 | |||
| 31611e5499 | |||
| fdf142db60 | |||
| 96db91de86 | |||
| e74db93344 | |||
| 8c4ef9264b | |||
| 409a1e5b90 | |||
| 2013b86dfe | |||
| cedd070bfe | |||
| 0fcd7bcfce | |||
| 0799ad5b73 | |||
| 9475ec3a48 | |||
| 2221bf2972 | |||
| 05df27fc3a | |||
| b4629fff90 | |||
| 7bbbde76d1 | |||
| ec0303606a | |||
| b7bfc6f751 | |||
| ada6fc457b | |||
| 57a2e585ba | |||
| d6ea73140e | |||
| a7e1217e82 | |||
| 09b6d6341b | |||
| a1866b4b28 | |||
| 2994430b88 |
@@ -261,12 +261,12 @@ compliance metadata review.
|
||||
- **Production rollout gone wrong** → Play Console → Production → **Halt
|
||||
rollout** (works during staged rollout), then ship a hotfix as above.
|
||||
|
||||
## Known branch divergence when syncing with upstream
|
||||
## Known branch divergence when merging upstream
|
||||
|
||||
Upstream is taken by **rebasing** the HealthChat commits onto the new release
|
||||
tag, never by merging — the procedure, Git LFS handling and force-push live in
|
||||
the `syncing-upstream-healthchat` skill. The HealthChat-only facts that must
|
||||
survive every sync:
|
||||
When merging a newer upstream Element X release tag into `release/healthchat`
|
||||
(temporarily add `https://github.com/element-hq/element-x-android` as a
|
||||
remote, fetch, merge, remove the remote — never keep a permanent upstream
|
||||
remote):
|
||||
|
||||
- **`.github/workflows/healthchat-release-playstore.yml`**,
|
||||
**`HEALTHCHAT_CHANGES.md`**, and
|
||||
@@ -279,13 +279,9 @@ survive every sync:
|
||||
Upstream uses `io.element.android.x` / `Element X` / nulls.
|
||||
- **`appconfig/.../ApplicationConfig.kt`** — `PRODUCTION_APPLICATION_NAME` /
|
||||
`DESKTOP_APPLICATION_NAME = "HealthChat"` must survive. Upstream: `Element`.
|
||||
- **`features/enterprise/impl-foss/.../DefaultEnterpriseService.kt`** — the
|
||||
homeserver lock: `accountProviderAllowList() = listOf(Generic("matrix.healthchat.ch"))`,
|
||||
`canConnectToAnyAccountProvider() = false`, `isAllowedToConnectToAccountProvider()`
|
||||
= membership in that list (since upstream 26.10.0; before that
|
||||
`defaultHomeserverList()`). Upstream returns an empty list / `true` / `true`.
|
||||
`BuildTimeConfig.OAUTH_CLIENT_URL_PATH` must stay `null` (upstream:
|
||||
`"apps/android"`), or MAS rejects the redirect scheme.
|
||||
- **`features/enterprise/impl-foss/.../DefaultEnterpriseService.kt`** —
|
||||
`defaultHomeserverList() = listOf("matrix.healthchat.ch")` must survive.
|
||||
Upstream returns an empty list.
|
||||
- **`app/build.gradle.kts`** — the `healthchat` signing config and the
|
||||
`appicon/healthchat` dependency must survive; upstream signs release with
|
||||
the debug keystore and depends on `appicon/element`.
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
---
|
||||
name: syncing-upstream-healthchat
|
||||
description: Bring the HealthChat fork (release/healthchat) onto a newer element-hq/element-x-android release tag. Use when the user asks to rebase, sync, merge or update to upstream, take a new Element X version, or asks why the fork is behind upstream. Covers the patch-stack model, the rebase procedure, Git LFS, conflicts, verification, the force-push and the rewind.
|
||||
---
|
||||
|
||||
# Syncing the HealthChat fork with upstream
|
||||
|
||||
`release/healthchat` = upstream release tag + HealthChat commits on top. Patch stack, same as
|
||||
healthchat-web and element-x-ios. A sync replaces the stack's base and force-pushes the branch.
|
||||
That is the one sanctioned history rewrite in this repo; everything else in `AGENTS.md` still
|
||||
holds. We rebase, we never merge upstream into the branch.
|
||||
|
||||
First done v26.07.0 → v26.10.0 (2026-10-16 to 10-19). Numbers below come from that run.
|
||||
|
||||
## Before touching anything
|
||||
|
||||
- [ ] **Toolchain.** Upstream pins the JDK (`java-version` in `.github/workflows/*.yml`, 21),
|
||||
`COMPILE_SDK` / `BUILD_TOOLS_VERSION` (`plugins/src/main/kotlin/Versions.kt`), AGP, Kotlin
|
||||
and Metro (`gradle/libs.versions.toml`). Gradle and AGP install themselves; the Rust SDK
|
||||
comes from Maven. What does not: the self-hosted incus runners ship no Android SDK — the
|
||||
`Set up Android SDK` step installs it (see the releasing skill, "Runner and build tuning").
|
||||
If `COMPILE_SDK` moved, check that step still resolves the platform. 26.10.0 moved nothing.
|
||||
- [ ] **Dev Mac Gradle.** A warm daemon can hang `assembleDebug` forever (0 % CPU, no log).
|
||||
`./gradlew --stop` first, then compile-only tasks with `--max-workers=2`, e.g.
|
||||
`:tests:uitests:compileDebugUnitTestKotlin` compiles every module. Gate scripts on the
|
||||
exit code — a `rebase --continue` after an unchecked compile committed a broken stop once.
|
||||
- [ ] **Upstream.** There is no upstream remote by default. Tags are `v<version>`.
|
||||
`git fetch https://github.com/element-hq/element-x-android.git tag v<new> --no-tags` is
|
||||
enough for the rebase. LFS needs a named remote:
|
||||
`git remote add upstream https://github.com/element-hq/element-x-android.git`. After
|
||||
adding it, `gh` may resolve to upstream — always pass `-R Novaloop-AG/element-x-android`.
|
||||
- [ ] **Decisions, product owner:** which upstream feature-flag defaults flipped
|
||||
(`git diff v<old> v<new> -- libraries/featureflag/api/.../FeatureFlags.kt`; 26.10.0:
|
||||
message search default on, kept on), where fork UI lands when upstream restructures a
|
||||
screen (26.10.0: settings split, HealthChat entries went to the account section), and
|
||||
whether fork commits may be squashed (only within one HealthChat release, never across).
|
||||
|
||||
## The stack
|
||||
|
||||
```bash
|
||||
git log --oneline --no-merges v<old>..release/healthchat # the fork's commits
|
||||
git merge-base --is-ancestor v<old> release/healthchat # must be true
|
||||
```
|
||||
|
||||
Upstream files the fork edits — conflicts live here every time:
|
||||
|
||||
- `features/enterprise/impl-foss/.../DefaultEnterpriseService.kt` + test — the homeserver
|
||||
lock (since 26.10.0: `accountProviderAllowList()` = `Generic("matrix.healthchat.ch")`,
|
||||
`canConnectToAnyAccountProvider() = false`, membership check).
|
||||
- `plugins/src/main/kotlin/config/BuildTimeConfig.kt` — app id, name, OIDC URLs,
|
||||
`OAUTH_CLIENT_URL_PATH = null` (MAS: redirect scheme must reverse-match the apex host).
|
||||
- `plugins/src/main/kotlin/Versions.kt` — version; dropped from the stack, set again at release.
|
||||
- `features/home/impl` (Members tab: `HomeView`, `HomePresenter`, `HomeState`,
|
||||
`HomeStatePreviewParam`, `HomeNavigationBarItem`, `HomeTopBar`, `HomeFlowNode`, tests) and
|
||||
`features/home/api/HomeEntryPoint.kt`, `appnav/.../LoggedInFlowNode.kt` (`navigateToUserProfile`).
|
||||
- `features/preferences/impl` root node/view + `PreferencesFlowNode` (profile editor replaces
|
||||
the display-name editor, account status banner).
|
||||
- `features/userprofile`, `features/roomdetails` (profile fields section).
|
||||
- `libraries/deeplink/impl`, `libraries/matrix/{api,impl}` permalink (HealthChat hosts),
|
||||
`features/login/impl/DefaultLoginIntentResolver.kt`.
|
||||
- `.github/workflows/{tests,quality,maestro-local,recordScreenshots}.yml` (self-hosted
|
||||
runners, heap, fork guards), `app/src/gplay/AndroidManifest.xml` (permission strip),
|
||||
`HEALTHCHAT_CHANGES.md`.
|
||||
|
||||
## Rebuilding the stack
|
||||
|
||||
Measure first: dry-run `git rebase --onto v<new> v<old> HEAD` in a throwaway worktree with a
|
||||
script that records the conflicting files per commit and resolves blindly, then abort. The
|
||||
conflict map decides the cleanup. 26.10.0: 11 of 31 commits stopped, 29 hunks, 62 goldens.
|
||||
|
||||
**1. Clean the series on the old base** (scratch branch off `release/healthchat`). Interactive
|
||||
rebase is not available to agents; rebuild with `cherry-pick` (`-n` + `commit -C <orig>` for
|
||||
edited commits; `cherry-pick` has no `-q`). Drop golden-only commits and the version bump;
|
||||
strip every PNG, and edits to files upstream deleted, out of feature commits; squash the CI
|
||||
run-of-commits within one release. `git diff --stat release/healthchat` must show only the
|
||||
intended removals; compile once.
|
||||
|
||||
**2. Plain rebase** — `git rebase --onto v<new> v<old> <scratch>`, resolve per stop, keep each
|
||||
commit's subject and intent. Upstream API removals cannot be "resolved", they need new code
|
||||
*inside that commit* (26.10.0: `EnterpriseService` redesign). After a large stop compile its
|
||||
modules before `--continue`. Cross-commit fallout (renames, moved fakes, signature changes)
|
||||
goes into ONE trailing `Adapt HealthChat to upstream v<new>` commit, not amended into owners —
|
||||
every amend replays the rest. A mistake in an already-committed stop: `git commit --fixup=<sha>`,
|
||||
then `GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash <sha>^` (conflict-free when later
|
||||
commits do not touch the file). Intermediates may not compile; the tip must.
|
||||
|
||||
**3. Review** — `git range-diff v<old>..release/healthchat v<new>..<scratch>`. `!` on a commit
|
||||
that applied cleanly is context drift; check the `-`/`+` lines are not ours.
|
||||
|
||||
Recurring fallout to expect: Konsist naming (`*PreviewParam` for `PreviewParameterProvider`
|
||||
subclasses, `*Event` for sealed interfaces), design-system signature changes (26.10.0:
|
||||
`ListItem(headlineContent)` → `ListItem(content)`), test fakes moving modules, action pins in
|
||||
our CI hunks regressing upstream's bumps, exhaustive `when` over `HomeNavigationBarItem`.
|
||||
|
||||
zsh: `for c in $VAR` does not split on newlines, a variable named `path` *is* `PATH`, and
|
||||
`$TAG:file` applies a history modifier — use `while read`, never name a variable `path`,
|
||||
write `${TAG}:file`.
|
||||
|
||||
## Git LFS — the part that bites
|
||||
|
||||
Goldens, compound screenshots, `docs/images-lfs`, and `libraries/mediaupload/impl` test assets
|
||||
are LFS-tracked (`.gitattributes`, ~4500 files). A fresh clone holds pointers only.
|
||||
|
||||
- origin is a standalone repo; upstream's LFS objects are not there. GitHub rejects a push
|
||||
whose *new commits* reference LFS objects it lacks (`GH008 … unknown Git LFS objects`). The
|
||||
rebased branch carries every upstream commit since the old tag. Fetch the LFS tree of each
|
||||
upstream commit that touched LFS paths, then push the objects before the branch:
|
||||
```bash
|
||||
git log --format=%H v<old>..v<new> -- '*.png' '*.jpg' '*.gif' '*.mp4' '*.m4a' '*.webp' \
|
||||
| sort -u | while read c; do git lfs fetch upstream "$c"; done
|
||||
git lfs push origin <scratch> # must report no "(missing)"; refetch that commit
|
||||
git push origin <scratch>
|
||||
```
|
||||
26.10.0: 210 such commits, 9837 objects, 525 MB locally, 3809 uploaded (174 MB). Run the loop
|
||||
detached (`nohup`) — an agent's tool call times out at 10 minutes — and keep a done-file so it
|
||||
resumes. Objects still "(missing)" after the loop are usually goldens from the fork's *own*
|
||||
commits that a pointer-only clone never held: `git lfs fetch origin <original commit sha>`
|
||||
for each commit on the old branch that touched them. The dry run lists every object until
|
||||
the branch itself is pushed; verify presence with the LFS batch API or trust a clean upload.
|
||||
- `git lfs push <branch>` only covers objects introduced by commits absent from the remote. 26.10.0:
|
||||
167 objects referenced by the v26.10.0 tree were outside that set and `Validate Git LFS` failed
|
||||
with "Object does not exist on the server". Read the oids from that job's log and
|
||||
`git lfs push --object-id origin <oid…>` them (they are local after the fetch loop). Treat a
|
||||
green `Validate Git LFS` on the PR as the only proof the store is complete.
|
||||
- The recorder commits as ElementBot, a non-member: its CI runs land in `action_required` and
|
||||
need `gh api -X POST repos/<repo>/actions/runs/<id>/approve` (or the PR's "Approve and run").
|
||||
Never push to the branch while a recording runs: the recorder's final `git push` is a plain
|
||||
push and is rejected once the tip has moved.
|
||||
- `git lfs checkout` before running `libraries/mediaupload/impl` tests locally; pointer files
|
||||
fail them. The screenshot tests are never run locally (`AGENTS.md`).
|
||||
- `validate-lfs.yml` fails a PR that commits a PNG outside LFS.
|
||||
|
||||
## Screenshots
|
||||
|
||||
Take upstream's goldens on every conflict and never commit locally rendered ones (macOS
|
||||
renders differ). After the push, add the `Record-Screenshots` label to the PR or
|
||||
`workflow_dispatch` `recordScreenshots.yml`; the Linux runner records and commits all goldens.
|
||||
Review only the HealthChat ones. A PR whose base is `release/healthchat` is *conflicting* by
|
||||
construction (old and new stack touch the same files) and GitHub runs no `pull_request`
|
||||
workflow on a conflicting PR — open the PR against a branch at the upstream tag
|
||||
(`upstream/v<new>`, pushed by the owner) so the PR shows the 19-odd stack commits and CI runs.
|
||||
Retargeting a PR is an `edited` event: tests/quality/build only start on the next push.
|
||||
The label path needs `persist-credentials: true` on the LFS checkout steps (private fork,
|
||||
fixed in 26.10.0). If the job dies on the LayoutLib static-initialiser crash,
|
||||
restore the "one JVM per screenshot test class" workaround (`forkEvery = 1L` in the root
|
||||
`build.gradle.kts`, dropped in 26.10.0 because upstream's Paparazzi bump fixed it).
|
||||
|
||||
## Verification of the tip
|
||||
|
||||
Local, all green before handover (26.10.0: 1200 unit tests):
|
||||
```bash
|
||||
./gradlew :tests:uitests:compileDebugUnitTestKotlin :app:compileGplayDebugKotlin
|
||||
./gradlew :features:healthchat:impl:testDebugUnitTest :features:home:impl:testDebugUnitTest \
|
||||
:features:preferences:impl:testDebugUnitTest :features:enterprise:impl-foss:testDebugUnitTest \
|
||||
:features:login:impl:testDebugUnitTest :libraries:matrix:impl:testDebugUnitTest :appnav:testDebugUnitTest
|
||||
./gradlew :tests:konsist:test ktlintCheck detekt lint
|
||||
```
|
||||
CI on the branch (tests, quality, screenshots) must be green on the self-hosted runners.
|
||||
|
||||
Device matrix (owner, debug and release build): MAS OIDC login on a fresh install — chooser
|
||||
skipped, client URI `https://healthchat.ch` without appended path — is the gate for everything
|
||||
else; previous release → new keeps the session; last Classic build → new lands on login without
|
||||
crashing; member directory, profile editor, invites, status banners; deeplinks on HealthChat
|
||||
hosts and `to.healthchat.ch` share links; push in background and killed; release build under
|
||||
R8 exercising the suspend Retrofit calls; whatever upstream switched on (26.10.0: German
|
||||
whole-word search in an encrypted room).
|
||||
|
||||
## Force-push, rewind
|
||||
|
||||
```bash
|
||||
git push origin release/healthchat:refs/heads/backup/release-healthchat-before-<new>
|
||||
git push --force-with-lease=release/healthchat:<old tip> origin <scratch>:release/healthchat
|
||||
git checkout release/healthchat && git reset --hard origin/release/healthchat # every clone
|
||||
```
|
||||
|
||||
`release/healthchat` is the default branch and the repository ruleset `healthchat-base`
|
||||
(id 20385846, rules `deletion` + `non_fast_forward`, no bypass actors) rejects the force-push with
|
||||
`GH013 … Cannot force-push to this branch`. Classic branch protection is empty, so
|
||||
`gh api repos/<repo>/branches/<branch>/protection` says "not protected" — check
|
||||
`gh api repos/<repo>/rules/branches/release%2Fhealthchat` instead. Decision (2026-10-20): disable the
|
||||
ruleset for the push and re-enable it right after, no permanent bypass:
|
||||
```bash
|
||||
gh api -X PUT repos/Novaloop-AG/element-x-android/rulesets/20385846 -f enforcement=disabled
|
||||
git push --force-with-lease=release/healthchat:<old tip> origin <stack>:release/healthchat
|
||||
gh api -X PUT repos/Novaloop-AG/element-x-android/rulesets/20385846 -f enforcement=active
|
||||
gh api repos/Novaloop-AG/element-x-android/rulesets/20385846 -q .enforcement # must print active
|
||||
```
|
||||
Old `healthchat-v*` tags keep their commits alive and off the branch line. Rewind =
|
||||
`git push --force origin healthchat-v<last>^{commit}:release/healthchat`. A rewind does not
|
||||
remove a tag or Play build made on the new history — tag only after the device tests. Then set
|
||||
the version, update `HEALTHCHAT_CHANGES.md`, the releasing skill ("Known branch divergence")
|
||||
and this skill with what changed.
|
||||
|
||||
## What not to do
|
||||
|
||||
- Merge upstream into `release/healthchat`, or squash the fork into a side branch and merge
|
||||
upstream there: both duplicate every fork commit and conflict on every touched file.
|
||||
- Pull on a rewritten branch (merges the old history back in). Fetch and reset.
|
||||
- Commit goldens from a Mac, or resolve a golden conflict by hand — take upstream's and record.
|
||||
- Amend compile fixes into owner commits mid-rebase; collect them in the adaptation commit.
|
||||
- Touch `release/healthchat` before the owner has reviewed the range-diff.
|
||||
@@ -69,18 +69,18 @@ body:
|
||||
description: |
|
||||
Trigger the defect, then click on the menu from the room list then "Report a bug". Describe the issue, mentioning that you have also filed a bug (it's helpful if you can include a link to the bug). Send the report to submit anonymous logs to the developers.
|
||||
options:
|
||||
- 'No'
|
||||
- 'Yes'
|
||||
- 'No'
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: pr
|
||||
attributes:
|
||||
label: Will you submit a Pull Request?
|
||||
label: Are you willing to provide a PR?
|
||||
description: |
|
||||
Submitting a Pull Request can drastically speed up the process of fixing this bug. Don't worry, it's still OK to answer 'No' :).
|
||||
Providing a PR can drastically speed up the process of fixing this bug. Don't worry, it's still OK to answer 'No' :).
|
||||
options:
|
||||
- 'No'
|
||||
- 'Yes'
|
||||
- 'No'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
name: Enhancement request
|
||||
description: Do you have a suggestion or feature request? Please open issue on the repo element-hq/element-meta if the request is global to all Element clients.
|
||||
description: Do you have a suggestion or feature request?
|
||||
labels: [T-Enhancement]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to propose an enhancement to an existing feature. If you would like to propose a new feature or a major cross-platform change, please open an issue on the [element-meta repository](https://github.com/element-hq/element-meta/issues/new?template=enhancement.yml) instead.
|
||||
Thank you for taking the time to propose an enhancement to an existing feature. If you would like to propose a new feature or a major cross-platform change, please [start a discussion here](https://github.com/element-hq/element-meta/discussions/new?category=ideas).
|
||||
- type: textarea
|
||||
id: usecase
|
||||
attributes:
|
||||
|
||||
@@ -28,10 +28,6 @@ changelog:
|
||||
labels:
|
||||
- PR-Dependencies
|
||||
|
||||
- title: 💼 Element Pro
|
||||
labels:
|
||||
- PR-Element-Pro
|
||||
|
||||
- title: Others
|
||||
labels:
|
||||
- PR-misc
|
||||
|
||||
+1
-22
@@ -7,17 +7,10 @@
|
||||
"PR-Dependencies",
|
||||
],
|
||||
"ignoreDeps": [
|
||||
"string:app_name",
|
||||
"gradle",
|
||||
],
|
||||
"minimumReleaseAge": "7 days",
|
||||
"hostRules": [
|
||||
{
|
||||
"matchHost": "https://repo1.maven.org/maven2/",
|
||||
"concurrentRequestLimit": 5,
|
||||
"maxRequestsPerSecond": 5,
|
||||
"timeout": 10000,
|
||||
}
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackageNames": [
|
||||
@@ -25,20 +18,6 @@
|
||||
"/^io.element.android/",
|
||||
],
|
||||
"minimumReleaseAge": null,
|
||||
// Max default value is 6, with this we give absolute priority to our own dependencies
|
||||
"prPriority": 7,
|
||||
"registryUrls": [ "https://repo1.maven.org/maven2/" ],
|
||||
},
|
||||
{
|
||||
// Fetch Android dependencies from Google's Maven repo instead of Maven Central.
|
||||
"matchPackageNames": [
|
||||
"/^androidx\\./",
|
||||
"/^android\\./",
|
||||
"/^com\\.android\\.tools/",
|
||||
"/^com\\.google\\.firebase/",
|
||||
"/^com\\.google\\.android/",
|
||||
],
|
||||
"registryUrls": [ "https://dl.google.com/android/maven2/" ],
|
||||
},
|
||||
{
|
||||
"groupName": "kotlin",
|
||||
|
||||
@@ -61,19 +61,19 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Assemble debug APKs
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
run: ./gradlew :app:assembleGplayDebug app:assembleFDroidDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Upload debug APKs
|
||||
if: ${{ matrix.variant == 'debug' }}
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: elementx-debug
|
||||
path: |
|
||||
@@ -121,18 +121,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Compile release sources
|
||||
if: ${{ matrix.variant == 'release' }}
|
||||
run: |
|
||||
./gradlew assembleGplayRelease -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
|
||||
./gradlew bundleGplayRelease -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Run aapt dump
|
||||
if: ${{ matrix.variant == 'release' }}
|
||||
run: |
|
||||
$ANDROID_HOME/build-tools/37.0.0/aapt dump badging \
|
||||
./app/build/outputs/apk/gplay/release/app-gplay-universal-release.apk \
|
||||
> /tmp/currentAaptDump.txt
|
||||
- name: Check manifest has not changed
|
||||
if: ${{ matrix.variant == 'release' }}
|
||||
run: diff /tmp/currentAaptDump.txt ./tools/manifest/gplay/release/aaptDump.txt
|
||||
run: ./gradlew bundleGplayRelease -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Compile nightly sources
|
||||
if: ${{ matrix.variant == 'nightly' }}
|
||||
run: ./gradlew compileGplayNightlySources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -70,12 +70,12 @@ jobs:
|
||||
- name: Clone submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Assemble debug Gplay Enterprise APK
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
run: ./gradlew :app:assembleGplayDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Upload debug Enterprise APKs
|
||||
if: ${{ matrix.variant == 'debug' }}
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: elementx-enterprise-debug
|
||||
path: |
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
name: Danger CI
|
||||
|
||||
on: [pull_request, merge_group]
|
||||
|
||||
permissions:
|
||||
# Private fork: actions/checkout needs contents:read to clone. Upstream is public,
|
||||
# where an empty token can still read the repo, so an empty permissions block works there and
|
||||
# fails here with `fatal: repository not found`.
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Danger main check
|
||||
# Skip in forks, it doesn't work even with the fallback token
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
- name: Clone submodules
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
run: git submodule update --init --recursive
|
||||
- run: |
|
||||
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||
- name: Danger
|
||||
uses: danger/danger-js@58355dc2b274a87699c8b7deac01baba16d6f061 # 13.0.8
|
||||
with:
|
||||
args: "--dangerfile ./tools/danger/dangerfile.js"
|
||||
env:
|
||||
DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
||||
# Fallback for forks
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -19,16 +19,16 @@ jobs:
|
||||
- name: ⏬ Checkout with LFS
|
||||
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.14
|
||||
- name: Run World screenshots generation script
|
||||
|
||||
@@ -13,11 +13,12 @@ jobs:
|
||||
# Skip in forks
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
- uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
name: Use JDK 21
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
# "The operation was canceled" and no other diagnostic.
|
||||
runs-on: [self-hosted, Linux, X64]
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
@@ -85,13 +85,10 @@ jobs:
|
||||
|
||||
- name: Resolve marketing version
|
||||
id: version
|
||||
env:
|
||||
# Passed through the environment, never expanded into the script (zizmor: template-injection).
|
||||
INPUT_VERSION: ${{ github.event.inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then
|
||||
VERSION="$INPUT_VERSION"
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
else
|
||||
VERSION="${GITHUB_REF_NAME#healthchat-v}"
|
||||
fi
|
||||
@@ -181,7 +178,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
@@ -195,10 +192,7 @@ jobs:
|
||||
# (SdkConstants.CURRENT_BUILD_TOOLS_VERSION), since nothing sets buildToolsVersion
|
||||
# here. Both need refreshing in the image when AGP or COMPILE_SDK moves.
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4
|
||||
with:
|
||||
# Release builds must not consume a cache another workflow could have poisoned.
|
||||
cache-disabled: true
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Decode keystore
|
||||
run: |
|
||||
@@ -228,7 +222,7 @@ jobs:
|
||||
|
||||
- name: Upload workflow artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: healthchat-${{ steps.version.outputs.version }}
|
||||
path: |
|
||||
@@ -238,7 +232,7 @@ jobs:
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Upload to Play Store
|
||||
uses: r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 # v1.1.5
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
|
||||
packageName: ch.healthchat.element
|
||||
|
||||
@@ -31,9 +31,6 @@ jobs:
|
||||
build-apk:
|
||||
name: Build APK
|
||||
runs-on: [self-hosted, Linux, X64]
|
||||
# Secrets are not available to pull_request runs from forks, so the Maestro job below
|
||||
# cannot run for them; skip the build too, as its only consumer is that job.
|
||||
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
concurrency:
|
||||
group: ${{ format('maestro-build-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
@@ -55,19 +52,19 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.ref }}
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
- uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
name: Use JDK 21
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Assemble debug APK
|
||||
@@ -77,7 +74,7 @@ jobs:
|
||||
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
|
||||
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
|
||||
- name: Upload APK as artifact
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: elementx-apk-maestro
|
||||
path: |
|
||||
@@ -90,16 +87,13 @@ jobs:
|
||||
name: Maestro test suite
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ build-apk ]
|
||||
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
# Allow only one to run at a time, since they use the same environment.
|
||||
# Otherwise, tests running in parallel can break each other.
|
||||
# queue: max lets several runs wait their turn instead of the default, where a
|
||||
# newly queued run cancels the one already pending (that run then never gets a verdict).
|
||||
concurrency:
|
||||
group: maestro-test
|
||||
queue: max
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -118,7 +112,7 @@ jobs:
|
||||
run: curl -fsSL "https://get.maestro.mobile.dev" | bash
|
||||
- name: Run Maestro tests in emulator
|
||||
id: maestro_test
|
||||
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
|
||||
uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0
|
||||
continue-on-error: true
|
||||
env:
|
||||
MAESTRO_USERNAME: maestroelement
|
||||
@@ -138,21 +132,8 @@ jobs:
|
||||
disk-size: 3G
|
||||
script: |
|
||||
.github/workflows/scripts/maestro/maestro-local-with-screen-recording.sh app-gplay-x86_64-debug.apk
|
||||
- name: Set up Python
|
||||
if: always()
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: 3.14
|
||||
- name: End all the remaining sessions of the Maestro account
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
env:
|
||||
MAS_PASSWORD: ${{ secrets.MATRIX_MAESTRO_ACCOUNT_PASSWORD }}
|
||||
run: |
|
||||
pip install requests
|
||||
python3 ./tools/mas/delete_all_sessions.py --homeserver matrix.org --username maestroelement
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: test-results
|
||||
path: |
|
||||
|
||||
@@ -32,11 +32,11 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
|
||||
@@ -37,28 +37,28 @@ jobs:
|
||||
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: false
|
||||
|
||||
- name: ⚙️ Run unit tests, debug and release
|
||||
run: ./gradlew test $CI_GRADLE_ARG_PROPERTIES --continue
|
||||
run: ./gradlew test $CI_GRADLE_ARG_PROPERTIES
|
||||
|
||||
- name: 📸 Run screenshot tests
|
||||
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES --continue
|
||||
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
||||
|
||||
- name: 📈 Generate kover report and verify coverage
|
||||
run: ./gradlew :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyAll $CI_GRADLE_ARG_PROPERTIES
|
||||
|
||||
- name: ✅ Upload kover report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: kover-results
|
||||
path: |
|
||||
@@ -76,23 +76,23 @@ jobs:
|
||||
name: Dependency analysis
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Dependency analysis
|
||||
run: ./gradlew dependencyCheckAnalyze $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Upload dependency analysis
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: dependency-analysis
|
||||
path: build/reports/dependency-check-report.html
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
name: PR Checks
|
||||
|
||||
on:
|
||||
# Privilege escalation necessary to comment on pull requests from forks.
|
||||
# 🚨 We must not check out or execute any code here, and be careful around use of user-controlled inputs.
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers]
|
||||
types: [ opened, reopened, edited, synchronize, labeled, unlabeled ]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
pr-checks:
|
||||
# Upstream-only: this fork does not use the PR- label convention.
|
||||
if: github.repository == 'element-hq/element-x-android'
|
||||
name: PR Checks
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
concurrency:
|
||||
# Only allow a single run of this workflow on each branch, automatically cancelling older runs.
|
||||
group: ${{ format('pr-checks-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
# Required to comment on the pull request.
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Check the pull request
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
// Classes that must not be modified anymore. Add the file name suffix here to freeze a class.
|
||||
const FROZEN_CLASSES = [
|
||||
];
|
||||
|
||||
// Only this user is allowed to edit the translation files, since they come from Localazy.
|
||||
const TRANSLATION_ALLOW_LIST = ["ElementBot"];
|
||||
|
||||
const pr = context.payload.pull_request;
|
||||
const body = pr.body ?? "";
|
||||
|
||||
const files = await github.paginate(github.rest.pulls.listFiles, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pr.number
|
||||
});
|
||||
// Deleted files are not interesting for any of the checks below.
|
||||
const paths = files.filter(file => file.status !== "removed").map(file => file.filename);
|
||||
|
||||
const warnings = [];
|
||||
const failures = [];
|
||||
|
||||
if (!body) {
|
||||
warnings.push("Please provide a description for this PR.");
|
||||
}
|
||||
|
||||
if (paths.length > 50) {
|
||||
warnings.push("This pull request seems relatively large. Please consider splitting it into multiple smaller ones.");
|
||||
}
|
||||
|
||||
if (pr.title.endsWith("…")) {
|
||||
failures.push("Please provide a complete title that can be used as a changelog entry.");
|
||||
}
|
||||
|
||||
if (pr.labels.filter(label => label.name.startsWith("PR-")).length !== 1) {
|
||||
failures.push("Please add a single `PR-` label to categorise the changelog entry.");
|
||||
}
|
||||
|
||||
for (const frozen of FROZEN_CLASSES) {
|
||||
if (paths.some(path => path.endsWith(frozen))) {
|
||||
failures.push(`Frozen class \`${frozen}\` has been modified. Please do not modify frozen class.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude the recorded screenshots, which are legitimately pngs.
|
||||
if (paths.some(path => path.toLowerCase().endsWith(".png") && !path.includes("snapshots/images/"))) {
|
||||
warnings.push("You seem to have made changes to some images. Please consider using a vector drawable.");
|
||||
}
|
||||
|
||||
if (!TRANSLATION_ALLOW_LIST.includes(pr.user.login) && paths.some(path => path.endsWith("translations.xml"))) {
|
||||
failures.push("Some translation files have been edited. Only user `ElementBot` (i.e. translations coming from Localazy) is allowed to do that. " +
|
||||
"Please read more about translations management [in the doc](https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md#strings).");
|
||||
}
|
||||
|
||||
warnings.forEach(warning => core.warning(warning));
|
||||
failures.forEach(failure => core.error(failure));
|
||||
|
||||
// Keep a single comment up to date rather than adding a new one on every run.
|
||||
const marker = "<!-- pr-checks -->";
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number
|
||||
});
|
||||
const existing = comments.find(comment => comment.body.includes(marker));
|
||||
|
||||
if (warnings.length || failures.length) {
|
||||
const lines = [...failures.map(failure => `- ❌ ${failure}`),
|
||||
...warnings.map(warning => `- ⚠️ ${warning}`)];
|
||||
const commentBody = `${marker}\n### PR Checks\n\n${lines.join("\n")}`;
|
||||
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existing.id,
|
||||
body: commentBody
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: commentBody
|
||||
});
|
||||
}
|
||||
} else if (existing) {
|
||||
await github.rest.issues.deleteComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existing.id
|
||||
});
|
||||
}
|
||||
|
||||
if (failures.length) {
|
||||
core.setFailed(`${failures.length} check(s) failed, see the comment on the pull request.`);
|
||||
}
|
||||
+53
-191
@@ -47,7 +47,7 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
@@ -65,11 +65,11 @@ jobs:
|
||||
name: Search for invalid screenshot files
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.14
|
||||
- name: Search for invalid screenshot files
|
||||
@@ -79,20 +79,20 @@ jobs:
|
||||
name: Search for invalid dependencies
|
||||
runs-on: [self-hosted, Linux, X64]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.14
|
||||
- name: Search for invalid dependencies
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('check-konsist-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-konsist-develop-{0}', github.sha) || format('check-konsist-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -122,19 +122,19 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Run Konsist tests
|
||||
run: ./gradlew :tests:konsist:testDebugUnitTest $CI_GRADLE_ARG_PROPERTIES --no-daemon
|
||||
- name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: konsist-report
|
||||
path: |
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
cat /sys/fs/cgroup/memory.max 2>/dev/null \
|
||||
|| cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null \
|
||||
|| echo "no cgroup memory limit visible"
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -172,12 +172,12 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Run compose tests
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('check-lint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-lint-develop-{0}', github.sha) || format('check-lint-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -206,12 +206,12 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Build Gplay Debug
|
||||
@@ -222,7 +222,7 @@ jobs:
|
||||
run: ./gradlew :app:lintGplayDebug :app:lintFdroidDebug lintDebug $CI_GRADLE_ARG_PROPERTIES --continue
|
||||
- name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: linting-report
|
||||
path: |
|
||||
@@ -236,7 +236,7 @@ jobs:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('check-detekt-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-detekt-develop-{0}', github.sha) || format('check-detekt-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -251,19 +251,19 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Run Detekt
|
||||
run: ./gradlew detekt $CI_GRADLE_ARG_PROPERTIES --no-daemon --continue
|
||||
run: ./gradlew detekt $CI_GRADLE_ARG_PROPERTIES --no-daemon
|
||||
- name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: detekt-report
|
||||
path: |
|
||||
@@ -277,7 +277,7 @@ jobs:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('check-ktlint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-ktlint-develop-{0}', github.sha) || format('check-ktlint-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -292,19 +292,19 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Run Ktlint check
|
||||
run: ./gradlew ktlintCheck $CI_GRADLE_ARG_PROPERTIES --continue
|
||||
run: ./gradlew ktlintCheck $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ktlint-report
|
||||
path: |
|
||||
@@ -318,7 +318,7 @@ jobs:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('check-docs-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-docs-develop-{0}', github.sha) || format('check-docs-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
@@ -342,7 +342,7 @@ jobs:
|
||||
name: Check shell scripts
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run shellcheck
|
||||
@@ -360,175 +360,37 @@ jobs:
|
||||
actions: read # Required for zizmor to read workflow files.
|
||||
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
|
||||
- uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
|
||||
|
||||
upload_reports:
|
||||
name: Project Check Suite
|
||||
runs-on: ubuntu-latest
|
||||
needs: [konsist, lint, ktlint, detekt]
|
||||
# Run even if a previous job failed: that is precisely when there are issues to report.
|
||||
if: ${{ !cancelled() }}
|
||||
permissions:
|
||||
# Required to list the files modified by the pull request.
|
||||
pull-requests: read
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
persist-credentials: false
|
||||
- name: Download reports from previous jobs
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- name: Annotate the lint, ktlint and detekt issues
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
- name: Prepare Danger
|
||||
if: always()
|
||||
run: |
|
||||
npm install --save-dev @babel/core
|
||||
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||
yarn add danger-plugin-lint-report --dev
|
||||
- name: Danger lint
|
||||
if: always()
|
||||
uses: danger/danger-js@58355dc2b274a87699c8b7deac01baba16d6f061 # 13.0.8
|
||||
with:
|
||||
script: |
|
||||
// GitHub only surfaces a handful of annotations per level and per step, so cap what we emit
|
||||
// and let the job summary carry the full list.
|
||||
const MAX_ANNOTATIONS = 20;
|
||||
const MAX_SUMMARY_ROWS = 200;
|
||||
|
||||
function unescapeXml(value) {
|
||||
return value
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, "\"")
|
||||
.replace(/'/g, "'")
|
||||
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code)))
|
||||
.replace(/&/g, "&");
|
||||
}
|
||||
|
||||
function attribute(tag, name) {
|
||||
const match = new RegExp(`\\b${name}="([^"]*)"`).exec(tag);
|
||||
return match ? unescapeXml(match[1]) : undefined;
|
||||
}
|
||||
|
||||
// Paths in the reports are absolute paths on the runner which generated them, i.e.
|
||||
// /home/runner/work/<repo>/<repo>/<path>.
|
||||
const workspace = `/work/${context.repo.repo}/${context.repo.repo}/`;
|
||||
function repoRelative(path) {
|
||||
const index = path.indexOf(workspace);
|
||||
return index === -1 ? path : path.slice(index + workspace.length);
|
||||
}
|
||||
|
||||
// Checkstyle XML, as produced by ktlint and detekt:
|
||||
// <file name="…"><error line=".." column=".." severity=".." message=".." source=".."/></file>
|
||||
function parseCheckstyle(content) {
|
||||
const issues = [];
|
||||
const fileRegex = /<file\s+([^>]*?)(\/>|>([\s\S]*?)<\/file>)/g;
|
||||
let file;
|
||||
while ((file = fileRegex.exec(content)) !== null) {
|
||||
const path = attribute(file[1], "name");
|
||||
if (!path) continue;
|
||||
const errorRegex = /<error\s+([^>]*?)\/?>/g;
|
||||
let error;
|
||||
while ((error = errorRegex.exec(file[3] ?? "")) !== null) {
|
||||
issues.push({
|
||||
file: repoRelative(path),
|
||||
line: Number(attribute(error[1], "line")) || 1,
|
||||
column: Number(attribute(error[1], "column")) || undefined,
|
||||
severity: (attribute(error[1], "severity") ?? "warning").toLowerCase(),
|
||||
rule: attribute(error[1], "source"),
|
||||
message: attribute(error[1], "message") ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
// Android lint XML, which is not Checkstyle:
|
||||
// <issue id=".." severity=".." message=".."><location file=".." line=".." column=".."/></issue>
|
||||
function parseAndroidLint(content) {
|
||||
const issues = [];
|
||||
const issueRegex = /<issue\s+([^>]*?)(\/>|>([\s\S]*?)<\/issue>)/g;
|
||||
let issue;
|
||||
while ((issue = issueRegex.exec(content)) !== null) {
|
||||
const location = /<location\s+([^>]*?)\/?>/.exec(issue[3] ?? "");
|
||||
if (!location) continue;
|
||||
const path = attribute(location[1], "file");
|
||||
if (!path) continue;
|
||||
issues.push({
|
||||
file: repoRelative(path),
|
||||
line: Number(attribute(location[1], "line")) || 1,
|
||||
column: Number(attribute(location[1], "column")) || undefined,
|
||||
severity: (attribute(issue[1], "severity") ?? "warning").toLowerCase(),
|
||||
rule: attribute(issue[1], "id"),
|
||||
message: attribute(issue[1], "message") ?? "",
|
||||
});
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
const reports = await (await glob.create("**/reports/**/*.xml")).glob();
|
||||
core.info(`Found ${reports.length} XML report(s).`);
|
||||
|
||||
const fs = require("fs");
|
||||
let issues = [];
|
||||
for (const report of reports) {
|
||||
const content = fs.readFileSync(report, "utf8");
|
||||
if (content.includes("<checkstyle")) {
|
||||
issues.push(...parseCheckstyle(content));
|
||||
} else if (content.includes("<issues")) {
|
||||
issues.push(...parseAndroidLint(content));
|
||||
}
|
||||
}
|
||||
|
||||
// Only report on the files touched by the pull request.
|
||||
const pr = context.payload.pull_request;
|
||||
if (pr) {
|
||||
const files = await github.paginate(github.rest.pulls.listFiles, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pr.number
|
||||
});
|
||||
const changed = new Set(files.map(file => file.filename));
|
||||
issues = issues.filter(issue => changed.has(issue.file));
|
||||
}
|
||||
|
||||
const seen = new Set();
|
||||
issues = issues.filter(issue => {
|
||||
const key = `${issue.file}:${issue.line}:${issue.rule}:${issue.message}`;
|
||||
if (seen.has(key)) return false;
|
||||
seen.add(key);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (issues.length === 0) {
|
||||
core.info("No issue found in the files modified by this pull request.");
|
||||
return;
|
||||
}
|
||||
|
||||
const level = severity => {
|
||||
if (severity === "error" || severity === "fatal") return "error";
|
||||
if (severity === "warning") return "warning";
|
||||
return "notice";
|
||||
};
|
||||
const rank = { error: 0, warning: 1, notice: 2 };
|
||||
issues.sort((a, b) =>
|
||||
rank[level(a.severity)] - rank[level(b.severity)] ||
|
||||
a.file.localeCompare(b.file) ||
|
||||
a.line - b.line
|
||||
);
|
||||
|
||||
issues.slice(0, MAX_ANNOTATIONS).forEach(issue => {
|
||||
const annotation = {
|
||||
title: issue.rule,
|
||||
file: issue.file,
|
||||
startLine: issue.line,
|
||||
startColumn: issue.column,
|
||||
};
|
||||
core[level(issue.severity)](issue.message, annotation);
|
||||
});
|
||||
|
||||
core.summary.addHeading(`${issues.length} code quality issue(s)`, 3);
|
||||
core.summary.addTable([
|
||||
[{ data: "Severity", header: true }, { data: "File", header: true }, { data: "Rule", header: true }, { data: "Message", header: true }],
|
||||
...issues.slice(0, MAX_SUMMARY_ROWS).map(issue => [
|
||||
level(issue.severity),
|
||||
`${issue.file}:${issue.line}`,
|
||||
issue.rule ?? "",
|
||||
issue.message,
|
||||
]),
|
||||
]);
|
||||
if (issues.length > MAX_SUMMARY_ROWS) {
|
||||
core.summary.addRaw(`…and ${issues.length - MAX_SUMMARY_ROWS} more, see the uploaded reports.`, true);
|
||||
}
|
||||
await core.summary.write();
|
||||
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
|
||||
env:
|
||||
DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
||||
# Fallback for forks
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -60,29 +60,25 @@ jobs:
|
||||
uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1.3.0
|
||||
with:
|
||||
labels: Record-Screenshots
|
||||
# Private fork: the action checks out and then runs `git lfs pull` on a cache miss. Upstream
|
||||
# can do that anonymously because it is public; here the pull needs the job token, so the
|
||||
# credentials must stay configured. Without this the step dies with
|
||||
# `could not read Username for 'https://github.com'`.
|
||||
- name: ⏬ Checkout with LFS (PR)
|
||||
if: github.event.label.name == 'Record-Screenshots'
|
||||
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5 zizmor: ignore[artipacked]
|
||||
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||
with:
|
||||
persist-credentials: true
|
||||
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@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5 zizmor: ignore[artipacked]
|
||||
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||
with:
|
||||
persist-credentials: true
|
||||
persist-credentials: false
|
||||
- name: ☕️ Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
# Add gradle cache, this should speed up the process
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Record screenshots
|
||||
|
||||
@@ -34,18 +34,16 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check Matrix Rust SDK is not a nightly version
|
||||
run: ./tools/release/check_no_nightly_sdk.sh
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
- name: Create app bundle
|
||||
env:
|
||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||
@@ -59,12 +57,50 @@ jobs:
|
||||
ELEMENT_CALL_RAGESHAKE_URL: ${{ secrets.ELEMENT_CALL_RAGESHAKE_URL }}
|
||||
run: ./gradlew bundleGplayRelease $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Upload bundle as artifact
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: elementx-app-gplay-bundle-unsigned
|
||||
path: |
|
||||
app/build/outputs/bundle/gplayRelease/app-gplay-release.aab
|
||||
|
||||
enterprise:
|
||||
name: Create App Bundle Enterprise
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
concurrency:
|
||||
group: ${{ format('build-release-main-enterprise-{0}', github.sha) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
|
||||
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 }}
|
||||
- name: Clone submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
- name: Create Enterprise app bundle
|
||||
env:
|
||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
|
||||
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
|
||||
run: ./gradlew bundleGplayRelease $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Upload bundle as artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: elementx-enterprise-app-gplay-bundle-unsigned
|
||||
path: |
|
||||
app/build/outputs/bundle/gplayRelease/app-gplay-release.aab
|
||||
|
||||
fdroid:
|
||||
name: Create APKs (FDroid)
|
||||
runs-on: ubuntu-latest
|
||||
@@ -86,18 +122,16 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check Matrix Rust SDK is not a nightly version
|
||||
run: ./tools/release/check_no_nightly_sdk.sh
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
- name: Create APKs
|
||||
env:
|
||||
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
|
||||
@@ -105,7 +139,7 @@ jobs:
|
||||
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
|
||||
run: ./gradlew assembleFdroidRelease $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Upload apks as artifact
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: elementx-app-fdroid-apks-unsigned
|
||||
path: |
|
||||
|
||||
@@ -51,13 +51,18 @@ if [[ -z ${REPO} ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Note: the previous screenshots are deliberately kept, so that Paparazzi can leave the ones that only
|
||||
# differ by less than `maxPercentDifference` untouched instead of rewriting their Git LFS blob. The
|
||||
# screenshots of previews that no longer exist are pruned afterwards by `pruneObsoleteSnapshots`, which
|
||||
# `recordPaparazziDebug` is finalized by.
|
||||
echo "Deleting previous screenshots"
|
||||
./gradlew removeOldSnapshots --stacktrace --warn $GRADLE_ARGS
|
||||
|
||||
echo "Record screenshots"
|
||||
./gradlew recordPaparazziDebug --stacktrace $GRADLE_ARGS
|
||||
|
||||
echo "Deleting previous screenshots"
|
||||
./gradlew removeOldScreenshots --stacktrace --warn $GRADLE_ARGS
|
||||
|
||||
echo "Record screenshots (Compound)"
|
||||
./gradlew :libraries:compound:recordRoborazziDebug --stacktrace -PpreDexEnable=false --max-workers 4 --warn $GRADLE_ARGS
|
||||
|
||||
echo "Committing changes"
|
||||
git config http.sslVerify false
|
||||
|
||||
@@ -73,10 +78,6 @@ else
|
||||
git config --local user.name "${INPUT_AUTHOR_EMAIL}"
|
||||
fi
|
||||
git add -A
|
||||
if git diff --cached --quiet; then
|
||||
echo "Screenshots are already up to date, nothing to commit."
|
||||
exit 0
|
||||
fi
|
||||
git commit -m "Update screenshots"
|
||||
|
||||
GITHUB_REPO="https://$GITHUB_ACTOR:$TOKEN@github.com/$REPO.git"
|
||||
|
||||
@@ -52,19 +52,19 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: false
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Build debug code and test fixtures
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
|
||||
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
||||
with:
|
||||
only-labels: "X-Needs-Info"
|
||||
days-before-issue-stale: 30
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
name: Sync Enterprise submodule
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
paths: [ enterprise ]
|
||||
schedule:
|
||||
# At 06:00 UTC every day
|
||||
- cron: '0 6 * * *'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
sync-enterprise-submodule:
|
||||
runs-on: ubuntu-latest
|
||||
# Skip in forks
|
||||
if: github.repository == 'element-hq/element-x-android'
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Add SSH private keys for submodule repositories
|
||||
uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
|
||||
- name: Check enterprise submodule reference
|
||||
id: check
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
run: |
|
||||
current=$(git ls-tree HEAD enterprise | awk '{print $3}')
|
||||
# Do not init the submodule: the current commit may be on a deleted branch.
|
||||
git init -q "$RUNNER_TEMP/enterprise"
|
||||
git -C "$RUNNER_TEMP/enterprise" fetch -q git@github.com:element-hq/element-android-enterprise.git main
|
||||
main=$(git -C "$RUNNER_TEMP/enterprise" rev-parse FETCH_HEAD)
|
||||
if git -C "$RUNNER_TEMP/enterprise" merge-base --is-ancestor "$current" "$main" 2>/dev/null; then
|
||||
on_main=true
|
||||
else
|
||||
on_main=false
|
||||
fi
|
||||
echo "Current: $current, main: $main, current is on main: $on_main"
|
||||
|
||||
if [ "$current" = "$main" ]; then
|
||||
echo "Submodule is already up to date."
|
||||
echo "update=false" >> "$GITHUB_OUTPUT"
|
||||
elif [ "$EVENT_NAME" = "push" ] && [ "$on_main" = "true" ]; then
|
||||
echo "Submodule targets a commit on main, nothing to do."
|
||||
echo "update=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "update=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "body<<EOF"
|
||||
if [ "$on_main" = "true" ]; then
|
||||
# Do not list the commit messages, the enterprise repository is private.
|
||||
count=$(git -C "$RUNNER_TEMP/enterprise" rev-list --count "$current..$main")
|
||||
echo "Update the \`enterprise\` submodule from \`$current\` to the latest commit of \`main\` (\`$main\`), $count new commit(s)."
|
||||
else
|
||||
echo "The \`enterprise\` submodule targets \`$current\`, which is not on the \`main\` branch."
|
||||
echo
|
||||
echo "Update it to the latest commit of \`main\` (\`$main\`)."
|
||||
fi
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ "$current" != "$main" ]; then
|
||||
# Update the submodule reference without cloning it.
|
||||
git update-index --cacheinfo "160000,$main,enterprise"
|
||||
git -c user.name="github-actions[bot]" -c user.email="41898282+github-actions[bot]@users.noreply.github.com" \
|
||||
commit -q -m "Update enterprise submodule to latest main" -m "Update to $main"
|
||||
fi
|
||||
- name: Create Pull Request to update the enterprise submodule
|
||||
# On schedule and manual runs, also run when there is nothing to update, so that a stale PR gets closed.
|
||||
if: ${{ steps.check.outputs.update == 'true' || github.event_name != 'push' }}
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
||||
title: Update enterprise submodule to latest main
|
||||
body: ${{ steps.check.outputs.body }}
|
||||
branch: sync-enterprise-submodule
|
||||
base: develop
|
||||
delete-branch: true
|
||||
labels: PR-Element-Pro
|
||||
@@ -7,29 +7,26 @@ on:
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||
|
||||
jobs:
|
||||
sync-localazy:
|
||||
runs-on: ubuntu-latest
|
||||
# Skip in forks
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.14
|
||||
- name: Setup Localazy
|
||||
@@ -42,7 +39,6 @@ jobs:
|
||||
./tools/localazy/downloadStrings.sh --all
|
||||
./tools/localazy/importSupportedLocalesFromLocalazy.py
|
||||
./tools/test/generateAllScreenshots.py
|
||||
./tools/manifest/aaptDump.sh
|
||||
- name: Create Pull Request for Strings
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
|
||||
@@ -14,11 +14,11 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
# No concurrency required, runs every time on a schedule.
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.14
|
||||
- name: Install Prerequisite dependencies
|
||||
|
||||
+10
-15
@@ -29,7 +29,7 @@ env:
|
||||
# JVMs against a 48 GiB cgroup limit, with no swap — the daemon gets OOM-killed and
|
||||
# Gradle reports "the daemon has disappeared". GitHub's 4-core hosted runners reach only
|
||||
# 2 x 2 x 2g = 16 GB and additionally have the 8 GB swapfile added above.
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache --max-workers=4
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache --max-workers=4
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
@@ -95,31 +95,25 @@ jobs:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
|
||||
run: git submodule update --init --recursive
|
||||
- name: ☕️ Use JDK 21
|
||||
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||
|
||||
- name: ⚙️ Run unit tests
|
||||
run: ./gradlew :app:testGplayDebugUnitTest testDebugUnitTest -x :tests:konsist:testDebugUnitTest $CI_GRADLE_ARG_PROPERTIES --continue
|
||||
|
||||
- name: ⚙️ Run screenshot tests
|
||||
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES --continue
|
||||
|
||||
- name: ⚙️ Generate kover coverage report
|
||||
run: ./gradlew :koverXmlReportMerged :koverHtmlReportMerged :koverVerifyAll -x testDebugUnitTest $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: ⚙️ Check coverage for debug variant (includes unit & screenshot tests)
|
||||
run: ./gradlew testDebugUnitTest :tests:uitests:verifyPaparazziDebug :koverXmlReportMerged :koverHtmlReportMerged :koverVerifyAll $CI_GRADLE_ARG_PROPERTIES
|
||||
|
||||
- name: 🚫 Upload kover failed coverage reports
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: kover-error-report
|
||||
path: |
|
||||
build/reports/kover
|
||||
app/build/reports/kover
|
||||
|
||||
- name: ✅ Upload kover report (disabled)
|
||||
if: always()
|
||||
@@ -127,11 +121,12 @@ jobs:
|
||||
|
||||
- name: 🚫 Upload test results on error
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: tests-and-screenshot-tests-results
|
||||
path: |
|
||||
**/build/paparazzi/failures/
|
||||
**/build/roborazzi/failures/
|
||||
**/build/reports/tests/*UnitTest/
|
||||
|
||||
- name: 🚫 Modify summary on error
|
||||
@@ -152,7 +147,7 @@ jobs:
|
||||
if: env.CODECOV_TOKEN != ''
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -31,6 +31,9 @@ __pycache__/
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
|
||||
Generated
-34
@@ -1,39 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="android" withSubpackages="true" static="true" />
|
||||
<package name="androidx" withSubpackages="true" static="true" />
|
||||
<package name="com" withSubpackages="true" static="true" />
|
||||
<package name="junit" withSubpackages="true" static="true" />
|
||||
<package name="net" withSubpackages="true" static="true" />
|
||||
<package name="org" withSubpackages="true" static="true" />
|
||||
<package name="java" withSubpackages="true" static="true" />
|
||||
<package name="javax" withSubpackages="true" static="true" />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
<emptyLine />
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="androidx" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="com" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="junit" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="net" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="org" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="LINE_BREAK_AFTER_MULTILINE_WHEN_ENTRY" value="false" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value="© &#36;today.year Element Creations Ltd. SPDX-License-Identifier: LicenseRef-Element-Commercial SPDX-License-Identifier: LicenseRef-Element-Proprietary" />
|
||||
<option name="notice" value="© &#36;today.year Element Creations Ltd. Element Creations Ltd, Element Software SARL, Element Software Inc., and Element Software GmbH (the "Element Group") only make this file available under a proprietary license model. Without a proprietary license with us, you cannot use this file. The terms of the proprietary license agreement between you and any member of the Element Group shall always apply to your use of this file. Unauthorised use, copying, distribution, or modification of this file, via any medium, is strictly prohibited. For details about the licensing terms, you must either visit our website or contact a member of our sales team." />
|
||||
<option name="myName" value="Element Enterprise" />
|
||||
</copyright>
|
||||
</component>
|
||||
Generated
+1
@@ -16,6 +16,7 @@
|
||||
<w>posthog</w>
|
||||
<w>rageshake</w>
|
||||
<w>securebackup</w>
|
||||
<w>showkase</w>
|
||||
<w>snackbar</w>
|
||||
<w>spdx</w>
|
||||
<w>swipeable</w>
|
||||
|
||||
Generated
+2
-2
@@ -2,6 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="externalSystemId" value="Gradle" />
|
||||
<option name="version" value="2.4.20" />
|
||||
<option name="version" value="2.4.0" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
appId: ${MAESTRO_APP_ID}
|
||||
---
|
||||
- tapOn:
|
||||
id: "login-change_server"
|
||||
- takeScreenshot: build/maestro/200-ChangeServer
|
||||
- tapOn: "matrix.org"
|
||||
- tapOn:
|
||||
id: "login-change_server"
|
||||
- tapOn: "Other"
|
||||
- tapOn:
|
||||
id: "change_server-server"
|
||||
- inputText: "element"
|
||||
- hideKeyboard
|
||||
- extendedWaitUntil:
|
||||
visible: "element.io"
|
||||
timeout: 10000
|
||||
- tapOn: "element.io"
|
||||
# Revert to matrix.org
|
||||
- tapOn:
|
||||
id: "login-change_server"
|
||||
- tapOn: "matrix.org"
|
||||
@@ -3,17 +3,11 @@ appId: ${MAESTRO_APP_ID}
|
||||
- tapOn: "Sign in manually"
|
||||
- runFlow: ../assertions/assertLoginDisplayed.yaml
|
||||
- takeScreenshot: build/maestro/100-SignIn
|
||||
# The account provider field defaults to matrix.org, so continue straight to the MAS login.
|
||||
- runFlow: changeServer.yaml
|
||||
- runFlow: ../assertions/assertLoginDisplayed.yaml
|
||||
- tapOn:
|
||||
id: "login-continue"
|
||||
## MAS page
|
||||
# Click on "Use another account" if MAS is already logged in:
|
||||
- runFlow:
|
||||
when:
|
||||
visible: "Use another account"
|
||||
commands:
|
||||
- tapOn:
|
||||
text: "Use another account"
|
||||
- retry:
|
||||
maxRetries: 3
|
||||
commands:
|
||||
|
||||
@@ -6,19 +6,10 @@ appId: ${MAESTRO_APP_ID}
|
||||
- takeScreenshot: build/maestro/900-SignOutScreen
|
||||
- back
|
||||
- tapOn: "Remove this device"
|
||||
# Ensure pressing back cancels
|
||||
- back
|
||||
- tapOn: "Remove this device"
|
||||
# Ensure cancel cancels
|
||||
- tapOn: "Cancel"
|
||||
- tapOn: "Remove this device"
|
||||
- tapOn:
|
||||
id: "sign-out-submit"
|
||||
# Cancel the dialog cancels the logout
|
||||
- tapOn:
|
||||
id: "dialog-negative"
|
||||
- tapOn:
|
||||
id: "sign-out-submit"
|
||||
- tapOn: "Remove this device"
|
||||
- tapOn:
|
||||
id: "dialog-positive"
|
||||
- runFlow: ../assertions/assertInitDisplayed.yaml
|
||||
|
||||
@@ -16,4 +16,4 @@ appId: ${MAESTRO_APP_ID}
|
||||
- extendedWaitUntil:
|
||||
visible: "Device verified"
|
||||
timeout: 30000
|
||||
- tapOn: "Done"
|
||||
- tapOn: "Continue"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
appId: ${MAESTRO_APP_ID}
|
||||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "Enter your account provider"
|
||||
visible: "Change account provider"
|
||||
timeout: 10000
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
appId: ${MAESTRO_APP_ID}
|
||||
---
|
||||
# Clean up the room list if it was left in a bad state by previous tests
|
||||
- runFlow:
|
||||
when:
|
||||
visible: 'aRoomName'
|
||||
commands:
|
||||
- longPressOn: 'aRoomName'
|
||||
- tapOn:
|
||||
text: "Leave room"
|
||||
index: 0
|
||||
- tapOn: "Leave"
|
||||
- runFlow:
|
||||
when:
|
||||
visible: ${MAESTRO_INVITEE1_MXID}
|
||||
commands:
|
||||
- longPressOn: ${MAESTRO_INVITEE1_MXID}
|
||||
- tapOn:
|
||||
text: "Leave room"
|
||||
index: 0
|
||||
- tapOn: "Leave"
|
||||
@@ -7,16 +7,7 @@ appId: ${MAESTRO_APP_ID}
|
||||
- tapOn:
|
||||
text: ${MAESTRO_INVITEE1_MXID}
|
||||
index: 1
|
||||
- runFlow:
|
||||
when:
|
||||
visible: 'Start a chat with this new contact?'
|
||||
commands:
|
||||
- tapOn: 'Continue'
|
||||
- runFlow:
|
||||
when:
|
||||
visible: 'Send invite?'
|
||||
commands:
|
||||
- tapOn: 'Send invite'
|
||||
- tapOn: "Continue"
|
||||
- takeScreenshot: build/maestro/330-createAndDeleteDM
|
||||
- tapOn: "maestroelement2"
|
||||
- scroll
|
||||
|
||||
@@ -8,12 +8,6 @@ appId: ${MAESTRO_APP_ID}
|
||||
- tapOn: "Add description…"
|
||||
- inputText: "aRoomTopic"
|
||||
- tapOn: "Create"
|
||||
|
||||
# It can take ages to create room on matrix.org
|
||||
- extendedWaitUntil:
|
||||
timeout: 30000
|
||||
visible: "Invite people"
|
||||
|
||||
- takeScreenshot: build/maestro/320-createAndDeleteRoom
|
||||
- tapOn: "Search for someone"
|
||||
- inputText: ${MAESTRO_INVITEE1_MXID}
|
||||
@@ -21,12 +15,6 @@ appId: ${MAESTRO_APP_ID}
|
||||
text: ${MAESTRO_INVITEE1_MXID}
|
||||
index: 1
|
||||
- tapOn: "Finish"
|
||||
- runFlow:
|
||||
when:
|
||||
visible: 'Invite new contact to this room?'
|
||||
commands:
|
||||
- tapOn:
|
||||
id: "confirm_invite_unknown"
|
||||
- tapOn: "aRoomName"
|
||||
- tapOn: "Invite"
|
||||
# assert there's 1 member and 1 invitee
|
||||
@@ -35,10 +23,6 @@ appId: ${MAESTRO_APP_ID}
|
||||
- tapOn:
|
||||
text: ${MAESTRO_INVITEE2_MXID}
|
||||
index: 1
|
||||
# Hide keyboard by tapping on an external element in case it's displayed, otherwise it will 'eat' the back button press
|
||||
- tapOn:
|
||||
# Navigation title
|
||||
text: "Invite people"
|
||||
- tapOn: "Invite"
|
||||
- runFlow:
|
||||
when:
|
||||
@@ -46,6 +30,10 @@ appId: ${MAESTRO_APP_ID}
|
||||
commands:
|
||||
- tapOn:
|
||||
id: "confirm_invite_unknown"
|
||||
# Close the keyboard if it's still open
|
||||
- tapOn: "Back"
|
||||
# Go back to the room details screen
|
||||
- tapOn: "Back"
|
||||
- scrollUntilVisible:
|
||||
direction: DOWN
|
||||
element:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
appId: ${MAESTRO_APP_ID}
|
||||
---
|
||||
- runFlow: cleanUpRoomList.yaml
|
||||
#- runFlow: searchRoomList.yaml
|
||||
- runFlow: searchRoomList.yaml
|
||||
- takeScreenshot: build/maestro/300-RoomList
|
||||
- runFlow: timeline/timeline.yaml
|
||||
- runFlow: roomContextMenu.yaml
|
||||
|
||||
@@ -3,5 +3,5 @@ appId: ${MAESTRO_APP_ID}
|
||||
- takeScreenshot: build/maestro/520-Timeline
|
||||
- tapOn: "Add attachment"
|
||||
- tapOn: "Share location"
|
||||
- tapOn: "Share my location"
|
||||
- tapOn: "Share selected location"
|
||||
- takeScreenshot: build/maestro/521-Timeline
|
||||
|
||||
@@ -4,12 +4,9 @@ appId: ${MAESTRO_APP_ID}
|
||||
id: "home_screen-settings"
|
||||
- assertVisible: "Settings"
|
||||
- takeScreenshot: build/maestro/600-Settings
|
||||
|
||||
## Account settings
|
||||
- tapOn:
|
||||
id: "settings-user_profile"
|
||||
- assertVisible: "Edit profile"
|
||||
- takeScreenshot: build/maestro/610-AccountEditProfile
|
||||
text: "Analytics"
|
||||
- assertVisible: "Share analytics data"
|
||||
- back
|
||||
|
||||
- tapOn:
|
||||
@@ -17,27 +14,16 @@ appId: ${MAESTRO_APP_ID}
|
||||
- assertVisible: "Enable notifications on this device"
|
||||
- back
|
||||
|
||||
- tapOn:
|
||||
text: "Encryption"
|
||||
- assertVisible: "Key storage"
|
||||
- back
|
||||
|
||||
- tapOn:
|
||||
text: "Moderation & safety"
|
||||
- assertVisible: "Your appearance"
|
||||
- back
|
||||
|
||||
- tapOn:
|
||||
text: "Report a problem"
|
||||
- assertVisible: "Report a problem"
|
||||
- back
|
||||
|
||||
## App settings section
|
||||
- assertVisible: "Appearance"
|
||||
|
||||
- tapOn:
|
||||
text: "Media upload quality"
|
||||
- assertVisible: "Media upload quality"
|
||||
text: "About"
|
||||
- assertVisible: "Copyright"
|
||||
- assertVisible: "Acceptable use policy"
|
||||
- assertVisible: "Privacy policy"
|
||||
- back
|
||||
|
||||
- tapOn:
|
||||
@@ -46,53 +32,14 @@ appId: ${MAESTRO_APP_ID}
|
||||
- hideKeyboard
|
||||
- back
|
||||
|
||||
- scrollUntilVisible:
|
||||
direction: DOWN
|
||||
element:
|
||||
text: "Location sharing"
|
||||
- tapOn:
|
||||
text: "Location sharing"
|
||||
- assertVisible: "Live location updates"
|
||||
text: "Advanced settings"
|
||||
- assertVisible: "View source"
|
||||
- back
|
||||
|
||||
## General section
|
||||
- scrollUntilVisible:
|
||||
direction: DOWN
|
||||
element:
|
||||
text: "Analytics"
|
||||
- tapOn:
|
||||
text: "Analytics"
|
||||
- assertVisible: "Share analytics data"
|
||||
- back
|
||||
|
||||
- scrollUntilVisible:
|
||||
direction: DOWN
|
||||
element:
|
||||
text: "Labs"
|
||||
- tapOn:
|
||||
text: "Labs"
|
||||
- assertVisible: "Feeling experimental?"
|
||||
- back
|
||||
|
||||
- scrollUntilVisible:
|
||||
direction: DOWN
|
||||
element:
|
||||
text: "About"
|
||||
- tapOn:
|
||||
text: "About"
|
||||
- assertVisible: "Copyright"
|
||||
- assertVisible: "Acceptable use policy"
|
||||
- assertVisible: "Privacy policy"
|
||||
- back
|
||||
|
||||
- scrollUntilVisible:
|
||||
direction: DOWN
|
||||
element:
|
||||
text: "Developer options"
|
||||
- tapOn:
|
||||
text: "Developer options"
|
||||
- assertVisible: "Feature flags"
|
||||
- assertVisible: "View source"
|
||||
- back
|
||||
|
||||
- back
|
||||
|
||||
@@ -37,9 +37,8 @@ PRs must meet these rules.
|
||||
|
||||
- Create previews for **all main states** of a Composable.
|
||||
- Use `@PreviewsDayNight` for consistency.
|
||||
- Use `PreviewParameterProvider` (e.g., `FooStatePreviewParam`) to provide states. Always add new states to existing providers at the end of the sequence.
|
||||
- Use `PreviewParameterProvider` (e.g., `FooStateProvider`) to provide states.
|
||||
- Wrap previews in `ElementPreview { ... }`.
|
||||
- When writing tests, never try to record the screenshots, the CI will do it.
|
||||
|
||||
---
|
||||
|
||||
@@ -50,10 +49,9 @@ PRs must meet these rules.
|
||||
- Title = changelog entry — descriptive, no "Fixes #…".
|
||||
- Leave description template for the developer. Redirect them to the [contributing etiquette](CONTRIBUTING.md#etiquette).
|
||||
- Screenshots/videos for visual changes.
|
||||
- 500 additions max into production code. Test code can be larger — split large changes.
|
||||
- 500 additions max — split large changes.
|
||||
- Commits need a title and description; no tiny or massive commits.
|
||||
- No history rewrites.
|
||||
- When creating a pull request and if changes contain new or update(s) of the Composable Previews, add the label "Record-Screenshots" to the PR so that the CI will record the screenshots.
|
||||
|
||||
---
|
||||
|
||||
@@ -90,7 +88,7 @@ We use [Appyx](https://bumble-tech.github.io/appyx/) for navigation and [Molecul
|
||||
| `FooView.kt` | Stateless Composable rendering the UI from `FooState`. |
|
||||
| `FooState.kt` | Data class representing the immutable UI state. |
|
||||
| `FooEvent.kt` | Sealed interface for UI actions sent to the Presenter. |
|
||||
| `FooStatePreviewParam.kt` | Provides sample states for Previews and Screenshot tests. |
|
||||
| `FooStateProvider.kt` | Provides sample states for Previews and Screenshot tests. |
|
||||
| `FooPresenterTest.kt` | Unit tests for the Presenter logic using Turbine. |
|
||||
|
||||
---
|
||||
|
||||
-634
@@ -1,637 +1,3 @@
|
||||
Changes in Element X v26.09.4
|
||||
=============================
|
||||
|
||||
## Hotfix version:
|
||||
|
||||
Fixes an issue with the room list not displaying all activity from unread messages.
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.09.3...v26.09.4
|
||||
|
||||
Changes in Element X v26.09.3
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.09.3 -->
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Integrate the new timeline renderer behind a feature flag by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7722
|
||||
* Add headers (`h1-h6`) tag support for the new timeline renderer by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7754
|
||||
### 🙌 Improvements
|
||||
* Add smooth animation to voice messages play progress by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7698
|
||||
* Add linkification to the new timeline renderer by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7731
|
||||
### 🐛 Bugfixes
|
||||
* Make global search use a `Joined` filter for room results by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7695
|
||||
* Fix flaky test in `TimelinePresenterTest` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7702
|
||||
* Close the threaded timelines created to create a poll, share a location or send media by @bmarty in https://github.com/element-hq/element-x-android/pull/7705
|
||||
* Start sync manually after completing the auth flow by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7734
|
||||
* Fix forwarding an event not working in threads by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7743
|
||||
* Fix blank screen at launch when the homeserver expires the sliding sync session by @bmarty in https://github.com/element-hq/element-x-android/pull/7692
|
||||
* Fix unread notification count in room list by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7749
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7699
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7736
|
||||
### 🧱 Build
|
||||
* Reduce parallel JVMs spawned for tests by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7701
|
||||
* Replace git flow with regular git commands in the release script V2 by @bmarty in https://github.com/element-hq/element-x-android/pull/7707
|
||||
* Remove Roborazzi, use Paparazzi for Compound by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7735
|
||||
* Fix regex for invalid placeholders by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7738
|
||||
* Add a script to install and launch an APK given a PR URL by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7752
|
||||
### 🚧 In development 🚧
|
||||
* New timeline event renderer: add new modules by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7721
|
||||
* Add recent search results to global search by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7742
|
||||
### Dependency upgrades
|
||||
* Update dependency io.sentry:sentry-android to v8.55.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7685
|
||||
* Update coil to v3.6.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7693
|
||||
* Update kotlinpoet to v2.4.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7709
|
||||
* Update plugin sonarqube to v7.5.0.8588 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7671
|
||||
* Update roborazzi to v1.74.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7712
|
||||
* Update dependency io.element.android:element-call-embedded to v0.26.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7715
|
||||
* Use patched Konsist version (forked, 0.18.0) by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7719
|
||||
* Update dependency net.zetetic:sqlcipher-android to v4.19.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7716
|
||||
* Update metro to v1.4.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7711
|
||||
* Update actions/setup-java action to v6.0.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7728
|
||||
* Update dependency androidx.compose:compose-bom to v2026.09.00 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7730
|
||||
* Update dependency androidx.sqlite:sqlite-ktx to v2.7.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7729
|
||||
* Update dependencyAnalysis to v3.19.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7755
|
||||
* Update media3 to v1.11.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7756
|
||||
* Upgrade SDK bindings to `v26.09.26` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7780
|
||||
### Others
|
||||
* Let the user give a reason when removing a message by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7538
|
||||
* Iterate on client creation by @bmarty in https://github.com/element-hq/element-x-android/pull/7591
|
||||
* Iteration on Acount provider by @bmarty in https://github.com/element-hq/element-x-android/pull/7703
|
||||
* Do not report detekt LargeClass issues on test classes by @bmarty in https://github.com/element-hq/element-x-android/pull/7713
|
||||
* Let the user give an optional reason when removing a message by @bmarty in https://github.com/element-hq/element-x-android/pull/7679
|
||||
* Make NotificationResultProcessor.emit wait until the batch is processed by @Johennes in https://github.com/element-hq/element-x-android/pull/7640
|
||||
* Settings iteration: move user status and update multi-account section by @bmarty in https://github.com/element-hq/element-x-android/pull/7741
|
||||
* Update remove message confirmation dialog. by @bmarty in https://github.com/element-hq/element-x-android/pull/7757
|
||||
* Add a script to end all the sessions of a Matrix account using MAS by @bmarty in https://github.com/element-hq/element-x-android/pull/7762
|
||||
* Settings iteration: split account settings and application settings. by @bmarty in https://github.com/element-hq/element-x-android/pull/7750
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.09.2...v26.09.3
|
||||
|
||||
Changes in Element X v26.09.2
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.09.2 -->
|
||||
|
||||
## What's Changed
|
||||
### 🙌 Improvements
|
||||
* Keep the push foreground service alive while notifications are fetched by @Johennes in https://github.com/element-hq/element-x-android/pull/7639
|
||||
* Stop clearing the remote config when clearing the cache by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7662
|
||||
### 🐛 Bugfixes
|
||||
* Fix fetched remote config being cached with the wrong key by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7626
|
||||
* Make sure `enforce_element_pro` is taken into account in FOSS by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7631
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7628
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7657
|
||||
### 🧱 Build
|
||||
* Allow retrying finding the release assets in release script by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7619
|
||||
* Improve detection of invalid placeholders in localazy strings by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7624
|
||||
* Add `GRADLE_OPTS` for `sync-localazy.yml` flow by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7629
|
||||
* Clean up ProGuard keep rules files by @bmarty in https://github.com/element-hq/element-x-android/pull/7647
|
||||
* Replace Danger with actions/github-script by @bmarty in https://github.com/element-hq/element-x-android/pull/7649
|
||||
* Remove Showkase and its in-app component browser by @bmarty in https://github.com/element-hq/element-x-android/pull/7678
|
||||
* Improve Renovate configuration to fix issues with Maven Central by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7677
|
||||
### Dependency upgrades
|
||||
* Update dependency com.posthog:posthog-android to v3.60.7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7616
|
||||
* Update dependency org.jsoup:jsoup to v1.23.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7620
|
||||
* Update dependency io.element.android:element-call-embedded to v0.25.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7622
|
||||
* Update dependencyAnalysis to v3.19.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7623
|
||||
* Update wysiwyg to v2.42.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7627
|
||||
* Update android.gradle.plugin to v9.3.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7603
|
||||
* Update actions/setup-java action to v6 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7614
|
||||
* Update danger/danger-js action to v14 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7602
|
||||
* Update camera to v1.6.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7632
|
||||
* Update coil to v3.6.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7630
|
||||
* Update dependency io.sentry:sentry-android to v8.54.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7636
|
||||
* Update dependency org.jetbrains.kotlinx:kotlinx-collections-immutable to v0.5.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7650
|
||||
* Bump SDK bindings to `v26.09.3` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7637
|
||||
* Update zizmorcore/zizmor-action action to v0.6.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7658
|
||||
* Update coil to v3.6.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7664
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.09.08 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7668
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.09.9 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7683
|
||||
### 💼 Element Pro
|
||||
* Fix remote config getting refreshed with `Origin.Cache` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7666
|
||||
### Others
|
||||
* Move knock and gallery to labs by @bmarty in https://github.com/element-hq/element-x-android/pull/7625
|
||||
* Say so when this session has no secrets to verify another one with by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7551
|
||||
* Expose the timeline item long press and swipe to reply as TalkBack actions by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7458
|
||||
* Bump PR priority for our dependencies in Renovate by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7642
|
||||
* Import string from Localazy and remove temporary file. by @bmarty in https://github.com/element-hq/element-x-android/pull/7645
|
||||
* Revert "Say so when this session has no secrets to verify another one with" by @bmarty in https://github.com/element-hq/element-x-android/pull/7644
|
||||
* Maestro (local): queue pending runs in the maestro-test concurrency group instead of cancelling them by @andreBurnt in https://github.com/element-hq/element-x-android/pull/7656
|
||||
* Maestro (local): skip the lane on pull requests from forks by @andreBurnt in https://github.com/element-hq/element-x-android/pull/7655
|
||||
* Update maplibre compose to 0.15 by @ganfra in https://github.com/element-hq/element-x-android/pull/7663
|
||||
* Try improving renovate issues with Maven Central rate limiting by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7667
|
||||
|
||||
## New Contributors
|
||||
* @andreBurnt made their first contribution in https://github.com/element-hq/element-x-android/pull/7656
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.09.1...v26.09.2
|
||||
|
||||
Changes in Element X v26.09.1
|
||||
=============================
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Support creating polls with multiple answers and send response with multiple answers by @bmarty in https://github.com/element-hq/element-x-android/pull/7613
|
||||
### 🙌 Improvements
|
||||
* Only track read receipts on message-like events by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7361
|
||||
* Stop rescanning message bodies for emoji only on every recomposition by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7531
|
||||
### 🐛 Bugfixes
|
||||
* Ensure that noisy notifications are still noisy when the app has a PIN code. by @bmarty in https://github.com/element-hq/element-x-android/pull/7584
|
||||
* Ensure that sessionVerifiedStatus is updated when the crypto is ready. by @bmarty in https://github.com/element-hq/element-x-android/pull/7604
|
||||
* Fix invalid placeholders in translations by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7612
|
||||
### 🧱 Build
|
||||
* Do not open the editor when finishing the release by @bmarty in https://github.com/element-hq/element-x-android/pull/7583
|
||||
* [release] Check that there is no unmerged PR with the label "Z-NextRelease", else exit by @bmarty in https://github.com/element-hq/element-x-android/pull/7606
|
||||
### Dependency upgrades
|
||||
* Update dependency net.zetetic:sqlcipher-android to v4.18.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7552
|
||||
* Update dependency com.google.firebase:firebase-bom to v34.18.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7581
|
||||
* Update dependency io.github.sergio-sastre.ComposablePreviewScanner:android to v0.9.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7535
|
||||
* Update dependency com.google.guava:guava to v33.7.1-android by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7534
|
||||
* Update dependency org.unifiedpush.android:connector to v3.3.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7586
|
||||
* Update dependency org.unifiedpush.android:connector to v3.3.5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7595
|
||||
* Update roborazzi to v1.73.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7618
|
||||
### 💼 Element Pro
|
||||
* Implement a hook allowing enterprise modules to perform actions on startup by @richvdh in https://github.com/element-hq/element-x-android/pull/7585
|
||||
### Others
|
||||
* Don't hide the thread of a deleted message inside the removed messages group by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7571
|
||||
* Name a sender the timeline has no profile for from the room member list by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7539
|
||||
* Offer conversations in the system share sheet by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7574
|
||||
* Rank the mention suggestions by how well they match what was typed by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7536
|
||||
* Log the SDK git Sha at application startup by @bmarty in https://github.com/element-hq/element-x-android/pull/7589
|
||||
* Limit usage of nullable callback. by @bmarty in https://github.com/element-hq/element-x-android/pull/7588
|
||||
* Fix crash due to JSON.stringify on a cirular structure in injectes js. by @toger5 in https://github.com/element-hq/element-x-android/pull/7600
|
||||
* Support multi-select polls by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6477
|
||||
* Remove ExternalSession, fallback login with Element Web is gone. by @bmarty in https://github.com/element-hq/element-x-android/pull/7608
|
||||
* Fall back to the message body when the formatted body has nothing to render by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7523
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.08.4...v26.09.1
|
||||
|
||||
Changes in Element X v26.08.4
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.08.4 -->
|
||||
|
||||
## What's Changed
|
||||
### 🧱 Build
|
||||
* Add releaseV2.sh, a release script automating the GitHub release by @bmarty in https://github.com/element-hq/element-x-android/pull/7579
|
||||
* Update submodule ref. by @bmarty in https://github.com/element-hq/element-x-android/pull/7582
|
||||
### Others
|
||||
* Keep the formatting of a caption that is being edited by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7572
|
||||
* Say when the server cannot be reached instead of showing nothing by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7567
|
||||
* Keep saving files to Downloads once 32 files share the same name by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7344
|
||||
* Fix removed messages hiding its thread on deletion by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7480
|
||||
* Drop the sender's name from the room list preview of two-person rooms by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7562
|
||||
* Document how the Element Call base URL developer option is used by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7546
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.08.3...v26.08.4
|
||||
|
||||
Changes in Element X v26.08.3
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.08.3 -->
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Let users enter their full Matrix user ID on the account provider screen by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7500
|
||||
* Enable user status feature and hide status text in the timeline by @bmarty in https://github.com/element-hq/element-x-android/pull/7517
|
||||
### 🐛 Bugfixes
|
||||
* Fix cold start with a mobile deep link by @bmarty in https://github.com/element-hq/element-x-android/pull/7488
|
||||
* Make the account provider screen scrollable so its description is not clipped by the keyboard by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7521
|
||||
### 🧱 Build
|
||||
* Update Gradle Wrapper from 9.7.0 to 9.7.1 by @ElementBot in https://github.com/element-hq/element-x-android/pull/7485
|
||||
* Fix flaky test in `DefaultVoiceMessageComposerPresenterTest` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7487
|
||||
* Fix warning about deprecated Gradle `keepRules.file` API by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7498
|
||||
* Update Enterprise Copyright in idea settings, so that new files get an expected header. by @bmarty in https://github.com/element-hq/element-x-android/pull/7501
|
||||
* Remove workaround for Renovate. by @bmarty in https://github.com/element-hq/element-x-android/pull/7502
|
||||
### 🚧 In development 🚧
|
||||
* Make PIP react to camera orientation changes in the other participant's device by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6951
|
||||
* Feature: global search by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7410
|
||||
### Dependency upgrades
|
||||
* Update dependency org.maplibre.gl:android-sdk to v13.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7486
|
||||
* Update dependency androidx.webkit:webkit to v1.17.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7484
|
||||
* Update dependency io.sentry:sentry-android to v8.53.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7481
|
||||
* Update dependency io.element.android:element-call-embedded to v0.24.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7474
|
||||
* Update roborazzi to v1.72.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7489
|
||||
* Update metro to v1.4.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7493
|
||||
* Update dependency com.squareup.okhttp3:okhttp-bom to v5.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7512
|
||||
* Update dependency com.google.guava:guava to v33.7.0-android by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7516
|
||||
* Update sdk to version 26.08.25 by @bmarty in https://github.com/element-hq/element-x-android/pull/7550
|
||||
### 💼 Element Pro
|
||||
* Add `ClientEnterpriseHook` and `ClientBuilderEnterpriseHook` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7495
|
||||
* Remove dependency on enterprise module. by @bmarty in https://github.com/element-hq/element-x-android/pull/7505
|
||||
### Others
|
||||
* Show the right image when opening one that has not finished sending by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7359
|
||||
* Move strings to the login module by @bmarty in https://github.com/element-hq/element-x-android/pull/7494
|
||||
* Allow empty problem description if the GitHub issue number is present for rageshakes by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7497
|
||||
* Apply the system theme when switching back to it from a forced light or dark theme by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7453
|
||||
* Show dialog with reason when user clicks on a message with a failure by @bmarty in https://github.com/element-hq/element-x-android/pull/7490
|
||||
* Keep a focused timeline focused until the event has been rendered by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7463
|
||||
* Do not render in-chat key verification requests in the timeline by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7443
|
||||
* Share a file whose mime type is not one of the supported ones by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7435
|
||||
* Show reactions in the pinned messages list by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6676
|
||||
* Send a concrete mime type for media shared with a wildcard one by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7449
|
||||
* Rename module and ensure that naming convention is followed by @bmarty in https://github.com/element-hq/element-x-android/pull/7514
|
||||
* Fix lint issue, ensure that PiP methods are not called on old devices not supporting them. by @bmarty in https://github.com/element-hq/element-x-android/pull/7515
|
||||
* Rename PreviewParameterProvider subclasses to the PreviewParam suffix by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7468
|
||||
* Update Ref. by @bmarty in https://github.com/element-hq/element-x-android/pull/7518
|
||||
* Remove the reference to the decommissioned OIDC playground by @sandhose in https://github.com/element-hq/element-x-android/pull/7519
|
||||
* Hide the encryption badge when key storage is set up on the server by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7447
|
||||
* Rename remaining events sealed interface by @bmarty in https://github.com/element-hq/element-x-android/pull/7533
|
||||
* Stop waiting on a verification request the other session never accepts by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7457
|
||||
* Point the proxy debugging doc at the method that builds the clients by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7543
|
||||
* Show the build date in the screenshot gallery footer by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7544
|
||||
* Run the database migrations again when a database is upgraded by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7570
|
||||
* Update fonts on the headings by @bmarty in https://github.com/element-hq/element-x-android/pull/7520
|
||||
* Add a "Push rules" entry in the developer options by @bmarty in https://github.com/element-hq/element-x-android/pull/7577
|
||||
|
||||
## New Contributors
|
||||
* @sandhose made their first contribution in https://github.com/element-hq/element-x-android/pull/7519
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.08.2...v26.08.3
|
||||
|
||||
Changes in Element X v26.08.2
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.08.2 -->
|
||||
|
||||
**Note: this release in an internal release, it will not be pushed to the stores.**
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Remove the legacy web-based account creation for matrix.org by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7412
|
||||
### 🙌 Improvements
|
||||
* Move the custom tile server URL config to the Element wellknown content by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7416
|
||||
* Explain a rate limiting push gateway in the notification troubleshooter by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7448
|
||||
* Move the `:libraries:wellknown` logic into `enterprise` modules by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7429
|
||||
* Refresh the room members when a mention is started by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7438
|
||||
* Close the invite screen once the invite has been declined by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7440
|
||||
* Use Profiles sliding sync extension check for own profile subscription by @ganfra in https://github.com/element-hq/element-x-android/pull/7403
|
||||
* Simplify choosing the account provider during sign in, with autocomplete of previously used ones by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7394
|
||||
### 🐛 Bugfixes
|
||||
* Fix wrong TextComposer placeholder displayed when in a thread. by @bmarty in https://github.com/element-hq/element-x-android/pull/7413
|
||||
* Fix bug reports failing to send after the app has crashed by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7395
|
||||
* Fix outgoing session verification stuck with incomplete recovery setup by @hardcore-sushi in https://github.com/element-hq/element-x-android/pull/7433
|
||||
* Keep transparency when an image is shared into the app with an imprecise mime type by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7452
|
||||
* Don't restart the sync service if the client is shutting down by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7472
|
||||
* Normalize the applicationName used in user agent by @bmarty in https://github.com/element-hq/element-x-android/pull/7477
|
||||
* Keep playing a voice message when another app plays a short sound by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7454
|
||||
* Keep the send button when the composer is emptied while editing a message by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7456
|
||||
* Do not open the room again when the app is resumed from the recents list by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7436
|
||||
* Stop waiting for a room alias that the remote server never resolves by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7446
|
||||
### 🧱 Build
|
||||
* build-rust-sdk: print the command we run by @richvdh in https://github.com/element-hq/element-x-android/pull/7407
|
||||
* Remove license check on Pro modules. by @bmarty in https://github.com/element-hq/element-x-android/pull/7418
|
||||
* Improve build time config and update the Firebase configuration by @bmarty in https://github.com/element-hq/element-x-android/pull/7473
|
||||
* Make the PUSHER_APP_ID configurable at build time. by @bmarty in https://github.com/element-hq/element-x-android/pull/7478
|
||||
### 📄 Documentation
|
||||
* Add KDoc to all the interfaces of the api modules by @bmarty in https://github.com/element-hq/element-x-android/pull/7426
|
||||
### 🚧 In development 🚧
|
||||
* [LinkNewDevice] Ensure the notice screen is also protected by the timeout. by @bmarty in https://github.com/element-hq/element-x-android/pull/7428
|
||||
### Dependency upgrades
|
||||
* Update kotlin to v2.3.11 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7396
|
||||
* Update plugin sonarqube to v7.4.0.8496 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7415
|
||||
* Update metro to v1.4.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7421
|
||||
* Update roborazzi to v1.71.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7423
|
||||
* Update dependency io.sentry:sentry-android to v8.52.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7414
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.08.13 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7420
|
||||
* Update dependency io.github.sergio-sastre.ComposablePreviewScanner:android to v0.9.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7431
|
||||
* Update dependency org.maplibre.compose:maplibre-compose to v0.14.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7432
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.08.17 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7460
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.08.19 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7479
|
||||
* Update media3 to v1.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7427
|
||||
* Update dependency androidx.compose:compose-bom to v2026.08.00 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7483
|
||||
### 💼 Element Pro
|
||||
* Add hooks to allow providing additional feature flags in EX pro by @richvdh in https://github.com/element-hq/element-x-android/pull/7419
|
||||
* Use the right base URL for the ESS config/well known endpoints by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7469
|
||||
### Others
|
||||
* Do not display pinned events that the client cannot render by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7379
|
||||
* Hide the forward action on live location shares by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7343
|
||||
* Do not fail the screenshot recording job when there is nothing to commit by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7355
|
||||
* Show the account a UnifiedPush registration belongs to in the distributor by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7345
|
||||
* Stop repeating messages that are already shown in a room notification by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7370
|
||||
* Turn a matrix: URI in a message into a pill by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7354
|
||||
* Send a message with Enter on a physical keyboard, Shift+Enter for a new line by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7353
|
||||
* Do not group timeline messages sent more than five minutes apart by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7349
|
||||
* Linkify URLs whose host is a bracketed IPv6 address by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7347
|
||||
* Add missing tests for `RustTemporaryMatrixClient` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7417
|
||||
* Revert "Keep manually revealed media visible when re-entering a room" by @bmarty in https://github.com/element-hq/element-x-android/pull/7424
|
||||
* Fix outdated documentation. by @bmarty in https://github.com/element-hq/element-x-android/pull/7430
|
||||
* Sync strings - New language supported: Arabic by @bmarty in https://github.com/element-hq/element-x-android/pull/7461
|
||||
* Keep the focused login field visible above the keyboard by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7455
|
||||
* Relax image compression so photos and screenshots keep more detail by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7451
|
||||
* Open the user profile of a user id that contains a slash by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7445
|
||||
* Remove the F-Droid dex patching step from the release script by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7442
|
||||
* Convert markdown to HTML when a caption is edited by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7441
|
||||
* Exclude carriage returns from the login and password fields by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7439
|
||||
* Show an error when the room to leave cannot be found by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7437
|
||||
* Give the room name in the timeline top bar a 48dp tap target by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7450
|
||||
* Model the fake voice recorder on a cancellable recording job by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7466
|
||||
|
||||
## New Contributors
|
||||
* @hardcore-sushi made their first contribution in https://github.com/element-hq/element-x-android/pull/7433
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.08.1...v26.08.2
|
||||
|
||||
Changes in Element X v26.08.1
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.08.1 -->
|
||||
|
||||
## What's Changed
|
||||
### 🙌 Improvements
|
||||
* Render push notifications for live location share starts (MSC4505) by @ara4n in https://github.com/element-hq/element-x-android/pull/7203
|
||||
* [Task] Show active call participants in the people list #7199 by @Eklavya47 in https://github.com/element-hq/element-x-android/pull/7300
|
||||
### 🐛 Bugfixes
|
||||
* Fix overlap issue. by @bmarty in https://github.com/element-hq/element-x-android/pull/7331
|
||||
* Announce checked/unchecked state for user rows in invite screen by @bmarty in https://github.com/element-hq/element-x-android/pull/7136
|
||||
* Display 'add reactions' menu in the threads screen by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7388
|
||||
* Expand the emoji picker to full height when the keyboard is shown by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7382
|
||||
* UI fixes by @bmarty in https://github.com/element-hq/element-x-android/pull/7409
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7389
|
||||
### 🧱 Build
|
||||
* Don't check enterprise previews in `KonsistPreviewTest` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7297
|
||||
* CI: split unit tests, screenshots and coverage in `tests.yml` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7296
|
||||
* Update Gradle Wrapper from 9.6.1 to 9.7.0 by @ElementBot in https://github.com/element-hq/element-x-android/pull/7335
|
||||
### 🚧 In development 🚧
|
||||
* QR code login iteration by @bmarty in https://github.com/element-hq/element-x-android/pull/6719
|
||||
* User status last pieces by @ganfra in https://github.com/element-hq/element-x-android/pull/7328
|
||||
* [Link new device] Add timeout by @bmarty in https://github.com/element-hq/element-x-android/pull/7338
|
||||
### Dependency upgrades
|
||||
* Update dependency org.maplibre.compose:maplibre-compose to v0.13.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7291
|
||||
* Update android.gradle.plugin to v9.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7248
|
||||
* Update dependency io.sentry:sentry-android to v8.50.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7298
|
||||
* Update zizmorcore/zizmor-action action to v0.6.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7306
|
||||
* Update android.gradle.plugin to v9.3.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7304
|
||||
* Update dependency com.posthog:posthog-android to v3.56.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7311
|
||||
* Update dependencyAnalysis to v3.18.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7318
|
||||
* Update actions/stale action to v11 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7317
|
||||
* Update dependency io.github.zxing-cpp:android to v3.1.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7322
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.08.05 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7324
|
||||
* Update dependency androidx.constraintlayout:constraintlayout to v2.2.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7329
|
||||
* Update reactivecircus/android-emulator-runner action to v2.38.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7189
|
||||
* Update dependency io.sentry:sentry-android to v8.51.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7301
|
||||
* Update dependency org.maplibre.gl:android-sdk to v13.4.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7305
|
||||
* Update dependency io.element.android:element-call-embedded to v0.23.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7323
|
||||
* Update metro to v1.4.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7334
|
||||
* Update dependency com.google.firebase:firebase-bom to v34.17.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7333
|
||||
* Update dependency androidx.compose.material3:material3 to v1.5.0-alpha23 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7175
|
||||
* Update dependency org.jsoup:jsoup to v1.23.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7332
|
||||
* Update dependency androidx.constraintlayout:constraintlayout-compose to v1.1.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7330
|
||||
* Update zizmorcore/zizmor-action action to v0.6.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7383
|
||||
* Update actions/setup-java action to v5.7.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7339
|
||||
* Update gradle/actions action to v6.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7384
|
||||
* Update plugin dependencycheck to v13 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7386
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.08.11 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7402
|
||||
### 💼 Element Pro
|
||||
* Add support for checking the ESS config in Element Pro by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7302
|
||||
### Others
|
||||
* Add extra vertical padding to `ReplyToInvalidContent` and `ReplyToNotFoundContent` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7288
|
||||
* Index messages locally in an encrypted on-device store so encrypted rooms can be searched at all by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7249
|
||||
* Bump enterprise module to the latest commit by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7307
|
||||
* Bump enterprise module to the latest commit by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7309
|
||||
* Move deviceId and git details to developer settings by @bmarty in https://github.com/element-hq/element-x-android/pull/7241
|
||||
* Show a Message not found placeholder when a replied to message cannot be loaded by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7376
|
||||
* Update in reply to message when message is not found. by @bmarty in https://github.com/element-hq/element-x-android/pull/7387
|
||||
* Add default `OAUTH_CLIENT_URL_PATH` alternative by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7385
|
||||
* Fix the feature module template writing the impl build script into the api module by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7374
|
||||
* Let users enter their full Matrix user ID when searching for a homeserver by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7375
|
||||
* Keep manually revealed media visible when re-entering a room by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7378
|
||||
* Copy a blocked user's Matrix ID by long pressing them by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7372
|
||||
* Do not draw the media loading progress bar under the status bar by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7366
|
||||
* Do not notify about an event sent by another account on the same device by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7360
|
||||
* Keep the opened image on screen when the media gallery fails to load by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7362
|
||||
* Do not linkify the server part of a fediverse handle as an email address by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7356
|
||||
* Expose global account data get and set on MatrixClient by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7373
|
||||
* Show the alias or the direct message user's Matrix ID in the room picker by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7371
|
||||
* Use a dedicated string for the Mute room notification mode by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7367
|
||||
* Keep playing a voice message when the screen turns off by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7365
|
||||
* Let a voice message without a waveform be seeked by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7363
|
||||
* Ignore a second voice recording start while one is already running by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7346
|
||||
* Do not offer to start a call in a DM with no other member by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7352
|
||||
* Do not notify about a redaction whose target event is unknown by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7350
|
||||
* Ignore a room list filter that is incompatible with an already selected one by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7342
|
||||
* Stop a right to left sender name from reordering a room list preview by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7348
|
||||
* Do not append the application MapTiler key to a custom map style url by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7340
|
||||
* Hide space children you have left that can only be joined by invitation by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7369
|
||||
* Never re-encode a video at a higher bitrate than the source by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7351
|
||||
* Display push notifications for knocks by @ara4n in https://github.com/element-hq/element-x-android/pull/7207
|
||||
|
||||
## New Contributors
|
||||
* @hayaksi1 made their first contribution in https://github.com/element-hq/element-x-android/pull/7249
|
||||
* @ara4n made their first contribution in https://github.com/element-hq/element-x-android/pull/7203
|
||||
* @Eklavya47 made their first contribution in https://github.com/element-hq/element-x-android/pull/7300
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.08.0...v26.08.1
|
||||
|
||||
Changes in Element X v26.08.0
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.08.0 -->
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* feat(call): Active call timeline rendering by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/7051
|
||||
### 🙌 Improvements
|
||||
* Announce expanded/collapsed state on the timeline group header by @unclejay80 in https://github.com/element-hq/element-x-android/pull/7226
|
||||
* Allow adding extra elements to the developer settings from external modules by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7230
|
||||
* Update keyboard visibility to track hasFocus by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7284
|
||||
### 🐛 Bugfixes
|
||||
* Live location sharing timeout by @ganfra in https://github.com/element-hq/element-x-android/pull/7184
|
||||
* Fix logging via QR code when using MDM-configured homeserver by @mxandreas in https://github.com/element-hq/element-x-android/pull/7228
|
||||
* Fix #7181: Remove back arrow from device verified screen and use 'Done' text by @thirumani-vihaan in https://github.com/element-hq/element-x-android/pull/7222
|
||||
* Fix padding for captions of media in 'not found' state by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7247
|
||||
* Fix banned mime type errors in `DefaultContentScannerService` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7255
|
||||
* Fix `ContentErrorView` not calculating its text size synchronously by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7281
|
||||
* Fix media viewer for medias not related to events displaying invalid state by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7280
|
||||
* Try making `HeaderFooterView` more robust by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7267
|
||||
* Fix insets in several screens by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7285
|
||||
### ⚠️ API Changes
|
||||
* Add a password strength estimator wrapping the SDK zxcvbn API by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7186
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7277
|
||||
### 🧱 Build
|
||||
* CI: fix 'Update Gradle Wrapper' by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7238
|
||||
* Update Gradle Wrapper from 9.5.1 to 9.6.1 by @ElementBot in https://github.com/element-hq/element-x-android/pull/7240
|
||||
* Localazy: improve search, parallelize checks by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7261
|
||||
* Run `Konsist` tests sequentially by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7293
|
||||
### 📄 Documentation
|
||||
* Group Element Pro changes into a dedicated release notes section by @manuroe in https://github.com/element-hq/element-x-android/pull/7236
|
||||
### 🚧 In development 🚧
|
||||
* Plug emoji picker for custom user status by @ganfra in https://github.com/element-hq/element-x-android/pull/7257
|
||||
* WIP : display user status by @ganfra in https://github.com/element-hq/element-x-android/pull/7232
|
||||
### Dependency upgrades
|
||||
* Update roborazzi to v1.67.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7188
|
||||
* Update dependency io.sentry:sentry-android to v8.48.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7217
|
||||
* Update dependency com.google.crypto.tink:tink-android to v1.23.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7229
|
||||
* Update kotlin to v2.3.10 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7234
|
||||
* Update dependency net.zetetic:sqlcipher-android to v4.17.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7218
|
||||
* Update dependency com.google.firebase:firebase-bom to v34.16.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7237
|
||||
* Update dependencyAnalysis to v3.16.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7212
|
||||
* Update actions/setup-java action to v5.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7214
|
||||
* Update dependency io.element.android:element-call-embedded to v0.22.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7161
|
||||
* Update kotlin to v2.4.10 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7244
|
||||
* Update roborazzi to v1.68.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7245
|
||||
* Update metro to v1.3.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7243
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.07.22 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7258
|
||||
* Update zizmorcore/zizmor-action action to v0.6.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7256
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.07.23 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7262
|
||||
* Update dependency io.sentry:sentry-android to v8.49.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7263
|
||||
* Update dependencyAnalysis to v3.17.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7265
|
||||
* Update actions/setup-java action to v5.6.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7268
|
||||
* Update dependency org.jetbrains.kotlinx:kover-gradle-plugin to v0.9.9 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7273
|
||||
* Update actions/setup-python action to v7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7278
|
||||
* Update roborazzi to v1.70.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7274
|
||||
* Update actions/checkout action to v7.0.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7282
|
||||
* Import Compound tokens from `v10.2.3` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7272
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.07.28 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7287
|
||||
* Update dependency org.maplibre.gl:android-sdk to v13.4.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7290
|
||||
### 💼 Element Pro
|
||||
* Content scanner (Element Pro): scan timeline events for unsafe content by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7159
|
||||
* Add content scanner support to media gallery and viewer (Element Pro) by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7233
|
||||
* Enforce HS disabling E2EE in DM creation too (Element Pro) by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7292
|
||||
* Allow adding a path suffix to the OAuth client URI by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7286
|
||||
### Others
|
||||
* [A11y] Improve rendering on focused UI items by @bmarty in https://github.com/element-hq/element-x-android/pull/7182
|
||||
* Add a button role to clickable icons and text on the login/onboarding screens by @unclejay80 in https://github.com/element-hq/element-x-android/pull/7225
|
||||
* Expose formatting toggle state to TalkBack by @unclejay80 in https://github.com/element-hq/element-x-android/pull/7227
|
||||
* Add warning to the 'Scan QR code' screen by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7264
|
||||
* Refactor SearchBarResultState.Initial/NoResultsFound to data objects by @ganfra in https://github.com/element-hq/element-x-android/pull/7266
|
||||
* Remove no longer temporary strings by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7260
|
||||
* Use the right insets in the home screen UI by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7271
|
||||
* Make settings section separators visible in black theme by @mvanhorn in https://github.com/element-hq/element-x-android/pull/7039
|
||||
|
||||
## New Contributors
|
||||
* @thirumani-vihaan made their first contribution in https://github.com/element-hq/element-x-android/pull/7222
|
||||
* @mvanhorn made their first contribution in https://github.com/element-hq/element-x-android/pull/7039
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.07.1...v26.08.0
|
||||
|
||||
Changes in Element X v26.07.1
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.07.1 -->
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Scroll to unread messages by @bmarty in https://github.com/element-hq/element-x-android/pull/7024
|
||||
### 🙌 Improvements
|
||||
* Collapse runs of deleted messages in the timeline by @manfrommedan in https://github.com/element-hq/element-x-android/pull/7195
|
||||
* Allow homeserver to disable encryption in Element Pro by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7198
|
||||
### 🐛 Bugfixes
|
||||
* Fix jump-to-unread requiring two taps for an out-of-window read marker by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/6943
|
||||
* Add content description to video preview for screen readers by @bmarty in https://github.com/element-hq/element-x-android/pull/7137
|
||||
* Fix accessibility for message composer input and read receipts button by @bmarty in https://github.com/element-hq/element-x-android/pull/7135
|
||||
* Add accessibility label to search input by @bmarty in https://github.com/element-hq/element-x-android/pull/7133
|
||||
* Fix timeline reading order distorted when TalkBack is active by @bmarty in https://github.com/element-hq/element-x-android/pull/7138
|
||||
* Add programmatic label and focus indicator to PIN entry field by @bmarty in https://github.com/element-hq/element-x-android/pull/7132
|
||||
* Fix timelines not loading after marking a room as read from notification when app was killed by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7202
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7192
|
||||
### 🧱 Build
|
||||
* Bump the default Gradle daemon heap to match what CI already uses by @PolynomialDivision in https://github.com/element-hq/element-x-android/pull/7152
|
||||
* Update screenshots by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7155
|
||||
* Update GitHub templates by @bmarty in https://github.com/element-hq/element-x-android/pull/7170
|
||||
* Add check on merged manifest by @bmarty in https://github.com/element-hq/element-x-android/pull/7172
|
||||
* Fix AAPT dump missing and reordering some permissions by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7193
|
||||
* Try fixing DM creation in Maestro by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7204
|
||||
### 🚧 In development 🚧
|
||||
* Support gallery messages by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6519
|
||||
* Support gallery messages by @bmarty in https://github.com/element-hq/element-x-android/pull/7112
|
||||
* Initial content scanner integration by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7139
|
||||
* WIP : Set user status by @ganfra in https://github.com/element-hq/element-x-android/pull/7094
|
||||
### Dependency upgrades
|
||||
* Update dependency io.sentry:sentry-android to v8.44.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7093
|
||||
* Update dependency org.maplibre.gl:android-sdk to v13.3.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7129
|
||||
* Update actions/setup-java action to v5.4.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7143
|
||||
* Update danger/danger-js action to v13.0.10 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7142
|
||||
* Update dependency io.sentry:sentry-android to v8.46.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7140
|
||||
* Update dependencyAnalysis to v3.16.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7144
|
||||
* Update plugin gms_google_services to v4.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7086
|
||||
* Update dependency androidx.compose.material3:material3 to v1.5.0-alpha21 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7007
|
||||
* Update dependency androidx.compose.material3:material3 to v1.5.0-alpha22 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7158
|
||||
* Update metro to v1.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7168
|
||||
* Update dependency io.sentry:sentry-android to v8.47.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7178
|
||||
* Update dependency androidx.compose:compose-bom to v2026.06.01 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7174
|
||||
* Update dependency org.jetbrains.kotlinx:kotlinx-collections-immutable to v0.5.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7180
|
||||
* Update dependency io.github.sergio-sastre.ComposablePreviewScanner:android to v0.9.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7201
|
||||
* Update dependency io.github.zxing-cpp:android to v3.1.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7206
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.07.13 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7194
|
||||
* Update dependency androidx.sqlite:sqlite-ktx to v2.7.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7177
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.07.15 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7216
|
||||
### Others
|
||||
* Timeline: jump-to-unread FAB and mark-as-read shortcut by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/6694
|
||||
* Iterate on visual list by @bmarty in https://github.com/element-hq/element-x-android/pull/7141
|
||||
* Fix wrong avatar shown in the thread list's top bar by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7044
|
||||
* Update onboarding screen text style by @bmarty in https://github.com/element-hq/element-x-android/pull/7157
|
||||
* Add full SVG support by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7020
|
||||
* Add skin tone picker to the emoji picker by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6917
|
||||
* Fix the message edit being lost when attaching media by @manfrommedan in https://github.com/element-hq/element-x-android/pull/7090
|
||||
* Cleanup AndroidManifest.xml files. by @bmarty in https://github.com/element-hq/element-x-android/pull/7171
|
||||
* Handle local network permission - Android 17 by @ganfra in https://github.com/element-hq/element-x-android/pull/7165
|
||||
* Add item count text as the placeholder for in reply to box for galleries by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7176
|
||||
* Default to dark theme for EC by @toger5 in https://github.com/element-hq/element-x-android/pull/7162
|
||||
|
||||
## New Contributors
|
||||
* @PolynomialDivision made their first contribution in https://github.com/element-hq/element-x-android/pull/7152
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.07.0...v26.07.1
|
||||
|
||||
Changes in Element X v26.07.0
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v26.07.0 -->
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Add read all messages feature by @hghgrtut in https://github.com/element-hq/element-x-android/pull/7049
|
||||
### 🐛 Bugfixes
|
||||
* Detect RTL text in events and adjust the text rendering by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6994
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7071
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7115
|
||||
### 🧱 Build
|
||||
* Fix Sonar with AGP 9 (some workarounds) by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7055
|
||||
* Disable periodically generating the screenshot gallery by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7110
|
||||
* Try a different workaround for flaky `ScanQrCodeViewTest` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7109
|
||||
### 🚧 In development 🚧
|
||||
* Protect link new device by @bmarty in https://github.com/element-hq/element-x-android/pull/6992
|
||||
### Dependency upgrades
|
||||
* Update metro to v1.2.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7057
|
||||
* Update gradle/actions action to v6.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7063
|
||||
* Update roborazzi to v1.64.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7068
|
||||
* Update rnkdsh/action-upload-diawi action to v1.5.13 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7067
|
||||
* Update dependency io.element.android:element-call-embedded to v0.20.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7075
|
||||
* Update dependency com.github.matrix-org:matrix-analytics-events to v0.37.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7079
|
||||
* Update actions/setup-java action to v5.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7088
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.06.18 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7052
|
||||
* Update firebaseAppDistribution to v5.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7096
|
||||
* Update dependency com.google.firebase:firebase-bom to v34.15.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7089
|
||||
* Update dependency com.google.crypto.tink:tink-android to v1.22.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7105
|
||||
* Update actions/checkout action to v7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7098
|
||||
* Update lifecycle to v2.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7097
|
||||
* Update danger/danger-js action to v13.0.8 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7104
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.06.25 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7107
|
||||
* Update dependency androidx.compose:compose-bom to v2026.06.00 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7095
|
||||
* Update dependency io.element.android:element-call-embedded to v0.20.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7117
|
||||
* Update zizmorcore/zizmor-action action to v0.5.7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7116
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v26.06.30 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7127
|
||||
* Update dependency com.posthog:posthog-android to v3.51.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7128
|
||||
### Others
|
||||
* Fix small code smell by @bmarty in https://github.com/element-hq/element-x-android/pull/7058
|
||||
* Hide message notification content when the app is locked by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6902
|
||||
|
||||
## New Contributors
|
||||
* @hghgrtut made their first contribution in https://github.com/element-hq/element-x-android/pull/7049
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.06.4...v26.07.0
|
||||
|
||||
Changes in Element X v26.06.4
|
||||
=============================
|
||||
|
||||
|
||||
@@ -6,18 +6,6 @@ top section matching the released version — the heading must have the exact
|
||||
form `## Changes in <version> (<date>)` and the whole section must stay under
|
||||
500 characters. See `.claude/skills/releasing-healthchat/SKILL.md`.
|
||||
|
||||
## Changes in 26.10.0 (2026-10-20)
|
||||
|
||||
🎉 Neue Funktionen
|
||||
|
||||
- Nachrichtensuche: Chats durchsuchen, auch in verschlüsselten Räumen.
|
||||
- Umfragen mit Mehrfachauswahl.
|
||||
|
||||
🙌 Verbesserungen
|
||||
|
||||
- Basis aktualisiert auf Element X 26.10.0 mit zahlreichen Fehlerbehebungen.
|
||||
- Einstellungen neu gegliedert in Konto- und App-Einstellungen.
|
||||
|
||||
## Changes in 26.08.0 (2026-08-06)
|
||||
|
||||
🎉 Neue Funktionen
|
||||
|
||||
@@ -14,16 +14,16 @@ import kotlin.reflect.KClass
|
||||
* Adds Node to the specified component graph.
|
||||
* Equivalent to the following declaration:
|
||||
*
|
||||
* ```
|
||||
* @BindingContainer
|
||||
* @ContributesTo(Scope::class)
|
||||
* interface YourNodeBindingContainer {
|
||||
* @Binds
|
||||
* @IntoMap
|
||||
* @NodeKey(YourNode::class)
|
||||
* fun bindYourNodeAssistedFactory(factory: YourNodeAssistedFactory): AssistedNodeFactory<*>
|
||||
* }
|
||||
* ```
|
||||
* abstract class YourNodeModule {
|
||||
|
||||
* @Binds
|
||||
* @IntoMap
|
||||
* @NodeKey(YourNode::class)
|
||||
* abstract fun bindYourNodeFactory(factory: YourNode.Factory): AssistedNodeFactory<*>
|
||||
*}
|
||||
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class ContributesNode(
|
||||
|
||||
+30
-45
@@ -21,7 +21,6 @@ import extension.allFeaturesImpl
|
||||
import extension.allLibrariesImpl
|
||||
import extension.allServicesImpl
|
||||
import extension.buildConfigFieldStr
|
||||
import extension.environmentOrGradleValue
|
||||
import extension.locales
|
||||
import extension.setupDependencyInjection
|
||||
import extension.testCommonDependencies
|
||||
@@ -89,14 +88,13 @@ android {
|
||||
storePassword = "android"
|
||||
}
|
||||
register("nightly") {
|
||||
keyAlias = environmentOrGradleValue("ELEMENT_ANDROID_NIGHTLY_KEYID", "signing.element.nightly.keyId")
|
||||
keyPassword = environmentOrGradleValue("ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD", "signing.element.nightly.keyPassword")
|
||||
storeFile = file(
|
||||
environmentOrGradleValue("ELEMENT_ANDROID_NIGHTLY_STOREFILE", "signing.element.nightly.storeFile")
|
||||
?.ifBlank { null }
|
||||
?: "./signature/nightly.keystore"
|
||||
)
|
||||
storePassword = environmentOrGradleValue("ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD", "signing.element.nightly.storePassword")
|
||||
keyAlias = System.getenv("ELEMENT_ANDROID_NIGHTLY_KEYID")
|
||||
?: project.property("signing.element.nightly.keyId") as? String?
|
||||
keyPassword = System.getenv("ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD")
|
||||
?: project.property("signing.element.nightly.keyPassword") as? String?
|
||||
storeFile = file("./signature/nightly.keystore")
|
||||
storePassword = System.getenv("ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD")
|
||||
?: project.property("signing.element.nightly.storePassword") as? String?
|
||||
}
|
||||
// HealthChat Play Store upload key. The keystore is not in the repo:
|
||||
// CI decodes it from the RELEASE_KEYSTORE_BASE64 secret (see
|
||||
@@ -147,11 +145,24 @@ android {
|
||||
optimization {
|
||||
enable = true
|
||||
keepRules {
|
||||
// Equivalent of adding `getDefaultProguardFile("proguard-android-optimize.txt")` (this is the default value).
|
||||
includeDefault = true
|
||||
files.add(File(projectDir, "common-proguard-rules.pro"))
|
||||
files.add(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
|
||||
// Depending on whether the app flavor is enterprise or not we want to use different proguard rules.
|
||||
val flavorProguardFile = if (isEnterpriseBuild) {
|
||||
// Custom rules for enterprise builds
|
||||
File(projectDir, "enterprise-proguard-rules.pro")
|
||||
} else {
|
||||
// These default rules prevent the OSS app from being obfuscated
|
||||
File(projectDir, "default-proguard-rules.pro")
|
||||
}
|
||||
|
||||
if (flavorProguardFile.exists()) {
|
||||
files.add(flavorProguardFile)
|
||||
} else {
|
||||
logger.warn("Proguard file ${flavorProguardFile.absolutePath} does not exist")
|
||||
}
|
||||
}
|
||||
// Our custom keep rules are registered as `keepRules` source folders in the `androidComponents` block below,
|
||||
// as the former `keepRules.files` DSL is deprecated since AGP 9.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,30 +247,6 @@ androidComponents {
|
||||
)
|
||||
|
||||
onVariants { variant ->
|
||||
// Register the R8 keep rules source folders for optimized build types (release, nightly).
|
||||
// Replaces the deprecated `optimization.keepRules.files` DSL (AGP 9+).
|
||||
if (variant.buildType != "debug") {
|
||||
variant.sources.keepRules?.let { keepRules ->
|
||||
// Common rules, always applied.
|
||||
keepRules.addStaticSourceDirectory("proguard/common")
|
||||
|
||||
// Depending on whether the app flavor is enterprise or not we want to use different proguard rules.
|
||||
val flavorProguardDir = if (isEnterpriseBuild) {
|
||||
// Custom rules for enterprise builds
|
||||
"../enterprise/proguard"
|
||||
} else {
|
||||
// Custom fules for FOSS builds
|
||||
"proguard/foss"
|
||||
}
|
||||
|
||||
if (File(projectDir, flavorProguardDir).exists()) {
|
||||
keepRules.addStaticSourceDirectory(flavorProguardDir)
|
||||
} else {
|
||||
logger.warn("Proguard folder ${File(projectDir, flavorProguardDir).absolutePath} does not exist")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assigns a different version code for each output APK
|
||||
// other than the universal APK.
|
||||
variant.outputs.forEach { output ->
|
||||
@@ -298,6 +285,7 @@ dependencies {
|
||||
implementation(projects.features.migration.api)
|
||||
implementation(projects.appnav)
|
||||
implementation(projects.appconfig)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.services.analytics.compose)
|
||||
|
||||
if (ModulesConfig.pushProvidersConfig.includeFirebase) {
|
||||
@@ -307,21 +295,22 @@ dependencies {
|
||||
implementation(projects.libraries.pushproviders.unifiedpush)
|
||||
}
|
||||
|
||||
// Google Play Services fused location backend. Discovered at runtime via ServiceLoader by the
|
||||
// location feature.
|
||||
"gplayImplementation"(libs.maplibre.compose.location.runtime.gms)
|
||||
|
||||
implementation(libs.appyx.core)
|
||||
implementation(libs.androidx.splash)
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.corektx)
|
||||
implementation(libs.androidx.lifecycle.runtime)
|
||||
implementation(libs.androidx.lifecycle.process)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.startup)
|
||||
implementation(libs.androidx.preference)
|
||||
implementation(libs.coil)
|
||||
|
||||
implementation(platform(libs.network.okhttp.bom))
|
||||
implementation(libs.network.okhttp.logging)
|
||||
implementation(libs.serialization.json)
|
||||
|
||||
implementation(libs.matrix.emojibase.bindings)
|
||||
|
||||
testCommonDependencies(libs)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
@@ -343,7 +332,6 @@ licensee {
|
||||
allow("BSD-3-Clause")
|
||||
allow("EPL-1.0")
|
||||
allowUrl("https://opensource.org/license/bsd-3-clause")
|
||||
allowUrl("https://opensource.org/license/bsd-2-clause")
|
||||
allowUrl("https://opensource.org/licenses/MIT")
|
||||
allowUrl("https://developer.android.com/studio/terms.html")
|
||||
allowUrl("https://www.zetetic.net/sqlcipher/license/")
|
||||
@@ -354,9 +342,6 @@ licensee {
|
||||
ignoreDependencies("com.github.matrix-org", "matrix-analytics-events")
|
||||
// Ignore dependency that are not third-party licenses to us.
|
||||
ignoreDependencies(groupId = "io.element.android")
|
||||
// The rustls-platform-verifier dependency does not provide a license file in their maven repo, but there are MIT and Apache licenses in
|
||||
// the source code repo: see https://github.com/rustls/rustls-platform-verifier
|
||||
ignoreDependencies(groupId = "org.rustls", artifactId = "rustls-platform-verifier")
|
||||
}
|
||||
|
||||
fun Project.configureLicensesTasks(reportingExtension: ReportingExtension) {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Don't obfuscate anything for non-enterprise builds
|
||||
-dontobfuscate
|
||||
@@ -1,9 +0,0 @@
|
||||
# Don't obfuscate anything for non-enterprise builds
|
||||
-dontobfuscate
|
||||
|
||||
# Strip PostHog's Firebase push token integration. We don't use it and it breaks F-Droid.
|
||||
# Removing the contents of this method will mark the Firebase-related classes as unused,
|
||||
# which will allow R8 to remove them from the final build.
|
||||
-assumevalues class com.posthog.android.PostHogAndroidConfig {
|
||||
boolean getCapturePushNotificationSubscriptions() return false;
|
||||
}
|
||||
@@ -8,6 +8,9 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- To be able to install APK from the application -->
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
|
||||
<application
|
||||
android:name=".ElementXApplication"
|
||||
android:allowBackup="false"
|
||||
@@ -53,10 +56,9 @@
|
||||
-->
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboardHidden|uiMode"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:theme="@style/Theme.ElementX.Splash"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
@@ -126,12 +128,6 @@
|
||||
<data android:host="user" />
|
||||
<data android:host="room" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
Conversation shortcuts offered in the Direct Share row of the system share sheet.
|
||||
-->
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
<!-- Incoming share simple -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
@@ -9,19 +9,14 @@
|
||||
package io.element.android.x
|
||||
|
||||
import android.app.Application
|
||||
import android.os.StrictMode
|
||||
import androidx.compose.material3.ComposeMaterial3Flags.isAnchoredDraggableComponentsStrictOffsetCheckEnabled
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.startup.AppInitializer
|
||||
import androidx.work.Configuration
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.createGraphFactory
|
||||
import io.element.android.libraries.architecture.bindings
|
||||
import io.element.android.libraries.di.DependencyInjectionGraphOwner
|
||||
import io.element.android.libraries.matrix.api.SdkMetadata
|
||||
import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory
|
||||
import io.element.android.x.di.AppGraph
|
||||
import io.element.android.x.di.ApplicationBindings
|
||||
import io.element.android.x.info.logApplicationInfo
|
||||
import io.element.android.x.initializer.CacheCleanerInitializer
|
||||
import io.element.android.x.initializer.CrashInitializer
|
||||
@@ -34,37 +29,19 @@ class ElementXApplication : Application(), DependencyInjectionGraphOwner, Config
|
||||
.setWorkerFactory(MetroWorkerFactory(graph.workerProviders))
|
||||
.build()
|
||||
|
||||
@Inject lateinit var sdkMetadata: SdkMetadata
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
enableStrictMode()
|
||||
|
||||
AppInitializer.getInstance(this).apply {
|
||||
initializeComponent(CrashInitializer::class.java)
|
||||
initializeComponent(PlatformInitializer::class.java)
|
||||
initializeComponent(CacheCleanerInitializer::class.java)
|
||||
}
|
||||
|
||||
bindings<ApplicationBindings>().inject(this)
|
||||
logApplicationInfo(this, sdkMetadata.sdkGitSha)
|
||||
logApplicationInfo(this)
|
||||
|
||||
// Disable the strict offset check for anchored draggable components, as it can cause issues with bottom sheets.
|
||||
// Remove once https://issuetracker.google.com/issues/477038695 is fixed.
|
||||
isAnchoredDraggableComponentsStrictOffsetCheckEnabled = false
|
||||
}
|
||||
|
||||
private fun enableStrictMode() {
|
||||
// Workaround to avoid NetworkOnMainThreadException when using the network on the main thread.
|
||||
// This is a temporary solution to rustls doing the certificate verification using blocking network requests.
|
||||
// It should be removed once the underlying issue is fixed.
|
||||
StrictMode.setThreadPolicy(
|
||||
StrictMode.ThreadPolicy.Builder()
|
||||
.detectNetwork()
|
||||
.penaltyLog()
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,26 +61,6 @@ class MainActivity : NodeActivity() {
|
||||
setContent {
|
||||
MainContent(appBindings)
|
||||
}
|
||||
|
||||
val activity = this
|
||||
appBindings.appStartupHooks().forEach {
|
||||
lifecycleScope.launch {
|
||||
it.onAppStartup(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The pre-Android 12 way into picture-in-picture for a native call.
|
||||
*
|
||||
* Everything else about it is installed where the call is drawn, which knows whose call it is.
|
||||
* This cannot be: an Activity override has no session in scope. From Android 12 the system is told
|
||||
* up front that this Activity would like to shrink, and the call handles it without ever coming
|
||||
* through here.
|
||||
*/
|
||||
override fun onUserLeaveHint() {
|
||||
super.onUserLeaveHint()
|
||||
appBindings.nativeCallPip().onUserLeaveHint(this)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -10,13 +10,10 @@ package io.element.android.x.di
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import dev.zacsweers.metro.Multibinds
|
||||
import io.element.android.features.callnative.api.NativeCallPip
|
||||
import io.element.android.features.enterprise.api.AppStartupHook
|
||||
import io.element.android.features.api.MigrationEntryPoint
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||
import io.element.android.features.lockscreen.api.LockScreenService
|
||||
import io.element.android.features.migration.api.MigrationEntryPoint
|
||||
import io.element.android.features.rageshake.api.reporter.BugReporter
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
@@ -53,15 +50,5 @@ interface AppBindings {
|
||||
|
||||
fun buildMeta(): BuildMeta
|
||||
|
||||
/**
|
||||
* For picture-in-picture. Only an Activity is told it is being left, and only the call knows
|
||||
* whether that is worth shrinking for, so `MainActivity` needs this much of the native call and
|
||||
* no more.
|
||||
*/
|
||||
fun nativeCallPip(): NativeCallPip
|
||||
|
||||
fun sentrySdkDsn(): SentrySdkDsn?
|
||||
|
||||
@Multibinds(allowEmpty = true)
|
||||
fun appStartupHooks(): Set<AppStartupHook>
|
||||
}
|
||||
|
||||
+15
-8
@@ -18,7 +18,7 @@ import dev.zacsweers.metro.ContributesTo
|
||||
import dev.zacsweers.metro.Provides
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.appconfig.ApplicationConfig
|
||||
import io.element.android.features.enterprise.api.IsEnterpriseBuild
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.libraries.androidutils.system.getVersionCodeFromManifest
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
@@ -28,6 +28,8 @@ import io.element.android.libraries.di.BaseDirectory
|
||||
import io.element.android.libraries.di.CacheDirectory
|
||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import io.element.android.libraries.recentemojis.api.EmojibaseProvider
|
||||
import io.element.android.libraries.recentemojis.impl.DefaultEmojibaseProvider
|
||||
import io.element.android.x.BuildConfig
|
||||
import io.element.android.x.R
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
@@ -38,7 +40,7 @@ import java.io.File
|
||||
|
||||
@BindingContainer
|
||||
@ContributesTo(AppScope::class)
|
||||
object AppBindingContainer {
|
||||
object AppModule {
|
||||
@Provides
|
||||
@BaseDirectory
|
||||
fun providesBaseDirectory(@ApplicationContext context: Context): File {
|
||||
@@ -74,18 +76,17 @@ object AppBindingContainer {
|
||||
fun providesBuildMeta(
|
||||
@ApplicationContext context: Context,
|
||||
buildType: BuildType,
|
||||
isEnterpriseBuild: IsEnterpriseBuild,
|
||||
enterpriseService: EnterpriseService,
|
||||
): BuildMeta {
|
||||
val applicationName = ApplicationConfig.APPLICATION_NAME.takeIf { it.isNotEmpty() } ?: context.getString(R.string.app_name)
|
||||
val isEnterprise = isEnterpriseBuild()
|
||||
return BuildMeta(
|
||||
isDebuggable = BuildConfig.DEBUG,
|
||||
buildType = buildType,
|
||||
applicationName = applicationName,
|
||||
productionApplicationName = if (isEnterprise) applicationName else ApplicationConfig.PRODUCTION_APPLICATION_NAME,
|
||||
desktopApplicationName = if (isEnterprise) applicationName else ApplicationConfig.DESKTOP_APPLICATION_NAME,
|
||||
productionApplicationName = if (enterpriseService.isEnterpriseBuild) applicationName else ApplicationConfig.PRODUCTION_APPLICATION_NAME,
|
||||
desktopApplicationName = if (enterpriseService.isEnterpriseBuild) applicationName else ApplicationConfig.DESKTOP_APPLICATION_NAME,
|
||||
applicationId = BuildConfig.APPLICATION_ID,
|
||||
isEnterpriseBuild = isEnterprise,
|
||||
isEnterpriseBuild = enterpriseService.isEnterpriseBuild,
|
||||
// TODO EAx Config.LOW_PRIVACY_LOG_ENABLE,
|
||||
lowPrivacyLoggingEnabled = false,
|
||||
versionName = BuildConfig.VERSION_NAME,
|
||||
@@ -111,7 +112,13 @@ object AppBindingContainer {
|
||||
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun providesSnackbarDispatcher(): SnackbarDispatcher {
|
||||
fun provideSnackbarDispatcher(): SnackbarDispatcher {
|
||||
return SnackbarDispatcher()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun providesEmojibaseProvider(@ApplicationContext context: Context): EmojibaseProvider {
|
||||
return DefaultEmojibaseProvider(context)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.di
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import io.element.android.x.ElementXApplication
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
interface ApplicationBindings {
|
||||
fun inject(application: ElementXApplication)
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultSessionGraphFactory(
|
||||
private val appGraph: AppGraph,
|
||||
private val appGraph: AppGraph
|
||||
) : SessionGraphFactory {
|
||||
override fun create(client: MatrixClient): Any {
|
||||
return appGraph.sessionGraphFactory.create(client)
|
||||
|
||||
@@ -10,22 +10,32 @@ package io.element.android.x.info
|
||||
|
||||
import android.content.Context
|
||||
import io.element.android.libraries.androidutils.system.getVersionCodeFromManifest
|
||||
import io.element.android.libraries.core.log.logger.wrapInBox
|
||||
import io.element.android.x.BuildConfig
|
||||
import timber.log.Timber
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
fun logApplicationInfo(context: Context, sdkGitSha: String) {
|
||||
val appVersion = "${BuildConfig.VERSION_NAME} (${context.getVersionCodeFromManifest()}) - ${BuildConfig.BUILD_TYPE} / ${BuildConfig.FLAVOR}"
|
||||
fun logApplicationInfo(context: Context) {
|
||||
val appVersion = buildString {
|
||||
append(BuildConfig.VERSION_NAME)
|
||||
append(" (")
|
||||
append(context.getVersionCodeFromManifest())
|
||||
append(") - ")
|
||||
append(BuildConfig.BUILD_TYPE)
|
||||
append(" / ")
|
||||
append(BuildConfig.FLAVOR)
|
||||
}
|
||||
// TODO Get SDK version somehow
|
||||
val sdkVersion = "SDK VERSION (TODO)"
|
||||
val date = SimpleDateFormat("MM-dd HH:mm:ss.SSSZ", Locale.US).format(Date())
|
||||
listOf(
|
||||
" Application version: $appVersion",
|
||||
" Element X: https://github.com/element-hq/element-x-android/commit/${BuildConfig.GIT_REVISION}",
|
||||
" SDK : https://github.com/matrix-org/matrix-rust-sdk/commit/$sdkGitSha",
|
||||
" Local time: $date",
|
||||
)
|
||||
.wrapInBox(minBoxInsideWidth = 80)
|
||||
.forEach { Timber.d(it) }
|
||||
|
||||
Timber.d("----------------------------------------------------------------")
|
||||
Timber.d("----------------------------------------------------------------")
|
||||
Timber.d(" Application version: $appVersion")
|
||||
Timber.d(" Git SHA: ${BuildConfig.GIT_REVISION}")
|
||||
Timber.d(" SDK version: $sdkVersion")
|
||||
Timber.d(" Local time: $date")
|
||||
Timber.d("----------------------------------------------------------------")
|
||||
Timber.d("----------------------------------------------------------------\n\n\n\n")
|
||||
}
|
||||
|
||||
@@ -8,14 +8,11 @@
|
||||
|
||||
package io.element.android.x.initializer
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.system.Os
|
||||
import android.util.Log
|
||||
import androidx.startup.Initializer
|
||||
import io.element.android.features.rageshake.api.logs.createWriteToFilesConfiguration
|
||||
import io.element.android.libraries.architecture.bindings
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.tracing.TracingConfiguration
|
||||
import io.element.android.x.di.AppBindings
|
||||
@@ -27,15 +24,6 @@ private const val ELEMENT_X_TARGET = "elementx"
|
||||
|
||||
class PlatformInitializer : Initializer<Unit> {
|
||||
override fun create(context: Context) {
|
||||
// Init the SDK with the Android Context, so it can be used with rustls and other Android specific features
|
||||
runCatchingExceptions {
|
||||
org.matrix.rustsdk.Android.init(context)
|
||||
}.onFailure {
|
||||
// We can't log this to Timber since the logs aren't initialized yet, so we log it to Logcat instead
|
||||
@SuppressLint("LogNotTimber")
|
||||
Log.e("PlatformInitializer", "Failed to init the SDK with an Android Context. The app probably won't work properly.", it)
|
||||
}
|
||||
|
||||
val appBindings = context.bindings<AppBindings>()
|
||||
val tracingService = appBindings.tracingService()
|
||||
val platformService = appBindings.platformService()
|
||||
@@ -53,8 +41,6 @@ class PlatformInitializer : Initializer<Unit> {
|
||||
sdkSentryDsn = appBindings.sentrySdkDsn()?.value?.takeIf { it.isNotBlank() },
|
||||
)
|
||||
bugReporter.setCurrentTracingLogLevel(logLevel.name)
|
||||
|
||||
// Init the tracing service with the configuration so we have logging enabled as soon as possible
|
||||
platformService.init(tracingConfiguration)
|
||||
// Also set env variable for rust back trace
|
||||
Os.setenv("RUST_BACKTRACE", "1", true)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<!-- File generated by importSupportedLocalesFromLocalazy.py, do not edit -->
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="ar"/>
|
||||
<locale android:name="be"/>
|
||||
<locale android:name="bg"/>
|
||||
<locale android:name="ca"/>
|
||||
|
||||
@@ -30,384 +30,5 @@
|
||||
<domain includeSubdomains="true">home</domain>
|
||||
<domain includeSubdomains="true">lan</domain>
|
||||
<domain includeSubdomains="true">localdomain</domain>
|
||||
|
||||
<!-- Domains below are imported from rustls-platform-verifier 0.2.0 by tools/dependencies/update_rustls_platform_verifier.py -->
|
||||
<!-- They are needed to fetch the certificate revocation lists over cleartext -->
|
||||
<domain includeSubdomains="true">abitabdv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">abitabev.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">abitabov.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">alpiro.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">anticdv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">antrustovsslg2r30ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">autontrustprodvtlsg2r31ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">bitcert.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">btdvtlsr35g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">c.cf-b.ssl.com</domain>
|
||||
<domain includeSubdomains="true">c.cf-i.ssl.com</domain>
|
||||
<domain includeSubdomains="true">c.pki.goog</domain>
|
||||
<domain includeSubdomains="true">ca1.agid.gov.it</domain>
|
||||
<domain includeSubdomains="true">cdn.d-trust-cloudcrl.net</domain>
|
||||
<domain includeSubdomains="true">cdp.disig.sk</domain>
|
||||
<domain includeSubdomains="true">cdp.elektronicznypodpis.pl</domain>
|
||||
<domain includeSubdomains="true">cdp.geotrust.com</domain>
|
||||
<domain includeSubdomains="true">cdp.rapidssl.com</domain>
|
||||
<domain includeSubdomains="true">cdp.thawte.com</domain>
|
||||
<domain includeSubdomains="true">cdp1.disig.sk</domain>
|
||||
<domain includeSubdomains="true">cdp1.pca.dfn.de</domain>
|
||||
<domain includeSubdomains="true">cdpa.digitalcertvalidation.com</domain>
|
||||
<domain includeSubdomains="true">cdpb.digitalcertvalidation.com</domain>
|
||||
<domain includeSubdomains="true">cdpc.digitalcertvalidation.com</domain>
|
||||
<domain includeSubdomains="true">cdpd.digitalcertvalidation.com</domain>
|
||||
<domain includeSubdomains="true">cdpe.digitalcertvalidation.com</domain>
|
||||
<domain includeSubdomains="true">cdpf.digitalcertvalidation.com</domain>
|
||||
<domain includeSubdomains="true">cdpg.digitalcertvalidation.com</domain>
|
||||
<domain includeSubdomains="true">cdph.digitalcertvalidation.com</domain>
|
||||
<domain includeSubdomains="true">cdpi.digitalcertvalidation.com</domain>
|
||||
<domain includeSubdomains="true">certcloud.crl.trust-provider.com</domain>
|
||||
<domain includeSubdomains="true">certdata.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">certera.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">certificates.godaddy.com</domain>
|
||||
<domain includeSubdomains="true">certum-crl.wt.trustasia.com</domain>
|
||||
<domain includeSubdomains="true">certum.crl.sheca.com</domain>
|
||||
<domain includeSubdomains="true">certumdvtlsg2e39ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">certumdvtlsg2r39ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">certumevtlsg2e39ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">certumevtlsg2r39ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">certumovtlsg2e39ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">certumovtlsg2r39ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">cfcadveccca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">cfcadvrsaca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">cfcaeveccca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">cfcaevrsaca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">cfcaoveccca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">cfcaovrsaca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">cnssldvtlsg2r36ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">cnsslovtlsg2r36ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">crl-01.r1.ica.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl-01.r1.ica.amznts.eu</domain>
|
||||
<domain includeSubdomains="true">crl-c.emsign.com</domain>
|
||||
<domain includeSubdomains="true">crl-pro.litessl.com</domain>
|
||||
<domain includeSubdomains="true">crl.anf.es</domain>
|
||||
<domain includeSubdomains="true">crl.apple.com</domain>
|
||||
<domain includeSubdomains="true">crl.businessid.telesec.de</domain>
|
||||
<domain includeSubdomains="true">crl.buypass.no</domain>
|
||||
<domain includeSubdomains="true">crl.catrust.oemssl.cn</domain>
|
||||
<domain includeSubdomains="true">crl.certigna.fr</domain>
|
||||
<domain includeSubdomains="true">crl.certsign.ro</domain>
|
||||
<domain includeSubdomains="true">crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">crl.cfca.com.cn</domain>
|
||||
<domain includeSubdomains="true">crl.cntrus.oemssl.cn</domain>
|
||||
<domain includeSubdomains="true">crl.cnwebtrust.oemssl.cn</domain>
|
||||
<domain includeSubdomains="true">crl.comodoca.com</domain>
|
||||
<domain includeSubdomains="true">crl.crlocsp.cn</domain>
|
||||
<domain includeSubdomains="true">crl.cybertrust.ne.jp</domain>
|
||||
<domain includeSubdomains="true">crl.d-trust.net</domain>
|
||||
<domain includeSubdomains="true">crl.dhimyotis.com</domain>
|
||||
<domain includeSubdomains="true">crl.digicert.cn</domain>
|
||||
<domain includeSubdomains="true">crl.digicert.eu</domain>
|
||||
<domain includeSubdomains="true">crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">crl.e2m01.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e2m01.eu.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e2m02.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e2m03.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e2m04.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e2s14.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e2s15.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e2s16.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e2s17.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e2s18.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e2s19.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e2s20.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e2s21.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e3m01.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e3m01.eu.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e3m02.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e3m03.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e3m04.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e3s22.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e3s23.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e3s24.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e3s25.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e3s26.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e3s27.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e3s28.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.e3s29.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.emsign.com</domain>
|
||||
<domain includeSubdomains="true">crl.ensuredca.com</domain>
|
||||
<domain includeSubdomains="true">crl.firmaprofesional.com</domain>
|
||||
<domain includeSubdomains="true">crl.gdca.com.cn</domain>
|
||||
<domain includeSubdomains="true">crl.global.sheca.com</domain>
|
||||
<domain includeSubdomains="true">crl.globalsign.com</domain>
|
||||
<domain includeSubdomains="true">crl.globalsign.net</domain>
|
||||
<domain includeSubdomains="true">crl.godaddy.com</domain>
|
||||
<domain includeSubdomains="true">crl.harica.gr</domain>
|
||||
<domain includeSubdomains="true">crl.izenpe.com</domain>
|
||||
<domain includeSubdomains="true">crl.litessl.com</domain>
|
||||
<domain includeSubdomains="true">crl.netsolssl.com</domain>
|
||||
<domain includeSubdomains="true">crl.oem.trustca.net</domain>
|
||||
<domain includeSubdomains="true">crl.pcsp.telesec.de</domain>
|
||||
<domain includeSubdomains="true">crl.pki.goog</domain>
|
||||
<domain includeSubdomains="true">crl.quovadisglobal.com</domain>
|
||||
<domain includeSubdomains="true">crl.r1001.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2001.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2m01.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2m01.eu.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2m02.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2m03.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2m04.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2s18.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2s19.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2s20.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2s21.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2s22.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2s23.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2s24.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r2s25.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r3001.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r4001.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r4m01.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r4m02.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r4m03.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.r4m04.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.root-e1.certainly.com</domain>
|
||||
<domain includeSubdomains="true">crl.root-r1.certainly.com</domain>
|
||||
<domain includeSubdomains="true">crl.rootca1.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.rootca2.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.rootca3.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.rootca4.amazontrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.rztrust.oemssl.cn</domain>
|
||||
<domain includeSubdomains="true">crl.sbca.telesec.de</domain>
|
||||
<domain includeSubdomains="true">crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">crl.sectigochina.com</domain>
|
||||
<domain includeSubdomains="true">crl.serverid.telesec.de</domain>
|
||||
<domain includeSubdomains="true">crl.starfieldtech.com</domain>
|
||||
<domain includeSubdomains="true">crl.swisssign.ch</domain>
|
||||
<domain includeSubdomains="true">crl.trust-provider.cn</domain>
|
||||
<domain includeSubdomains="true">crl.trust-provider.com</domain>
|
||||
<domain includeSubdomains="true">crl.tuntrust.tn</domain>
|
||||
<domain includeSubdomains="true">crl.usertrust.com</domain>
|
||||
<domain includeSubdomains="true">crl.wt.trustasia.com</domain>
|
||||
<domain includeSubdomains="true">crl.xinchacha.oemssl.cn</domain>
|
||||
<domain includeSubdomains="true">crl.zw.oemssl.cn</domain>
|
||||
<domain includeSubdomains="true">crl05.actalis.it</domain>
|
||||
<domain includeSubdomains="true">crl06.actalis.it</domain>
|
||||
<domain includeSubdomains="true">crl09.actalis.it</domain>
|
||||
<domain includeSubdomains="true">crl1.ecert.gov.hk</domain>
|
||||
<domain includeSubdomains="true">crl13.actalis.it</domain>
|
||||
<domain includeSubdomains="true">crl14.actalis.it</domain>
|
||||
<domain includeSubdomains="true">crl15.actalis.it</domain>
|
||||
<domain includeSubdomains="true">crl2.microsoft.com</domain>
|
||||
<domain includeSubdomains="true">crl3.digicert.com</domain>
|
||||
<domain includeSubdomains="true">crl4.digicert.com</domain>
|
||||
<domain includeSubdomains="true">crls.certainly.com</domain>
|
||||
<domain includeSubdomains="true">crls.ssl.com</domain>
|
||||
<domain includeSubdomains="true">cyberfolks2.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">depo.kamusm.gov.tr</domain>
|
||||
<domain includeSubdomains="true">digitaltrust.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">dnencrypt.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">dvtlsca2025-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">dvtlsca2026-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">e5.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">e6.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">e7.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">e8.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">ec2ca2017-crl1.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ec2ca2017-crl2.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ec2ca2017-crl3.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ec2sslca2017-crl1.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ec2sslca2017-crl2.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ec2sslca2017-crl3.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ec3ca2017-crl1.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ec3ca2017-crl2.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ec3ca2017-crl3.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ec3sslca2017-crl1.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ec3sslca2017-crl2.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ec3sslca2017-crl3.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eca.hinet.net</domain>
|
||||
<domain includeSubdomains="true">edvtlsca2023-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">edvtlsca2025-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">edvtlsca2026-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">emudhra.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">eosslca2017-crl1.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eosslca2017-crl2.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eosslca2017-crl3.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eovtlsca2023-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eovtlsca2025-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eovtlsca2026-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqca2017-crl1.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqca2017-crl2.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqca2017-crl3.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqcpca2017-crl1.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqcpca2017-crl2.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqcpca2017-crl3.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqoca2017-crl1.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqoca2017-crl2.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqoca2017-crl3.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqtlsca2018-crl1.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqtlsca2018-crl2.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqtlsca2018-crl3.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqtlsca2023-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqtlsca2025-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">eqtlsca2026-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">esafer.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">eue2m1.crl.root.amznts.eu</domain>
|
||||
<domain includeSubdomains="true">eue3m1.crl.root.amznts.eu</domain>
|
||||
<domain includeSubdomains="true">eur2m1.crl.root.amznts.eu</domain>
|
||||
<domain includeSubdomains="true">eveccca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">evrsaca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">gdcadv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">gdcaev.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">gdcaov.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">gdcatrustauthdvtlsg3r31ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">gdcatrustauthevtlsg3r31ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">gdcatrustauthovtlsg3r31ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">geant.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">genious.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">geossldvtlsr34g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">geosslevtlsr34g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">geosslovtlsr34g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">globaltrust.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">globessl.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">gogetssldv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">gogetsslev.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">gogetsslov.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">homepldvtlsg2r35ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">homepldvtlsg2r36ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">homeplovtlsg2r35ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">homeplovtlsg2r36ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">httpcrl.trust.telia.com</domain>
|
||||
<domain includeSubdomains="true">ica.navercloudtrust.com</domain>
|
||||
<domain includeSubdomains="true">ica.navercorp.com</domain>
|
||||
<domain includeSubdomains="true">issauth.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">itrusdv2.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">itrusdvtlsr35g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">itrusov2.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">itrusovtlsr35g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">itso.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">joyssldvtlsg2r33ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">joysslovtlsg2r33ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">kingnettechdv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">kingnettechev.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">kingnettechov.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">kr.crl.digicert.com</domain>
|
||||
<domain includeSubdomains="true">lh2.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">namecheap.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">nazwassldvtlsg2e29ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">nazwassldvtlsg2r29ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">netartcadv2.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">netartssldvtlsg2e31ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">nijimo.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">nyalabscadv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">nyatworkdv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">omitsecurity.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">onesign.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">osslca2016-crl1.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">osslca2016-crl2.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">osslca2016-crl3.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ovtlsca2025-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ovtlsca2026-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">pki-crl.atos.net</domain>
|
||||
<domain includeSubdomains="true">pki.telesec.de</domain>
|
||||
<domain includeSubdomains="true">pkipro.certsign.ro</domain>
|
||||
<domain includeSubdomains="true">positiwise.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">psw.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">public.wisekey.com</domain>
|
||||
<domain includeSubdomains="true">qiduodv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">qtlsca2018-crl1.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">qtlsca2018-crl2.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">qtlsca2018-crl3.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">qtlsca2026-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">r10.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">r11.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">r12.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">r13.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">rca.navercloudtrust.com</domain>
|
||||
<domain includeSubdomains="true">rca.navercorp.com</domain>
|
||||
<domain includeSubdomains="true">repo.pubcert.jprs.jp</domain>
|
||||
<domain includeSubdomains="true">repo1.secomtrust.net</domain>
|
||||
<domain includeSubdomains="true">repository.secomtrust.net</domain>
|
||||
<domain includeSubdomains="true">repository.tls.hinet.net</domain>
|
||||
<domain includeSubdomains="true">rootca.twca.com.tw</domain>
|
||||
<domain includeSubdomains="true">rootglobaldv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">rootnetworksdv2.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">shenzhendv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">shenzhenev.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">shenzhenov.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">shoperdvtlsg2r34ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">shuididv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">shuidiev.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">shuidiov.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">soomadv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">ssl2ca2016-crl1.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ssl2ca2016-crl2.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">ssl2ca2016-crl3.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">sslca2014-crl1.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">sslca2014-crl2.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">sslca2014-crl3.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">sslcom.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">ssllimiteddv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">sslserver.twca.com.tw</domain>
|
||||
<domain includeSubdomains="true">subca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">titrust.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">tlcdvtlsr34g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">tlcevtlsr34g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">tlcovtlsr34g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">tlsrootca2025-crl.e-szigno.hu</domain>
|
||||
<domain includeSubdomains="true">trustasia.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">trustasiadv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">trustasiadvtlsr35g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">trustasiaev.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">trustasiaevtlse35g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">trustasiaevtlsr35g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">trustasiaov.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">trustasiaovtlse35g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">trustasiaovtlsr35g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">trustocean.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">tstlser20.crl.telesec.de</domain>
|
||||
<domain includeSubdomains="true">tstlsrr23.crl.telesec.de</domain>
|
||||
<domain includeSubdomains="true">unitedtrustov.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">validation.identrust.com</domain>
|
||||
<domain includeSubdomains="true">verokey.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">vtrusdvtlsg3r31ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">vtrusovtlsg3r31ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">webnic.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">whitessldvtlsg2e36ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">wosign.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">wotrus-dvca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">wotrus-evca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">wotrus-ovca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">wotrus.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">wtca-cafiles.itrus.com.cn</domain>
|
||||
<domain includeSubdomains="true">wtca-crl.itrus.com.cn</domain>
|
||||
<domain includeSubdomains="true">www.accv.es</domain>
|
||||
<domain includeSubdomains="true">www.cert.fnmt.es</domain>
|
||||
<domain includeSubdomains="true">www.d-trust.net</domain>
|
||||
<domain includeSubdomains="true">www.microsoft.com</domain>
|
||||
<domain includeSubdomains="true">x1.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">x2.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">xcctrustdvtlsg3r31ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">xcctrustevtlsg3r31ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">xcctrustovtlsg3r31ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">xinchacha2dv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">xinchacha2ov.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">xinchachatrustdvtlsg2r34ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">xinchachatrustevtlsg2r34ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">xinchachatrustovtlsg2r34ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">xinnetdvtlsr34g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">xinnetevtlsr34g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">xinnetovtlsr34g2ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">ye.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">ye1.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">ye2.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">yektadv.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">yektaov.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">yr.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">yr1.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">yr2.c.lencr.org</domain>
|
||||
<domain includeSubdomains="true">zerossl.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">ziwit.crl.sectigo.com</domain>
|
||||
<domain includeSubdomains="true">zycatrustdvtlsg3r31ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">zycatrustevtlsg3r31ca.crl.certum.pl</domain>
|
||||
<domain includeSubdomains="true">zycatrustovtlsg3r31ca.crl.certum.pl</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Ref: https://developer.android.com/training/articles/security-config.html -->
|
||||
<!-- By default, do not allow clearText traffic -->
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
<certificates
|
||||
src="user"
|
||||
tools:ignore="AcceptsUserCertificates" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
|
||||
<!-- Allow clearText traffic on some specified host -->
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<!-- Localhost -->
|
||||
<domain includeSubdomains="true">localhost</domain>
|
||||
<domain includeSubdomains="true">127.0.0.1</domain>
|
||||
<!-- Localhost for Android emulator -->
|
||||
<domain includeSubdomains="true">10.0.2.2</domain>
|
||||
<!-- Onion services -->
|
||||
<domain includeSubdomains="true">onion</domain>
|
||||
|
||||
<!-- Domains that are used for LANs -->
|
||||
<!-- These are IANA recognized special use domain names, see https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml -->
|
||||
<domain includeSubdomains="true">home.arpa</domain>
|
||||
<domain includeSubdomains="true">local</domain> <!-- Note this has been reserved for use with mDNS -->
|
||||
<domain includeSubdomains="true">test</domain>
|
||||
<!-- These are observed in the wild either by convention or RFCs that have not been accepted, and are not currently TLDs -->
|
||||
<domain includeSubdomains="true">home</domain>
|
||||
<domain includeSubdomains="true">lan</domain>
|
||||
<domain includeSubdomains="true">localdomain</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (c) 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!--
|
||||
Lets the system offer the conversation shortcuts published by
|
||||
DefaultNotificationConversationService in the Direct Share row of the share sheet.
|
||||
The category must match SHARE_TARGET_CATEGORY in that class.
|
||||
-->
|
||||
<share-target android:targetClass="io.element.android.x.MainActivity">
|
||||
<data android:mimeType="*/*" />
|
||||
<category android:name="io.element.android.category.SHARE_TARGET" />
|
||||
</share-target>
|
||||
</shortcuts>
|
||||
@@ -22,13 +22,19 @@ android {
|
||||
defaultConfig {
|
||||
buildConfigFieldStr(
|
||||
name = "URL_POLICY",
|
||||
// HealthChat fork: a configured value wins for every build type, not only enterprise ones.
|
||||
value = BuildTimeConfig.URL_POLICY ?: if (isEnterpriseBuild) "" else "https://element.io/cookie-policy",
|
||||
value = if (isEnterpriseBuild) {
|
||||
BuildTimeConfig.URL_POLICY ?: ""
|
||||
} else {
|
||||
"https://element.io/cookie-policy"
|
||||
},
|
||||
)
|
||||
buildConfigFieldStr(
|
||||
name = "BUG_REPORT_URL",
|
||||
// HealthChat fork: a configured value wins for every build type, not only enterprise ones.
|
||||
value = BuildTimeConfig.BUG_REPORT_URL ?: if (isEnterpriseBuild) "" else "https://rageshakes.element.io/api/submit",
|
||||
value = if (isEnterpriseBuild) {
|
||||
BuildTimeConfig.BUG_REPORT_URL ?: ""
|
||||
} else {
|
||||
"https://rageshakes.element.io/api/submit"
|
||||
},
|
||||
)
|
||||
buildConfigFieldStr(
|
||||
name = "BUG_REPORT_APP_NAME",
|
||||
@@ -42,6 +48,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.coroutines.core)
|
||||
implementation(libs.androidx.annotationjvm)
|
||||
implementation(libs.androidx.corektx)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
|
||||
@@ -9,8 +9,15 @@
|
||||
package io.element.android.appconfig
|
||||
|
||||
object AuthenticationConfig {
|
||||
const val MATRIX_ORG_URL = "https://matrix.org"
|
||||
|
||||
/**
|
||||
* URL with some docs that explain what's sliding sync and how to add it to your home server.
|
||||
*/
|
||||
const val SLIDING_SYNC_READ_MORE_URL = "https://github.com/matrix-org/sliding-sync/blob/main/docs/Landing.md"
|
||||
|
||||
/**
|
||||
* Force a sliding sync proxy url, if not null, the proxy url in the .well-known file will be ignored.
|
||||
*/
|
||||
val SLIDING_SYNC_PROXY_URL: String? = null
|
||||
}
|
||||
|
||||
@@ -9,10 +9,9 @@
|
||||
package io.element.android.appconfig
|
||||
|
||||
object LearnMoreConfig {
|
||||
// HealthChat fork: every "learn more" link points at the HealthChat site instead of element.io/help.
|
||||
const val ENCRYPTION_URL: String = "https://www.healthchat.ch"
|
||||
const val DEVICE_VERIFICATION_URL: String = "https://www.healthchat.ch"
|
||||
const val SECURE_BACKUP_URL: String = "https://www.healthchat.ch"
|
||||
const val IDENTITY_CHANGE_URL: String = "https://www.healthchat.ch"
|
||||
const val HISTORY_VISIBLE_URL: String = "https://www.healthchat.ch"
|
||||
const val ENCRYPTION_URL: String = "https://element.io/help#encryption"
|
||||
const val DEVICE_VERIFICATION_URL: String = "https://element.io/help#encryption-device-verification"
|
||||
const val SECURE_BACKUP_URL: String = "https://element.io/help#encryption5"
|
||||
const val IDENTITY_CHANGE_URL: String = "https://element.io/help#encryption18"
|
||||
const val HISTORY_VISIBLE_URL: String = "https://element.io/en/help#e2ee-history-sharing"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appconfig
|
||||
|
||||
object PushConfig {
|
||||
/**
|
||||
* Note: pusher_app_id cannot exceed 64 chars.
|
||||
*/
|
||||
const val PUSHER_APP_ID: String = "im.vector.app.android"
|
||||
}
|
||||
@@ -35,11 +35,4 @@ object RageshakeConfig {
|
||||
* The maximum number of log lines a rageshake can contain.
|
||||
*/
|
||||
const val MAX_LOG_LINES_SIZE = 1_000_000
|
||||
|
||||
/**
|
||||
* The maximum size of the description. The bug report server copies it verbatim into a GitHub issue body,
|
||||
* which GitHub rejects above 65536 characters. The remaining budget is left to the other reported fields,
|
||||
* mainly the list of the omitted log file names.
|
||||
*/
|
||||
const val MAX_DESCRIPTION_SIZE = 40 * 1024
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ dependencies {
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.oauth.api)
|
||||
implementation(projects.libraries.permissions.api)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.push.api)
|
||||
implementation(projects.libraries.pushproviders.api)
|
||||
@@ -49,7 +48,6 @@ dependencies {
|
||||
implementation(libs.coil)
|
||||
|
||||
implementation(projects.features.announcement.api)
|
||||
implementation(projects.features.callnative.api)
|
||||
implementation(projects.features.ftue.api)
|
||||
implementation(projects.features.linknewdevice.api)
|
||||
implementation(projects.features.share.api)
|
||||
@@ -61,23 +59,15 @@ dependencies {
|
||||
testCommonDependencies(libs)
|
||||
testImplementation(projects.features.login.test)
|
||||
testImplementation(projects.features.share.test)
|
||||
testImplementation(projects.libraries.accountselect.test)
|
||||
testImplementation(projects.libraries.featureflag.test)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.matrixmedia.test)
|
||||
testImplementation(projects.libraries.oauth.test)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.libraries.pushproviders.test)
|
||||
testImplementation(projects.libraries.sessionStorage.test)
|
||||
testImplementation(projects.features.announcement.test)
|
||||
testImplementation(projects.features.forward.test)
|
||||
testImplementation(projects.features.messages.test)
|
||||
testImplementation(projects.features.networkmonitor.test)
|
||||
testImplementation(projects.features.preferences.test)
|
||||
testImplementation(projects.features.rageshake.test)
|
||||
testImplementation(projects.features.signedout.test)
|
||||
testImplementation(projects.services.apperror.test)
|
||||
testImplementation(projects.services.appnavstate.test)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
|
||||
@@ -8,15 +8,9 @@
|
||||
|
||||
package io.element.android.appnav
|
||||
|
||||
import com.bumble.appyx.core.navigation.NavKey
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.BackStackElement
|
||||
import com.bumble.appyx.navmodel.backstack.BackStackElements
|
||||
import com.bumble.appyx.navmodel.backstack.operation.BackStackOperation
|
||||
import com.bumble.appyx.navmodel.backstack.operation.NewRoot
|
||||
import com.bumble.appyx.navmodel.backstack.operation.Remove
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.parcelize.RawValue
|
||||
|
||||
/**
|
||||
* Don't process NewRoot if the nav target already exists in the stack.
|
||||
@@ -38,45 +32,3 @@ fun <T : Any> BackStack<T>.removeLast(element: T) {
|
||||
} ?: return
|
||||
accept(Remove(lastExpectedNavElement.key))
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the root element of the backstack, and keep the elements on top of it, contrary to [safeRoot] which removes all the elements.
|
||||
*/
|
||||
fun <T : Any> BackStack<T>.replaceRoot(element: T) {
|
||||
accept(ReplaceRoot(element))
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the root element of the backstack, keeping the elements on top of it.
|
||||
*
|
||||
* Operation:
|
||||
*
|
||||
* [A, B, C] + ReplaceRoot(D) = [D, B, C]
|
||||
* [A] + ReplaceRoot(D) = [D]
|
||||
*/
|
||||
@Parcelize
|
||||
internal data class ReplaceRoot<T : Any>(
|
||||
private val element: @RawValue T
|
||||
) : BackStackOperation<T> {
|
||||
override fun isApplicable(elements: BackStackElements<T>): Boolean =
|
||||
elements.rootElement()?.key?.navTarget != element
|
||||
|
||||
override fun invoke(elements: BackStackElements<T>): BackStackElements<T> {
|
||||
val root = elements.rootElement() ?: return elements
|
||||
if (root.targetState == BackStack.State.ACTIVE) {
|
||||
// Nothing on top of the root, so this is the same as NewRoot
|
||||
return NewRoot(element).invoke(elements)
|
||||
}
|
||||
// The root is stashed below other elements: drop it, as Remove does for a non active element,
|
||||
// and add the new root, stashed, in place of it.
|
||||
val newRoot = BackStackElement(
|
||||
key = NavKey(element),
|
||||
fromState = BackStack.State.STASHED,
|
||||
targetState = BackStack.State.STASHED,
|
||||
operation = this,
|
||||
)
|
||||
return elements.map { if (it == root) newRoot else it }
|
||||
}
|
||||
|
||||
private fun BackStackElements<T>.rootElement() = firstOrNull { it.targetState != BackStack.State.DESTROYED }
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@ import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.DependencyInjectionGraphOwner
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrixmedia.api.ImageLoaderHolder
|
||||
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
@@ -60,7 +59,6 @@ class LoggedInAppScopeFlowNode(
|
||||
interface Callback : Plugin {
|
||||
fun navigateToBugReport()
|
||||
fun navigateToAddAccount()
|
||||
fun navigateToSession(sessionId: SessionId)
|
||||
}
|
||||
|
||||
private val callback: Callback = callback()
|
||||
@@ -93,10 +91,6 @@ class LoggedInAppScopeFlowNode(
|
||||
override fun navigateToAddAccount() {
|
||||
callback.navigateToAddAccount()
|
||||
}
|
||||
|
||||
override fun navigateToSession(sessionId: SessionId) {
|
||||
callback.navigateToSession(sessionId)
|
||||
}
|
||||
}
|
||||
return createNode<LoggedInFlowNode>(buildContext, listOf(callback))
|
||||
}
|
||||
|
||||
@@ -8,51 +8,28 @@
|
||||
|
||||
package io.element.android.appnav
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedFactory
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.coroutine.childScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
|
||||
import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
@AssistedInject
|
||||
@Inject
|
||||
class LoggedInEventProcessor(
|
||||
@Assisted private val snackbarDispatcher: SnackbarDispatcher,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
private val roomMembershipObserver: RoomMembershipObserver,
|
||||
private val sessionId: SessionId,
|
||||
private val syncService: SyncService,
|
||||
private val notificationCleaner: NotificationCleaner,
|
||||
private val dispatchers: CoroutineDispatchers
|
||||
) {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(snackbarDispatcher: SnackbarDispatcher): LoggedInEventProcessor
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal var currentChildScope: CoroutineScope? = null
|
||||
private var observingJob: Job? = null
|
||||
|
||||
fun observeEvents(coroutineScope: CoroutineScope) {
|
||||
if (currentChildScope != null) return
|
||||
|
||||
val childScope = coroutineScope.childScope(dispatchers.computation, "LoggedInEventProcessor")
|
||||
.also { currentChildScope = it }
|
||||
|
||||
roomMembershipObserver.updates
|
||||
observingJob = roomMembershipObserver.updates
|
||||
.filter { !it.isUserInRoom }
|
||||
.distinctUntilChanged()
|
||||
.onEach { roomMemberShipUpdate ->
|
||||
@@ -71,19 +48,12 @@ class LoggedInEventProcessor(
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
.launchIn(childScope)
|
||||
|
||||
// Use the sync service heartbeat as a 'heartbeat' update to check existing notifications
|
||||
syncService.heartBeat
|
||||
.onEach {
|
||||
notificationCleaner.clearReadRoomsNotifications(sessionId)
|
||||
}
|
||||
.launchIn(childScope)
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
fun stopObserving() {
|
||||
currentChildScope?.cancel()
|
||||
currentChildScope = null
|
||||
observingJob?.cancel()
|
||||
observingJob = null
|
||||
}
|
||||
|
||||
private fun displayMessage(message: Int) {
|
||||
|
||||
@@ -11,7 +11,6 @@ package io.element.android.appnav
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -47,7 +46,6 @@ import io.element.android.appnav.room.RoomFlowNode
|
||||
import io.element.android.appnav.room.RoomNavigationTarget
|
||||
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
||||
import io.element.android.compound.colors.SemanticColorsLightDark
|
||||
import io.element.android.features.callnative.api.NativeCallHost
|
||||
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.api.SessionEnterpriseService
|
||||
@@ -56,8 +54,6 @@ import io.element.android.features.ftue.api.state.FtueService
|
||||
import io.element.android.features.ftue.api.state.FtueState
|
||||
import io.element.android.features.home.api.HomeEntryPoint
|
||||
import io.element.android.features.linknewdevice.api.LinkNewDeviceEntryPoint
|
||||
import io.element.android.features.location.api.LocalMapTilerConfig
|
||||
import io.element.android.features.location.api.LocationService
|
||||
import io.element.android.features.location.api.live.ActiveLiveLocationShareManager
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
@@ -66,6 +62,8 @@ import io.element.android.features.preferences.api.PreferencesEntryPoint
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
|
||||
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
|
||||
import io.element.android.features.share.api.ShareEntryPoint
|
||||
import io.element.android.features.share.api.ShareIntentData
|
||||
import io.element.android.features.startchat.api.StartChatEntryPoint
|
||||
import io.element.android.features.userprofile.api.UserProfileEntryPoint
|
||||
import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint
|
||||
@@ -85,16 +83,15 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||
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.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService
|
||||
import io.element.android.libraries.ui.common.nodes.EmptyState
|
||||
import io.element.android.libraries.ui.common.nodes.emptyNode
|
||||
import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
@@ -108,10 +105,13 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import timber.log.Timber
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.toKotlinDuration
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom as JoinedRoomAnalyticsEvent
|
||||
|
||||
// The maximum number of room nodes that should be kept in the backstack at the same time.
|
||||
@@ -135,9 +135,9 @@ class LoggedInFlowNode(
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
private val ftueService: FtueService,
|
||||
private val roomDirectoryEntryPoint: RoomDirectoryEntryPoint,
|
||||
private val shareEntryPoint: ShareEntryPoint,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val sendingQueue: SendQueues,
|
||||
private val nativeCallHost: NativeCallHost,
|
||||
private val incomingVerificationEntryPoint: IncomingVerificationEntryPoint,
|
||||
private val mediaPreviewConfigMigration: MediaPreviewConfigMigration,
|
||||
private val sessionEnterpriseService: SessionEnterpriseService,
|
||||
@@ -153,8 +153,6 @@ class LoggedInFlowNode(
|
||||
private val analyticsRoomListStateWatcher: AnalyticsRoomListStateWatcher,
|
||||
private val createRoomEntryPoint: CreateRoomEntryPoint,
|
||||
private val activeLiveLocationShareManager: ActiveLiveLocationShareManager,
|
||||
private val locationService: LocationService,
|
||||
private val loggedInEventProcessorFactory: LoggedInEventProcessor.Factory,
|
||||
) : BaseFlowNode<LoggedInFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Placeholder,
|
||||
@@ -170,26 +168,44 @@ class LoggedInFlowNode(
|
||||
interface Callback : Plugin {
|
||||
fun navigateToBugReport()
|
||||
fun navigateToAddAccount()
|
||||
fun navigateToSession(sessionId: SessionId)
|
||||
}
|
||||
|
||||
private val callback: Callback = callback()
|
||||
private val loggedInFlowProcessor = loggedInEventProcessorFactory.create(snackbarDispatcher)
|
||||
private val loggedInFlowProcessor = LoggedInEventProcessor(
|
||||
snackbarDispatcher = snackbarDispatcher,
|
||||
roomMembershipObserver = matrixClient.roomMembershipObserver,
|
||||
)
|
||||
|
||||
/**
|
||||
* Display the incoming verification request, the app is expected to be in foreground.
|
||||
*/
|
||||
fun onIncomingVerificationRequest(verificationRequest: VerificationRequest.Incoming) {
|
||||
lifecycleScope.launch {
|
||||
// Wait for the RoomList UI to be ready so the incoming verification screen can be displayed on top of it
|
||||
// Otherwise, the RoomList UI may be incorrectly displayed on top
|
||||
withTimeout(5.seconds) {
|
||||
backstack.elements.first { elements ->
|
||||
elements.any { it.key.navTarget == NavTarget.Home }
|
||||
private val verificationListener = object : SessionVerificationServiceListener {
|
||||
override fun onIncomingSessionRequest(verificationRequest: VerificationRequest.Incoming) {
|
||||
// Without this launch the rendering and actual state of this Appyx node's children gets out of sync, resulting in a crash.
|
||||
// This might be because this method is called back from Rust in a background thread.
|
||||
lifecycleScope.launch {
|
||||
val receivedAt = Instant.now()
|
||||
|
||||
// Wait until the app is in foreground to display the incoming verification request
|
||||
appNavigationStateService.appNavigationState.first { it.isInForeground }
|
||||
|
||||
// TODO there should also be a timeout for > 10 minutes elapsed since the request was created, but the SDK doesn't expose that info yet
|
||||
val now = Instant.now()
|
||||
val elapsedTimeSinceReceived = Duration.between(receivedAt, now).toKotlinDuration()
|
||||
|
||||
// Discard the incoming verification request if it has timed out
|
||||
if (elapsedTimeSinceReceived > 2.minutes) {
|
||||
Timber.w("Incoming verification request ${verificationRequest.details.flowId} discarded due to timeout.")
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
backstack.singleTop(NavTarget.IncomingVerificationRequest(verificationRequest))
|
||||
// Wait for the RoomList UI to be ready so the incoming verification screen can be displayed on top of it
|
||||
// Otherwise, the RoomList UI may be incorrectly displayed on top
|
||||
withTimeout(5.seconds) {
|
||||
backstack.elements.first { elements ->
|
||||
elements.any { it.key.navTarget == NavTarget.Home }
|
||||
}
|
||||
}
|
||||
|
||||
backstack.singleTop(NavTarget.IncomingVerificationRequest(verificationRequest))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +220,7 @@ class LoggedInFlowNode(
|
||||
analyticsRoomListStateWatcher.start()
|
||||
appNavigationStateService.onNavigateToSession(id, matrixClient.sessionId)
|
||||
loggedInFlowProcessor.observeEvents(sessionCoroutineScope)
|
||||
matrixClient.sessionVerificationService.setListener(verificationListener)
|
||||
mediaPreviewConfigMigration()
|
||||
sessionCoroutineScope.launch {
|
||||
// Wait for the network to be connected before pre-fetching the max file upload size
|
||||
@@ -232,6 +249,7 @@ class LoggedInFlowNode(
|
||||
onDestroy = {
|
||||
appNavigationStateService.onLeavingSession(id)
|
||||
loggedInFlowProcessor.stopObserving()
|
||||
matrixClient.sessionVerificationService.setListener(null)
|
||||
analyticsRoomListStateWatcher.stop()
|
||||
}
|
||||
)
|
||||
@@ -292,15 +310,16 @@ class LoggedInFlowNode(
|
||||
@Parcelize
|
||||
data object RoomDirectory : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class IncomingShare(val shareIntentData: ShareIntentData) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class IncomingVerificationRequest(val data: VerificationRequest.Incoming) : NavTarget
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
// Rendered while the FtueState is Unknown. Render a loading state rather than nothing at all,
|
||||
// since there is no guarantee that this state will be left.
|
||||
NavTarget.Placeholder -> emptyNode(buildContext, state = EmptyState(delayBeforeShowingContent = 500.milliseconds))
|
||||
NavTarget.Placeholder -> emptyNode(buildContext)
|
||||
NavTarget.LoggedInPermanent -> {
|
||||
val callback = object : LoggedInNode.Callback {
|
||||
override fun navigateToNotificationTroubleshoot() {
|
||||
@@ -311,11 +330,11 @@ class LoggedInFlowNode(
|
||||
}
|
||||
NavTarget.Home -> {
|
||||
val callback = object : HomeEntryPoint.Callback {
|
||||
override fun navigateToRoom(roomId: RoomId, eventId: EventId?, joinedRoom: JoinedRoom?) {
|
||||
override fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) {
|
||||
lifecycleScope.launch {
|
||||
attachRoom(
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
initialElement = RoomNavigationTarget.Root(joinedRoom = joinedRoom, eventId = eventId),
|
||||
initialElement = RoomNavigationTarget.Root(joinedRoom = joinedRoom),
|
||||
clearBackstack = false,
|
||||
)
|
||||
}
|
||||
@@ -355,14 +374,6 @@ class LoggedInFlowNode(
|
||||
callback.navigateToBugReport()
|
||||
}
|
||||
|
||||
override fun navigateToAddAccount() {
|
||||
callback.navigateToAddAccount()
|
||||
}
|
||||
|
||||
override fun navigateToSession(sessionId: SessionId) {
|
||||
callback.navigateToSession(sessionId)
|
||||
}
|
||||
|
||||
// HealthChat fork: open a user profile from the member directory tab.
|
||||
override fun navigateToUserProfile(userId: UserId) {
|
||||
backstack.push(NavTarget.UserProfile(userId))
|
||||
@@ -459,10 +470,6 @@ class LoggedInFlowNode(
|
||||
callback.navigateToAddAccount()
|
||||
}
|
||||
|
||||
override fun navigateToSession(sessionId: SessionId) {
|
||||
callback.navigateToSession(sessionId)
|
||||
}
|
||||
|
||||
override fun navigateToLinkNewDevice() {
|
||||
backstack.push(NavTarget.LinkNewDevice)
|
||||
}
|
||||
@@ -576,6 +583,30 @@ class LoggedInFlowNode(
|
||||
},
|
||||
)
|
||||
}
|
||||
is NavTarget.IncomingShare -> {
|
||||
shareEntryPoint.createNode(
|
||||
parentNode = this,
|
||||
buildContext = buildContext,
|
||||
params = ShareEntryPoint.Params(shareIntentData = navTarget.shareIntentData),
|
||||
callback = object : ShareEntryPoint.Callback {
|
||||
override fun onDone(roomIds: List<RoomId>) {
|
||||
// Remove the incoming share screen
|
||||
backstack.pop()
|
||||
|
||||
// Navigate to the room if the text/media was shared to a single one
|
||||
roomIds.singleOrNull()?.let { roomId ->
|
||||
lifecycleScope.launch {
|
||||
// Wait until the incoming share screen is removed
|
||||
backstack.elements.first { it.lastOrNull()?.key?.navTarget !is NavTarget.IncomingShare }
|
||||
|
||||
// Then attach the room
|
||||
attachRoom(roomId.toRoomIdOrAlias(), clearBackstack = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
is NavTarget.IncomingVerificationRequest -> {
|
||||
incomingVerificationEntryPoint.createNode(
|
||||
parentNode = this,
|
||||
@@ -591,15 +622,6 @@ class LoggedInFlowNode(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the home screen to be attached, i.e. for the FTUE to be complete.
|
||||
*/
|
||||
internal suspend fun waitForHome() {
|
||||
waitForNavTargetAttached { navTarget ->
|
||||
navTarget is NavTarget.Home
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun attachRoom(
|
||||
roomIdOrAlias: RoomIdOrAlias,
|
||||
serverNames: List<String> = emptyList(),
|
||||
@@ -644,14 +666,22 @@ class LoggedInFlowNode(
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend fun attachIncomingShare(shareIntentData: ShareIntentData) {
|
||||
waitForNavTargetAttached { navTarget ->
|
||||
navTarget is NavTarget.Home
|
||||
}
|
||||
attachChild<Node> {
|
||||
backstack.push(
|
||||
NavTarget.IncomingShare(shareIntentData)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val colors by remember {
|
||||
enterpriseService.semanticColorsFlow(sessionId = matrixClient.sessionId)
|
||||
}.collectAsState(SemanticColorsLightDark.default)
|
||||
|
||||
val mapTilerConfig by locationService.mapTilerConfig.collectAsState()
|
||||
|
||||
ElementThemeApp(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
featureFlagService = featureFlagService,
|
||||
@@ -659,22 +689,16 @@ class LoggedInFlowNode(
|
||||
compoundDark = colors.dark,
|
||||
buildMeta = buildMeta,
|
||||
) {
|
||||
CompositionLocalProvider(LocalMapTilerConfig provides mapTilerConfig) {
|
||||
val isOnline by syncService.isOnline.collectAsState()
|
||||
// Outside the connectivity indicator so the two strips stack rather than fight: an
|
||||
// offline banner during a call belongs under the call bar, not over it.
|
||||
nativeCallHost.Render(modifier = modifier) { callContentModifier ->
|
||||
ConnectivityIndicatorContainer(
|
||||
isOnline = isOnline,
|
||||
modifier = callContentModifier,
|
||||
) { contentModifier ->
|
||||
Box(modifier = contentModifier) {
|
||||
val ftueState by ftueService.state.collectAsState()
|
||||
BackstackView(transitionHandler = rememberLoggedInFlowTransitionHandler(backstack))
|
||||
if (ftueState is FtueState.Complete) {
|
||||
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent)
|
||||
}
|
||||
}
|
||||
val isOnline by syncService.isOnline.collectAsState()
|
||||
ConnectivityIndicatorContainer(
|
||||
isOnline = isOnline,
|
||||
modifier = modifier,
|
||||
) { contentModifier ->
|
||||
Box(modifier = contentModifier) {
|
||||
val ftueState by ftueService.state.collectAsState()
|
||||
BackstackView(transitionHandler = rememberLoggedInFlowTransitionHandler(backstack))
|
||||
if (ftueState is FtueState.Complete) {
|
||||
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ package io.element.android.appnav
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import coil3.SingletonImageLoader
|
||||
import coil3.annotation.DelicateCoilApi
|
||||
import com.bumble.appyx.core.lifecycle.subscribe
|
||||
@@ -25,8 +24,6 @@ import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.features.announcement.api.Announcement
|
||||
import io.element.android.features.announcement.api.AnnouncementService
|
||||
import io.element.android.features.login.api.LoginEntryPoint
|
||||
import io.element.android.features.login.api.LoginParams
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
@@ -36,11 +33,8 @@ import io.element.android.libraries.architecture.callback
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.designsystem.utils.ForceOrientationInMobileDevices
|
||||
import io.element.android.libraries.designsystem.utils.ScreenOrientation
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrixmedia.api.ImageLoaderHolder
|
||||
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
|
||||
import io.element.android.services.analytics.api.watchers.AnalyticsColdStartWatcher
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ContributesNode(AppScope::class)
|
||||
@@ -51,8 +45,6 @@ class NotLoggedInFlowNode(
|
||||
private val loginEntryPoint: LoginEntryPoint,
|
||||
private val imageLoaderHolder: ImageLoaderHolder,
|
||||
private val analyticsColdStartWatcher: AnalyticsColdStartWatcher,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val announcementService: AnnouncementService,
|
||||
) : BaseFlowNode<NotLoggedInFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Root,
|
||||
@@ -76,13 +68,6 @@ class NotLoggedInFlowNode(
|
||||
override fun onBuilt() {
|
||||
super.onBuilt()
|
||||
analyticsColdStartWatcher.whenLoggingIn()
|
||||
lifecycleScope.launch {
|
||||
// Users who enter the login flow while the feature is available, either for the first time or to add
|
||||
// another account, do not need to be informed about the multi-account feature.
|
||||
if (featureFlagService.isFeatureEnabled(FeatureFlags.MultiAccount)) {
|
||||
announcementService.onAnnouncementDismissed(Announcement.MultiAccount)
|
||||
}
|
||||
}
|
||||
lifecycle.subscribe(
|
||||
onResume = {
|
||||
SingletonImageLoader.setUnsafe(imageLoaderHolder.get())
|
||||
|
||||
@@ -32,6 +32,7 @@ import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.appnav.di.MatrixSessionCache
|
||||
import io.element.android.appnav.intent.IntentResolver
|
||||
import io.element.android.appnav.intent.ResolvedIntent
|
||||
import io.element.android.appnav.room.RoomFlowNode
|
||||
@@ -39,16 +40,10 @@ import io.element.android.appnav.room.RoomNavigationTarget
|
||||
import io.element.android.appnav.root.RootNavStateFlowFactory
|
||||
import io.element.android.appnav.root.RootPresenter
|
||||
import io.element.android.appnav.root.RootView
|
||||
import io.element.android.appnav.session.MatrixSessionCache
|
||||
import io.element.android.appnav.verification.IncomingVerificationRequestData
|
||||
import io.element.android.appnav.verification.IncomingVerificationRequestObserver
|
||||
import io.element.android.appnav.verification.OtherSessionIncomingVerificationNode
|
||||
import io.element.android.appnav.verification.OtherSessionVerificationNode
|
||||
import io.element.android.features.announcement.api.AnnouncementService
|
||||
import io.element.android.features.login.api.LoginParams
|
||||
import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl
|
||||
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
|
||||
import io.element.android.features.share.api.ShareEntryPoint
|
||||
import io.element.android.features.share.api.ShareIntentData
|
||||
import io.element.android.features.signedout.api.SignedOutEntryPoint
|
||||
import io.element.android.libraries.accountselect.api.AccountSelectEntryPoint
|
||||
@@ -57,20 +52,17 @@ import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.architecture.appyx.rememberDelegateTransitionHandler
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.architecture.waitForChildAttached
|
||||
import io.element.android.libraries.core.coroutine.withPreviousValue
|
||||
import io.element.android.libraries.core.uri.ensureProtocol
|
||||
import io.element.android.libraries.deeplink.api.DeeplinkData
|
||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.api.core.asEventId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
||||
import io.element.android.libraries.oauth.api.OAuthAction
|
||||
import io.element.android.libraries.oauth.api.OAuthActionFlow
|
||||
import io.element.android.libraries.sessionstorage.api.LoggedInState
|
||||
@@ -83,16 +75,11 @@ import io.element.android.services.appnavstate.api.ROOM_OPENED_FROM_NOTIFICATION
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@ContributesNode(AppScope::class)
|
||||
@AssistedInject
|
||||
@@ -113,8 +100,6 @@ class RootFlowNode(
|
||||
private val announcementService: AnnouncementService,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val analyticsColdStartWatcher: AnalyticsColdStartWatcher,
|
||||
private val incomingVerificationRequestObserver: IncomingVerificationRequestObserver,
|
||||
private val shareEntryPoint: ShareEntryPoint,
|
||||
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
|
||||
) : BaseFlowNode<RootFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
@@ -124,13 +109,6 @@ class RootFlowNode(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
) {
|
||||
/**
|
||||
* Login params coming from a launch or new [Intent], waiting to be consumed by the not logged in flow.
|
||||
* Kept here so that the root nav target can be computed from both the logged in state and the pending
|
||||
* login params, whatever the order in which the intent and the first nav state emission are processed.
|
||||
*/
|
||||
private var pendingLoginParams: LoginParams? = null
|
||||
|
||||
override fun onBuilt() {
|
||||
analyticsColdStartWatcher.start()
|
||||
appCoroutineScope.launch {
|
||||
@@ -142,8 +120,6 @@ class RootFlowNode(
|
||||
observeNavState(false)
|
||||
}
|
||||
}
|
||||
observeIncomingVerificationRequests()
|
||||
observeRemovedSessions()
|
||||
super.onBuilt()
|
||||
}
|
||||
|
||||
@@ -176,7 +152,7 @@ class RootFlowNode(
|
||||
}
|
||||
}
|
||||
LoggedInState.NotLoggedIn -> {
|
||||
switchToNotLoggedInFlow(pendingLoginParams)
|
||||
switchToNotLoggedInFlow(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,93 +194,16 @@ class RootFlowNode(
|
||||
}
|
||||
|
||||
private fun switchToLoggedInFlow(sessionId: SessionId, navId: Int) {
|
||||
pendingLoginParams = null
|
||||
setRoot(NavTarget.LoggedInFlow(sessionId, navId))
|
||||
restoreAllSessions()
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore all the sessions with a valid token, so that they sync and can receive incoming verification requests.
|
||||
*/
|
||||
private fun restoreAllSessions() = lifecycleScope.launch {
|
||||
sessionStore.getAllSessions()
|
||||
.filter { it.isTokenValid }
|
||||
.forEach { sessionData ->
|
||||
matrixSessionCache.getOrRestore(SessionId(sessionData.userId)).onFailure {
|
||||
Timber.e(it, "Failed to restore session ${sessionData.userId}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove from the cache the sessions which have been removed from the session store, for instance after a logout.
|
||||
*/
|
||||
private fun observeRemovedSessions() {
|
||||
sessionStore.sessionsFlow()
|
||||
.map { sessions -> sessions.map { SessionId(it.userId) }.toSet() }
|
||||
.withPreviousValue()
|
||||
.onEach { (previous, current) ->
|
||||
(previous.orEmpty() - current).forEach { sessionId ->
|
||||
// The verification of a removed session cannot continue, for instance if the user has signed out from it
|
||||
backstack.removeLast(NavTarget.OtherAccountSessionVerification(sessionId))
|
||||
Timber.d("Session $sessionId removed, remove it from the cache")
|
||||
matrixSessionCache.remove(sessionId)
|
||||
}
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
private fun observeIncomingVerificationRequests() {
|
||||
incomingVerificationRequestObserver.incomingVerificationRequests()
|
||||
.onEach(::onIncomingVerificationRequest)
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
private fun onIncomingVerificationRequest(data: IncomingVerificationRequestData) = lifecycleScope.launch {
|
||||
// Wait for a logged in flow to be displayed
|
||||
val currentSessionId = withTimeoutOrNull(5.seconds) {
|
||||
backstack.elements.map { elements ->
|
||||
elements.firstNotNullOfOrNull { (it.key.navTarget as? NavTarget.LoggedInFlow)?.sessionId }
|
||||
}.filterNotNull().first()
|
||||
}
|
||||
if (currentSessionId == null) {
|
||||
Timber.w("Incoming verification request ${data.verificationRequest.details.flowId} discarded, no logged in flow.")
|
||||
return@launch
|
||||
}
|
||||
if (currentSessionId == data.sessionId) {
|
||||
waitForChildAttached<LoggedInAppScopeFlowNode, NavTarget> { navTarget ->
|
||||
navTarget is NavTarget.LoggedInFlow && navTarget.sessionId == currentSessionId
|
||||
}
|
||||
.attachSession()
|
||||
.onIncomingVerificationRequest(data.verificationRequest)
|
||||
} else {
|
||||
// Display the request on top of the current session, without switching session
|
||||
backstack.push(NavTarget.OtherAccountIncomingVerificationRequest(data.sessionId, data.verificationRequest))
|
||||
}
|
||||
backstack.safeRoot(NavTarget.LoggedInFlow(sessionId, navId))
|
||||
}
|
||||
|
||||
private fun switchToNotLoggedInFlow(params: LoginParams?) {
|
||||
Timber.d("switchToNotLoggedInFlow, hasLoginParams=${params != null}")
|
||||
matrixSessionCache.removeAll()
|
||||
backstack.safeRoot(NavTarget.NotLoggedInFlow(params))
|
||||
}
|
||||
|
||||
private fun switchToSignedOutFlow(sessionId: SessionId) {
|
||||
pendingLoginParams = null
|
||||
setRoot(NavTarget.SignedOutFlow(sessionId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the root of the backstack. If an incoming share is displayed, keep it on top of the new root, so that the user can
|
||||
* finish sharing, for instance with another account if the current one has been signed out.
|
||||
*/
|
||||
private fun setRoot(navTarget: NavTarget) {
|
||||
val hasIncomingShare = backstack.elements.value.any { it.key.navTarget is NavTarget.IncomingShare }
|
||||
if (hasIncomingShare) {
|
||||
backstack.replaceRoot(navTarget)
|
||||
} else {
|
||||
backstack.safeRoot(navTarget)
|
||||
}
|
||||
backstack.safeRoot(NavTarget.SignedOutFlow(sessionId))
|
||||
}
|
||||
|
||||
private suspend fun restoreSessionIfNeeded(
|
||||
@@ -368,7 +267,8 @@ class RootFlowNode(
|
||||
|
||||
@Parcelize data class AccountSelect(
|
||||
val currentSessionId: SessionId,
|
||||
val permalinkData: PermalinkData,
|
||||
val shareIntentData: ShareIntentData?,
|
||||
val permalinkData: PermalinkData?,
|
||||
) : NavTarget
|
||||
|
||||
@Parcelize data class NotLoggedInFlow(
|
||||
@@ -384,20 +284,6 @@ class RootFlowNode(
|
||||
) : NavTarget
|
||||
|
||||
@Parcelize data object BugReport : NavTarget
|
||||
|
||||
@Parcelize data class IncomingShare(
|
||||
val sessionId: SessionId,
|
||||
val shareIntentData: ShareIntentData,
|
||||
) : NavTarget
|
||||
|
||||
@Parcelize data class OtherAccountIncomingVerificationRequest(
|
||||
val sessionId: SessionId,
|
||||
val verificationRequest: VerificationRequest.Incoming,
|
||||
) : NavTarget
|
||||
|
||||
@Parcelize data class OtherAccountSessionVerification(
|
||||
val sessionId: SessionId,
|
||||
) : NavTarget
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
@@ -416,12 +302,6 @@ class RootFlowNode(
|
||||
override fun navigateToAddAccount() {
|
||||
backstack.push(NavTarget.NotLoggedInFlow(null))
|
||||
}
|
||||
|
||||
override fun navigateToSession(sessionId: SessionId) {
|
||||
lifecycleScope.launch {
|
||||
sessionStore.setLatestSession(sessionId.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
val savedNavState = extractSavedStateForNavTarget(navTarget, this.buildContext.savedStateMap)
|
||||
val buildContext = if (savedNavState != null) {
|
||||
@@ -439,7 +319,6 @@ class RootFlowNode(
|
||||
}
|
||||
|
||||
override fun onDone() {
|
||||
pendingLoginParams = null
|
||||
backstack.pop()
|
||||
}
|
||||
}
|
||||
@@ -458,73 +337,6 @@ class RootFlowNode(
|
||||
)
|
||||
}
|
||||
NavTarget.SplashScreen -> emptyNode(buildContext)
|
||||
is NavTarget.OtherAccountIncomingVerificationRequest -> {
|
||||
if (matrixSessionCache.getOrNull(navTarget.sessionId) == null) {
|
||||
Timber.w("Couldn't find session ${navTarget.sessionId} for the incoming verification request")
|
||||
lifecycleScope.launch { backstack.pop() }
|
||||
return emptyNode(buildContext)
|
||||
}
|
||||
val inputs = OtherSessionIncomingVerificationNode.Inputs(
|
||||
sessionId = navTarget.sessionId,
|
||||
verificationRequest = navTarget.verificationRequest,
|
||||
)
|
||||
val callback = object : OtherSessionIncomingVerificationNode.Callback {
|
||||
override fun onDone() {
|
||||
backstack.pop()
|
||||
}
|
||||
}
|
||||
createNode<OtherSessionIncomingVerificationNode>(buildContext, plugins = listOf(inputs, callback))
|
||||
}
|
||||
is NavTarget.OtherAccountSessionVerification -> {
|
||||
if (matrixSessionCache.getOrNull(navTarget.sessionId) == null) {
|
||||
Timber.w("Couldn't find session ${navTarget.sessionId} to verify")
|
||||
lifecycleScope.launch { backstack.pop() }
|
||||
return emptyNode(buildContext)
|
||||
}
|
||||
val inputs = OtherSessionVerificationNode.Inputs(sessionId = navTarget.sessionId)
|
||||
val callback = object : OtherSessionVerificationNode.Callback {
|
||||
override fun onDone() {
|
||||
backstack.pop()
|
||||
}
|
||||
}
|
||||
createNode<OtherSessionVerificationNode>(buildContext, plugins = listOf(inputs, callback))
|
||||
}
|
||||
is NavTarget.IncomingShare -> {
|
||||
val callback = object : ShareEntryPoint.Callback {
|
||||
override fun onDone(sessionId: SessionId, roomIds: List<RoomId>) {
|
||||
// Remove the incoming share screen
|
||||
backstack.pop()
|
||||
lifecycleScope.launch {
|
||||
// Switch to the session which has been used to share the data, if necessary,
|
||||
// and navigate to the room if the data was shared to a single one.
|
||||
val loggedInFlowNode = attachSession(sessionId)
|
||||
roomIds.singleOrNull()?.let { roomId ->
|
||||
loggedInFlowNode.attachRoom(roomId.toRoomIdOrAlias(), clearBackstack = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
backstack.pop()
|
||||
}
|
||||
|
||||
override fun onSessionVerificationRequired(sessionId: SessionId) {
|
||||
val navTarget = NavTarget.OtherAccountSessionVerification(sessionId)
|
||||
if (backstack.elements.value.lastOrNull()?.key?.navTarget != navTarget) {
|
||||
backstack.push(navTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
shareEntryPoint.createNode(
|
||||
parentNode = this,
|
||||
buildContext = buildContext,
|
||||
params = ShareEntryPoint.Params(
|
||||
sessionId = navTarget.sessionId,
|
||||
shareIntentData = navTarget.shareIntentData,
|
||||
),
|
||||
callback = callback,
|
||||
)
|
||||
}
|
||||
NavTarget.BugReport -> {
|
||||
val callback = object : BugReportEntryPoint.Callback {
|
||||
override fun onDone() {
|
||||
@@ -546,7 +358,13 @@ class RootFlowNode(
|
||||
// Do not pop when the account is changed to avoid a UI flicker.
|
||||
backstack.pop()
|
||||
}
|
||||
attachSession(sessionId).attachPermalinkData(navTarget.permalinkData)
|
||||
attachSession(sessionId).apply {
|
||||
if (navTarget.shareIntentData != null) {
|
||||
attachIncomingShare(navTarget.shareIntentData)
|
||||
} else if (navTarget.permalinkData != null) {
|
||||
attachPermalinkData(navTarget.permalinkData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,8 +399,7 @@ class RootFlowNode(
|
||||
}
|
||||
|
||||
private suspend fun onLoginLink(params: LoginParams) {
|
||||
val accountProvider = AccountProvider.Generic(params.accountProvider)
|
||||
if (accountProviderAccessControl.isAllowedToConnectToAccountProvider(accountProvider)) {
|
||||
if (accountProviderAccessControl.isAllowedToConnectToAccountProvider(params.accountProvider.ensureProtocol())) {
|
||||
// Is there a session already?
|
||||
val sessions = sessionStore.getAllSessions()
|
||||
if (sessions.isNotEmpty()) {
|
||||
@@ -601,7 +418,6 @@ class RootFlowNode(
|
||||
Timber.w("Login link ignored, multi account is disabled")
|
||||
}
|
||||
} else {
|
||||
pendingLoginParams = params
|
||||
switchToNotLoggedInFlow(params)
|
||||
}
|
||||
} else {
|
||||
@@ -616,12 +432,21 @@ class RootFlowNode(
|
||||
// No session, open login
|
||||
switchToNotLoggedInFlow(null)
|
||||
} else {
|
||||
// Wait for the current session to be restored and for the FTUE to be complete, so that the session is verified,
|
||||
// then display the incoming share on top of it.
|
||||
// The incoming share does not belong to the current session: in case of multiple accounts, the user can switch account
|
||||
// from the room select screen, and the share is kept if the current session is signed out in the meantime.
|
||||
attachSession(latestSessionId).waitForHome()
|
||||
backstack.push(NavTarget.IncomingShare(latestSessionId, shareIntentData))
|
||||
// wait for the current session to be restored
|
||||
val loggedInFlowNode = attachSession(latestSessionId)
|
||||
if (sessionStore.numberOfSessions() > 1) {
|
||||
// Several accounts, let the user choose which one to use
|
||||
backstack.push(
|
||||
NavTarget.AccountSelect(
|
||||
currentSessionId = latestSessionId,
|
||||
shareIntentData = shareIntentData,
|
||||
permalinkData = null,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
// Only one account, directly attach the incoming share node.
|
||||
loggedInFlowNode.attachIncomingShare(shareIntentData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,6 +469,7 @@ class RootFlowNode(
|
||||
backstack.push(
|
||||
NavTarget.AccountSelect(
|
||||
currentSessionId = latestSessionId,
|
||||
shareIntentData = null,
|
||||
permalinkData = permalinkData,
|
||||
)
|
||||
)
|
||||
|
||||
+2
-18
@@ -6,7 +6,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav.session
|
||||
package io.element.android.appnav.di
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.bumble.appyx.core.state.MutableSavedStateMap
|
||||
@@ -21,9 +21,6 @@ import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analyticsproviders.api.AnalyticsUserData
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
@@ -44,12 +41,6 @@ class MatrixSessionCache(
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : MatrixClientProvider {
|
||||
private val sessionIdsToMatrixSession = ConcurrentHashMap<SessionId, InMemoryMatrixSession>()
|
||||
private val _matrixClients = MutableStateFlow<List<MatrixClient>>(emptyList())
|
||||
|
||||
/**
|
||||
* The [MatrixClient]s currently in the cache.
|
||||
*/
|
||||
val matrixClients: StateFlow<List<MatrixClient>> = _matrixClients.asStateFlow()
|
||||
private val restoreMutex = Mutex()
|
||||
|
||||
init {
|
||||
@@ -60,12 +51,10 @@ class MatrixSessionCache(
|
||||
|
||||
fun removeAll() {
|
||||
sessionIdsToMatrixSession.clear()
|
||||
updateMatrixClients()
|
||||
}
|
||||
|
||||
fun remove(sessionId: SessionId) {
|
||||
sessionIdsToMatrixSession.remove(sessionId)
|
||||
updateMatrixClients()
|
||||
}
|
||||
|
||||
override fun getOrNull(sessionId: SessionId): MatrixClient? {
|
||||
@@ -126,20 +115,15 @@ class MatrixSessionCache(
|
||||
|
||||
private fun onNewMatrixClient(matrixClient: MatrixClient) {
|
||||
val syncOrchestrator = syncOrchestratorFactory.create(
|
||||
matrixClient = matrixClient,
|
||||
syncService = matrixClient.syncService,
|
||||
sessionCoroutineScope = matrixClient.sessionCoroutineScope,
|
||||
)
|
||||
sessionIdsToMatrixSession[matrixClient.sessionId] = InMemoryMatrixSession(
|
||||
matrixClient = matrixClient,
|
||||
syncOrchestrator = syncOrchestrator,
|
||||
)
|
||||
updateMatrixClients()
|
||||
syncOrchestrator.start()
|
||||
}
|
||||
|
||||
private fun updateMatrixClients() {
|
||||
_matrixClients.value = sessionIdsToMatrixSession.values.map { it.matrixClient }
|
||||
}
|
||||
}
|
||||
|
||||
private data class InMemoryMatrixSession(
|
||||
+7
-79
@@ -6,7 +6,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav.session
|
||||
package io.element.android.appnav.di
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import dev.zacsweers.metro.Assisted
|
||||
@@ -16,7 +16,7 @@ import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.coroutine.childScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.api.recordTransaction
|
||||
@@ -28,20 +28,16 @@ import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@AssistedInject
|
||||
class SyncOrchestrator(
|
||||
@Assisted private val matrixClient: MatrixClient,
|
||||
@Assisted private val syncService: SyncService,
|
||||
@Assisted sessionCoroutineScope: CoroutineScope,
|
||||
private val appForegroundStateService: AppForegroundStateService,
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
@@ -51,25 +47,17 @@ class SyncOrchestrator(
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(
|
||||
matrixClient: MatrixClient,
|
||||
syncService: SyncService,
|
||||
sessionCoroutineScope: CoroutineScope,
|
||||
): SyncOrchestrator
|
||||
}
|
||||
|
||||
private val tag = "SyncOrchestrator"
|
||||
|
||||
private val syncService = matrixClient.syncService
|
||||
|
||||
private val coroutineScope = sessionCoroutineScope.childScope(dispatchers.io, tag)
|
||||
|
||||
private val started = AtomicBoolean(false)
|
||||
|
||||
/**
|
||||
* Number of consecutive attempts at restarting a sync service which stopped on its own.
|
||||
* Reset once the sync service has been running for [RESTART_BACKOFF_RESET_DELAY].
|
||||
*/
|
||||
private val restartAttempt = AtomicInteger(0)
|
||||
|
||||
/**
|
||||
* Starting observing the app state and network state to start/stop the sync service.
|
||||
*
|
||||
@@ -101,23 +89,12 @@ class SyncOrchestrator(
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun observeStates() = coroutineScope.launch {
|
||||
Timber.tag(tag).d("start observing the app and network state")
|
||||
// Reset the restart backoff once the sync service has been steadily running for a while. Note that
|
||||
// SyncService.startSync() moves the state to Running right away, so resetting on any Running value
|
||||
// would defeat the backoff when the sync service dies again immediately after each restart.
|
||||
syncService.syncState
|
||||
.debounce(RESTART_BACKOFF_RESET_DELAY)
|
||||
.onEach { syncState ->
|
||||
if (syncState == SyncState.Running) {
|
||||
restartAttempt.set(0)
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
val isAppActiveFlows = listOf(
|
||||
appForegroundStateService.isInForeground,
|
||||
appForegroundStateService.isInCall,
|
||||
appForegroundStateService.isSyncingNotificationEvent,
|
||||
appForegroundStateService.hasRingingCall,
|
||||
appForegroundStateService.isSharingLiveLocation,
|
||||
appForegroundStateService.isSharingLiveLocation
|
||||
)
|
||||
val isAppActiveFlow = combine(isAppActiveFlows) { actives -> actives.any { it } }
|
||||
combine(
|
||||
@@ -126,11 +103,6 @@ class SyncOrchestrator(
|
||||
networkMonitor.connectivity,
|
||||
isAppActiveFlow,
|
||||
) { syncState, networkState, isAppActive ->
|
||||
if (matrixClient.isShuttingDown) {
|
||||
Timber.tag(tag).d("Matrix client is shutting down, no need to start the sync service again")
|
||||
return@combine SyncStateAction.NoOp
|
||||
}
|
||||
|
||||
val isNetworkAvailable = networkState == NetworkStatus.Connected
|
||||
|
||||
Timber.tag(tag).d("isAppActive=$isAppActive, isNetworkAvailable=$isNetworkAvailable")
|
||||
@@ -138,26 +110,14 @@ class SyncOrchestrator(
|
||||
SyncStateAction.StopSync
|
||||
} else if (syncState == SyncState.Idle && isAppActive && isNetworkAvailable) {
|
||||
SyncStateAction.StartSync
|
||||
} else if (syncState == SyncState.Error && isAppActive && isNetworkAvailable) {
|
||||
// The sync service has given up, for instance because the homeserver has expired our sliding sync
|
||||
// session and answered 400 M_UNKNOWN_POS. Nothing else will ever restart it, so do it from here,
|
||||
// else the application is stuck until its data is cleared.
|
||||
SyncStateAction.RestartSync
|
||||
} else {
|
||||
SyncStateAction.NoOp
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.debounce { action ->
|
||||
when (action) {
|
||||
// Don't stop the sync immediately, wait a bit to avoid starting/stopping the sync too often
|
||||
SyncStateAction.StopSync -> 3.seconds
|
||||
// Back off before restarting a dead sync service, to avoid hammering a failing homeserver.
|
||||
// A state change in the meantime cancels the pending restart.
|
||||
SyncStateAction.RestartSync -> restartDelay(restartAttempt.get())
|
||||
SyncStateAction.StartSync,
|
||||
SyncStateAction.NoOp -> 0.seconds
|
||||
}
|
||||
// Don't stop the sync immediately, wait a bit to avoid starting/stopping the sync too often
|
||||
if (action == SyncStateAction.StopSync) 3.seconds else 0.seconds
|
||||
}
|
||||
.onCompletion {
|
||||
Timber.tag(tag).d("has been stopped")
|
||||
@@ -167,11 +127,6 @@ class SyncOrchestrator(
|
||||
SyncStateAction.StartSync -> {
|
||||
syncService.startSync()
|
||||
}
|
||||
SyncStateAction.RestartSync -> {
|
||||
val attempt = restartAttempt.incrementAndGet()
|
||||
Timber.tag(tag).w("sync service is dead, restarting it (attempt $attempt)")
|
||||
syncService.startSync()
|
||||
}
|
||||
SyncStateAction.StopSync -> {
|
||||
syncService.stopSync()
|
||||
}
|
||||
@@ -181,35 +136,8 @@ class SyncOrchestrator(
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal val FIRST_RESTART_DELAY = 1.seconds
|
||||
|
||||
/**
|
||||
* Exponential backoff, from [FIRST_RESTART_DELAY], doubling on every attempt, up to [FIRST_RESTART_DELAY * 32].
|
||||
*/
|
||||
@VisibleForTesting
|
||||
internal val RESTART_BACKOFF_DELAYS = listOf(
|
||||
FIRST_RESTART_DELAY,
|
||||
FIRST_RESTART_DELAY * 2,
|
||||
FIRST_RESTART_DELAY * 4,
|
||||
FIRST_RESTART_DELAY * 8,
|
||||
FIRST_RESTART_DELAY * 16,
|
||||
FIRST_RESTART_DELAY * 32,
|
||||
)
|
||||
|
||||
/**
|
||||
* Duration during which the sync service has to stay running before the restart backoff is reset.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
internal val RESTART_BACKOFF_RESET_DELAY = 30.seconds
|
||||
|
||||
private fun restartDelay(attempt: Int): Duration {
|
||||
return RESTART_BACKOFF_DELAYS[attempt.coerceIn(0, RESTART_BACKOFF_DELAYS.lastIndex)]
|
||||
}
|
||||
|
||||
private enum class SyncStateAction {
|
||||
StartSync,
|
||||
RestartSync,
|
||||
StopSync,
|
||||
NoOp,
|
||||
}
|
||||
@@ -76,7 +76,6 @@ class IntentResolver(
|
||||
}
|
||||
|
||||
private fun Intent.canBeIgnored(): Boolean {
|
||||
if (flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0) return true
|
||||
return action == Intent.ACTION_MAIN &&
|
||||
categories?.contains(Intent.CATEGORY_LAUNCHER) == true
|
||||
}
|
||||
|
||||
+4
-7
@@ -8,11 +8,8 @@
|
||||
|
||||
package io.element.android.appnav.loggedin
|
||||
|
||||
sealed interface LoggedInEvent {
|
||||
data class CloseErrorDialog(val doNotShowAgain: Boolean) : LoggedInEvent
|
||||
data object CheckSlidingSyncProxyAvailability : LoggedInEvent
|
||||
data object LogoutAndMigrateToNativeSlidingSync : LoggedInEvent
|
||||
data object DismissLocalNetworkPermissionPrompt : LoggedInEvent
|
||||
|
||||
data object RequestLocationNetworkPermission : LoggedInEvent
|
||||
sealed interface LoggedInEvents {
|
||||
data class CloseErrorDialog(val doNotShowAgain: Boolean) : LoggedInEvents
|
||||
data object CheckSlidingSyncProxyAvailability : LoggedInEvents
|
||||
data object LogoutAndMigrateToNativeSlidingSync : LoggedInEvents
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
package io.element.android.appnav.loggedin
|
||||
|
||||
import android.Manifest
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
@@ -16,7 +15,6 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -38,10 +36,6 @@ import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||
import io.element.android.libraries.permissions.api.PermissionsEvent
|
||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionAdvisor
|
||||
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||
import io.element.android.libraries.push.api.PushService
|
||||
import io.element.android.libraries.push.api.PusherRegistrationFailure
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
@@ -64,12 +58,7 @@ class LoggedInPresenter(
|
||||
private val encryptionService: EncryptionService,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
private val localNetworkPermissionAdvisor: LocalNetworkPermissionAdvisor,
|
||||
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
||||
) : Presenter<LoggedInState> {
|
||||
private val localNetworkPermissionsPresenter: PermissionsPresenter =
|
||||
permissionsPresenterFactory.create(Manifest.permission.ACCESS_LOCAL_NETWORK)
|
||||
|
||||
@Composable
|
||||
override fun present(): LoggedInState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
@@ -105,18 +94,9 @@ class LoggedInPresenter(
|
||||
}
|
||||
val syncIndicator by matrixClient.roomListService.syncIndicator.collectAsState()
|
||||
val isOnline by syncService.isOnline.collectAsState()
|
||||
val hasNetwork by networkMonitor.connectivity.collectAsState()
|
||||
val syncIndicatorState by remember {
|
||||
val showSyncSpinner by remember {
|
||||
derivedStateOf {
|
||||
when {
|
||||
isOnline -> if (syncIndicator == RoomListService.SyncIndicator.Show) {
|
||||
SyncIndicatorState.Syncing
|
||||
} else {
|
||||
SyncIndicatorState.Hidden
|
||||
}
|
||||
hasNetwork == NetworkStatus.Connected -> SyncIndicatorState.ServerUnreachable
|
||||
else -> SyncIndicatorState.Hidden
|
||||
}
|
||||
isOnline && syncIndicator == RoomListService.SyncIndicator.Show
|
||||
}
|
||||
}
|
||||
var forceNativeSlidingSyncMigration by remember { mutableStateOf(false) }
|
||||
@@ -129,12 +109,6 @@ class LoggedInPresenter(
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
// Keep automatic call status (m.call) in sync with homeserver support.
|
||||
val enabled = matrixClient.isUserStatusSupported().getOrDefault(false)
|
||||
matrixClient.enableAutomaticCallStatus(enabled)
|
||||
}
|
||||
|
||||
val networkConnectivity by networkMonitor.connectivity.collectAsState()
|
||||
LaunchedEffect(networkConnectivity) {
|
||||
if (networkConnectivity == NetworkStatus.Connected) {
|
||||
@@ -143,24 +117,9 @@ class LoggedInPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
val localNetworkPermissionState = localNetworkPermissionsPresenter.present()
|
||||
var localNetworkPromptDismissedThisSession by remember { mutableStateOf(false) }
|
||||
val advisorRequestsPrompt by produceState(initialValue = false, localNetworkPermissionState.permissionGranted) {
|
||||
value = localNetworkPermissionAdvisor.shouldRequestPermissionFor(matrixClient.homeserverUrl)
|
||||
}
|
||||
LaunchedEffect(localNetworkPermissionState.permissionGranted) {
|
||||
if (localNetworkPermissionState.permissionGranted) localNetworkPromptDismissedThisSession = false
|
||||
}
|
||||
val localNetworkPermissionDialog = when {
|
||||
!advisorRequestsPrompt -> LocalNetworkPermissionDialog.None
|
||||
localNetworkPromptDismissedThisSession -> LocalNetworkPermissionDialog.None
|
||||
localNetworkPermissionState.shouldShowRationale -> LocalNetworkPermissionDialog.Rationale
|
||||
else -> LocalNetworkPermissionDialog.Settings
|
||||
}
|
||||
|
||||
fun handleEvent(event: LoggedInEvent) {
|
||||
fun handleEvent(event: LoggedInEvents) {
|
||||
when (event) {
|
||||
is LoggedInEvent.CloseErrorDialog -> {
|
||||
is LoggedInEvents.CloseErrorDialog -> {
|
||||
pusherRegistrationState.value = AsyncData.Uninitialized
|
||||
if (event.doNotShowAgain) {
|
||||
coroutineScope.launch {
|
||||
@@ -168,34 +127,22 @@ class LoggedInPresenter(
|
||||
}
|
||||
}
|
||||
}
|
||||
LoggedInEvent.CheckSlidingSyncProxyAvailability -> coroutineScope.launch {
|
||||
LoggedInEvents.CheckSlidingSyncProxyAvailability -> coroutineScope.launch {
|
||||
forceNativeSlidingSyncMigration = matrixClient.needsForcedNativeSlidingSyncMigration().getOrDefault(false)
|
||||
}
|
||||
LoggedInEvent.LogoutAndMigrateToNativeSlidingSync -> coroutineScope.launch {
|
||||
LoggedInEvents.LogoutAndMigrateToNativeSlidingSync -> coroutineScope.launch {
|
||||
// Force the logout since Native Sliding Sync is already enforced by the SDK
|
||||
matrixClient.logout(userInitiated = true, ignoreSdkError = true)
|
||||
}
|
||||
LoggedInEvent.DismissLocalNetworkPermissionPrompt -> {
|
||||
localNetworkPromptDismissedThisSession = true
|
||||
}
|
||||
LoggedInEvent.RequestLocationNetworkPermission -> {
|
||||
if (localNetworkPermissionDialog == LocalNetworkPermissionDialog.Settings) {
|
||||
localNetworkPermissionState.eventSink(PermissionsEvent.OpenSystemSettingAndCloseDialog)
|
||||
} else {
|
||||
localNetworkPermissionState.eventSink(PermissionsEvent.RequestPermissions)
|
||||
}
|
||||
localNetworkPromptDismissedThisSession = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return LoggedInState(
|
||||
syncIndicatorState = syncIndicatorState,
|
||||
showSyncSpinner = showSyncSpinner,
|
||||
pusherRegistrationState = pusherRegistrationState.value,
|
||||
ignoreRegistrationError = ignoreRegistrationError,
|
||||
forceNativeSlidingSyncMigration = forceNativeSlidingSyncMigration,
|
||||
appName = buildMeta.applicationName,
|
||||
localNetworkPermissionDialog = localNetworkPermissionDialog,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,20 +9,12 @@
|
||||
package io.element.android.appnav.loggedin
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||
|
||||
enum class SyncIndicatorState {
|
||||
Hidden,
|
||||
Syncing,
|
||||
ServerUnreachable,
|
||||
}
|
||||
|
||||
data class LoggedInState(
|
||||
val syncIndicatorState: SyncIndicatorState,
|
||||
val showSyncSpinner: Boolean,
|
||||
val pusherRegistrationState: AsyncData<Unit>,
|
||||
val ignoreRegistrationError: Boolean,
|
||||
val forceNativeSlidingSyncMigration: Boolean,
|
||||
val appName: String,
|
||||
val localNetworkPermissionDialog: LocalNetworkPermissionDialog,
|
||||
val eventSink: (LoggedInEvent) -> Unit,
|
||||
val eventSink: (LoggedInEvents) -> Unit,
|
||||
)
|
||||
|
||||
+4
-10
@@ -10,34 +10,28 @@ package io.element.android.appnav.loggedin
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||
import io.element.android.libraries.push.api.PusherRegistrationFailure
|
||||
|
||||
open class LoggedInStatePreviewParam : PreviewParameterProvider<LoggedInState> {
|
||||
open class LoggedInStateProvider : PreviewParameterProvider<LoggedInState> {
|
||||
override val values: Sequence<LoggedInState>
|
||||
get() = sequenceOf(
|
||||
aLoggedInState(),
|
||||
aLoggedInState(syncIndicatorState = SyncIndicatorState.Syncing),
|
||||
aLoggedInState(syncIndicatorState = SyncIndicatorState.ServerUnreachable),
|
||||
aLoggedInState(showSyncSpinner = true),
|
||||
aLoggedInState(pusherRegistrationState = AsyncData.Failure(PusherRegistrationFailure.NoDistributorsAvailable())),
|
||||
aLoggedInState(forceNativeSlidingSyncMigration = true),
|
||||
aLoggedInState(localNetworkPermissionDialog = LocalNetworkPermissionDialog.Rationale),
|
||||
aLoggedInState(localNetworkPermissionDialog = LocalNetworkPermissionDialog.Settings),
|
||||
)
|
||||
}
|
||||
|
||||
fun aLoggedInState(
|
||||
syncIndicatorState: SyncIndicatorState = SyncIndicatorState.Hidden,
|
||||
showSyncSpinner: Boolean = false,
|
||||
pusherRegistrationState: AsyncData<Unit> = AsyncData.Uninitialized,
|
||||
forceNativeSlidingSyncMigration: Boolean = false,
|
||||
appName: String = "Element X",
|
||||
localNetworkPermissionDialog: LocalNetworkPermissionDialog = LocalNetworkPermissionDialog.None,
|
||||
) = LoggedInState(
|
||||
syncIndicatorState = syncIndicatorState,
|
||||
showSyncSpinner = showSyncSpinner,
|
||||
pusherRegistrationState = pusherRegistrationState,
|
||||
ignoreRegistrationError = false,
|
||||
forceNativeSlidingSyncMigration = forceNativeSlidingSyncMigration,
|
||||
appName = appName,
|
||||
localNetworkPermissionDialog = localNetworkPermissionDialog,
|
||||
eventSink = {},
|
||||
)
|
||||
@@ -25,7 +25,6 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||
import io.element.android.libraries.matrix.api.exception.isNetworkError
|
||||
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialogView
|
||||
import io.element.android.libraries.push.api.PusherRegistrationFailure
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@@ -37,7 +36,7 @@ fun LoggedInView(
|
||||
) {
|
||||
OnLifecycleEvent { _, event ->
|
||||
if (event == Lifecycle.Event.ON_RESUME) {
|
||||
state.eventSink(LoggedInEvent.CheckSlidingSyncProxyAvailability)
|
||||
state.eventSink(LoggedInEvents.CheckSlidingSyncProxyAvailability)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
@@ -47,7 +46,7 @@ fun LoggedInView(
|
||||
) {
|
||||
SyncStateView(
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
state = state.syncIndicatorState,
|
||||
isVisible = state.showSyncSpinner,
|
||||
)
|
||||
}
|
||||
when (state.pusherRegistrationState) {
|
||||
@@ -63,10 +62,10 @@ fun LoggedInView(
|
||||
content = stringResource(id = CommonStrings.common_error_registering_pusher_android, reason),
|
||||
cancelText = stringResource(id = CommonStrings.common_settings),
|
||||
onDismiss = {
|
||||
state.eventSink(LoggedInEvent.CloseErrorDialog(it))
|
||||
state.eventSink(LoggedInEvents.CloseErrorDialog(it))
|
||||
},
|
||||
onCancel = {
|
||||
state.eventSink(LoggedInEvent.CloseErrorDialog(false))
|
||||
state.eventSink(LoggedInEvents.CloseErrorDialog(false))
|
||||
navigateToNotificationTroubleshoot()
|
||||
}
|
||||
)
|
||||
@@ -79,16 +78,10 @@ fun LoggedInView(
|
||||
ForceNativeSlidingSyncMigrationDialog(
|
||||
appName = state.appName,
|
||||
onSubmit = {
|
||||
state.eventSink(LoggedInEvent.LogoutAndMigrateToNativeSlidingSync)
|
||||
state.eventSink(LoggedInEvents.LogoutAndMigrateToNativeSlidingSync)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
LocalNetworkPermissionDialogView(
|
||||
dialog = state.localNetworkPermissionDialog,
|
||||
onSubmit = { state.eventSink(LoggedInEvent.RequestLocationNetworkPermission) },
|
||||
onDismiss = { state.eventSink(LoggedInEvent.DismissLocalNetworkPermissionPrompt) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun Throwable.getReason(): String? {
|
||||
@@ -124,7 +117,7 @@ private fun ForceNativeSlidingSyncMigrationDialog(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun LoggedInViewPreview(@PreviewParameter(LoggedInStatePreviewParam::class) state: LoggedInState) = ElementPreview {
|
||||
internal fun LoggedInViewPreview(@PreviewParameter(LoggedInStateProvider::class) state: LoggedInState) = ElementPreview {
|
||||
LoggedInView(
|
||||
state = state,
|
||||
navigateToNotificationTroubleshoot = {},
|
||||
|
||||
@@ -41,7 +41,7 @@ class SendQueues(
|
||||
fun launchIn(coroutineScope: CoroutineScope) {
|
||||
combine(
|
||||
syncService.syncState,
|
||||
matrixClient.sendQueueDisabledFlow().onEach { Timber.tag("SendQueues").d("Send queue disabled for room: $it") },
|
||||
matrixClient.sendQueueDisabledFlow(),
|
||||
) { syncState, _ -> syncState }
|
||||
.debounce(SEND_QUEUES_RETRY_DELAY_MILLIS)
|
||||
.onEach { syncState ->
|
||||
|
||||
@@ -25,23 +25,18 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
fun SyncStateView(
|
||||
state: SyncIndicatorState,
|
||||
isVisible: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = state != SyncIndicatorState.Hidden,
|
||||
visible = isVisible,
|
||||
modifier = modifier,
|
||||
enter = fadeIn(spring(stiffness = 500F)),
|
||||
exit = fadeOut(spring(stiffness = 500F)),
|
||||
) {
|
||||
when (state) {
|
||||
SyncIndicatorState.ServerUnreachable -> AsyncIndicator.Failure(
|
||||
text = stringResource(id = CommonStrings.common_server_unreachable),
|
||||
)
|
||||
else -> AsyncIndicator.Loading(
|
||||
text = stringResource(id = CommonStrings.common_syncing),
|
||||
)
|
||||
}
|
||||
AsyncIndicator.Loading(
|
||||
text = stringResource(id = CommonStrings.common_syncing),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,18 +46,7 @@ internal fun SyncStateViewPreview() = ElementPreview {
|
||||
// Add a box to see the shadow
|
||||
Box(modifier = Modifier.padding(24.dp)) {
|
||||
SyncStateView(
|
||||
state = SyncIndicatorState.Syncing
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun SyncStateViewServerUnreachablePreview() = ElementPreview {
|
||||
// Add a box to see the shadow
|
||||
Box(modifier = Modifier.padding(24.dp)) {
|
||||
SyncStateView(
|
||||
state = SyncIndicatorState.ServerUnreachable
|
||||
isVisible = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ 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.features.joinroom.api.JoinRoomEntryPoint
|
||||
import io.element.android.features.roomaliasresolver.api.RoomAliasResolverEntryPoint
|
||||
import io.element.android.features.roomaliasresolver.api.RoomAliasResolverEntryPoint.Params
|
||||
import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint
|
||||
import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint.Params
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package io.element.android.appnav.room.joined
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
|
||||
+1
-1
@@ -201,7 +201,7 @@ class JoinedRoomLoadedFlowNode(
|
||||
} else {
|
||||
(graph as TimelineBindings).timelineProvider
|
||||
}
|
||||
val params = ForwardEntryPoint.Params(listOf(navTarget.eventId), timelineProvider)
|
||||
val params = ForwardEntryPoint.Params(navTarget.eventId, timelineProvider)
|
||||
val callback = object : ForwardEntryPoint.Callback {
|
||||
override fun onDone(roomIds: List<RoomId>) {
|
||||
backstack.pop()
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -28,7 +29,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.utils.DelayedVisibility
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomState
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomStatePreviewParam
|
||||
import io.element.android.libraries.matrix.ui.room.LoadingRoomStateProvider
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
@@ -67,6 +68,7 @@ fun LoadingRoomNodeView(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun LoadingRoomTopBar(
|
||||
onBackClick: () -> Unit,
|
||||
@@ -82,7 +84,7 @@ private fun LoadingRoomTopBar(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun LoadingRoomNodeViewPreview(@PreviewParameter(LoadingRoomStatePreviewParam::class) state: LoadingRoomState) = ElementPreview {
|
||||
internal fun LoadingRoomNodeViewPreview(@PreviewParameter(LoadingRoomStateProvider::class) state: LoadingRoomState) = ElementPreview {
|
||||
LoadingRoomNodeView(
|
||||
state = state,
|
||||
onBackClick = {}
|
||||
|
||||
@@ -11,9 +11,9 @@ package io.element.android.appnav.root
|
||||
import com.bumble.appyx.core.state.MutableSavedStateMap
|
||||
import com.bumble.appyx.core.state.SavedStateMap
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.appnav.session.MatrixSessionCache
|
||||
import io.element.android.appnav.di.MatrixSessionCache
|
||||
import io.element.android.features.preferences.api.CacheService
|
||||
import io.element.android.libraries.matrixmedia.api.ImageLoaderHolder
|
||||
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import io.element.android.features.rageshake.api.detection.aRageshakeDetectionSt
|
||||
import io.element.android.services.apperror.api.AppErrorState
|
||||
import io.element.android.services.apperror.api.aAppErrorState
|
||||
|
||||
open class RootStatePreviewParam : PreviewParameterProvider<RootState> {
|
||||
open class RootStateProvider : PreviewParameterProvider<RootState> {
|
||||
override val values: Sequence<RootState>
|
||||
get() = sequenceOf(
|
||||
aRootState().copy(
|
||||
@@ -60,7 +60,7 @@ fun RootView(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun RootViewPreview(@PreviewParameter(RootStatePreviewParam::class) rootState: RootState) = ElementPreview {
|
||||
internal fun RootViewPreview(@PreviewParameter(RootStateProvider::class) rootState: RootState) = ElementPreview {
|
||||
RootView(
|
||||
state = rootState,
|
||||
onOpenBugReport = {},
|
||||
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav.verification
|
||||
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.appnav.session.MatrixSessionCache
|
||||
import io.element.android.libraries.core.coroutine.withPreviousValue
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
||||
import io.element.android.services.appnavstate.api.AppForegroundStateService
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
data class IncomingVerificationRequestData(
|
||||
val sessionId: SessionId,
|
||||
val verificationRequest: VerificationRequest.Incoming,
|
||||
)
|
||||
|
||||
/**
|
||||
* Listens to the incoming verification requests of all the sessions in [MatrixSessionCache], not only the current one.
|
||||
*/
|
||||
@Inject
|
||||
class IncomingVerificationRequestObserver(
|
||||
private val matrixSessionCache: MatrixSessionCache,
|
||||
private val appForegroundStateService: AppForegroundStateService,
|
||||
) {
|
||||
/**
|
||||
* Emits the incoming verification requests once the app is in foreground.
|
||||
* Requests received while the app is in background for too long are discarded.
|
||||
*/
|
||||
fun incomingVerificationRequests(): Flow<IncomingVerificationRequestData> = channelFlow {
|
||||
launch {
|
||||
matrixSessionCache.matrixClients.withPreviousValue().collect { (previousMatrixClients, matrixClients) ->
|
||||
val previousMatrixClientSet = previousMatrixClients.orEmpty().toSet()
|
||||
// Remove the listener of the clients which are not in the cache anymore
|
||||
(previousMatrixClientSet - matrixClients.toSet()).forEach { matrixClient ->
|
||||
matrixClient.sessionVerificationService.setListener(null)
|
||||
}
|
||||
(matrixClients - previousMatrixClientSet).forEach { matrixClient ->
|
||||
val sessionId = matrixClient.sessionId
|
||||
matrixClient.sessionVerificationService.setListener(
|
||||
object : SessionVerificationServiceListener {
|
||||
override fun onIncomingSessionRequest(verificationRequest: VerificationRequest.Incoming) {
|
||||
launch {
|
||||
// Wait until the app is in foreground to display the incoming verification request
|
||||
// TODO there should also be a timeout for > 10 minutes elapsed since the request was created,
|
||||
// but the SDK doesn't expose that info yet
|
||||
val isInForeground = withTimeoutOrNull(2.minutes) {
|
||||
appForegroundStateService.isInForeground.first { it }
|
||||
}
|
||||
if (isInForeground == null) {
|
||||
Timber.w("Incoming verification request ${verificationRequest.details.flowId} discarded due to timeout.")
|
||||
} else {
|
||||
send(IncomingVerificationRequestData(sessionId, verificationRequest))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
awaitClose {
|
||||
matrixSessionCache.matrixClients.value.forEach { matrixClient ->
|
||||
matrixClient.sessionVerificationService.setListener(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav.verification
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.composable.Children
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.navigation.model.permanent.PermanentNavModel
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.node.ParentNode
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.appnav.di.SessionGraphFactory
|
||||
import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.callback
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.DependencyInjectionGraphOwner
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* Display an incoming verification request received by a session which is not the current one, without switching the current session.
|
||||
* It sets up the Session graph of the session receiving the request.
|
||||
*/
|
||||
@ContributesNode(AppScope::class)
|
||||
@AssistedInject
|
||||
class OtherSessionIncomingVerificationNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
sessionGraphFactory: SessionGraphFactory,
|
||||
matrixClientProvider: MatrixClientProvider,
|
||||
private val incomingVerificationEntryPoint: IncomingVerificationEntryPoint,
|
||||
) : ParentNode<OtherSessionIncomingVerificationNode.NavTarget>(
|
||||
navModel = PermanentNavModel(
|
||||
navTargets = setOf(NavTarget),
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
), DependencyInjectionGraphOwner {
|
||||
interface Callback : Plugin {
|
||||
fun onDone()
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
object NavTarget : Parcelable
|
||||
|
||||
data class Inputs(
|
||||
val sessionId: SessionId,
|
||||
val verificationRequest: VerificationRequest.Incoming,
|
||||
) : NodeInputs
|
||||
|
||||
private val callback: Callback = callback()
|
||||
private val inputs: Inputs = inputs()
|
||||
override val graph = sessionGraphFactory.create(
|
||||
requireNotNull(matrixClientProvider.getOrNull(inputs.sessionId)) { "No MatrixClient for session ${inputs.sessionId}" }
|
||||
)
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return incomingVerificationEntryPoint.createNode(
|
||||
parentNode = this,
|
||||
buildContext = buildContext,
|
||||
params = IncomingVerificationEntryPoint.Params(inputs.verificationRequest),
|
||||
callback = object : IncomingVerificationEntryPoint.Callback {
|
||||
override fun onDone() {
|
||||
callback.onDone()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
Children(
|
||||
navModel = navModel,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
-89
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav.verification
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.composable.Children
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.navigation.model.permanent.PermanentNavModel
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.node.ParentNode
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.appnav.di.SessionGraphFactory
|
||||
import io.element.android.features.ftue.api.SessionVerificationEntryPoint
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.callback
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.DependencyInjectionGraphOwner
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* Display the verification of a session which is not the current one, without switching the current session.
|
||||
* It sets up the Session graph of the session to verify.
|
||||
*/
|
||||
@ContributesNode(AppScope::class)
|
||||
@AssistedInject
|
||||
class OtherSessionVerificationNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
sessionGraphFactory: SessionGraphFactory,
|
||||
matrixClientProvider: MatrixClientProvider,
|
||||
private val sessionVerificationEntryPoint: SessionVerificationEntryPoint,
|
||||
) : ParentNode<OtherSessionVerificationNode.NavTarget>(
|
||||
navModel = PermanentNavModel(
|
||||
navTargets = setOf(NavTarget),
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
), DependencyInjectionGraphOwner {
|
||||
interface Callback : Plugin {
|
||||
fun onDone()
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
object NavTarget : Parcelable
|
||||
|
||||
data class Inputs(
|
||||
val sessionId: SessionId,
|
||||
) : NodeInputs
|
||||
|
||||
private val callback: Callback = callback()
|
||||
private val inputs: Inputs = inputs()
|
||||
override val graph = sessionGraphFactory.create(
|
||||
requireNotNull(matrixClientProvider.getOrNull(inputs.sessionId)) { "No MatrixClient for session ${inputs.sessionId}" }
|
||||
)
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return sessionVerificationEntryPoint.createNode(
|
||||
parentNode = this,
|
||||
buildContext = buildContext,
|
||||
callback = object : SessionVerificationEntryPoint.Callback {
|
||||
override fun onDone() {
|
||||
callback.onDone()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
Children(
|
||||
navModel = navModel,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<?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>
|
||||
@@ -2,5 +2,5 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="banner_migrate_to_native_sliding_sync_action">"Odhlásit se a upgradovat"</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s již nepodporuje starý protokol. Odhlaste se a znovu přihlaste, abyste mohli pokračovat v používání aplikace."</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Váš poskytovatel účtu již nepodporuje starý protokol. Chcete-li aplikaci nadále používat, odhlaste se a znovu se přihlaste."</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Váš domovský server již nepodporuje starý protokol. Chcete-li pokračovat v používání aplikace, odhlaste se a znovu se přihlaste."</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="banner_migrate_to_native_sliding_sync_action">"Abmelden und aktualisieren"</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s unterstützt das alte Protokoll nicht mehr. Bitte melde dich ab und wieder an, um die App weiter nutzen zu können."</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Dein Kontoanbieter unterstützt das alte Protokoll nicht mehr. Bitte melde dich ab und wieder an, um die App weiter zu nutzen."</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Dein Homeserver unterstützt das alte Protokoll nicht mehr. Bitte logge dich aus und melde dich wieder an, um die App weiter zu nutzen."</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<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>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Ο οικιακός διακομιστής σου δεν υποστηρίζει πλέον το παλιό πρωτόκολλο. Αποσυνδέσου και συνδέσου ξανά για να συνεχίσεις να χρησιμοποιείς την εφαρμογή."</string>
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user