241 Commits

Author SHA1 Message Date
Jorge Martín c10f4e521d Merge branch 'release/26.07.1' 2026-07-15 15:46:59 +02:00
Jorge Martín 0524b3c274 Adding fastlane file for version 26.07.1 2026-07-15 15:46:58 +02:00
Jorge Martín 4187f9814d Setting version for the release 26.07.1 2026-07-15 15:44:43 +02:00
renovate[bot] d732f043dc Update dependency org.matrix.rustcomponents:sdk-android to v26.07.15 (#7216)
* Update dependency org.matrix.rustcomponents:sdk-android to v26.07.15

* Fix API breaks: add `status` and `call` parameters to `aRustRoomMember` helper

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jorge Martín <jorgem@element.io>
2026-07-15 15:38:20 +02:00
Jorge Martin Espinosa d0b4a21cfe Fix timelines not loading after marking a room as read from notification when app was killed (#7202)
This can only happen when the app was killed by the OS and then you received some notification.

This notification action creates a new `DefaultSessionPreferencesStore` when trying to fetch `isSendPublicReadReceiptsEnabled`, which would contain a different session coroutine scope than the one created when instantiating the navigation graph (`@SessionCoroutineScope`), resulting in flows that never emit any items and close automatically as 'completed'.

This in turn prevented the timeline items subscription in `TimelinePresenter` from progressing, since we're also checking `isRenderReadReceiptsEnabled` there, which is closed and in turn closes the timeline items flow.
2026-07-15 15:01:35 +02:00
ganfra f9513193e2 Merge pull request #7177 from element-hq/renovate/androidx.sqlite-sqlite-ktx-2.x
Update dependency androidx.sqlite:sqlite-ktx to v2.7.0
2026-07-15 13:51:45 +02:00
Jorge Martin Espinosa 62cc7e0505 Allow homeserver to disable encryption in Element Pro (#7198)
* Allow HS to disable encryption in Element Pro

Add `force_disable_e2ee` support in the Element well known retriever and cache to allow a homeserver to disable encrypted room creation and enabling encryption in an existing room in Element Pro clients

* Bump enterprise repo back to `main` branch
2026-07-15 13:29:16 +02:00
Jorge Martin Espinosa 03f3e8b3e0 Try fixing DM creation in Maestro (#7204)
* Try fixing DM creation in Maestro

* Also handle 'invite new people to this room' with first invite to aRoomName

* Try adding a room list clean up sub-flow

* No need to press back button after successful invites

* Looks like 'hideKeyboard' works as 'back' and messes up with the navigation. Try using the workaround described in https://docs.maestro.dev/reference/commands-available/hidekeyboard#workarounds
2026-07-15 10:56:50 +02:00
Benoit Marty 264ec2416f Merge pull request #7194 from element-hq/renovate/org.matrix.rustcomponents-sdk-android-26.x
Update dependency org.matrix.rustcomponents:sdk-android to v26.07.13
2026-07-15 10:56:00 +02:00
Benoit Marty fefcb7a41d Merge pull request #7213 from element-hq/revert-7209-a11y/linear-progress-indicator
Revert "Expose current/max progress values to accessibility services"
2026-07-15 10:25:50 +02:00
Benoit Marty f794cb9f06 Revert "Expose current/max progress values to accessibility services (#7209)"
This reverts commit e915cd242b.
2026-07-15 09:49:06 +02:00
Jürgen Wischer e915cd242b Expose current/max progress values to accessibility services (#7209) 2026-07-15 09:23:54 +02:00
renovate[bot] afdf07b23e Update dependency io.github.zxing-cpp:android to v3.1.0 (#7206)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-15 08:36:59 +02:00
Timo 3bb7fe97e6 Default to dark theme for EC (#7162)
* Default to dark theme for EC
(this is the intended design on light and dark system configuration)

* Use `theme=dark` url param for element call

* Update CallScreenPresenter.kt
2026-07-14 17:49:40 +02:00
renovate[bot] 1619155b87 Update dependency io.github.sergio-sastre.ComposablePreviewScanner:android to v0.9.1 (#7201)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-14 15:48:26 +02:00
Jorge Martín 09b7732c1b Fix API breaks:
- Add `Beacon` variants for `NotificationData`.
- Add `call` and `status` to `UserProfile`.
2026-07-14 15:45:26 +02:00
Jorge Martin Espinosa f9112cc583 Collapse runs of deleted messages in the timeline (#7195)
* Collapse runs of deleted messages in the timeline (#7111)

* Collapse runs of deleted messages in the timeline

Collapse a run of three or more consecutive deleted messages into a single
expandable group, reusing the existing state-change grouping (the same way
element-web does it) instead of showing one "Message removed" placeholder per
deletion. The header shows the count only ("N deleted messages"); the Rust SDK
does not expose who performed the redaction, so we don't attribute it.

On by default. Runs shorter than three are left as individual placeholders, and
day dividers and read receipts are preserved (a run is broken by any
non-redacted item, so a day is never emptied).

Run the collapse as the final step of TimelineItemGrouper.group() instead of
a separate pass in the presenter, as suggested in review. Kept as its own
finalization step rather than via canBeGrouped() so the group stays all
redacted and the count-only header is preserved.
The group id is resolved through the grouper's existing id registry, like the
other groups, so it stays stable however the run grows and the user's
expand/collapse state is kept across timeline updates.

* Replace temporary strings with final ones

* Update screenshots

---------

Co-authored-by: Hi Dude! <klarkmonty@tuta.io>
Co-authored-by: manfrommedan <manfrommedan@users.noreply.github.com>
Co-authored-by: ElementBot <android@element.io>
2026-07-13 15:57:55 +02:00
ElementBot 03304b3ece Sync Strings (#7192)
* Sync Strings from Localazy

* Update screenshots

---------

Co-authored-by: jmartinesp <480955+jmartinesp@users.noreply.github.com>
2026-07-13 12:21:55 +02:00
Jorge Martin Espinosa 3edf4a6a69 Fix AAPT dump missing and reordering some permissions (#7193) 2026-07-13 10:26:49 +02:00
renovate[bot] b0c9947a11 Update dependency org.matrix.rustcomponents:sdk-android to v26.07.13 2026-07-13 08:06:26 +00:00
ganfra 14e4dd34ed Merge pull request #7180 from element-hq/renovate/kotlin
Update dependency org.jetbrains.kotlinx:kotlinx-collections-immutable to v0.5.1
2026-07-10 20:25:22 +02:00
ganfra 00135bbfea Merge pull request #7174 from element-hq/renovate/compose.bom
Update dependency androidx.compose:compose-bom to v2026.06.01
2026-07-10 20:24:55 +02:00
Benoit Marty 8a54ad6e6b Merge pull request #7178 from element-hq/renovate/io.sentry-sentry-android-8.x
Update dependency io.sentry:sentry-android to v8.47.0
2026-07-10 17:08:51 +02:00
Benoit Marty 5ef90250d9 Merge pull request #7176 from bxdxnn/misc/gallery-reply-text
Add item count text as the placeholder for in reply to box for galleries
2026-07-10 16:10:52 +02:00
Benoit Marty 07ea371806 Move strings to Localazy 2026-07-10 15:24:14 +02:00
Benoit Marty 7f13bb95cc Ignore large test class. 2026-07-10 15:16:38 +02:00
Benoit Marty 754eb3d0c1 Improve code and use type Video when necessary 2026-07-10 15:14:14 +02:00
Benoit Marty 68bf671f10 Merge pull request #7172 from element-hq/feature/bma/mergedManifest
Add check on merged manifest
2026-07-10 14:53:17 +02:00
ganfra 8923ffb7f1 Merge pull request #7165 from element-hq/feature/fga/local_network_permission
Handle local network permission - Android 17
2026-07-10 10:26:13 +02:00
ElementBot 3ad0ca7974 Update screenshots 2026-07-10 07:46:06 +00:00
renovate[bot] 330dd243e2 Update dependency org.jetbrains.kotlinx:kotlinx-collections-immutable to v0.5.1 2026-07-09 15:52:03 +00:00
ganfra 3f7ed5d87a Merge branch 'develop' into feature/fga/local_network_permission
# Conflicts:
#	libraries/androidutils/src/main/AndroidManifest.xml
2026-07-09 17:50:24 +02:00
ganfra 5f4bb30b18 Address PR review comments 2026-07-09 17:22:15 +02:00
ganfra 02a40cf2d5 Merge pull request #7094 from element-hq/feature/fga/set_user_status
WIP : Set user status
2026-07-09 14:34:23 +02:00
ganfra 96d443e443 Fix compilation warning 2026-07-09 13:51:37 +02:00
renovate[bot] 9e160e027d Update dependency io.sentry:sentry-android to v8.47.0 2026-07-09 10:51:24 +00:00
renovate[bot] 6296a538eb Update metro to v1.3.0 (#7168)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-09 10:52:45 +02:00
Benoit Marty b8d934058c Merge pull request #7132 from element-hq/fix/a11y-pin-screen
Add programmatic label and focus indicator to PIN entry field
2026-07-09 09:02:16 +02:00
Benoit Marty 7624391bdd Merge pull request #7170 from element-hq/feature/bma/updateGitHubTemplate
Update GitHub templates
2026-07-09 09:01:27 +02:00
renovate[bot] c7f4c96f70 Update dependency androidx.sqlite:sqlite-ktx to v2.7.0 2026-07-08 22:04:39 +00:00
bxdxnn 566b0c6f86 Add item count text as the placeholder for in reply to box for galleries 2026-07-08 20:01:21 +00:00
renovate[bot] 101e23da38 Update dependency androidx.compose:compose-bom to v2026.06.01 2026-07-08 17:20:43 +00:00
ganfra fad8dce166 Address PR review comments 2026-07-08 18:16:13 +02:00
Benoit Marty 3642065202 Merge pull request #7171 from element-hq/feature/bma/cleanUpManifest
Cleanup AndroidManifest.xml files.
2026-07-08 17:59:36 +02:00
ganfra d25362dd36 Merge branch 'develop' into feature/fga/set_user_status 2026-07-08 17:59:30 +02:00
Benoit Marty d7a2366a60 Merge pull request #7090 from manfrommedan/fix/keep-edit-when-attaching-media
Fix the message edit being lost when attaching media
2026-07-08 17:39:58 +02:00
Benoit Marty 3dabece5f8 Restore AndroidManifest.xml to fix lint warning. 2026-07-08 17:29:51 +02:00
ElementBot f25ca5b963 Update screenshots 2026-07-08 17:11:27 +02:00
ganfra a8b3db4621 Prompt for local network permission when changing server 2026-07-08 17:11:27 +02:00
ganfra 6f0788fd34 Introduce LoginModePresenter with local network permission gate 2026-07-08 17:11:27 +02:00
ganfra e92923a77d Prompt for local network permission on logged-in home 2026-07-08 17:11:27 +02:00
ganfra c2e8912fe2 Add LocalNetworkPermissionGate with rationale dialog 2026-07-08 17:11:27 +02:00
ganfra e9abed9178 Expose homeserverUrl on MatrixClient 2026-07-08 17:11:26 +02:00
ganfra 7f4d264ee0 Add ForceRequestPermissions event to bypass soft-denial intercept 2026-07-08 17:11:26 +02:00
ganfra 74bc2c0f5d Add local network address classifier and permission advisor 2026-07-08 17:11:26 +02:00
Benoit Marty 9d7ee09715 Merge pull request #6917 from bxdxnn/feat/skin-tone-picker
Add skin tone picker to the emoji picker
2026-07-08 15:15:56 +02:00
Benoit Marty 2e161914a9 Update aaptDump.txt when syncing strings from Localazy
New locales added via Localazy would change the locales line in
aaptDump.txt and break the manifest CI check if the reference file
is not kept in sync.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-08 14:42:13 +02:00
Benoit Marty ebc16bb3e1 Update version in ./tools/manifest/gplay/release/aaptDump.txt during the release. 2026-07-08 14:39:04 +02:00
manfrommedan 55f3a1acfb Keep the in-progress edit when attaching media
Picking an attachment reset the composer to Normal, which dropped an
in-progress edit: the attachment went out as a new message and the
leftover edit text then created another one instead of editing the
original.

Route every attach path through a single resetComposerModeAfterAttaching()
helper that clears Reply/Normal but keeps an edit pending, so the typed
text still edits the original on the next send. This also covers the
multi-item gallery picker added in #6519, which reset the mode
unconditionally.

Reply is still cleared (the attachment becomes the reply).

Fixes #7083.
2026-07-08 15:32:01 +03:00
Benoit Marty a7f4418555 Update dump. 2026-07-08 14:13:12 +02:00
Benoit Marty ac822544f2 Split gradle invocation, else universal build is not generated. 2026-07-08 14:12:32 +02:00
Benoit Marty 06c9c9b77c Ensure that Manifest content (aapt dump) is unchanged. 2026-07-08 13:30:44 +02:00
Benoit Marty 8731e2243a Fix script issue 2026-07-08 13:29:50 +02:00
Benoit Marty 20a55f4840 aapt dump 2026-07-08 13:16:33 +02:00
Benoit Marty 28abf3049e Cleanup AndroidManifest.xml files. 2026-07-08 11:55:50 +02:00
Benoit Marty 6124d99273 Merge pull request #7020 from bxdxnn/misc/svg-support
Add full SVG support
2026-07-08 10:57:12 +02:00
Benoit Marty e0161e5b25 Fix typo 2026-07-08 10:02:04 +02:00
Benoit Marty 15149af6b7 Update bug template question as many users do not understand the question.
Also change the default answer to `No`.
2026-07-08 09:59:16 +02:00
Benoit Marty 25479480c4 Update enhancement template so that global feature request are opened on element-meta 2026-07-08 09:55:08 +02:00
Jorge Martin Espinosa 885e042c12 Initial content scanner integration (#7139)
* Create a `ContentScanner` component:

- Add `:features:contentscanner:api` module with the interface.
- Implement the concrete component using the Rust SDK in `:libraries:matrix:impl`.

This can be used to both set up the content scanner media fetching in the SDK Client and manually scanning media sources from the Android side

* Create `ContentScannerUrlProvider` in a new `:features:contentscaner:api` module as a noop implementation

Use it as a placeholder for the code to instantiate a `RustContentScanner` with the fetched URL and set it as part of the SDK client.

* Expose the optional `ContentScanner` in the `@SessionScope`

* Split the storage part of the Element well-known retriever so it can be reused for the non-session bound retriever.

Also add the new `content_scanner_url` field to it.

This will be used by the enterprise code to fetch the actual content scanner url.

* Bump enterprise commit to have access to the actual content scanner url provider implementation

* Fix tests and lint issues

* Use the right namespace for `:features:contentscanner:api`, also use the right namespace for `impl`

---------

Co-authored-by: Benoit Marty <benoitm@element.io>
2026-07-08 07:50:22 +00:00
bxdxnn 4fd0be1fe5 Review 2026-07-07 16:46:42 +00:00
ElementBot 4190939ff8 Update screenshots 2026-07-07 14:54:55 +00:00
Benoit Marty a5d8b6eb2a Merge branch 'develop' into feat/skin-tone-picker 2026-07-07 15:21:27 +02:00
Benoit Marty 243acf1ee0 Add more screenshots 2026-07-07 15:19:18 +02:00
Benoit Marty 3ec7aa2a55 Remove default parameter values and make them not nullable. 2026-07-07 14:34:14 +02:00
Benoit Marty 387022a9af Use color from theme and improve preview of EmojiItem 2026-07-07 14:24:52 +02:00
Benoit Marty 7c3d1e2e80 Merge pull request #7158 from element-hq/renovate/androidx.compose.material3-material3-1.x
Update dependency androidx.compose.material3:material3 to v1.5.0-alpha22
2026-07-07 10:29:42 +02:00
ElementBot 56ad052575 Update screenshots 2026-07-07 07:53:22 +00:00
Benoit Marty 9672950512 Merge pull request #7138 from element-hq/fix/a11y-reading-order
Fix timeline reading order distorted when TalkBack is active
2026-07-07 09:42:00 +02:00
Benoit Marty 445b9a1ee7 Merge pull request #7157 from element-hq/feature/bma/updateOnboarding
Update onboarding screen text style
2026-07-07 09:40:29 +02:00
renovate[bot] 99b6151be7 Update dependency androidx.compose.material3:material3 to v1.5.0-alpha22 2026-07-06 16:33:47 +00:00
Benoit Marty 1ddb32645c Merge pull request #7007 from element-hq/renovate/androidx.compose.material3-material3-1.x
Update dependency androidx.compose.material3:material3 to v1.5.0-alpha21
2026-07-06 18:31:45 +02:00
ElementBot 654f149df9 Update screenshots 2026-07-06 15:28:56 +00:00
Benoit Marty 09c109a6cb Update OnBoarding view to match Figma
Closes #7156
2026-07-06 17:11:34 +02:00
Benoit Marty 30a22eeae6 Merge pull request #7024 from element-hq/feature/bma/scroll-to-unread-messages
Scroll to unread messages
2026-07-06 16:02:28 +02:00
renovate[bot] e4aee9eccd Update plugin gms_google_services to v4.5.0 (#7086)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-06 13:47:09 +02:00
renovate[bot] 8f9dfb5119 Update dependencyAnalysis to v3.16.0 (#7144)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-06 13:46:32 +02:00
renovate[bot] 474b039a87 Update dependency io.sentry:sentry-android to v8.46.0 (#7140)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-06 13:46:11 +02:00
renovate[bot] fdb2958183 Update danger/danger-js action to v13.0.10 (#7142)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-06 13:45:31 +02:00
Benoit Marty 5c7bac58ce Merge branch 'develop' into feature/bma/scroll-to-unread-messages 2026-07-06 13:36:28 +02:00
Benoit Marty e346f68fc8 Merge pull request #7044 from bxdxnn/fix/thread-list-avatar
Fix wrong avatar shown in the thread list's top bar
2026-07-06 11:34:14 +02:00
Jorge Martin Espinosa e4ce5a821a Update screenshots (#7155)
Co-authored-by: ElementBot <android@element.io>
2026-07-06 11:25:29 +02:00
ElementBot e91df7416a Update screenshots 2026-07-06 09:06:47 +00:00
Benoit Marty e3a2e98068 Fix compilation issue 2026-07-06 10:50:17 +02:00
Nick Hainke 8d9ad76531 Bump the default Gradle daemon heap to match what CI already uses (#7152)
A local release build runs R8 over the full app and reliably runs out of
heap at the checked-in 4g default. CI has used 8g for release builds all
along via GRADLE_OPTS, so bring the local default in line with that.

Same reasoning as 6e55f2d7 (#1087), which bumped 2048m to 4g.
2026-07-06 09:36:08 +02:00
Benoit Marty ad2655c87b Merge branch 'develop' into feature/bma/scroll-to-unread-messages 2026-07-06 09:15:34 +02:00
renovate[bot] 31d4ba3793 Update actions/setup-java action to v5.4.0 (#7143)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-06 09:06:24 +02:00
Benoit Marty ac1be8c87a Merge branch 'develop' into feature/bma/scroll-to-unread-messages 2026-07-03 14:30:14 +02:00
Benoit Marty 8840e2f96b Merge pull request #7112 from element-hq/feature/bma/galleryMessages
Support gallery messages
2026-07-03 14:09:20 +02:00
Benoit Marty b6f79a883e Improve accessibility text on RR 2026-07-03 12:36:50 +02:00
Benoit Marty bad39d62e9 Optimize code 2026-07-03 10:24:22 +02:00
ElementBot 06d778859c Update screenshots 2026-07-03 07:58:47 +00:00
Benoit Marty 762823ebd7 SampleMediaRenderer can be internal. 2026-07-03 09:52:12 +02:00
Benoit Marty 74f69b7eb3 Optimize code 2026-07-03 09:50:35 +02:00
Benoit Marty 4566712ad3 Take user choice into consideration when pre-processing the media. 2026-07-03 09:49:06 +02:00
Benoit Marty ac60d0fce7 Merge pull request #7141 from element-hq/feature/bma/visualListItem
Iterate on visual list
2026-07-03 09:35:31 +02:00
Benoit Marty 60837c0d7d Hide floating badge date from accessibility 2026-07-03 09:24:42 +02:00
ElementBot 301f930bcc Update screenshots 2026-07-02 17:55:54 +00:00
ganfra bfe21698f2 Tweak some UserStatusView ui 2026-07-02 18:04:43 +02:00
Benoit Marty 2010620fbc Merge branch 'develop' into fix/a11y-reading-order 2026-07-02 16:13:55 +02:00
Benoit Marty 6181f60b5e Fix compilation issue 2026-07-02 16:12:54 +02:00
Benoit Marty bf1ace48f2 Improve preview. 2026-07-02 15:38:53 +02:00
Benoit Marty 25716f6f3a Fix regression 2026-07-02 15:35:33 +02:00
Benoit Marty 00ffff4c0a Merge pull request #7133 from element-hq/fix/a11y-chats-search
Add accessibility label to search input
2026-07-02 15:30:39 +02:00
Benoit Marty 6b68ddcb25 Merge pull request #7135 from element-hq/fix/a11y-message-composer
Fix accessibility for message composer input and read receipts button
2026-07-02 15:30:09 +02:00
Benoit Marty 1fdc778fe2 Merge pull request #7137 from element-hq/fix/a11y-video-preview
Add content description to video preview for screen readers
2026-07-02 15:29:38 +02:00
ElementBot 7015172df1 Update screenshots 2026-07-02 13:12:22 +00:00
Benoit Marty f35546b473 Change text style of VisualList.
Ref: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=2001-140
2026-07-02 14:57:11 +02:00
Benoit Marty d8b1a41f56 Create dedicated package for visual list and add Figma link. 2026-07-02 14:45:22 +02:00
Benoit Marty 416da32c40 Set focused state only on the digit. 2026-07-02 13:38:25 +02:00
Benoit Marty a3fbbed8f4 A11Y: Improve accessibility of search field.
Closes #6389
2026-07-02 13:16:01 +02:00
Benoit Marty 57ed76bd03 A11Y: Do not read the +x Text. 2026-07-02 12:26:49 +02:00
Benoit Marty c1dfd6d6ae A11Y: Improve talkback for video and image / preview not preview. 2026-07-02 11:10:14 +02:00
Benoit Marty 54d452c364 Fix accessibility: remove TalkBack reverseLayout workaround (#6392)
The LazyColumn was using reverseLayout=false when TalkBack was active to work
around a Compose accessibility traversal bug. This placed new messages at the
top of the screen, distorting the reading order for TalkBack users.

With Compose BOM 2026.06.00, reverseLayout=true now works correctly with
TalkBack traversal, so remove both workarounds:
- Always use reverseLayout=true in TimelineView
- Remove the .reversed() for expanded groups in TimelineItemGroupedEventsRow

Note: this change should be manually tested with TalkBack to verify correct
traversal order (oldest messages first, newest last).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 17:55:36 +02:00
Benoit Marty 01c8064a16 Fix accessibility: add content description to video preview (#6382)
Set contentDescription on the ExoPlayer PlayerView so TalkBack announces
the video preview to screen reader users instead of treating it as
decorative/invisible content.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 17:52:56 +02:00
Benoit Marty 79f1423a30 Fix accessibility: message composer label and 'Seen by' button (#6390, #6393)
- MarkdownTextInput: set contentDescription on MarkdownEditText so TalkBack
  announces a persistent label even when the field has content (fixes #6390)
- TimelineItemReadReceiptView: remove hideFromAccessibility() so the 'Seen by'
  button is exposed to TalkBack; use semantics (not clearAndSetSemantics) to
  preserve click action, and add Role.Button (fixes #6393)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 17:49:32 +02:00
Benoit Marty 4efc82fa74 A11y: Add programmatic label and keyboard focus indicator to PIN entry field
Fixes: https://github.com/element-hq/element-x-android/issues/6391
Fixes: https://github.com/element-hq/element-x-android/issues/6401

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 17:29:09 +02:00
renovate[bot] 58b3830187 Update dependency org.maplibre.gl:android-sdk to v13.3.1 (#7129)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-01 11:14:49 +02:00
renovate[bot] a8d6652e03 Update dependency io.sentry:sentry-android to v8.44.1 (#7093)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-01 11:14:16 +02:00
Benoit Marty f954a20f67 Changelog for version 26.07.0 2026-07-01 11:09:22 +02:00
Benoit Marty 71ad652c38 Merge tag 'v26.07.0' into develop
tag
2026-07-01 10:07:55 +02:00
Benoit Marty 6cce74db88 [Gallery] Set media optimation user choice per media 2026-07-01 09:45:08 +02:00
Benoit Marty 16eec4d024 Simplify code 2026-06-30 12:33:10 +02:00
Benoit Marty 5ca1435e9e Remove dead code. 2026-06-30 12:26:43 +02:00
Benoit Marty 29743d3c89 Some renaming for clarity. 2026-06-30 12:16:04 +02:00
Benoit Marty 89b4722a58 Restore previous code. 2026-06-30 12:06:34 +02:00
Benoit Marty 99343926ff Extract SingleItemPreviewRenderer, make it a class and rename it to SampleMediaRenderer 2026-06-30 12:00:12 +02:00
Benoit Marty 96e1c93f38 Introduce derived state editedAttachments 2026-06-30 11:50:58 +02:00
Benoit Marty f91fd4b535 Rename method to sendMedia 2026-06-30 11:35:20 +02:00
Benoit Marty 5d0d8bedcd Fix name regarding naming convention 2026-06-29 14:28:14 +02:00
ElementBot 5f0461b1cd Update screenshots 2026-06-29 10:04:39 +00:00
Benoit Marty 79163b68b5 Fix quality issues 2026-06-29 11:50:28 +02:00
Benoit Marty c80eb12e2f Remove duplicated fixtures. 2026-06-29 11:36:20 +02:00
Benoit Marty 73ef038035 Preview: add video item in the list 2026-06-29 11:31:21 +02:00
Benoit Marty ad84122285 Revert change on TimelineItemVideoView, it was dead code. 2026-06-29 11:21:02 +02:00
Benoit Marty fd799a3bdf Add unit test on TimelineEventContentMapper. 2026-06-29 11:13:29 +02:00
Benoit Marty dd3beb11bd Add unit test on GalleryMediaGalleryDataSource and simplify the code. 2026-06-29 10:28:10 +02:00
Benoit Marty fb7bc50cdb Rename test class. 2026-06-29 10:15:28 +02:00
Benoit Marty 1a5336e650 Add missing test on EventItemFactory 2026-06-29 10:15:00 +02:00
Benoit Marty b2a7c6548a Add missing test on TimelineItemContentMessageFactory 2026-06-29 10:01:21 +02:00
Benoit Marty 95e656cc32 Fix regression on tests. 2026-06-29 09:11:51 +02:00
Benoit Marty b62242cdd2 Fix sharing gallery from pinned event timeline. 2026-06-26 17:36:27 +02:00
Benoit Marty a7e6d8af6e Better fix to render all items in the gallery. Should be able to render mixed items. 2026-06-26 16:24:57 +02:00
ElementBot e282212270 Update screenshots 2026-06-26 14:18:39 +00:00
bxdxnn 87aff40b26 Support gallery messages (#6519)
* Support gallery messages

* Address review

* Remove unused function

* Fix indent

* Add "Gallery" message prefix

* Support galleries for image editing

* Fix tapping on a tile opens the last item

* Fix overflow count

* Add caption editing to gallery messages

* Use the new gallery string for prefix

* Lock sending galleries behind a feature flag

* Fix detekts

* Fix

* Ensure image edition is saved when navigating

* Fix sending media broken on edited gallery.

* Fix tests

* Order imports

* Remove unused parameters.

* Fix copyright header of new files.

* Fix Konsist test

* Extract new previews to a dedicated file.

* Sync strings

* Remove unused import

* Update screenshots

* Trigger CI

* Remove parameters with default value.

* More cleanup

* Restore sendAsFile behavior.

* Improve Preview.

* Improve Preview.

* Improve Preview.

* Fix gallery sending cancel and retry issue

* Ensure any previous job is cancelled.

* Fix issue in summary message

* Gallery feature is disabled by default.

* Kotlin convention

* Remove useless parenthesis

* Update screenshots

* Fix test

* List -> ImmutableList

* Remove useless code.

* Render formatted caption for attachment list.

* Replace set of Booleans by an enum

* Remove unused model for individual caption in a gallery Event.

* Fix tests

* Fix tests

* Rework MediaViewer entry point.

And ensure that the clicked image from the gallery is displayed first.

* Ensure gallery item can be click in the pinned message list
Improve the gallery item click handling code.

* Improve code and fix separator color
Closes #7101

* React on attachment item click
Improve code

* Improve code and support 0 items in gallery.

* Fix click on attachment item not rendering anything.

---------

Co-authored-by: Benoit Marty <benoitm@element.io>
Co-authored-by: Benoit Marty <benoit@matrix.org>
Co-authored-by: ElementBot <android@element.io>
2026-06-26 16:00:56 +02:00
ganfra 0aea5f94ec Merge branch 'develop' into fix/thread-list-avatar 2026-06-26 11:07:31 +02:00
ElementBot f9a8acf4a5 Update screenshots 2026-06-25 10:50:51 +00:00
ganfra 29b8068d4a Address quality issues on user status 2026-06-25 12:32:32 +02:00
ganfra 4a143c1053 Format code 2026-06-25 12:32:32 +02:00
ganfra be872e6bec Move user status section below multi-account in preferences 2026-06-25 12:32:32 +02:00
ganfra 061fd82c1a Clip avatar to a circle on the user status screen 2026-06-25 12:32:32 +02:00
ganfra d29dad40f9 Extract custom status text max length into a constant 2026-06-25 12:32:32 +02:00
ganfra dd24e14cf6 Gate user status behind UserStatus feature flag 2026-06-25 12:32:32 +02:00
ganfra 1cfd3e4dfb Improve UI/UX for setting status 2026-06-25 12:32:32 +02:00
ganfra 553a4785e1 Rename Event 2026-06-25 12:32:32 +02:00
ganfra 2a0e41652a Match row heights: add vertical padding to plain text rows to align with TextField row 2026-06-25 12:32:32 +02:00
ganfra 078b63fd7c Move preview cases to UserStatusStateProvider, use @PreviewParameter in view 2026-06-25 12:32:32 +02:00
ganfra 889fd08b5c Fix button width jitter using rememberTextMeasurer to derive stable min-width 2026-06-25 12:32:32 +02:00
ganfra 167c1ced80 Add clear button in custom status text field, toggle Cancel/Save on text presence 2026-06-25 12:32:32 +02:00
ganfra 57617a362a Add TextFieldState overload to design system TextField, use it in UserStatusView 2026-06-25 12:32:32 +02:00
ganfra 73a8a3d5d8 Use TextFieldState in CustomInput state for proper MVI text handling 2026-06-25 12:32:32 +02:00
ganfra eeaaac2969 Move custom status emoji/text into CustomInput state (MVI) 2026-06-25 12:32:32 +02:00
ganfra 7309e49286 Wire UserStatusPresenter and UserStatusRow into PreferencesRoot 2026-06-25 12:32:32 +02:00
ganfra 6c34d647ca Wire UserStatusPresenter and UserStatusRow into PreferencesRoot 2026-06-25 12:32:32 +02:00
ganfra af5a237b4a Add UserStatusView composables with previews 2026-06-25 12:32:32 +02:00
ganfra 0528b787c9 Add UserStatusPresenter with full test coverage 2026-06-25 12:32:31 +02:00
ganfra eca45b574e Add UserStatusState and UserStatusEvent 2026-06-25 12:32:31 +02:00
ganfra 1640adcf9d Fix thread-safety and dispatcher consistency in user status stubs 2026-06-25 12:32:31 +02:00
ganfra 42966b393f Add user status string resources and PredefinedUserStatus enum 2026-06-25 12:32:31 +02:00
ganfra f4c79be328 Stub setUserStatus/clearUserStatus in-memory in RustMatrixClient 2026-06-25 12:32:31 +02:00
ganfra 742902ef02 Add setUserStatus/clearUserStatus stubs to FakeMatrixClient 2026-06-25 12:32:31 +02:00
ganfra 5697c9664f Add KDoc to InCall.callJoinedTs and clearUserStatus 2026-06-25 12:32:31 +02:00
ganfra bad32cbbda Add UserStatus and DisplayedStatus types to matrix API 2026-06-25 12:32:31 +02:00
Jorge Martín 2902549a88 Fix wrong selection behaviour 2026-06-23 17:14:31 +02:00
bxdxnn c4c0c482f0 Address review 2026-06-23 14:02:15 +00:00
ElementBot b3b8467636 Update screenshots 2026-06-22 12:36:29 +00:00
Jorge Martín 0bda1cd0d0 Fix double selection of emoji when long-pressing one that doesn't have skin variants 2026-06-22 12:03:00 +02:00
Jorge Martín 35dce84c7f Fix lint issues 2026-06-22 09:17:15 +02:00
Benoit Marty 6ca04ef772 Rename Preview. 2026-06-19 15:10:45 +02:00
Benoit Marty fa5e871e7e Fix detekt issue 2026-06-19 15:02:29 +02:00
ElementBot 3a5e9f43b9 Update screenshots 2026-06-19 12:22:22 +00:00
Benoit Marty 123037a612 Fix broken preview of BottomSheet in Android Studio. 2026-06-19 14:04:46 +02:00
Benoit Marty e98cbdfce1 BottomSheet for maps must be Expanded 2026-06-19 11:26:20 +02:00
Benoit Marty 643ece6af7 sheetStateForPreview does not need to be annotated with @Composable 2026-06-19 11:16:37 +02:00
Benoit Marty 82c6167441 Bottom sheet: initial state must be Hidden else there is no animation when opening the bottom sheet. 2026-06-19 10:38:20 +02:00
Benoit Marty 33e2f72792 Fix deprecation warning 2026-06-18 18:52:38 +02:00
Benoit Marty 5e30f94ccc Fix deprecation warning 2026-06-18 18:46:58 +02:00
Benoit Marty 3a2f19f26c Fix deprecation warning 2026-06-18 18:44:43 +02:00
renovate[bot] b226b81082 Update dependency androidx.compose.material3:material3 to v1.5.0-alpha21 2026-06-18 16:18:00 +00:00
bxdxnn cc736ec759 Fix wrong avatar in the thread list top bar 2026-06-17 16:20:34 +00:00
ElementBot 154a554d65 Update screenshots 2026-06-16 12:44:50 +00:00
Benoit Marty fdc98f6b8c Merge branch 'develop' into feature/bma/scroll-to-unread-messages 2026-06-16 14:28:44 +02:00
bxdxnn 5b53686c3e Add full SVG support 2026-06-14 10:06:17 +00:00
bxdxnn 88d419c03f Revert "Send SVG images as files (#4595)" 2026-06-14 10:06:17 +00:00
Jenna Vassar 5a2977134b Gate out-of-window jump-to-unread on genuinely unread displayable content
The jump-to-unread FAB could surface an OutOfWindow target when the
fully-read marker event was either loaded-but-not-displayed (state/filtered
events) or when there was nothing displayable to jump to. Add two guards to
the recompute:

- Require roomInfo.numUnreadMessages > 0, so the FAB only appears when there
  is genuinely unread "interesting" content (never state/hidden events).
- Add Timeline.isEventLoaded to distinguish "in the loaded window but not
  rendered" from "genuinely outside the window", falling back to the SDK only
  after the cheap display-index check (isKnown) misses.

The RustTimeline implementation reads the replay cache first and releases the
EventTimelineItem native handle via use {} when probing the SDK. Adds presenter
tests for the loaded-but-not-displayed and no-unread-messages branches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 18:05:22 -07:00
Benoit Marty ea94996db7 Set LaunchedEffect key on identifier list. 2026-06-09 09:27:05 +02:00
ElementBot 3312526055 Update screenshots 2026-06-08 16:28:13 +00:00
Benoit Marty bb36787371 Merge branch 'develop' into feature/bma/scroll-to-unread-messages 2026-06-08 17:30:11 +02:00
Jenna Vassar a201b3766e Fix jump-to-unread requiring two taps for an out-of-window read marker (#6943)
* Fix jump-to-unread requiring two taps for an out-of-window read marker

* Update features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt

Co-authored-by: Benoit Marty <benoitm@element.io>

---------

Co-authored-by: Benoit Marty <benoitm@element.io>
2026-06-08 15:52:24 +02:00
ElementBot 716d6d20b2 Update screenshots 2026-06-02 14:54:51 +00:00
Benoit Marty 7722107001 Merge pull request #6694 from jennaharris7/feature/scroll-to-unread-messages
Timeline: jump-to-unread FAB and mark-as-read shortcut
2026-06-02 16:39:01 +02:00
Jorge Martín 5e3b1463d4 Fix skin tone picker always selecting the middle item by default instead of based on the current pointer position 2026-06-02 12:55:23 +02:00
Jorge Martín cd64b5e376 Remove apparently unneeded selectedSkinUnicodes 2026-06-02 11:50:26 +02:00
Jorge Martín bfcce0e94b Tweak Konsist check to allow LongPress actions 2026-06-01 11:38:35 +02:00
Jorge Martín 77dbd56e4e Fix Konsist lint issue 2026-06-01 11:38:02 +02:00
Jorge Martín 0e5a7e9396 Fix selection behaviour when long pressing emojis with no skin tone variants
Also improve the threshold calculation to dismiss the displayed skin tone picker a bit
2026-06-01 11:37:55 +02:00
ElementBot aeadd9879e Update screenshots 2026-05-30 14:48:33 +00:00
bxdxnn a98fd96303 Add skin tone picker 2026-05-30 14:14:55 +00:00
Jenna Vassar 2c2d803e58 Update a11y string 2026-05-28 09:06:31 -07:00
Jenna Vassar 40f8b48256 Reuse UnreadAtom composable 2026-05-28 09:06:01 -07:00
Jenna Vassar d653ceb84c Merge remote-tracking branch 'upstream/develop' into feature/scroll-to-unread-messages
Resolved conflicts:
- libraries/matrix/impl/.../fakes/FakeFfiSpaceRoomList.kt: took upstream's
  direct return for rooms() — the simulateLongTask wrapper is for IO-style
  operations like paginate/reset, not a quick read.
- libraries/ui-strings/src/main/res/values/temporary.xml: kept the file with
  only the live strings (action_mark_as_read, a11y_jump_to_unread_messages).
  Dropped common_black (upstream moved theme strings to module-local R.string)
  and a11y_jump_to_unread_messages_count (badge count was removed earlier).
- RoomInfo.kt and its fixtures/tests: took upstream's version since both
  branches independently added fullyReadEventId via the same SDK bump
  (matrix-rust-components-kotlin 26.05.20).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 11:26:14 -07:00
Jenna Vassar 0399dab1da Dismiss mark as unread button eagerly 2026-05-21 08:48:38 -07:00
Jenna Vassar bdb42759de Show jump-to-unread when read marker is outside the loaded window
When the m.fully_read marker event is older than the loaded timeline
window, no virtual ReadMarker item is inserted and the chevron-up FAB
silently no-ops. Surfaces the SDK's new fully_read_event_id on RoomInfo
and routes the tap through the existing FocusOnEvent flow so the FAB
appears in catch-up scenarios and lands the user on the marker.

Bumps matrix-rust-components-kotlin to 26.05.20 to pick up the FFI
field; absorbs the ClientBuildException.InvalidRawKey and suspend
SpaceRoomList.rooms / subscribeToRoomUpdate API breaks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 07:11:53 -07:00
Jenna Vassar 257040f5ca Merge remote-tracking branch 'upstream/develop' into feature/scroll-to-unread-messages
# Conflicts:
#	features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
#	features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt
#	features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt
2026-05-12 06:57:52 -07:00
Jenna Vassar 8d8ac9373e Rename preview to reflect actual state 2026-05-11 12:27:00 -07:00
Jenna Vassar 65ed527384 Add comment to explain jump to bottom button containing box 2026-05-11 12:26:59 -07:00
Jenna Vassar e0046e9862 Return null instead of return@launch
Co-authored-by: Benoit Marty <benoit.marty@gmail.com>
2026-05-11 11:27:31 -07:00
Jenna Vassar 4b479638a4 Keep jump to unread button position stationary 2026-05-06 11:55:52 -07:00
Jenna Vassar 0daf7ecc7d Konsist fixes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 06:49:42 -07:00
Jenna Vassar 784f5df426 Mark as read menu ui tweaks 2026-05-05 18:44:11 -07:00
Jenna Vassar 880a189617 Code cleanup, accessibility checks 2026-05-05 17:27:36 -07:00
Jenna Vassar aed149731b Add mark as read buttons 2026-05-05 17:26:57 -07:00
Jenna Vassar 51816e0281 Move jumpToUnreadButton to bottom, remove badge count 2026-05-05 13:04:37 -07:00
Jenna Vassar 200b98b72b Consolidate JumpToUnreadState 2026-05-01 09:13:40 -07:00
Jenna Vassar 7148f2f28f Semantic fixes: clearer naming conventions, when statements 2026-05-01 09:01:32 -07:00
Jenna Vassar 78aa500d23 Fold newMessagesCount into NewEventState.FromOther 2026-05-01 08:16:35 -07:00
Jenna Vassar af9da10254 Merge branch 'element-hq:develop' into feature/scroll-to-unread-messages 2026-05-01 06:08:28 -07:00
Jenna Vassar ef7cb35de8 Quality checks/fixes 2026-04-29 18:13:38 -07:00
Jenna Vassar 2579e68edc Don't show NEW timeline divider when not applicable in previews 2026-04-29 17:29:51 -07:00
Jenna Vassar 9f26079b84 Add missing preview: both fabs have counts 2026-04-29 16:34:47 -07:00
Jenna Vassar 7f5ce76860 Feature: add jump to unread button with badge count 2026-04-29 15:41:05 -07:00
927 changed files with 14165 additions and 4165 deletions
@@ -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.
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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:
+13 -2
View File
@@ -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
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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'
+2 -2
View File
@@ -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'
+7 -7
View File
@@ -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:
+1 -1
View File
@@ -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'
+3 -3
View File
@@ -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'
+1 -1
View File
@@ -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'
+2 -1
View File
@@ -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:
+1 -1
View File
@@ -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'
-3
View File
@@ -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"
+10 -1
View File
@@ -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
View File
@@ -1,5 +1,6 @@
appId: ${MAESTRO_APP_ID}
---
- runFlow: cleanUpRoomList.yaml
- runFlow: searchRoomList.yaml
- takeScreenshot: build/maestro/300-RoomList
- runFlow: timeline/timeline.yaml
+49
View File
@@ -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
=============================
-15
View File
@@ -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.
+2 -20
View File
@@ -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)
-63
View File
@@ -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>
+27 -16
View File
@@ -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"
@@ -52,7 +49,7 @@
Using launchMode singleTask to avoid multiple instances of the Activity
when the app is already open. This is important for incoming share (see
https://github.com/element-hq/element-x-android/issues/4074) and for opening
the application from a permalink.
the application from a mobile.element.io link.
-->
<activity
android:name=".MainActivity"
@@ -72,7 +69,7 @@
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="open"
android:scheme="healthchat" />
android:scheme="elementx" />
</intent-filter>
<!--
OAuth redirection
@@ -86,9 +83,7 @@
<data android:scheme="@string/login_redirect_scheme" />
</intent-filter>
<!--
HealthChat web links
Note: hosts are listed one by one on purpose, a "*.healthchat.ch" wildcard would
also intercept the homeserver and authentication domains.
Element web links
-->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
@@ -97,12 +92,28 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<!-- HealthChat web client permalinks, example: https://app.healthchat.ch/#/room/#room:healthchat.ch -->
<!-- Matching asset file: https://app.healthchat.ch/.well-known/assetlinks.json -->
<data android:host="app.healthchat.ch" />
<!-- HealthChat matrix.to deployment, example: https://to.healthchat.ch/#/#room:healthchat.ch -->
<!-- Matching asset file: https://to.healthchat.ch/.well-known/assetlinks.json -->
<data android:host="to.healthchat.ch" />
<!-- Note: we can't use "*.element.io" here because it'll intercept the "mas.element.io" domain too. -->
<!-- Matching asset file: https://app.element.io/.well-known/assetlinks.json -->
<data android:host="app.element.io" />
<!-- Matching asset file: https://develop.element.io/.well-known/assetlinks.json -->
<data android:host="develop.element.io" />
<!-- Matching asset file: https://staging.element.io/.well-known/assetlinks.json -->
<data android:host="staging.element.io" />
</intent-filter>
<!--
Element mobile links
Example: https://mobile.element.io/element/?account_provider=example.org&login_hint=mxid:@alice:example.org
-->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<!-- Matching asset file: https://mobile.element.io/.well-known/assetlinks.json -->
<data android:host="mobile.element.io" />
<data android:path="/element/" />
</intent-filter>
<!--
matrix: links
@@ -116,7 +127,7 @@
<data android:scheme="matrix" />
</intent-filter>
<!--
links from the HealthChat matrix.to website, example: healthchat://room/%23room%3Ahealthchat.ch
links from matrix.to website
-->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -124,7 +135,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="healthchat" />
<data android:scheme="element" />
<data android:host="user" />
<data android:host="room" />
</intent-filter>
@@ -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)
-9
View File
@@ -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>
-9
View File
@@ -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>
-9
View File
@@ -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>
-13
View File
@@ -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"
}
@@ -10,19 +10,5 @@ package io.element.android.appconfig
object MatrixConfiguration {
const val MATRIX_TO_PERMALINK_BASE_URL: String = "https://matrix.to/#/"
/**
* HealthChat's own matrix.to deployment (https://github.com/Novaloop-AG/healthchat-matrix.to).
* It uses the exact same URL format as matrix.to, so links can be translated by swapping the
* base URL. Used both to recognise incoming links and to build links shared outside of Matrix.
*/
const val HEALTHCHAT_TO_PERMALINK_BASE_URL: String = "https://to.healthchat.ch/#/"
/**
* Base URL of the HealthChat web client, which is what the healthchat:// custom scheme
* handed out by [HEALTHCHAT_TO_PERMALINK_BASE_URL] points to.
*/
const val HEALTHCHAT_WEB_BASE_URL: String = "https://app.healthchat.ch/#/"
val clientPermalinkBaseUrl: String? = null
}
-17
View File
@@ -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")
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

+2
View File
@@ -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(),
)
}
}
+20 -40
View File
@@ -1,4 +1,4 @@
# HealthChat Android deeplink
# Element X Android deeplink
<!--- TOC -->
@@ -12,45 +12,31 @@
## Introduction
HealthChat Android supports deep linking to specific screens in the application. This document explains how to use deep links in HealthChat Android.
Element X Android supports deep linking to specific screens in the application. This document explains how to use deep links in Element X Android.
### Asset Links
Two hosts are claimed as verified Android App Links, and each one must serve a matching asset links
file listing `ch.healthchat.element` and the SHA-256 of the Play app signing certificate:
| Host | Asset links file | Served from |
|---|---|---|
| `app.healthchat.ch` | https://app.healthchat.ch/.well-known/assetlinks.json | the `element-web` fork (`apps/web/res/.well-known/assetlinks.json`) |
| `to.healthchat.ch` | https://to.healthchat.ch/.well-known/assetlinks.json | the `healthchat-matrix.to` fork (generated by `scripts/build.js`) |
The asset links file is available at https://element.io/.well-known/assetlinks.json
### Supported links
Element Call link:
> https://call.element.io/Example
Link to a user:
> https://to.healthchat.ch/#/@alice:healthchat.ch
> https://app.healthchat.ch/#/user/@alice:healthchat.ch
> https://app.element.io/#/user/@alice:matrix.org
Link to a room by id or alias:
> https://to.healthchat.ch/#/!roomid:healthchat.ch
> https://app.healthchat.ch/#/room/#healthchat:healthchat.ch
> https://app.element.io/#/room/!roomid:matrix.org
> https://app.element.io/#/room/#element-x-android:matrix.org
Link to a room with a specific event:
> https://app.healthchat.ch/#/room/!roomid:healthchat.ch/$eventid
> https://app.element.io/#/room/!roomid:matrix.org/$eventid
The HealthChat matrix.to deployment hands out a custom scheme to Android, which is registered too:
> healthchat://room/%23healthchat%3Ahealthchat.ch
> healthchat://user/%40alice%3Ahealthchat.ch
Standard Matrix URIs also work:
> matrix:r/healthchat:healthchat.ch
Only the two hosts above are declared in the manifest, so only those open the app from a browser.
`DefaultMatrixToConverter` is host-agnostic though: any URL using the `/#/room/`, `/#/user/` or
`/#/group/` path, as well as plain matrix.to links, are still resolved once they reach the app
(for instance when tapped inside a message).
Finally, notifications use an internal deep link that is not browsable:
> healthchat://open/{sessionId}/{roomId}/{eventId}
Note that it will also work with other domain such as:
> https://mobile.element.io
> https://develop.element.io
> https://staging.element.io
## Developer tools
@@ -58,34 +44,28 @@ Using an Android 12 or higher emulator
Ensure links verification is enabled
```bash
adb shell am compat enable 175408749 ch.healthchat.element.debug
adb shell am compat enable 175408749 io.element.android.x.debug
```
Reset link verifications for the given package id
```bash
adb shell pm set-app-links --package ch.healthchat.element.debug 0 all
adb shell pm set-app-links --package io.element.android.x.debug 0 all
```
Force the package id links to be verified
```bash
adb shell pm verify-app-links --re-verify ch.healthchat.element.debug
adb shell pm verify-app-links --re-verify io.element.android.x.debug
```
Print the link verification of the package id
```bash
adb shell pm get-app-links ch.healthchat.element.debug
adb shell pm get-app-links io.element.android.x.debug
```
```
ch.healthchat.element.debug:
io.element.android.x.debug:
ID: e2ece472-c266-4bf0-829c-be79959a6270
Signatures: [B0:B0:51:DC:56:5C:81:2F:E1:7F:6F:3E:94:5B:4D:79:04:71:23:AB:0D:A6:12:86:76:9E:B2:94:91:97:13:0E]
Domain verification state:
app.healthchat.ch: verified
to.healthchat.ch: verified
*.element.io: 1024
```
Verification only succeeds if the signature above is listed in the asset links files. Release builds
are signed by Google Play, so use the app signing certificate SHA-256 from the Play Console. For a
debug build, add the fingerprint of `app/signature/debug.keystore` (alias `androiddebugkey`) to both
files, or test with `healthchat://` links instead, which need no verification.
@@ -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.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

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
View File
@@ -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
}

Some files were not shown because too many files have changed in this diff Show More