Compare commits
241 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c10f4e521d | |||
| 0524b3c274 | |||
| 4187f9814d | |||
| d732f043dc | |||
| d0b4a21cfe | |||
| f9513193e2 | |||
| 62cc7e0505 | |||
| 03f3e8b3e0 | |||
| 264ec2416f | |||
| fefcb7a41d | |||
| f794cb9f06 | |||
| e915cd242b | |||
| afdf07b23e | |||
| 3bb7fe97e6 | |||
| 1619155b87 | |||
| 09b7732c1b | |||
| f9112cc583 | |||
| 03304b3ece | |||
| 3edf4a6a69 | |||
| b0c9947a11 | |||
| 14e4dd34ed | |||
| 00135bbfea | |||
| 8a54ad6e6b | |||
| 5ef90250d9 | |||
| 07ea371806 | |||
| 7f13bb95cc | |||
| 754eb3d0c1 | |||
| 68bf671f10 | |||
| 8923ffb7f1 | |||
| 3ad0ca7974 | |||
| 330dd243e2 | |||
| 3f7ed5d87a | |||
| 5f4bb30b18 | |||
| 02a40cf2d5 | |||
| 96d443e443 | |||
| 9e160e027d | |||
| 6296a538eb | |||
| b8d934058c | |||
| 7624391bdd | |||
| c7f4c96f70 | |||
| 566b0c6f86 | |||
| 101e23da38 | |||
| fad8dce166 | |||
| 3642065202 | |||
| d25362dd36 | |||
| d7a2366a60 | |||
| 3dabece5f8 | |||
| f25ca5b963 | |||
| a8b3db4621 | |||
| 6f0788fd34 | |||
| e92923a77d | |||
| c2e8912fe2 | |||
| e9abed9178 | |||
| 7f4d264ee0 | |||
| 74bc2c0f5d | |||
| 9d7ee09715 | |||
| 2e161914a9 | |||
| ebc16bb3e1 | |||
| 55f3a1acfb | |||
| a7f4418555 | |||
| ac822544f2 | |||
| 06c9c9b77c | |||
| 8731e2243a | |||
| 20a55f4840 | |||
| 28abf3049e | |||
| 6124d99273 | |||
| e0161e5b25 | |||
| 15149af6b7 | |||
| 25479480c4 | |||
| 885e042c12 | |||
| 4fd0be1fe5 | |||
| 4190939ff8 | |||
| a5d8b6eb2a | |||
| 243acf1ee0 | |||
| 3ec7aa2a55 | |||
| 387022a9af | |||
| 7c3d1e2e80 | |||
| 56ad052575 | |||
| 9672950512 | |||
| 445b9a1ee7 | |||
| 99b6151be7 | |||
| 1ddb32645c | |||
| 654f149df9 | |||
| 09c109a6cb | |||
| 30a22eeae6 | |||
| e4aee9eccd | |||
| 8f9dfb5119 | |||
| 474b039a87 | |||
| fdb2958183 | |||
| 5c7bac58ce | |||
| e346f68fc8 | |||
| e4ce5a821a | |||
| e91df7416a | |||
| e3a2e98068 | |||
| 8d9ad76531 | |||
| ad2655c87b | |||
| 31d4ba3793 | |||
| ac1be8c87a | |||
| 8840e2f96b | |||
| b6f79a883e | |||
| bad39d62e9 | |||
| 06d778859c | |||
| 762823ebd7 | |||
| 74f69b7eb3 | |||
| 4566712ad3 | |||
| ac60d0fce7 | |||
| 60837c0d7d | |||
| 301f930bcc | |||
| bfe21698f2 | |||
| 2010620fbc | |||
| 6181f60b5e | |||
| bf1ace48f2 | |||
| 25716f6f3a | |||
| 00ffff4c0a | |||
| 6b68ddcb25 | |||
| 1fdc778fe2 | |||
| 7015172df1 | |||
| f35546b473 | |||
| d8b1a41f56 | |||
| 416da32c40 | |||
| a3fbbed8f4 | |||
| 57ed76bd03 | |||
| c1dfd6d6ae | |||
| 54d452c364 | |||
| 01c8064a16 | |||
| 79f1423a30 | |||
| 4efc82fa74 | |||
| 58b3830187 | |||
| a8d6652e03 | |||
| f954a20f67 | |||
| 71ad652c38 | |||
| 6cce74db88 | |||
| 16eec4d024 | |||
| 5ca1435e9e | |||
| 29743d3c89 | |||
| 89b4722a58 | |||
| 99343926ff | |||
| 96e1c93f38 | |||
| f91fd4b535 | |||
| 5d0d8bedcd | |||
| 5f0461b1cd | |||
| 79163b68b5 | |||
| c80eb12e2f | |||
| 73ef038035 | |||
| ad84122285 | |||
| fd799a3bdf | |||
| dd3beb11bd | |||
| fb7bc50cdb | |||
| 1a5336e650 | |||
| b2a7c6548a | |||
| 95e656cc32 | |||
| b62242cdd2 | |||
| a7e6d8af6e | |||
| e282212270 | |||
| 87aff40b26 | |||
| 0aea5f94ec | |||
| f9a8acf4a5 | |||
| 29b8068d4a | |||
| 4a143c1053 | |||
| be872e6bec | |||
| 061fd82c1a | |||
| d29dad40f9 | |||
| dd24e14cf6 | |||
| 1cfd3e4dfb | |||
| 553a4785e1 | |||
| 2a0e41652a | |||
| 078b63fd7c | |||
| 889fd08b5c | |||
| 167c1ced80 | |||
| 57617a362a | |||
| 73a8a3d5d8 | |||
| eeaaac2969 | |||
| 7309e49286 | |||
| 6c34d647ca | |||
| af5a237b4a | |||
| 0528b787c9 | |||
| eca45b574e | |||
| 1640adcf9d | |||
| 42966b393f | |||
| f4c79be328 | |||
| 742902ef02 | |||
| 5697c9664f | |||
| bad32cbbda | |||
| 2902549a88 | |||
| c4c0c482f0 | |||
| b3b8467636 | |||
| 0bda1cd0d0 | |||
| 35dce84c7f | |||
| 6ca04ef772 | |||
| fa5e871e7e | |||
| 3a5e9f43b9 | |||
| 123037a612 | |||
| e98cbdfce1 | |||
| 643ece6af7 | |||
| 82c6167441 | |||
| 33e2f72792 | |||
| 5e30f94ccc | |||
| 3a2f19f26c | |||
| b226b81082 | |||
| cc736ec759 | |||
| 154a554d65 | |||
| fdc98f6b8c | |||
| 5b53686c3e | |||
| 88d419c03f | |||
| 5a2977134b | |||
| ea94996db7 | |||
| 3312526055 | |||
| bb36787371 | |||
| a201b3766e | |||
| 716d6d20b2 | |||
| 7722107001 | |||
| 5e3b1463d4 | |||
| cd64b5e376 | |||
| bfcce0e94b | |||
| 77dbd56e4e | |||
| 0e5a7e9396 | |||
| aeadd9879e | |||
| a98fd96303 | |||
| 2c2d803e58 | |||
| 40f8b48256 | |||
| d653ceb84c | |||
| 0399dab1da | |||
| bdb42759de | |||
| 257040f5ca | |||
| 8d8ac9373e | |||
| 65ed527384 | |||
| e0046e9862 | |||
| 4b479638a4 | |||
| 0daf7ecc7d | |||
| 784f5df426 | |||
| 880a189617 | |||
| aed149731b | |||
| 51816e0281 | |||
| 200b98b72b | |||
| 7148f2f28f | |||
| 78aa500d23 | |||
| af9da10254 | |||
| ef7cb35de8 | |||
| 2579e68edc | |||
| 9f26079b84 | |||
| 7f5ce76860 |
@@ -1,278 +0,0 @@
|
||||
---
|
||||
name: releasing-healthchat
|
||||
description: Cut a HealthChat Google Play release of element-x-android by tagging release/healthchat. Use when the user asks to release, publish, tag, deploy, ship, or cut a new HealthChat Android version, asks about the Play Store release process, asks about rotating the Android release keystore, or asks about the required GitHub Actions secrets for the release pipeline.
|
||||
---
|
||||
|
||||
# Releasing HealthChat (element-x-android)
|
||||
|
||||
Releases are driven by git tags of the form `healthchat-v<version>` pushed to
|
||||
`origin`. The tag triggers `.github/workflows/healthchat-release-playstore.yml`,
|
||||
which runs on the self-hosted `novaloop-incus-large` runner, builds a signed
|
||||
AAB, and uploads it to the Play Store **internal** track. No GitHub Release is
|
||||
created.
|
||||
|
||||
**Promotion to production is always a manual step** in Play Console after an
|
||||
internal build has been validated — see "Promoting to production" below.
|
||||
|
||||
## Preflight checklist
|
||||
|
||||
Verify each item before tagging. If any fail, fix and commit first.
|
||||
|
||||
- [ ] On branch `release/healthchat` with a clean working tree (`git status`).
|
||||
- [ ] `HEALTHCHAT_CHANGES.md` has a section `## Changes in <new-version> (<date>)`
|
||||
at the top of the file. The pipeline's awk extractor matches this exact
|
||||
heading format; no match means a fallback placeholder ships as the Play
|
||||
Store "What's new" text.
|
||||
- [ ] `plugins/src/main/kotlin/Versions.kt` `versionYear`, `versionMonth`, and
|
||||
`versionReleaseNumber` produce the tag's core version (format
|
||||
`<year>.<month zero-padded to 2>.<releaseNumber>`, e.g. `26.07.1`).
|
||||
**The workflow hard-fails** if they don't match — this is load-bearing,
|
||||
not cosmetic.
|
||||
- [ ] If you edited `HEALTHCHAT_CHANGES.md` or `Versions.kt`, both commits are
|
||||
pushed to `release/healthchat` **before** you push the tag.
|
||||
|
||||
## Cutting the release (normal path)
|
||||
|
||||
```bash
|
||||
# from release/healthchat, fully committed:
|
||||
git tag healthchat-v<YEAR>.<MONTH>.<RELEASE>
|
||||
git push origin healthchat-v<YEAR>.<MONTH>.<RELEASE>
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
- `git tag healthchat-v26.07.1` (next release in July 2026)
|
||||
- `git tag healthchat-v26.08.0` (first release in August 2026)
|
||||
- `git tag healthchat-v26.07.2-rc1` (release candidate)
|
||||
|
||||
Watch the workflow with `gh run watch` or in the GitHub Actions UI. When it
|
||||
finishes, the build appears in Play Console → Internal testing within a few
|
||||
minutes (Play Store's processing queue).
|
||||
|
||||
## Manual / retry path (workflow_dispatch)
|
||||
|
||||
Use when retrying a failed run or rebuilding without moving tags.
|
||||
|
||||
1. GitHub → Actions → "HealthChat Release to Google Play Store" → **Run workflow**.
|
||||
2. Fill in:
|
||||
- **version** (required): e.g. `26.07.1`. Same regex validation as tag path.
|
||||
Must still match `Versions.kt`.
|
||||
- **track**: default `internal`; pick `alpha`/`beta`/`production` to promote
|
||||
an already-uploaded build to a higher track without rebuilding (note:
|
||||
Play Console is usually a better UX for promotion).
|
||||
- **release_status**: default `completed`; use `draft` for dry-runs that
|
||||
land in Play Console but stay unpublished.
|
||||
- **release_notes_override** (optional): replaces the
|
||||
`HEALTHCHAT_CHANGES.md` lookup. Useful for ad-hoc test builds.
|
||||
3. Run. The AAB is built and uploaded exactly as the tag path would.
|
||||
|
||||
## Runner and build tuning
|
||||
|
||||
The job runs on `novaloop-incus-large` — an incus container with 4 cores and
|
||||
24 GiB, not a GitHub-hosted runner. Two consequences the workflow handles, and
|
||||
that anyone editing it needs to keep in mind:
|
||||
|
||||
- **The image ships no Android SDK.** The `Set up Android SDK` step installs
|
||||
it. Platform packages are minor-versioned, so `COMPILE_SDK = 37` means
|
||||
`platforms;android-37.0`. Build-tools follows AGP's own default
|
||||
(`SdkConstants.CURRENT_BUILD_TOOLS_VERSION`), not `BUILD_TOOLS_VERSION` in
|
||||
`Versions.kt` — nothing sets `buildToolsVersion`, so bump it when AGP is
|
||||
upgraded.
|
||||
- **`GRADLE_OPTS` is deliberately smaller than upstream's.** Upstream's
|
||||
workflows use `-Xmx8g` with a 4 GB Kotlin daemon, sized for the 4-core/16GB
|
||||
`ubuntu-latest` that *public* repos get; this repo is private, so its hosted
|
||||
runners are 2-core/7GB and its container is 24 GiB. At upstream's numbers the
|
||||
daemon plus the Kotlin daemon plus four parallel lint/R8 workers exceed the
|
||||
limit and the daemon is SIGKILLed — the symptom is
|
||||
`DaemonDisappearedException` with **no** `OutOfMemoryError` and no heap dump,
|
||||
which is what an external kill looks like. `-Xmx6g` with a 2 GB Kotlin daemon
|
||||
fits.
|
||||
|
||||
The `Report runner capacity` step prints `free`, `nproc`, `df` and the enforced
|
||||
cgroup limit on every run, so a future failure can be read against the runner's
|
||||
actual capacity rather than guessed at. Note `free` reports the container's
|
||||
limit only when lxcfs virtualises `/proc/meminfo`; `/sys/fs/cgroup/memory.max`
|
||||
is the number the kernel enforces.
|
||||
|
||||
## Versioning (calendar scheme — differs from the old Classic repo)
|
||||
|
||||
Element X uses calendar versioning, defined in
|
||||
`plugins/src/main/kotlin/Versions.kt`:
|
||||
|
||||
```
|
||||
versionName = <versionYear>.<versionMonth padded to 2>.<versionReleaseNumber> // e.g. 26.07.1
|
||||
versionCode = ((2000 + versionYear) * 10_000 + versionMonth * 100 + versionReleaseNumber) * 10
|
||||
```
|
||||
|
||||
The trailing `* 10` is the ABI multiplier applied per output APK
|
||||
(`app/build.gradle.kts` androidComponents block); the AAB gets ABI code `0`.
|
||||
Example: `26.07.1` → versionCode `202607010`.
|
||||
|
||||
To bump: edit `versionYear`/`versionMonth`/`versionReleaseNumber` in
|
||||
`Versions.kt` (increment `versionReleaseNumber` within a month; reset it to 0
|
||||
when the month changes). `versionCode` must be strictly monotonic in Play
|
||||
Store across all uploads — the calendar scheme guarantees this as long as you
|
||||
never decrease the triple. All HealthChat codes (≥ 202,607,000) supersede the
|
||||
old Classic app's last code (40,154,100).
|
||||
|
||||
**Keep the same version as the upstream Element X release the branch is based
|
||||
on unless HealthChat ships an intermediate build** — then bump
|
||||
`versionReleaseNumber` past upstream's. When merging a newer upstream release
|
||||
into `release/healthchat`, take upstream's `Versions.kt` if its version is
|
||||
higher.
|
||||
|
||||
## Release notes
|
||||
|
||||
`HEALTHCHAT_CHANGES.md` is the single source. The workflow extracts the
|
||||
section matching the version being released, then writes it to
|
||||
`fastlane/metadata/android/en-US/changelogs/<versionCode>.txt` and
|
||||
`fastlane/metadata/android/de-DE/changelogs/<versionCode>.txt`, which
|
||||
`r0adkll/upload-google-play` picks up via its `whatsNewDirectory` input.
|
||||
|
||||
Format:
|
||||
|
||||
```markdown
|
||||
## Changes in 26.07.1 (2026-07-20)
|
||||
|
||||
🙌 Improvements
|
||||
|
||||
- Bullet one.
|
||||
- Bullet two.
|
||||
```
|
||||
|
||||
Keep the entire section under **500 characters** — Play Store's "What's new"
|
||||
hard cap per locale. The workflow emits a warning if you exceed it; Play Store
|
||||
will truncate.
|
||||
|
||||
Never hand-edit the generated `fastlane/metadata/android/*/changelogs/<N>.txt`
|
||||
files for this branch — they'll be overwritten on the next run. Only edit
|
||||
`HEALTHCHAT_CHANGES.md`. (Older checked-in files from upstream are harmless.)
|
||||
|
||||
If no matching section exists, the pipeline emits a workflow warning and
|
||||
falls back to `HealthChat <version> — automated build from <ref>.`. Prefer
|
||||
fixing the changelog over relying on the fallback.
|
||||
|
||||
## After the release
|
||||
|
||||
- [ ] Workflow run is green in GitHub Actions.
|
||||
- [ ] Build appears under Play Console → Internal testing within ~10 minutes.
|
||||
- [ ] Internal testers get the update automatically if they're enrolled.
|
||||
- [ ] AAB + `mapping.txt` are attached to the workflow run, retained 90 days.
|
||||
|
||||
## Required GitHub Actions secrets
|
||||
|
||||
The pipeline depends on these repository secrets. Missing any causes the
|
||||
workflow to fail at the corresponding step. The values are identical to the
|
||||
ones configured on the old `Novaloop-AG/element-android` repository (same
|
||||
upload keystore, same Play listing) — except `GOOGLE_SERVICES_JSON_BASE64`,
|
||||
which element-x-android does **not** use (Firebase config is compiled in via
|
||||
`libraries/pushproviders/firebase/src/main/res/values/firebase.xml`).
|
||||
|
||||
| Secret | Source |
|
||||
|---|---|
|
||||
| `RELEASE_KEYSTORE_BASE64` | `base64 -w0 release.keystore` (the HealthChat upload keystore, alias `healthchat-upload`) |
|
||||
| `RELEASE_KEYSTORE_PASSWORD` | Keystore password |
|
||||
| `RELEASE_KEY_ALIAS` | Key alias inside the keystore |
|
||||
| `RELEASE_KEY_PASSWORD` | Key password |
|
||||
| `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` | Play Console → Users and permissions → invite a service account → download JSON. Needs "Release manager" permissions scoped to `ch.healthchat.element`. |
|
||||
|
||||
## Keystore rotation (rare, high-risk)
|
||||
|
||||
Unlike Apple Distribution certificates, **Android upload keystores do not
|
||||
expire** and should not be rotated casually. The upload key is tied to the
|
||||
app's identity in Play Console (Play App Signing is enabled; the repo key is
|
||||
the upload key); rotating it is a one-way process that uses Play App
|
||||
Signing's "Upload key reset" flow.
|
||||
|
||||
If the keystore is lost or believed to be compromised:
|
||||
|
||||
1. Play Console → Release → Setup → App integrity → Upload key → **Request
|
||||
upload key reset**. Instructions follow, requiring you to upload a new
|
||||
keystore and wait for Google to rotate.
|
||||
2. Generate a fresh keystore on the operator's machine:
|
||||
```bash
|
||||
keytool -genkey -v -keystore healthchat-upload-new.jks \
|
||||
-keyalg RSA -keysize 2048 -validity 10000 -alias healthchat
|
||||
```
|
||||
3. Once Google confirms the rotation, re-encode and update secrets:
|
||||
`base64 -w0 healthchat-upload-new.jks | pbcopy`, then replace
|
||||
`RELEASE_KEYSTORE_BASE64`, `RELEASE_KEYSTORE_PASSWORD`, `RELEASE_KEY_ALIAS`,
|
||||
`RELEASE_KEY_PASSWORD` in repo settings.
|
||||
4. Run the release workflow via `workflow_dispatch` with a throwaway version
|
||||
bump and `release_status: draft` to verify end-to-end. Discard the draft.
|
||||
|
||||
**Do not attempt rotation without reading Play App Signing docs end-to-end**
|
||||
and coordinating a maintenance window. A botched rotation means users can't
|
||||
receive updates until Google completes the reset.
|
||||
|
||||
## Promoting internal → production
|
||||
|
||||
Internal builds and production builds are the same AAB. To release a
|
||||
validated internal build:
|
||||
|
||||
1. Play Console → HealthChat → Testing → **Internal testing** → find the
|
||||
release.
|
||||
2. Confirm it passed internal testing.
|
||||
3. **Promote release** → Production → **Release details** → fill in "What's
|
||||
new" (Play Console copies it from the internal release — verify) →
|
||||
**Review release** → **Start rollout to Production**.
|
||||
4. Choose staged rollout % if desired.
|
||||
|
||||
**This step is deliberately not automated.** Production releases require
|
||||
human judgment on rollout percentage, Play Store listing changes, and
|
||||
compliance metadata review.
|
||||
|
||||
## Rollback
|
||||
|
||||
- **Bad build, release notes fine** → fix the cause, bump
|
||||
`versionReleaseNumber`, commit, tag `healthchat-v<ver>`. The new build
|
||||
supersedes the bad one on the internal track.
|
||||
- **Bad release notes only** → edit `HEALTHCHAT_CHANGES.md` on
|
||||
`release/healthchat`, then update the "What's new" text in the Play Console
|
||||
release manually (the pipeline only writes it at upload time).
|
||||
- **Completely wrong version number** → treat as burned. Bump
|
||||
`versionReleaseNumber` further, tag a higher `healthchat-v<ver>`. Play
|
||||
Store's `versionCode` rule means the next run naturally supersedes.
|
||||
- **Production rollout gone wrong** → Play Console → Production → **Halt
|
||||
rollout** (works during staged rollout), then ship a hotfix as above.
|
||||
|
||||
## Known branch divergence when merging upstream
|
||||
|
||||
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
|
||||
**`.claude/skills/releasing-healthchat/SKILL.md`** are HealthChat-only —
|
||||
any merge conflict here means something upstream accidentally overlaps;
|
||||
keep `--ours`.
|
||||
- **`plugins/src/main/kotlin/config/BuildTimeConfig.kt`** —
|
||||
`APPLICATION_ID = "ch.healthchat.element"`, `APPLICATION_NAME = "HealthChat"`,
|
||||
`METADATA_HOST_REVERSED`, and the healthchat.ch URLs must survive merges.
|
||||
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`** —
|
||||
`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`.
|
||||
- **`app/src/gplay/AndroidManifest.xml`** — HealthChat-only file that strips
|
||||
`REQUEST_INSTALL_PACKAGES` and the location permissions
|
||||
(`FOREGROUND_SERVICE_LOCATION`, `ACCESS_FINE_LOCATION`,
|
||||
`ACCESS_COARSE_LOCATION`, plus `LiveLocationSharingService`) from the Play
|
||||
build. Play Console rejects the release at edit-commit time while either is
|
||||
declared but not justified on the listing. Upstream has no `app/src/gplay/`
|
||||
manifest, so this should never conflict. If HealthChat ever ships maps
|
||||
(a MapTiler key in `BuildTimeConfig`), the location removals must come out
|
||||
first — the feature is gated on that key, not on these declarations.
|
||||
- **`plugins/src/main/kotlin/Versions.kt`** — take upstream's version when
|
||||
higher (see Versioning above).
|
||||
- **`fastlane/metadata/android/`** — HealthChat listing texts (en-US, de-DE)
|
||||
must survive; upstream ships Element X texts. The per-versionCode changelog
|
||||
files generated by this pipeline carry HealthChat text; conflicts there
|
||||
mean a versionCode collided — regenerate from `HEALTHCHAT_CHANGES.md` on
|
||||
the next release run rather than hand-merging.
|
||||
@@ -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:
|
||||
- 'Yes'
|
||||
- 'No'
|
||||
- 'Yes'
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: pr
|
||||
attributes:
|
||||
label: Are you willing to provide a PR?
|
||||
label: Will you submit a Pull Request?
|
||||
description: |
|
||||
Providing a PR can drastically speed up the process of fixing this bug. Don't worry, it's still OK to answer 'No' :).
|
||||
Submitting a Pull Request can drastically speed up the process of fixing this bug. Don't worry, it's still OK to answer 'No' :).
|
||||
options:
|
||||
- 'Yes'
|
||||
- 'No'
|
||||
- 'Yes'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
name: Enhancement request
|
||||
description: Do you have a suggestion or feature 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.
|
||||
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 [start a discussion here](https://github.com/element-hq/element-meta/discussions/new?category=ideas).
|
||||
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.
|
||||
- type: textarea
|
||||
id: usecase
|
||||
attributes:
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
@@ -104,7 +104,18 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Compile release sources
|
||||
if: ${{ matrix.variant == 'release' }}
|
||||
run: ./gradlew bundleGplayRelease -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
|
||||
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
|
||||
- name: Compile nightly sources
|
||||
if: ${{ matrix.variant == 'nightly' }}
|
||||
run: ./gradlew compileGplayNightlySources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
- name: Clone submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
- run: |
|
||||
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||
- name: Danger
|
||||
uses: danger/danger-js@58355dc2b274a87699c8b7deac01baba16d6f061 # 13.0.8
|
||||
uses: danger/danger-js@e9b58820039524318198d8059ec98b0c02b7875c # 13.0.10
|
||||
with:
|
||||
args: "--dangerfile ./tools/danger/dangerfile.js"
|
||||
env:
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: ⏬ Checkout with LFS
|
||||
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
name: Use JDK 21
|
||||
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
|
||||
with:
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
name: HealthChat Release to Google Play Store
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'healthchat-v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Marketing version (e.g. 26.07.0). Required when running manually.'
|
||||
required: true
|
||||
type: string
|
||||
track:
|
||||
description: 'Play Store track'
|
||||
required: true
|
||||
default: 'internal'
|
||||
type: choice
|
||||
options:
|
||||
- internal
|
||||
- alpha
|
||||
- beta
|
||||
- production
|
||||
release_status:
|
||||
description: 'Release status'
|
||||
required: true
|
||||
default: 'completed'
|
||||
type: choice
|
||||
options:
|
||||
- completed
|
||||
- inProgress
|
||||
- halted
|
||||
- draft
|
||||
release_notes_override:
|
||||
description: 'Optional release notes. If empty, falls back to HEALTHCHAT_CHANGES.md section for the given version.'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
# Only need read access to clone. Upload uses the Play Console service
|
||||
# account JSON from secrets, not GITHUB_TOKEN.
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: healthchat-release-${{ github.event.inputs.version || github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# Upstream's workflows hardcode -Xmx8g / Kotlin daemon -Xmx4g, sized for the
|
||||
# 4-core/16GB ubuntu-latest that public repos get. On this container the
|
||||
# daemon keeps getting SIGKILLed mid-build (DaemonDisappearedException, no
|
||||
# OutOfMemoryError, no heap dump), so stay well under upstream's ceiling and
|
||||
# leave room for the Kotlin daemon and the parallel lint workers.
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build and upload AAB to Play Store
|
||||
runs-on: novaloop-incus-large
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
# Tag pushes must originate from release/healthchat. Dispatch runs must be
|
||||
# invoked from that branch too. Reject anything else up-front so we don't
|
||||
# ship off main/develop.
|
||||
- name: Verify ref is on release/healthchat
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# On tag pushes, GITHUB_REF_NAME is the tag, not the branch. Resolve
|
||||
# the commit's branch membership instead.
|
||||
if ! git branch -r --contains "$GITHUB_SHA" | grep -qE 'origin/release/healthchat$'; then
|
||||
echo "::error::Commit $GITHUB_SHA is not on origin/release/healthchat. Refusing to release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Resolve marketing version
|
||||
id: version
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
else
|
||||
VERSION="${GITHUB_REF_NAME#healthchat-v}"
|
||||
fi
|
||||
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
|
||||
echo "::error::Invalid marketing version '$VERSION' — expected e.g. 26.07.0"
|
||||
exit 1
|
||||
fi
|
||||
# Drop any pre-release/build suffix (e.g. -rc1, -hotfix1) for the Versions.kt comparison.
|
||||
BASE_VERSION=$(echo "$VERSION" | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+)([.-].*)?$/\1/')
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "base_version=$BASE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "Resolved marketing version: $VERSION (base: $BASE_VERSION)"
|
||||
|
||||
- name: Verify Versions.kt matches tag
|
||||
id: gradle_version
|
||||
env:
|
||||
BASE_VERSION: ${{ steps.version.outputs.base_version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSIONS_FILE=plugins/src/main/kotlin/Versions.kt
|
||||
YEAR=$(grep -E 'private const val versionYear' "$VERSIONS_FILE" | sed -E 's/.*=\s*([0-9]+).*/\1/')
|
||||
MONTH=$(grep -E 'private const val versionMonth' "$VERSIONS_FILE" | sed -E 's/.*=\s*([0-9]+).*/\1/')
|
||||
REL=$(grep -E 'private const val versionReleaseNumber' "$VERSIONS_FILE" | sed -E 's/.*=\s*([0-9]+).*/\1/')
|
||||
if [[ -z "$YEAR" || -z "$MONTH" || -z "$REL" ]]; then
|
||||
echo "::error::Could not parse versionYear/versionMonth/versionReleaseNumber from $VERSIONS_FILE"
|
||||
exit 1
|
||||
fi
|
||||
# VERSION_NAME format (see Versions.kt): year on 2 digits, month zero-padded to 2, release number unpadded.
|
||||
GRADLE_VERSION="${YEAR}.$(printf '%02d' "$MONTH").${REL}"
|
||||
echo "Parsed $VERSIONS_FILE: $GRADLE_VERSION"
|
||||
if [[ "$GRADLE_VERSION" != "$BASE_VERSION" ]]; then
|
||||
echo "::error::Tag version $BASE_VERSION does not match Versions.kt ($GRADLE_VERSION). Bump versionYear/versionMonth/versionReleaseNumber and commit before tagging."
|
||||
exit 1
|
||||
fi
|
||||
# Formula (see Versions.kt VERSION_CODE): (2000+year)*10_000 + month*100 + releaseNumber,
|
||||
# then *10 for the AAB output (ABI code 0 — see app/build.gradle.kts androidComponents block).
|
||||
BASE_CODE=$(( (2000 + YEAR) * 10000 + MONTH * 100 + REL ))
|
||||
VERSION_CODE=$(( BASE_CODE * 10 ))
|
||||
echo "version_code=$VERSION_CODE" >> "$GITHUB_OUTPUT"
|
||||
echo "Computed versionCode: $VERSION_CODE"
|
||||
|
||||
- name: Extract release notes
|
||||
id: notes
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.base_version }}
|
||||
OVERRIDE: ${{ github.event.inputs.release_notes_override }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -n "${OVERRIDE:-}" ]]; then
|
||||
NOTES="$OVERRIDE"
|
||||
echo "Using release notes from workflow input"
|
||||
else
|
||||
NOTES=$(awk -v ver="$VERSION" '
|
||||
/^## Changes in / {
|
||||
if (found) { exit }
|
||||
if ($0 ~ "## Changes in " ver "( |$|\\()") { found = 1; next }
|
||||
}
|
||||
found { print }
|
||||
' HEALTHCHAT_CHANGES.md | sed '/^[[:space:]]*$/{N;/^\n$/D;}')
|
||||
if [[ -z "$NOTES" ]]; then
|
||||
NOTES="HealthChat ${VERSION} — automated build from ${GITHUB_REF_NAME}."
|
||||
echo "::warning::No HEALTHCHAT_CHANGES.md section found for $VERSION — using fallback"
|
||||
fi
|
||||
fi
|
||||
# Play Store "What's new" has a 500-character limit per locale.
|
||||
CHAR_COUNT=$(printf '%s' "$NOTES" | wc -c)
|
||||
if (( CHAR_COUNT > 500 )); then
|
||||
echo "::warning::Release notes are $CHAR_COUNT chars; Play Store truncates at 500"
|
||||
fi
|
||||
{
|
||||
echo 'notes<<NOTES_EOF'
|
||||
echo "$NOTES"
|
||||
echo 'NOTES_EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate fastlane changelog files
|
||||
env:
|
||||
VERSION_CODE: ${{ steps.gradle_version.outputs.version_code }}
|
||||
NOTES: ${{ steps.notes.outputs.notes }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for locale in en-US de-DE; do
|
||||
dir="fastlane/metadata/android/${locale}/changelogs"
|
||||
mkdir -p "$dir"
|
||||
printf '%s\n' "$NOTES" > "${dir}/${VERSION_CODE}.txt"
|
||||
echo "Wrote ${dir}/${VERSION_CODE}.txt"
|
||||
done
|
||||
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
# The self-hosted runner image has no Android SDK (unlike GitHub-hosted
|
||||
# ubuntu-latest), so install it here.
|
||||
# - platform: follows COMPILE_SDK in plugins/src/main/kotlin/Versions.kt.
|
||||
# Platform packages are minor-versioned, so compileSdk 37 is android-37.0.
|
||||
# - build-tools: nothing sets buildToolsVersion, so AGP picks its own
|
||||
# default (SdkConstants.CURRENT_BUILD_TOOLS_VERSION). Bump it when AGP is
|
||||
# upgraded, not when Versions.kt changes.
|
||||
- name: Set up Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
with:
|
||||
packages: 'platforms;android-37.0 build-tools;36.0.0'
|
||||
|
||||
- name: Configure gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Decode keystore
|
||||
run: |
|
||||
echo "${{ secrets.RELEASE_KEYSTORE_BASE64 }}" | base64 -d > app/signature/release.keystore
|
||||
|
||||
# Recorded on every run so build failures can be read against the
|
||||
# runner's actual capacity instead of guessed at.
|
||||
- name: Report runner capacity
|
||||
run: |
|
||||
free -h
|
||||
nproc
|
||||
df -h .
|
||||
# free reads /proc/meminfo, which in a container may report the host's
|
||||
# total rather than the enforced limit. This is what the kernel enforces.
|
||||
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"
|
||||
|
||||
- name: Build Release AAB
|
||||
run: |
|
||||
./gradlew bundleGplayRelease $CI_GRADLE_ARG_PROPERTIES
|
||||
env:
|
||||
RELEASE_STORE_FILE: signature/release.keystore
|
||||
RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
|
||||
RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
|
||||
RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
|
||||
|
||||
- name: Upload workflow artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: healthchat-${{ steps.version.outputs.version }}
|
||||
path: |
|
||||
app/build/outputs/bundle/gplayRelease/*.aab
|
||||
app/build/outputs/mapping/gplayRelease/mapping.txt
|
||||
retention-days: 90
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Upload to Play Store
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
|
||||
packageName: ch.healthchat.element
|
||||
releaseFiles: app/build/outputs/bundle/gplayRelease/*.aab
|
||||
tracks: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.track || 'internal' }}
|
||||
status: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_status || 'completed' }}
|
||||
whatsNewDirectory: fastlane/metadata/android
|
||||
mappingFile: app/build/outputs/mapping/gplayRelease/mapping.txt
|
||||
changesNotSentForReview: true
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
ref: ${{ github.ref }}
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
name: Use JDK 21
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
@@ -108,7 +108,7 @@ 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
@@ -149,7 +149,7 @@ 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
@@ -183,7 +183,7 @@ 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
@@ -228,7 +228,7 @@ 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
@@ -269,7 +269,7 @@ 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
@@ -360,7 +360,7 @@ jobs:
|
||||
yarn add danger-plugin-lint-report --dev
|
||||
- name: Danger lint
|
||||
if: always()
|
||||
uses: danger/danger-js@58355dc2b274a87699c8b7deac01baba16d6f061 # 13.0.8
|
||||
uses: danger/danger-js@e9b58820039524318198d8059ec98b0c02b7875c # 13.0.10
|
||||
with:
|
||||
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
|
||||
env:
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: ☕️ Use JDK 21
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
- name: Clone submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use JDK 21
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
@@ -39,6 +39,7 @@ 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:
|
||||
|
||||
@@ -63,7 +63,7 @@ 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@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '21'
|
||||
|
||||
@@ -82,9 +82,6 @@ captures/
|
||||
#*.jks
|
||||
#*.keystore
|
||||
|
||||
# HealthChat upload keystore, materialized from a CI secret — never commit it
|
||||
app/signature/release.keystore
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
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,7 +7,16 @@ appId: ${MAESTRO_APP_ID}
|
||||
- tapOn:
|
||||
text: ${MAESTRO_INVITEE1_MXID}
|
||||
index: 1
|
||||
- tapOn: "Continue"
|
||||
- runFlow:
|
||||
when:
|
||||
visible: 'Start a chat with this new contact?'
|
||||
commands:
|
||||
- tapOn: 'Continue'
|
||||
- runFlow:
|
||||
when:
|
||||
visible: 'Send invite?'
|
||||
commands:
|
||||
- tapOn: 'Send invite'
|
||||
- takeScreenshot: build/maestro/330-createAndDeleteDM
|
||||
- tapOn: "maestroelement2"
|
||||
- scroll
|
||||
|
||||
@@ -15,6 +15,12 @@ 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
|
||||
@@ -23,6 +29,10 @@ 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:
|
||||
@@ -30,10 +40,6 @@ 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,5 +1,6 @@
|
||||
appId: ${MAESTRO_APP_ID}
|
||||
---
|
||||
- runFlow: cleanUpRoomList.yaml
|
||||
- runFlow: searchRoomList.yaml
|
||||
- takeScreenshot: build/maestro/300-RoomList
|
||||
- runFlow: timeline/timeline.yaml
|
||||
|
||||
@@ -1,3 +1,52 @@
|
||||
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
|
||||
=============================
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# HealthChat Android changelog
|
||||
|
||||
This file is the single source for Play Store "What's new" texts. The release
|
||||
pipeline (`.github/workflows/healthchat-release-playstore.yml`) extracts the
|
||||
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.07.0 (2026-07-16)
|
||||
|
||||
🎉 Komplett neue App-Generation
|
||||
|
||||
- HealthChat basiert neu auf Element X: schnellere Synchronisation, moderne Oberfläche und verbesserte Verschlüsselung.
|
||||
- Anmeldung neu über den sicheren HealthChat Login (SSO).
|
||||
- Hinweis: Nach dem Update ist eine einmalige Neuanmeldung erforderlich.
|
||||
@@ -96,18 +96,6 @@ android {
|
||||
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
|
||||
// .github/workflows/healthchat-release-playstore.yml).
|
||||
register("healthchat") {
|
||||
keyAlias = System.getenv("RELEASE_KEY_ALIAS")
|
||||
?: project.property("signing.healthchat.keyId") as? String?
|
||||
keyPassword = System.getenv("RELEASE_KEY_PASSWORD")
|
||||
?: project.property("signing.healthchat.keyPassword") as? String?
|
||||
storeFile = file(System.getenv("RELEASE_STORE_FILE") ?: "./signature/release.keystore")
|
||||
storePassword = System.getenv("RELEASE_STORE_PASSWORD")
|
||||
?: project.property("signing.healthchat.storePassword") as? String?
|
||||
}
|
||||
}
|
||||
|
||||
val baseAppName = BuildTimeConfig.APPLICATION_NAME
|
||||
@@ -134,13 +122,7 @@ android {
|
||||
"login_redirect_scheme",
|
||||
oAuthRedirectSchemeBase,
|
||||
)
|
||||
// Sign with the HealthChat upload key when it is available (CI),
|
||||
// fall back to the debug key for local release builds.
|
||||
signingConfig = if (System.getenv("RELEASE_STORE_FILE") != null || file("./signature/release.keystore").exists()) {
|
||||
signingConfigs.getByName("healthchat")
|
||||
} else {
|
||||
signingConfigs.getByName("debug")
|
||||
}
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
|
||||
optimization {
|
||||
enable = true
|
||||
@@ -279,7 +261,7 @@ dependencies {
|
||||
implementation(projects.appicon.enterprise)
|
||||
} else {
|
||||
implementation(projects.features.enterprise.implFoss)
|
||||
implementation(projects.appicon.healthchat)
|
||||
implementation(projects.appicon.element)
|
||||
}
|
||||
allFeaturesImpl(project)
|
||||
implementation(projects.features.migration.api)
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2026 Novaloop AG
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!--
|
||||
HealthChat does not want APK sideloading from chat. Upstream declares
|
||||
REQUEST_INSTALL_PACKAGES so that tapping an .apk attachment in the media
|
||||
viewer deep-links to "Install unknown apps" and hands the file to the
|
||||
system installer (see AndroidLocalMediaActions.open) — a workflow aimed at
|
||||
Element's nightly/F-Droid audience, and not one we want in a healthcare
|
||||
messenger. Play Console also rejects releases carrying this permission
|
||||
unless it is declared and justified on the listing.
|
||||
|
||||
Removing it is a configuration upstream supports: the media viewer checks
|
||||
for the permission before using it and otherwise falls through to the
|
||||
normal system open path, so .apk attachments still open, just without the
|
||||
in-app install shortcut.
|
||||
-->
|
||||
<uses-permission
|
||||
android:name="android.permission.REQUEST_INSTALL_PACKAGES"
|
||||
tools:node="remove" />
|
||||
|
||||
<!--
|
||||
HealthChat ships no location features, so it should not ask for location.
|
||||
They are already unreachable at runtime: DefaultLocationService gates the
|
||||
whole feature on BuildConfig.MAPTILER_API_KEY, and HealthChat sets no
|
||||
MapTiler key (BuildTimeConfig.SERVICES_MAPTILER_APIKEY = null, and the
|
||||
ELEMENT_ANDROID_MAPTILER_API_KEY fallback is unset in CI). The composer's
|
||||
"Share location" action and the location viewer are both hidden as a
|
||||
result, so LiveLocationSharingService can never start — only these
|
||||
declarations survive, and FOREGROUND_SERVICE_LOCATION forces a foreground
|
||||
service declaration on the Play listing for a feature we do not ship.
|
||||
|
||||
Removing the declarations changes nothing users can reach, and keeps the
|
||||
location module in the dependency graph so upstream merges stay clean.
|
||||
|
||||
The service must be named fully qualified: the library declares it
|
||||
relative to its own namespace (io.element.android.features.location.impl),
|
||||
whereas a leading dot here would resolve against the app's namespace.
|
||||
|
||||
FOREGROUND_SERVICE itself stays — the call and push modules need it.
|
||||
-->
|
||||
<uses-permission
|
||||
android:name="android.permission.FOREGROUND_SERVICE_LOCATION"
|
||||
tools:node="remove" />
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_FINE_LOCATION"
|
||||
tools:node="remove" />
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_COARSE_LOCATION"
|
||||
tools:node="remove" />
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name="io.element.android.features.location.impl.live.service.LiveLocationSharingService"
|
||||
tools:node="remove" />
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -8,9 +8,6 @@
|
||||
<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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2026 Novaloop AG
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<resources>
|
||||
<string name="screen_onboarding_welcome_title">"Willkommen bei HealthChat"</string>
|
||||
</resources>
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2026 Novaloop AG
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<resources>
|
||||
<string name="screen_onboarding_welcome_title">"Bienvenue chez HealthChat"</string>
|
||||
</resources>
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2026 Novaloop AG
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<resources>
|
||||
<string name="screen_onboarding_welcome_title">"Benvenuto su HealthChat"</string>
|
||||
</resources>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2026 Novaloop AG
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- HealthChat override of the Element onboarding title ("Be in your element").
|
||||
Overridden here app-side instead of editing the Localazy-managed login
|
||||
strings, so upstream merges stay clean. Only the main HealthChat locales
|
||||
are overridden; other locales fall back to their Element translation. -->
|
||||
<string name="screen_onboarding_welcome_title" tools:ignore="MissingTranslation">"Welcome to HealthChat"</string>
|
||||
</resources>
|
||||
@@ -24,12 +24,12 @@ object ApplicationConfig {
|
||||
* Cannot be empty.
|
||||
* For Element, the value is "Element".
|
||||
*/
|
||||
const val PRODUCTION_APPLICATION_NAME: String = "HealthChat"
|
||||
const val PRODUCTION_APPLICATION_NAME: String = "Element"
|
||||
|
||||
/**
|
||||
* Used in the strings to reference the Element Desktop client, for instance Element Web.
|
||||
* Cannot be empty.
|
||||
* For Element, the value is "Element". We use the same name for desktop and mobile for now.
|
||||
*/
|
||||
const val DESKTOP_APPLICATION_NAME: String = "HealthChat"
|
||||
const val DESKTOP_APPLICATION_NAME: String = "Element"
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Novaloop AG
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.appicon.healthchat"
|
||||
|
||||
buildTypes {
|
||||
register("nightly")
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 59 KiB |
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2026 Novaloop AG
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#12325C"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="m0,0h108v108h-108z" />
|
||||
</vector>
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2026 Novaloop AG
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
|
||||
</adaptive-icon>
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2026 Novaloop AG
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
@@ -34,6 +34,7 @@ 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)
|
||||
@@ -61,6 +62,7 @@ dependencies {
|
||||
testImplementation(projects.features.share.test)
|
||||
testImplementation(projects.libraries.matrix.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)
|
||||
|
||||
@@ -12,4 +12,7 @@ sealed interface LoggedInEvents {
|
||||
data class CloseErrorDialog(val doNotShowAgain: Boolean) : LoggedInEvents
|
||||
data object CheckSlidingSyncProxyAvailability : LoggedInEvents
|
||||
data object LogoutAndMigrateToNativeSlidingSync : LoggedInEvents
|
||||
data object DismissLocalNetworkPermissionPrompt : LoggedInEvents
|
||||
|
||||
data object RequestLocationNetworkPermission : LoggedInEvents
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package io.element.android.appnav.loggedin
|
||||
|
||||
import android.Manifest
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
@@ -15,6 +16,7 @@ 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
|
||||
@@ -36,6 +38,10 @@ 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
|
||||
@@ -58,7 +64,12 @@ 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()
|
||||
@@ -117,6 +128,21 @@ 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: LoggedInEvents) {
|
||||
when (event) {
|
||||
is LoggedInEvents.CloseErrorDialog -> {
|
||||
@@ -134,6 +160,17 @@ class LoggedInPresenter(
|
||||
// Force the logout since Native Sliding Sync is already enforced by the SDK
|
||||
matrixClient.logout(userInitiated = true, ignoreSdkError = true)
|
||||
}
|
||||
LoggedInEvents.DismissLocalNetworkPermissionPrompt -> {
|
||||
localNetworkPromptDismissedThisSession = true
|
||||
}
|
||||
LoggedInEvents.RequestLocationNetworkPermission -> {
|
||||
if (localNetworkPermissionDialog == LocalNetworkPermissionDialog.Settings) {
|
||||
localNetworkPermissionState.eventSink(PermissionsEvent.OpenSystemSettingAndCloseDialog)
|
||||
} else {
|
||||
localNetworkPermissionState.eventSink(PermissionsEvent.RequestPermissions)
|
||||
}
|
||||
localNetworkPromptDismissedThisSession = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +180,7 @@ class LoggedInPresenter(
|
||||
ignoreRegistrationError = ignoreRegistrationError,
|
||||
forceNativeSlidingSyncMigration = forceNativeSlidingSyncMigration,
|
||||
appName = buildMeta.applicationName,
|
||||
localNetworkPermissionDialog = localNetworkPermissionDialog,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package io.element.android.appnav.loggedin
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||
|
||||
data class LoggedInState(
|
||||
val showSyncSpinner: Boolean,
|
||||
@@ -16,5 +17,6 @@ data class LoggedInState(
|
||||
val ignoreRegistrationError: Boolean,
|
||||
val forceNativeSlidingSyncMigration: Boolean,
|
||||
val appName: String,
|
||||
val localNetworkPermissionDialog: LocalNetworkPermissionDialog,
|
||||
val eventSink: (LoggedInEvents) -> Unit,
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ 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 LoggedInStateProvider : PreviewParameterProvider<LoggedInState> {
|
||||
@@ -19,6 +20,8 @@ open class LoggedInStateProvider : PreviewParameterProvider<LoggedInState> {
|
||||
aLoggedInState(showSyncSpinner = true),
|
||||
aLoggedInState(pusherRegistrationState = AsyncData.Failure(PusherRegistrationFailure.NoDistributorsAvailable())),
|
||||
aLoggedInState(forceNativeSlidingSyncMigration = true),
|
||||
aLoggedInState(localNetworkPermissionDialog = LocalNetworkPermissionDialog.Rationale),
|
||||
aLoggedInState(localNetworkPermissionDialog = LocalNetworkPermissionDialog.Settings),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -27,11 +30,13 @@ fun aLoggedInState(
|
||||
pusherRegistrationState: AsyncData<Unit> = AsyncData.Uninitialized,
|
||||
forceNativeSlidingSyncMigration: Boolean = false,
|
||||
appName: String = "Element X",
|
||||
localNetworkPermissionDialog: LocalNetworkPermissionDialog = LocalNetworkPermissionDialog.None,
|
||||
) = LoggedInState(
|
||||
showSyncSpinner = showSyncSpinner,
|
||||
pusherRegistrationState = pusherRegistrationState,
|
||||
ignoreRegistrationError = false,
|
||||
forceNativeSlidingSyncMigration = forceNativeSlidingSyncMigration,
|
||||
appName = appName,
|
||||
localNetworkPermissionDialog = localNetworkPermissionDialog,
|
||||
eventSink = {},
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ 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
|
||||
|
||||
@@ -82,6 +83,12 @@ fun LoggedInView(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
LocalNetworkPermissionDialogView(
|
||||
dialog = state.localNetworkPermissionDialog,
|
||||
onSubmit = { state.eventSink(LoggedInEvents.RequestLocationNetworkPermission) },
|
||||
onDismiss = { state.eventSink(LoggedInEvents.DismissLocalNetworkPermissionPrompt) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun Throwable.getReason(): String? {
|
||||
|
||||
@@ -36,6 +36,8 @@ import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
import io.element.android.libraries.permissions.test.FakeLocalNetworkPermissionAdvisor
|
||||
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
|
||||
import io.element.android.libraries.push.api.PushService
|
||||
import io.element.android.libraries.push.api.PusherRegistrationFailure
|
||||
import io.element.android.libraries.push.test.FakePushService
|
||||
@@ -124,6 +126,8 @@ class LoggedInPresenterTest {
|
||||
encryptionService = encryptionService,
|
||||
buildMeta = buildMeta,
|
||||
networkMonitor = networkMonitor,
|
||||
localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(),
|
||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(),
|
||||
).test {
|
||||
encryptionService.emitRecoveryState(RecoveryState.UNKNOWN)
|
||||
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
|
||||
@@ -368,6 +372,8 @@ class LoggedInPresenterTest {
|
||||
encryptionService = encryptionService,
|
||||
buildMeta = buildMeta,
|
||||
networkMonitor = networkMonitor,
|
||||
localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(),
|
||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
HealthChat ist die durchgehend verschlüsselte (Ende-zu-Ende-verschlüsselte, E2EE) Kommunikationslösung für HealthProfessionals – entwickelt in der Schweiz, für das Schweizer Gesundheitssystem.
|
||||
|
||||
Ob Ärztinnen und Ärzte, Pflegefachpersonen, Medizinische Praxisassistentinnen, Spitex-Fachkräfte oder Therapeutinnen – HealthChat ermöglicht eine sichere, einfache und effiziente Kommunikation innerhalb und zwischen interprofessionellen Teams.
|
||||
@@ -1 +0,0 @@
|
||||
Sichere Kommunikation für Gesundheitsfachpersonen
|
||||
@@ -1 +0,0 @@
|
||||
HealthChat
|
||||
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: bug fixes and improvements, including fixing the local network permission usage in Android 17.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
@@ -1,3 +1,29 @@
|
||||
HealthChat ist die durchgehend verschlüsselte (Ende-zu-Ende-verschlüsselte, E2EE) Kommunikationslösung für HealthProfessionals – entwickelt in der Schweiz, für das Schweizer Gesundheitssystem.
|
||||
Freedom to communicate on your own terms
|
||||
|
||||
Ob Ärztinnen und Ärzte, Pflegefachpersonen, Medizinische Praxisassistentinnen, Spitex-Fachkräfte oder Therapeutinnen – HealthChat ermöglicht eine sichere, einfache und effiziente Kommunikation innerhalb und zwischen interprofessionellen Teams.
|
||||
For individuals and communities - private communication between family, friends, hobby groups, clubs, etc.
|
||||
|
||||
Element X gives you fast, secure and private instant messaging and video calls built on Matrix, the open standard for real-time communication. This is a free and open-source app maintained at https://github.com/element-hq/element-x-android.
|
||||
|
||||
Stay in touch with friends, family and communities with:
|
||||
• Real time messaging & video calls
|
||||
• Public rooms for open group communication
|
||||
• Private rooms for closed group communication
|
||||
• Rich messaging features: emoji reactions, replies, polls, pinned messages and more.
|
||||
• Video calling while browsing messages.
|
||||
• Interoperability with other Matrix-based apps such as FluffyChat, Cinny and many more.
|
||||
|
||||
<b>Privacy-first<b>
|
||||
Unlike some other messengers from Big Tech companies, we don’t mine your data or monitor your communications.
|
||||
|
||||
<b>Own your conversations</b>
|
||||
Choose where to host your data - from any public server (the largest free server is matrix.org, but there are plenty of others to choose from) to creating your own personal server and hosting it on your own domain. This ability to choose a server is a large part of what differentiates us from other real time communication apps. However you host, you have ownership; it’s your data. You’re not the product. You’re in control.
|
||||
|
||||
<b>Communicate in real time, all the time</b>
|
||||
Use Element everywhere. Stay in touch wherever you are with fully synchronised message history across all your devices, including on the web at https://app.element.io
|
||||
|
||||
<b>Element X is our next-generation app</b>
|
||||
If you’re using the previous-generation Element Classic app, it’s time to try Element X! It’s faster, easier to use, and more powerful than the classic app. It’s better in every way and we’re adding new features all the time.
|
||||
|
||||
The application requires the android.permission.REQUEST_INSTALL_PACKAGES permission to enable the installation of applications received as attachments, ensuring seamless and convenient access to new software within the app.
|
||||
|
||||
The application requires the USE_FULL_SCREEN_INTENT permission to ensure our users can effectively receive call notifications even when their devices are locked.
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 391 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 98 KiB |
@@ -1 +1 @@
|
||||
Sichere Kommunikation für Gesundheitsfachpersonen
|
||||
Sovereign. Seamless. On Matrix
|
||||
@@ -1 +1 @@
|
||||
HealthChat
|
||||
Element X - Secure Chat & Call
|
||||
@@ -31,12 +31,12 @@ import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.analytics.api.AnalyticsOptInEvents
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.background.OnboardingBackground
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.ClickableLinkText
|
||||
import io.element.android.libraries.designsystem.components.visuallist.VisualList
|
||||
import io.element.android.libraries.designsystem.components.visuallist.VisualListItemData
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
|
||||
@@ -129,22 +129,21 @@ private fun AnalyticsOptInContent() {
|
||||
verticalBias = -0.4f
|
||||
)
|
||||
) {
|
||||
InfoListOrganism(
|
||||
VisualList(
|
||||
items = persistentListOf(
|
||||
InfoListItem(
|
||||
VisualListItemData(
|
||||
message = stringResource(id = R.string.screen_analytics_prompt_data_usage),
|
||||
iconVector = CompoundIcons.CheckCircle(),
|
||||
),
|
||||
InfoListItem(
|
||||
VisualListItemData(
|
||||
message = stringResource(id = R.string.screen_analytics_prompt_third_party_sharing),
|
||||
iconVector = CompoundIcons.CheckCircle(),
|
||||
),
|
||||
InfoListItem(
|
||||
VisualListItemData(
|
||||
message = stringResource(id = R.string.screen_analytics_prompt_settings),
|
||||
iconVector = CompoundIcons.CheckCircle(),
|
||||
),
|
||||
),
|
||||
textStyle = ElementTheme.typography.fontBodyLgMedium,
|
||||
iconTint = ElementTheme.colors.iconSuccessPrimary,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,10 +36,10 @@ import io.element.android.features.announcement.impl.AnnouncementState
|
||||
import io.element.android.features.announcement.impl.AnnouncementStateProvider
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.visuallist.VisualList
|
||||
import io.element.android.libraries.designsystem.components.visuallist.VisualListItemData
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
@@ -134,11 +134,9 @@ private fun FullscreenAnnouncementContent(
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
InfoListOrganism(
|
||||
VisualList(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
items = announcement.items(),
|
||||
textStyle = ElementTheme.typography.fontBodyLgMedium,
|
||||
iconTint = ElementTheme.colors.iconSecondary,
|
||||
iconSize = 24.dp
|
||||
)
|
||||
announcement.notice()?.let { notice ->
|
||||
@@ -186,25 +184,25 @@ private fun Announcement.Fullscreen.icon() = when (this) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Announcement.Fullscreen.items(): ImmutableList<InfoListItem> = when (this) {
|
||||
private fun Announcement.Fullscreen.items(): ImmutableList<VisualListItemData> = when (this) {
|
||||
Announcement.Fullscreen.Space -> persistentListOf(
|
||||
InfoListItem(
|
||||
VisualListItemData(
|
||||
message = "View spaces you\'ve created or joined",
|
||||
iconVector = CompoundIcons.VisibilityOn(),
|
||||
),
|
||||
InfoListItem(
|
||||
VisualListItemData(
|
||||
message = "Accept or decline invites to spaces",
|
||||
iconVector = CompoundIcons.Email(),
|
||||
),
|
||||
InfoListItem(
|
||||
VisualListItemData(
|
||||
message = "Discover any rooms you can join in your spaces",
|
||||
iconVector = CompoundIcons.Search(),
|
||||
),
|
||||
InfoListItem(
|
||||
VisualListItemData(
|
||||
message = "Join public spaces",
|
||||
iconVector = CompoundIcons.Explore(),
|
||||
),
|
||||
InfoListItem(
|
||||
VisualListItemData(
|
||||
message = "Leave any spaces you’ve joined",
|
||||
iconVector = CompoundIcons.Leave(),
|
||||
),
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
android:name="android.hardware.microphone"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
@@ -22,7 +22,6 @@ import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedFactory
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.call.api.CallData
|
||||
import io.element.android.features.call.impl.data.WidgetMessage
|
||||
import io.element.android.features.call.impl.utils.ActiveCallManager
|
||||
@@ -84,7 +83,7 @@ class CallScreenPresenter(
|
||||
var ignoreWebViewError by rememberSaveable { mutableStateOf(false) }
|
||||
var webViewError by remember { mutableStateOf<String?>(null) }
|
||||
val languageTag = languageTagProvider.provideLanguageTag()
|
||||
val theme = if (ElementTheme.isLightTheme) "light" else "dark"
|
||||
val theme = "dark"
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
coroutineScope.launch {
|
||||
|
||||
@@ -38,6 +38,7 @@ import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.compound.colors.SemanticColorsLightDark
|
||||
import io.element.android.compound.theme.ForcedDarkElementTheme
|
||||
import io.element.android.features.call.api.CallData
|
||||
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
||||
import io.element.android.features.call.impl.di.CallBindings
|
||||
@@ -143,24 +144,28 @@ class ElementCallActivity :
|
||||
compoundDark = colors.dark,
|
||||
buildMeta = buildMeta,
|
||||
) {
|
||||
val state = presenter.present()
|
||||
eventSink = state.eventSink
|
||||
LaunchedEffect(state.isCallActive) {
|
||||
if (state.isCallActive) {
|
||||
setCallIsActive()
|
||||
ForcedDarkElementTheme(
|
||||
colors = colors,
|
||||
) {
|
||||
val state = presenter.present()
|
||||
eventSink = state.eventSink
|
||||
LaunchedEffect(state.isCallActive) {
|
||||
if (state.isCallActive) {
|
||||
setCallIsActive()
|
||||
}
|
||||
}
|
||||
CallScreenView(
|
||||
state = state,
|
||||
pipState = pipState,
|
||||
onConsoleMessage = {
|
||||
consoleMessageLogger.log("ElementCall", it)
|
||||
},
|
||||
requestPermissions = { permissions, callback ->
|
||||
requestPermissionCallback = callback
|
||||
requestPermissionsLauncher.launch(permissions)
|
||||
}
|
||||
)
|
||||
}
|
||||
CallScreenView(
|
||||
state = state,
|
||||
pipState = pipState,
|
||||
onConsoleMessage = {
|
||||
consoleMessageLogger.log("ElementCall", it)
|
||||
},
|
||||
requestPermissions = { permissions, callback ->
|
||||
requestPermissionCallback = callback
|
||||
requestPermissionsLauncher.launch(permissions)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.contentscanner.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import extension.setupDependencyInjection
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.contentscanner.impl"
|
||||
}
|
||||
|
||||
setupDependencyInjection()
|
||||
|
||||
dependencies {
|
||||
api(projects.features.contentscanner.api)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
}
|
||||
@@ -43,6 +43,7 @@ dependencies {
|
||||
implementation(libs.coil.compose)
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
implementation(projects.features.invitepeople.api)
|
||||
implementation(projects.features.enterprise.api)
|
||||
api(projects.features.createroom.api)
|
||||
|
||||
testCommonDependencies(libs, true)
|
||||
@@ -53,4 +54,5 @@ dependencies {
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.features.startchat.test)
|
||||
testImplementation(projects.libraries.featureflag.test)
|
||||
testImplementation(projects.features.enterprise.test)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedFactory
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.CreatedRoom
|
||||
import io.element.android.features.enterprise.api.SessionEnterpriseService
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
@@ -73,6 +74,7 @@ class ConfigureRoomPresenter(
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val roomAliasHelper: RoomAliasHelper,
|
||||
private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider,
|
||||
private val sessionEnterpriseService: SessionEnterpriseService,
|
||||
) : Presenter<ConfigureRoomState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
@@ -256,7 +258,7 @@ class ConfigureRoomPresenter(
|
||||
CreateRoomParameters(
|
||||
name = config.roomName,
|
||||
topic = config.topic,
|
||||
isEncrypted = true,
|
||||
isEncrypted = !sessionEnterpriseService.isEncryptionDisabledByHomeserver(),
|
||||
isDirect = false,
|
||||
visibility = RoomVisibility.Private,
|
||||
historyVisibilityOverride = RoomHistoryVisibility.Invited,
|
||||
|
||||
@@ -11,7 +11,8 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.rememberBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -85,9 +86,9 @@ internal fun SelectParentSpaceOptions(
|
||||
)
|
||||
|
||||
if (displaySelectSpaceBottomSheet) {
|
||||
val sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true,
|
||||
confirmValueChange = { true },
|
||||
val sheetState = rememberBottomSheetState(
|
||||
initialValue = SheetValue.Hidden,
|
||||
enabledValues = setOf(SheetValue.Hidden, SheetValue.Expanded),
|
||||
)
|
||||
ModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
|
||||
@@ -3,14 +3,34 @@
|
||||
<string name="screen_create_room_action_create_room">"Ruangan baru"</string>
|
||||
<string name="screen_create_room_add_people_title">"Undang orang-orang"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Terjadi kesalahan saat membuat ruangan"</string>
|
||||
<string name="screen_create_room_error_creating_space">"Ruang tersebut tidak dapat dibuat karena kesalahan yang tidak diketahui. Coba lagi nanti."</string>
|
||||
<string name="screen_create_room_name_placeholder">"Tambahkan nama…"</string>
|
||||
<string name="screen_create_room_new_room_title">"Ruangan baru"</string>
|
||||
<string name="screen_create_room_new_space_title">"Ruang baru"</string>
|
||||
<string name="screen_create_room_private_option_description">"Hanya orang-orang yang diundang dapat mengakses ruangan ini. Semua pesan terenkripsi secara ujung ke ujung."</string>
|
||||
<string name="screen_create_room_private_option_title">"Pribadi"</string>
|
||||
<string name="screen_create_room_public_option_description">"Siapa pun dapat mencari ruangan ini.
|
||||
Anda dapat mengubah ini kapan pun dalam pengaturan ruangan."</string>
|
||||
<string name="screen_create_room_public_option_short_description">"Siapa pun bisa bergabung."</string>
|
||||
<string name="screen_create_room_public_option_title">"Umum"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Siapa pun dapat meminta untuk bergabung dengan ijin administrator atau moderator"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Minta untuk bergabung"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Siapa pun di %1$s dapat bergabung, tetapi semua orang lain harus meminta akses."</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Ajukan permohonan untuk bergabung"</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_description">"Hanya orang-orang yang diundang dapat mengakses ruangan ini. Semua pesan terenkripsi secara ujung ke ujung."</string>
|
||||
<string name="screen_create_room_room_access_section_private_option_title">"Pribadi"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_description">"Siapa pun dapat bergabung dengan ruangan ini"</string>
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"Umum"</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_description">"Siapa pun di %1$s dapat bergabung."</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_title">"Standar"</string>
|
||||
<string name="screen_create_room_room_access_section_title">"Yang memiliki akses"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Supaya ruangan ini terlihat di direktori ruangan publik, Anda memerlukan alamat ruangan."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Alamat ruangan"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Keterlihatan ruangan"</string>
|
||||
<string name="screen_create_room_space_selection_no_space_description">"(ruang sudah penuh)"</string>
|
||||
<string name="screen_create_room_space_selection_no_space_option">"Jangan menambahkan sesuatu ke ruang yang sudah ada."</string>
|
||||
<string name="screen_create_room_space_selection_no_space_title">"Tidak ada ruang yang dipilih"</string>
|
||||
<string name="screen_create_room_space_selection_sheet_title">"Tambahkan ke ruang"</string>
|
||||
<string name="screen_create_room_topic_label">"Topik (opsional)"</string>
|
||||
<string name="screen_create_room_topic_placeholder">"Tambahkan deskripsi…"</string>
|
||||
</resources>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<string name="screen_create_room_room_access_section_public_option_title">"公開"</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_description">"%1$s にいる全員が参加することができます。"</string>
|
||||
<string name="screen_create_room_room_access_section_restricted_option_title">"スタンダード"</string>
|
||||
<string name="screen_create_room_room_access_section_title">"参加できるユーザー"</string>
|
||||
<string name="screen_create_room_room_access_section_title">"参加可能なユーザー"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"公開ディレクトリで自分を見つけられるようにするには、アドレスが必要です。"</string>
|
||||
<string name="screen_create_room_room_address_section_title">"アドレス"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"ルームの公開度"</string>
|
||||
|
||||
@@ -20,11 +20,13 @@ import io.element.android.features.createroom.impl.configureroom.CreateRoomConfi
|
||||
import io.element.android.features.createroom.impl.configureroom.JoinRuleItem
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomAddress
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState
|
||||
import io.element.android.features.enterprise.test.FakeSessionEnterpriseService
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
|
||||
@@ -56,6 +58,7 @@ import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.matching
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import io.element.android.tests.testutils.test
|
||||
import io.mockk.mockk
|
||||
@@ -517,6 +520,30 @@ class ConfigureRoomPresenterTest : RobolectricTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - creating a private room when encryption is disabled by the HS creates it unencrypted`() = runTest {
|
||||
val sessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { true })
|
||||
val createRoomLambda = lambdaRecorder<CreateRoomParameters, Result<RoomId>> { Result.success(A_ROOM_ID) }
|
||||
val matrixClient = createMatrixClient().apply {
|
||||
createRoomResult = createRoomLambda
|
||||
}
|
||||
val presenter = createConfigureRoomPresenter(
|
||||
matrixClient = matrixClient,
|
||||
sessionEnterpriseService = sessionEnterpriseService,
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = initialState()
|
||||
|
||||
initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PrivateVisibility.Private))
|
||||
initialState.eventSink(ConfigureRoomEvents.CreateRoom)
|
||||
|
||||
assertThat(awaitItem().createRoomAction.isLoading()).isTrue()
|
||||
assertThat(awaitItem().createRoomAction.isSuccess()).isTrue()
|
||||
|
||||
createRoomLambda.assertions().isCalledOnce().with(matching<CreateRoomParameters> { !it.isEncrypted })
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun TurbineTestContext<ConfigureRoomState>.initialState(): ConfigureRoomState {
|
||||
skipItems(1)
|
||||
return awaitItem()
|
||||
@@ -552,6 +579,7 @@ class ConfigureRoomPresenterTest : RobolectricTest() {
|
||||
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
|
||||
isKnockFeatureEnabled: Boolean = true,
|
||||
mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(),
|
||||
sessionEnterpriseService: FakeSessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { false }),
|
||||
) = ConfigureRoomPresenter(
|
||||
isSpace = isSpace,
|
||||
initialParentSpaceId = initialParenSpaceId,
|
||||
@@ -566,5 +594,6 @@ class ConfigureRoomPresenterTest : RobolectricTest() {
|
||||
mapOf(FeatureFlags.Knock.key to isKnockFeatureEnabled)
|
||||
),
|
||||
mediaOptimizationConfigProvider = mediaOptimizationConfigProvider,
|
||||
sessionEnterpriseService = sessionEnterpriseService,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -48,11 +48,11 @@ import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.deactivation.impl.R
|
||||
import io.element.android.features.logout.impl.ui.AccountDeactivationActionDialog
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.form.textFieldState
|
||||
import io.element.android.libraries.designsystem.components.list.SwitchListItem
|
||||
import io.element.android.libraries.designsystem.components.visuallist.VisualList
|
||||
import io.element.android.libraries.designsystem.components.visuallist.VisualListItemData
|
||||
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
@@ -175,9 +175,9 @@ private fun Content(
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
InfoListOrganism(
|
||||
VisualList(
|
||||
items = persistentListOf(
|
||||
InfoListItem(
|
||||
VisualListItemData(
|
||||
message = buildAnnotatedStringWithStyledPart(
|
||||
R.string.screen_deactivate_account_list_item_1,
|
||||
R.string.screen_deactivate_account_list_item_1_bold_part,
|
||||
@@ -194,7 +194,7 @@ private fun Content(
|
||||
)
|
||||
},
|
||||
),
|
||||
InfoListItem(
|
||||
VisualListItemData(
|
||||
message = stringResource(R.string.screen_deactivate_account_list_item_2),
|
||||
iconComposable = {
|
||||
Icon(
|
||||
@@ -205,7 +205,7 @@ private fun Content(
|
||||
)
|
||||
},
|
||||
),
|
||||
InfoListItem(
|
||||
VisualListItemData(
|
||||
message = stringResource(R.string.screen_deactivate_account_list_item_3),
|
||||
iconComposable = {
|
||||
Icon(
|
||||
@@ -216,7 +216,7 @@ private fun Content(
|
||||
)
|
||||
},
|
||||
),
|
||||
InfoListItem(
|
||||
VisualListItemData(
|
||||
message = stringResource(R.string.screen_deactivate_account_list_item_4),
|
||||
iconComposable = {
|
||||
Icon(
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_deactivate_account_confirmation_dialog_content">"Bekreft at du vil deaktivere kontoen din. Denne handlingen kan ikke angres."</string>
|
||||
<string name="screen_deactivate_account_confirmation_dialog_content">"Bekreft at du vil slette kontoen din. Denne handlingen kan ikke angres."</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages">"Slett alle meldingene mine"</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages_notice">"Advarsel: Fremtidige brukere vil kunne se ufullstendige samtaler."</string>
|
||||
<string name="screen_deactivate_account_description">"Deaktivering av kontoen din er %1$s , det vil:"</string>
|
||||
<string name="screen_deactivate_account_description">"Hvis %1$s sletter kontoen din, vil følgende skje:"</string>
|
||||
<string name="screen_deactivate_account_description_bold_part">"irreversibel"</string>
|
||||
<string name="screen_deactivate_account_list_item_1">"%1$s kontoen din (du kan ikke logge på igjen, og ID-en din kan ikke brukes på nytt)."</string>
|
||||
<string name="screen_deactivate_account_list_item_1_bold_part">"Deaktiver permanent"</string>
|
||||
<string name="screen_deactivate_account_list_item_2">"Fjern deg fra alle chatterom."</string>
|
||||
<string name="screen_deactivate_account_list_item_3">"Slett kontoinformasjonen din fra vår identitetsserver."</string>
|
||||
<string name="screen_deactivate_account_list_item_4">"Meldingene dine vil fortsatt være synlige for registrerte brukere, men vil ikke være tilgjengelige for nye eller uregistrerte brukere hvis du velger å slette dem."</string>
|
||||
<string name="screen_deactivate_account_title">"Deaktiver kontoen"</string>
|
||||
<string name="screen_deactivate_account_title">"Slett kontoen"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_deactivate_account_confirmation_dialog_content">"Iltimos, hisobingizni o‘chirishni xohlayotganingizni tasdiqlang. Bu amalni qaytarib bo‘lmaydi."</string>
|
||||
<string name="screen_deactivate_account_confirmation_dialog_content">"Iltimos, hisobingizni oʻchirishni xohlayotganingizni tasdiqlang. Bu amalni qaytarib boʻlmaydi."</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages">"Barcha xabarlarimni o‘chirib tashlang"</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages_notice">"Ogohlantirish: Kelgusi foydalanuvchilar chala suhbatlarni ko‘rishi mumkin."</string>
|
||||
<string name="screen_deactivate_account_description">"Hisobingiz %1$s faolsizlantirilmoqda, u quyidagilarni bajaradi:"</string>
|
||||
|
||||
@@ -12,5 +12,7 @@ interface SessionEnterpriseService {
|
||||
suspend fun isElementCallAvailable(): Boolean
|
||||
suspend fun tweakMasUrl(url: String): String
|
||||
|
||||
suspend fun isEncryptionDisabledByHomeserver(): Boolean
|
||||
|
||||
suspend fun init()
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class DefaultEnterpriseService : EnterpriseService {
|
||||
|
||||
override suspend fun isEnterpriseUser(sessionId: SessionId) = false
|
||||
override suspend fun tweakMasUrl(url: String, homeserver: String) = url
|
||||
override fun defaultHomeserverList(): List<String> = listOf("matrix.healthchat.ch")
|
||||
override fun defaultHomeserverList(): List<String> = emptyList()
|
||||
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String) = true
|
||||
|
||||
override suspend fun overrideBrandColor(sessionId: SessionId?, brandColor: String?) = Unit
|
||||
|
||||
@@ -17,4 +17,5 @@ class DefaultSessionEnterpriseService : SessionEnterpriseService {
|
||||
override suspend fun init() = Unit
|
||||
override suspend fun tweakMasUrl(url: String): String = url
|
||||
override suspend fun isElementCallAvailable(): Boolean = true
|
||||
override suspend fun isEncryptionDisabledByHomeserver(): Boolean = false
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ class DefaultEnterpriseServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `defaultHomeserverList should return the HealthChat homeserver only`() {
|
||||
fun `defaultHomeserverList should return empty list`() {
|
||||
val defaultEnterpriseService = DefaultEnterpriseService()
|
||||
assertThat(defaultEnterpriseService.defaultHomeserverList()).containsExactly("matrix.healthchat.ch")
|
||||
assertThat(defaultEnterpriseService.defaultHomeserverList()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -15,6 +15,7 @@ import io.element.android.tests.testutils.simulateLongTask
|
||||
class FakeSessionEnterpriseService(
|
||||
private val isElementCallAvailableResult: () -> Boolean = { lambdaError() },
|
||||
private val tweakMasUrlResult: (String) -> String = { lambdaError() },
|
||||
private val isEncryptionDisabledResult: () -> Boolean = { lambdaError() },
|
||||
) : SessionEnterpriseService {
|
||||
override suspend fun init() {
|
||||
}
|
||||
@@ -26,4 +27,8 @@ class FakeSessionEnterpriseService(
|
||||
override suspend fun isElementCallAvailable(): Boolean = simulateLongTask {
|
||||
isElementCallAvailableResult()
|
||||
}
|
||||
|
||||
override suspend fun isEncryptionDisabledByHomeserver(): Boolean {
|
||||
return isEncryptionDisabledResult()
|
||||
}
|
||||
}
|
||||
|
||||