zizmor flags the HealthChat release workflow now that the rebase PR shows
the whole file as new: five actions referenced by floating major tags, the
workflow_dispatch version input expanded straight into a shell script, and
a Gradle cache on a release build.
- Pin checkout, setup-java, setup-gradle, upload-artifact and
upload-google-play to commit SHAs with their version in a comment, the
way every upstream workflow does.
- Pass the version input through the environment instead of expanding it
into the run script.
- Disable the Gradle cache for the release build so a poisoned cache from
another workflow cannot reach the signed bundle.
The cached LFS checkout action checks out with persist-credentials: false
and then runs `git lfs pull` on a cache miss. Upstream is public, so that
pull works anonymously; on this private fork it fails with
`could not read Username for 'https://github.com'`, which is why the
Record-Screenshots label never produced a recording here (the only
successful runs were manual dispatches with a warm LFS cache).
Keep the job token configured for both LFS checkout steps. The push of
the recorded goldens already authenticates with the same token.
Follow-ups needed after rebasing the HealthChat series from v26.07.0 onto
v26.10.0 that span several of the replayed commits:
- Rename the five PreviewParameterProvider subclasses in features/healthchat
to the *PreviewParam suffix now enforced by Konsist.
- Rename MemberDirectoryEvents and HealthChatProfileSectionEvents to the
*Event suffix used by every upstream sealed interface.
- Import FakeStartDMAction from features/startchat/test, where it lives now.
- ListItem lost its headlineContent parameter in favour of content.
- HomeView gained onAddAccountClick and onSwitchToAccount, and onRoomClick
now carries an EventId; update HomeViewBackHandlingTest accordingly.
- Restore upstream's codecov-action pin (v7.1.1) that the CI squash carried
back to v7.0.0.
- Gate the new pr-checks.yml on the upstream repository: this fork does not
use the PR- label convention.
- Add the 26.10.0 section to HEALTHCHAT_CHANGES.md.
* feat(healthchat): add profile editor, member invite and status banners
Native 13-field profile editor with diff-only saves and avatar upload
through the HealthChat API (replaces the upstream display-name editor),
email invitation for approved accounts, and non-dismissible account
status banners in settings and the member directory.
* fix(healthchat): satisfy detekt and konsist rules
Konsist presenter helper shape, no '=' in state parameter docs, present-tense invite callback, and complete renderer KDoc.
* feat(healthchat): add member directory tab to home navigation
Third bottom-nav tab backed by the HealthChat API: searchable member
cards with native start-chat, approval-pending banner, retry on
failure and navigation into the native profile screens.
* fix(healthchat): stop tab back handler swallowing back presses
While a screen is pushed on top of Home, the still-composed tab
BackHandler intercepted back presses during the transition and
invisibly switched to the Chats tab. Gate it on the node's target
visibility in both the parent and Home's own backstack.
* fix(healthchat): satisfy detekt, ktlint and konsist rules
Import alias order, composable parameter order in HomeView, and konsist presenter helper shape.
* ci: pass the real repository name to the screenshot recorder
recordScreenshots.yml set GITHUB_REPOSITORY from `secrets.GITHUB_REPOSITORY`.
No such secret exists on this fork, so the expression expands to an empty
string and overrides the built-in environment variable of the same name.
recordScreenshots.sh checks it up front and exits 1 with "No repo specified",
so the workflow could never record anything here.
Use the `github.repository` context instead, which is always populated.
Claude-Session: https://claude.ai/code/session_01MQbp1EPM4TSXq8P8Pyq44F
* Update screenshots
* Revert "Update screenshots"
This reverts commit b99e0d643f.
* Reapply "Update screenshots"
This reverts commit 85bceb6d67.
* test(screenshots): stop a LayoutLib teardown crash failing the shard tests
Paparazzi 2.0.0-alpha05 / LayoutLib 16.2.1 races on HandlerThread startup:
HandlerThread_Delegate.run calls Process.setThreadPriority, which throws
NoSuchMethodError for Thread.setPosixNicenessInternal and kills the
"android.bg" thread. If the test thread reaches getLooper() after that, it
gets null, BackgroundThread.ensureThreadReady() NPEs, and the static
initialiser of LatencyTracker fails. The JVM then marks that class erroneous
for the lifetime of the fork, so every later preview showing the soft
keyboard dies with NoClassDefFoundError.
LayoutLibErrorFilterStatement already swallowed the bare NoSuchMethodError,
but PaparazziLogger re-throws collected background errors from teardown()
wrapped in a MultipleFailuresException when there is more than one, and that
wrapper slipped through. A recent run reported 17 failures with zero image
comparisons failing and zero delta images written.
Unwrap the wrapper by reflection and swallow it only when every collected
cause matches the known-bug list; anything unrecognised, and anything that
cannot be decomposed, is rethrown.
Also close a hole this filter had from the start. Paparazzi runs the test as
`try { body } finally { teardown() }`, so a teardown error discards whatever
the body threw — including a genuine snapshot mismatch. renderFailureCapture
is registered inside the Paparazzi rule to record the body's exception so it
wins over any teardown error, and the filter refuses to swallow anything if
that rule is not registered. Two HorizontalFloatingToolbar previews have been
mismatching silently behind this, and may now surface as real failures.
Claude-Session: https://claude.ai/code/session_01MQbp1EPM4TSXq8P8Pyq44F
---------
Co-authored-by: ElementBot <android@element.io>
Renderer slot in UserProfileView (all three profile screens) shows
specialty, practice, address, contact links and further info from
the HealthChat API, with inline retry on failure.
* feat(healthchat): add HealthChat API service layer
New features/healthchat modules: typed Retrofit client with
per-request Matrix token auth, member directory, profile and
invite services, plus test fakes and fixtures.
* test(healthchat): use runCatchingExceptions
Detekt's RunCatchingNotAllowed rule forbids plain runCatching.
Squash of ten CI commits from 2026-08-04 (all part of HealthChat release 26.08.0):
- ci(release): move releases to the persistent self-hosted runners
- ci: grant contents:read so checkout works on this private fork
- ci: disable the Label Community PRs job on this fork
- ci: run the heavy Gradle jobs on the self-hosted runners
- ci: rely on the runner image for the Android SDK
- ci: size the Gradle heap for the self-hosted containers
- ci: report runner capacity on the self-hosted jobs
- ci: record why the Gradle heap is reduced
- ci: cap Gradle workers so test JVMs fit the container
- ci: skip the codecov upload when no token is configured
The danger.yml hunk from the contents:read commit is omitted: upstream
replaced Danger with a github-script workflow in v26.09.
Since #7861 all the sessions of the Maestro account are ended after each run,
so the device under test is always the last session. In this case the title of
the sign out screen does not contain "Remove this device" anymore, and tapping
on the second occurrence of this text fails. Tap on the submit button using its
test tag instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Prevent foreground backfill from running when the app is active
This should be a background task, since it can severely degrade performance if the app is in use while it runs.
The worker won't run any more if the app is in foreground and will stop running if it was in background and now it's in foreground.
* Fix return inside `withTimeout`
Co-authored-by: Benoit Marty <benoitm@element.io>
* Remove initial delay: it shouldn't be necessary if the foreground state of the app is checked when the worker starts
Instead, if the app was in foreground, cancel the work and retry in 5 minutes.
---------
Co-authored-by: Benoit Marty <benoitm@element.io>
Thread notification tags have the form "roomId|threadId". clearReadRoomsNotifications
was building a RoomId from the whole tag, which is only validated in debug builds, so
in release an invalid room id reached RoomListService.room(). Add
NotificationCreator.getMessageNotificationRoomId(), the inverse of messageTag(), which
only keeps the part before '|' and checks it is a valid room id.
Also make RoomListServiceInterface.roomOrNull take a RoomId instead of a String.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
HomeView sends HomeEvent.DismissSwipeAvatarAnnouncement right before switching account. Switching
account replaces the LoggedInFlow root node, which destroys HomePresenter and cancels its
composition coroutine scope, so the dismissal could be cancelled before the announcement status
was stored. Use the app coroutine scope instead, which survives the account switch.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The test source set also has a RoomListState.kt file in the same package, so its RoomListStateKt
class was shadowing the one of the main source set, and the toRoomListBanner() functions were
not found when running the tests (NoSuchMethodError).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Account switching is now handled by RootFlowNode, through the onSwitchToAccount callback, instead
of HomeEvent.SwitchToAccount. To keep dismissing the swipe avatar announcement when the user
switches account by swiping their avatar, HomeView now also sends the new
HomeEvent.DismissSwipeAvatarAnnouncement.
Also opt in to ExperimentalCoroutinesApi in the HomePresenter tests using advanceTimeBy.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Replace DismissibleBanner, which used one AnimatedVisibility per banner, by RoomListBannerSlot,
which renders the banner to display with a single AnimatedContent. All the changes are now
animated: dismissing a banner, replacing a banner by the next one, and displaying a new banner.
Previously, when a banner was dismissed, the next banner was displayed without animation.
The dismiss event is now sent immediately, AnimatedContent keeps rendering the dismissed banner
while it animates out.
The banner slot of the LazyColumn now has a min height of 1px. When it was 0px high, the
LazyColumn was considering it as not visible and moved the first visible item to the first room,
so a new banner was added above the visible area of the list.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The distributors are now loaded on the IO dispatcher, which was a StandardTestDispatcher in the test, so the initial state could be read before they were loaded. Use an unconfined test dispatcher so that they are loaded deterministically.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add --continue to the unit tests, screenshot tests, detekt and ktlint
Gradle invocations, so that a failure in one module does not prevent
the other modules from being checked, and the CI reports all the
failures at once.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Extract the logic to observe the total unread count of a MatrixClient, refreshed on each sync
heartbeat, to a shared extension and use it in HomePresenter and PreferencesRootPresenter.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This is mostly done when uploading bug reports and on one hand, it doesn't really make sense since the binary contents will be unreadable anyway, and on the other it also messes up with our debug tools, making debugging some issues harder.
In the settings screen, when the other accounts section is expanded, display a counter with the
total number of unread notifications for each non-active account. The count is read from the
account's MatrixClient (if in memory) and refreshed on its sync heartbeat.
Part of #7769
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Follow the multi-account design for the "Confirm your digital identity"
screen: use the dedicated "Sign out" string for the top bar action and add
a leading icon to the "Use another device" button.
Add a test to check that clicking on "Sign out" emits the expected event.
Part of #7768
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The screen where the user enters the server address only supported the
system back gesture. Add a top app bar with a back button, consistent with
the screen to choose the account provider and FlowStepPage, so the user can
navigate back from it, especially when adding another account.
Part of #7768
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The case where we move the pointer once we've started the click on a link wasn't taken into account, so the click or long click gestures weren't canceled if the gesture ended up being a 'move' one.
Simplify the handler and make it more idiomatic too.
The `File.length()` docs state:
> Returns the length of the file denoted by this abstract pathname. The return value is unspecified if this pathname denotes a directory.
On Android, it *seems* to currently have the right behaviour, summing up the size of its children, which made `File.getSizeOfFiles()` double-count the size of some directories.
To avoid this, and be able to control the actual behaviour no matter what future Android versions do, let's count those ourselves.
PreferencesRootPresenter and HomePresenter were updating the latest session
in the session store directly, relying on RootNavStateFlow to trigger the
navigation as a side effect. Changing the current session is app-level
navigation, so it is now forwarded through a new `navigateToSession` callback
up to RootFlowNode, which performs the session store update.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Remove the default values from the Gradle file, the public MapTiler defaults
are now only defined in MapTilerDefaults. buildConfigFieldStr now accepts a
null value.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The map URL builders now only use the MapTilerConfig they are given, falling
back to the public MapTiler defaults for the missing values, instead of the
values defined at build time. This avoids for instance sending the build time
API key to a tile server provided by the homeserver.
When there is no config, no static map URL builder is created, so no request
is performed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LocationService now exposes the MapTiler config to use: the one provided by the
homeserver takes precedence over the build time one, and null means that no map
provider is configured. It is now session scoped, and isServiceAvailable()
returns a StateFlow derived from this config.
When no config is available:
- the location sharing option is hidden from the composer, and location events
cannot be opened in the location viewer;
- the "Location sharing" entry is hidden from the settings;
- location events in the timeline render a placeholder, without performing any
request to MapTiler.
The show and share location screens now get the config from LocationService
rather than fetching the homeserver config by themselves.
Closeselement-hq/element-android-enterprise#69
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Add total unread notification count to home screen
This will be calculated on each sync heartbeat (for now, using the state updates for the room list service).
* Simplify `counter` parameter logic
* Read the total unread count when the home screen is first displayed
---------
Co-authored-by: Benoit Marty <benoitm@element.io>
Add DismissibleBanner, which makes a banner shrink and fade out when it is dismissed, and
invokes the dismiss callback when the animation is over, so that the banner is removed from
the list when its height is 0. Apply it to all the dismissible banners of the room list.
The banners are now rendered in a slot which is always the first item of the room list, so
that the rooms, which do not have a key, keep the same index and the scroll position is
preserved when a banner is removed. The visible range of rooms sent to the SDK does not
include this slot anymore.
FullScreenIntentPermissionBanner and BatteryOptimizationBanner now take callbacks instead
of their state, like the other banners.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Display a banner on the room list when 2 or more accounts are logged in, to let the user know
that they can swipe their avatar to switch account. The banner is stored as a new
Announcement.SwipeAvatarToSwitchAccount, and is dismissed when the user taps on it, or when
the user switches account by swiping their avatar.
The banner is displayed at the same place as the new notification sound banner, but never
at the same time.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Update Matrix Rust SDK to 26.10.07-nightly
* Adapt to the new QR code login task API
`generate()` and `scan()` of `GrantLoginWithQrCodeHandler` and `LoginWithQrCodeHandler`
now return a task instead of suspending until the flow is over. Wait for the task to
complete, and cancel it if the coroutine is cancelled, so that the flow is also stopped
on the Rust side.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Add tests for the cancellation of the QR code login tasks
Check that cancelling the coroutine of the link new device handlers and of the QR code
login cancels the task returned by the SDK, so that the flow is stopped on the Rust side.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Fix import ordering in the link new device handler tests
ktlint expects the imports to be sorted in lexicographic order.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Benoit Marty <benoit@matrix.org>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LocalMediaFactory.createFromUri queries the ContentResolver and can decode the media, it was called on the main thread from the picker callbacks and when sharing a file.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Getting the distributors queries the PackageManager and the Play Services, it was done during the first composition of the screen.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The push history was written to the database synchronously, from the app coroutine scope which runs on the main thread, for every received push. Make PushHistoryService.onPushResult suspend and run all the database queries on the IO dispatcher.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Checking if the work is already scheduled does a blocking WorkManager query, which was done on the main thread at session start.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Same issue as when refreshing the profile manually: the profile listener stored the profile (Base64 encoding and session database access) from the session coroutine scope, which runs on the main thread.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The thread list subscription was launched in the room coroutine scope, which runs on the main thread, so the initial sync FFI call and the mapping of all the items and diffs were done on the main thread every time a room was opened.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`getUserProfile()` was storing the user profile (DB read, avatar Base64 encoding, DB write) on the caller
dispatcher, i.e. the main thread when called from a presenter, causing glitches in the screen transition.
- Run the whole `getUserProfile()` on the session dispatcher.
- Make `DatabaseSessionStore` main-safe by running the queries on the IO dispatcher.
- Do not refresh the profile twice from the settings screen.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Render an AccountInfoCard with the current user on the "Confirm your digital identity" screen,
so the user knows which account needs to be verified.
Closes#7898
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The incoming share is now displayed on top of the root, so the FTUE, and the session verification in particular, could be bypassed.
- When the current account is not verified, wait for the FTUE to be complete before displaying the incoming share, as before.
- When the user selects another account which is not verified in the room select screen, display the session verification flow of
the FTUE for this account on top of the share. As in the FTUE, the user has to verify the session or to sign out.
- Expose the FTUE session verification flow with a new SessionVerificationEntryPoint, displayed by OtherSessionVerificationNode
which sets up the session graph of the account to verify.
- Introduce RoomSelectNavigator so that RoomSelectPresenter can request the verification of the selected session.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Scroll the list to the top using a LaunchedEffect instead of recreating the LazyListState on each search query change.
- Use isBlank to check the search query, to match the logic of RoomSelectSearchDataSource.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Use try/finally instead of the awaitClose block, so that the entries stream, the controller and the
dynamic adapters are destroyed even if the suspending onControllerCreated callback is cancelled or
throws before awaitClose is reached.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The enterprise repository is private, so only mention the number of new
commits in the generated PR description.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Expose edit/redaction send states and retry/abort send from the Rust SDK
Pending edits and redactions have their own send queue entries, separate
from the original event. Surface them so the UI can reflect and act on them:
- Add `SendTarget` (Event, Edit, Redaction, Reaction) to the matrix API,
with a mapper to the Rust SDK type.
- Add `editSendState` and `redactionSendState` to `EventTimelineItem` and
map them from the SDK item.
- Add `Timeline.retrySend` / `Timeline.abortSend`, which take a `SendTarget`
so the right queued request is retried or removed. Implement them in
`RustTimeline` and in `FakeTimeline`.
* Track edit and redaction send states on timeline events
`TimelineItem.Event` now carries `editSendState` and `redactionSendState`
next to `localSendState`.
- `failedToSend` is true when any of the three states is failed, so a
failed edit or redaction is shown like a failed message.
- `pendingSend()` returns the first pending state together with its
`SendTarget`; the read receipt row, the send failure dialog and the
timeline presenter use it instead of reading `localSendState` directly.
- `TimelineItemReadReceiptView` is restructured so the row is only
rendered when there is something to show, and the send state takes
precedence over read receipts.
- Update the previews provider and test fixtures for the new fields, and
make `aMessageEvent`'s send state nullable.
* Retry and remove failed sends through the timeline
Retrying or removing a failed message used the event's `SendHandle`, which
only knows about the original send. Route both through the timeline with
the `SendTarget` of the pending request instead:
- `TimelineController` gains `retrySend` (which also re-enables the send
queue) and `abortSend`, acting on the current timeline.
- `MessagesPresenter` retries via `retrySend`, and does nothing when the
event has no pending send state.
- Redacting something that was never sent (no event ID) or a pending
edit/redaction now aborts the queued request rather than sending a
redaction, and no confirmation is asked for.
- Update the presenter tests accordingly.
* Log send queue errors
Log when the SDK reports a send queue error for a room, and when the
send queue is disabled, to help diagnose messages stuck in the queue.
* Make sure we display send queue errors before verification states
* Fix logic for displaying read receipts and add more screenshot tests
* Only use states with read receipts in `ReadReceiptBottomSheetStatePreviewParam`
* Handle `LocalEventSendState = null` state just like `LocalEventSendState.Sent`
* Add `TimelineItemAction.AbortSend` and use it for both:
1. Aborting sending a message/edit/redaction from the 'sending failed' dialog.
2. Abort sending a local message when selected from the timeline -> redact action.
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
When a PR is merged on develop with the enterprise submodule targeting a commit
which is not on the enterprise main branch, open a PR to update it to the latest
commit of main. The workflow also runs daily and manually to keep the submodule
up to date.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Previously, only the small remove button could get the keyboard focus, and the
focus ring was barely visible. Now the whole item is focusable, the focus ring is
drawn around it, and pressing an activation key (Enter, Space, D-pad center)
removes the item. Touch behavior is unchanged: only the remove button is clickable.
Also add a shared `activationKeys` set in the designsystem module.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The dynamic entries controller of a room list is created asynchronously, once
the entries are observed. Filters set before that were silently dropped, so for
instance the search query was not applied to the rooms of a newly selected
account in the room select screen.
RustDynamicRoomList now owns the controller, remembers the last requested filter,
and applies it when the controller is created if it differs from the initial one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The room select screen was using a SearchBar, with an active mode displaying the search results instead of the room
list. Use a SearchField instead, displayed above the room list, which filters the rooms directly. The account switch
section and the selected rooms are now also displayed while searching.
- Remove isSearchActive from the state, and the ToggleSearchActive event.
- The "No results" message is displayed when the query is not blank and no room matches it.
- The back button now always closes the screen.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Fix timestamp color to match the designs.
* Don't limit the call and unread row height to 16dp: the font size is not fixed and it might display components in a broken state
* Ensure the alignment works
* Remove `SemanticColors.unreadIndicator` and use `.iconAccentPrimary` and `.textActionAccent` instead
* Simplify and document `UnreadIndicatorAtom`
* Use `fontBodySmMedium` for the font size in the unread indicator
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
The incoming share was displayed in the logged in flow of the current
session, so it was destroyed when the current session was signed out, for
instance from another device. When another session was signed out, the room
select screen was kept and displayed an error if this session was selected.
To make the behavior consistent:
- The incoming share is now displayed by the RootFlowNode, on top of the
logged in flow, and does not depend on a session anymore. The latest used
session is passed from the RootFlowNode to the room select screen, through the
share flow, and is used as its initial session.
- When the root of the RootFlowNode backstack changes while an incoming share
is displayed, the new root is set below the incoming share, using the new
replaceRoot backstack operation, instead of removing all the elements.
- The room select screen handles the invalidation of the current session like
for any other session.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Move the "Rooms" section header inside the LazyColumn of the search results, so that it scrolls with the list
instead of staying fixed above it, as it is done for the room list when the search is not active.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The nested scroll connection of the account switch section was always attached. When the section is not displayed
(single account, Forward mode, or search active), its height offset limit is never set and stays at -Float.MAX_VALUE,
so the connection consumed all the scroll events and the room list could not be scrolled.
Only attach the connection when the account switch section is displayed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The config is the same for all the files and all the rooms, so there is no need to read it for each file.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
All the accounts are proposed in the account switch, even the ones with an
invalid token, so that the user can still see them. If the selected account
has an invalid token, or is invalidated while the screen is displayed (for
instance signed out from another device), the error is displayed instead of
the room list, the selected rooms are cleared, and the client of this account
is not used anymore.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
If the client of the selected account cannot be restored, the room list was
never loaded and there was no feedback to the user.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The share feature does not need it anymore, so there is no reason to expose it
in the matrix api module.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Instead of creating a SharePresenter from the graph of another session, the
SharePresenter now gets the client of the selected session from the
MatrixClientProvider and uses an AppScope ShareDataSender to send the data.
To be able to send media from AppScope, MediaSenderRoomFactory is now bound in
AppScope and takes the MatrixClient as a parameter, and a new
MediaOptimizationConfigProviderFactory provides the media optimization config
of any session.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When several accounts are signed in, sharing into the app no longer goes through the account selection screen. The room select screen (Share mode) now displays the selected account and an account switch on top of the room list, with a design similar to the multi account section of the settings.
Selecting another account displays the rooms of this account in place, scrolled to the top, without recreating the screen. The content is then shared from the room select screen using the selected account, with the same progress and error dialogs as when sharing with the current account. The current account is switched only once the share has succeeded, then the room is opened if the content has been shared to a single room.
Implementation:
- RoomSelectNode is now in the AppScope and uses MatrixClientProvider to get the room list of the selected session.
- ShareNode gets the SharePresenter of the selected session from its session graph, using the new ShareBindings.
- SessionGraphFactory is moved to the matrix api module so that it can be used by features.
The account selection screen is still used for permalinks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
In the new timeline renderer, the line end position is sometimes incorrectly returned when you pass the offset of the last character in the line, returning `0.0f`.
This caused the shape to draw for the line to be reversed, where the start was `0.0f` and the end was the actual start.
When we detect a `0.0f` returned value for this, just get the coordinates for the end of the line instead.
The RustMatrixClientFactory was using a single RustClientSessionDelegate for
all the clients. The delegate keeps a reference to the last bound client only,
and didReceiveAuthError does not tell which session is concerned. With several
accounts, when the session of one account was revoked, the session of the last
restored account was invalidated and its local data deleted instead, while the
revoked session kept on syncing with an invalid token. Also the isLoggingOut
flag was never reset, so any later auth error was ignored.
The clients which are not associated to a stored session (temporary clients
and clients used to perform a login) now use a NoOpClientSessionDelegate.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Add search backfill worker
This will run on background at most once an hour while the device has internet connectivity and the device is charging and start back-paginating through the user's rooms to make sure the search results are populated.
It'll run for the maximum time the default workers allow, 10 minutes.
* Create `RustMatrixClient.Factory` AssistedFactory to help with DI and add `SearchBackfillService`
* Start search backfill when entering the search screen, just once. This should help getting initial search results if none or few are found at first
Toggling this value had no effect because the feature was marked as finished so it wouldn't be displayed in the developer settings, which makes our code ignore any user-set value for it.
Instead, just create a `Feature` outside the `FeatureFlags` enum.
The test was in `libraries.fullscreenintent.test`, while the presenter is in `libraries.fullscreenintent.impl`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The fakes were in `libraries.voicerecorder.test`, the package of the `voicerecorder/test` module. Move them to `libraries.voicerecorder.impl.audio` and `libraries.voicerecorder.impl.file`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The tests and fakes were in `features.viewfolder.test.*`, which looks like a package of a test module, while they are in the `impl` module and test code from `features.viewfolder.impl.*`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The test was in `libraries.mediapickers`, while `PickerType` is in
`libraries.mediapickers.api`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
It was the only file of the `startchat/impl` module in the
`features.startchat` package.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Some test sources were in `features.call`, `features.call.ui`,
`features.call.utils` and `features.call.notifications`, while the code
they test is in the corresponding `features.call.impl.*` packages.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The namespace was `libraries.usersearch`, without the `.test` suffix, while
the sources are in `libraries.usersearch.test`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The namespace was `libraries.push.pushstore.impl`, while the sources are in
`libraries.pushstore.impl`. Also update the `adb uninstall` hint of the
instrumentation test accordingly.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The matrixmedia modules were using the `libraries.matrix.ui.media` package
(shared with the matrixui module) and `libraries.matrix.ui.media.*`
namespaces. Use `libraries.matrixmedia.api`, `libraries.matrixmedia.impl`
and `libraries.matrixmedia.test` instead, matching the module paths.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AppMigration11 was run once, in the first release containing it, whatever
the state of the MultiAccount feature flag. Users installing the app
while the feature flag was disabled were considered as new users and
would never see the announcement once the feature flag got enabled.
The multi-account announcement is now pending by default (NeverShown),
and is marked as shown when the user enters the login flow while the
feature is enabled, either for the first time or to add another account.
Users who were already logged in before the feature became available
will see the announcement.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
If the announcement was queued at upgrade while the user had a single
account, and the user then added another account without seeing it, the
announcement stayed pending and was shown later if the user removed an
account. Mark it as dismissed as soon as several accounts are detected.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Show a one-time bottom sheet on the home screen to announce that several
accounts can now be added (element-hq/element-meta#3349).
- Add `Announcement.MultiAccount`, which is never shown by default.
- AppMigration11 queues the announcement on application upgrade only,
and marks it as shown if the user already has several accounts.
- HomePresenter shows the bottom sheet only when the announcement is
pending, the MultiAccount feature flag is enabled and the user has a
single account.
- The "Add account" button hides the sheet, marks the announcement as
dismissed and starts the add account flow through the new
`HomeEntryPoint.Callback.navigateToAddAccount()`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add the `screen.multi_account_announcement.*` keys to the home module
Localazy configuration and import the latest strings.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The permission is granted from the system settings, outside of the app,
so nothing triggered a recomposition of FullScreenIntentPermissionsPresenter
when the user came back. The "Enhance your call experience" banner kept
being displayed until something else caused a recomposition.
Check the permission again with LifecycleResumeEffect, as done in
BatteryOptimizationPresenter, and add a test for this case.
Each Maestro run logs in to @maestroelement:matrix.org, and the sessions
pile up on the account. Run tools/mas/delete_all_sessions.py at the end
of the Maestro job, even if the tests failed, to end them all.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Some modules were using package names or namespaces inherited from copy-paste
or from older module names (e.g. `features.logout.impl` in the deactivation
module, `features.api` in the migration api module, `sessionstorage` in the
cachestore module), or containing typos (`roomaliasesolver`,
`roomdetetailsedit`).
Move the sources to the package matching their module, update the imports, and
rename the impacted screenshots accordingly.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Since Android 13, the system displays its own confirmation when content
is copied to the clipboard, so the application does not render its own
Toast or Snackbar anymore on those versions.
Context.copyToClipboard, CopyToClipboardUseCase and the direct
ClipboardManager usage in EventDebugInfoView are removed.
ClipboardHelper.copyPlainText(text, isSensitive, onSuccessOnOldDevice)
is now the only entry point, and AndroidClipboardHelper is the only
class using ClipboardManager.
Copy actions from the views are moved to their Presenter through new
events, or to the Node when there is no Presenter. A ToastHelper is
introduced so that Toasts are kept where they were used before.
Snackbars are kept where they were already used.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Use the password keyboard type, mark the field as a password for
autofill, and submit the reset with the IME "Done" action, matching
the login password field.
The recovery key was copied without EXTRA_IS_SENSITIVE, so Android 13+
showed it in the clipboard preview and keyboards could store it in
their clipboard history.
Add an isSensitive option to CopyToClipboardUseCase and
Context.copyToClipboard, and set it when copying the recovery key.
Code merged on develop (AccountInfoCard in SignedOutView) now uses
libraries.matrixui, which was removed as unused before the rebase.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Removing unused dependencies changed the library manifest merge order, so
RECEIVE_BOOT_COMPLETED is now listed after DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION.
The set of permissions is unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PinUnlockActivity calls EnterpriseService.semanticColorsFlow(SessionId?), so the
Kotlin compiler needs matrix.api on the classpath even if no symbol from it is
imported directly. enterprise.api only exposes it as an implementation dependency.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
It is not used anymore since the dependency has been removed from features/messages/impl.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Remove dependencies flagged as unused by the Dependency Analysis Gradle Plugin
(`./gradlew buildHealth`) and explicitly declared in module build files.
Removals were skipped when the dependency provides transitively something the
module (or a consumer of an `api` dependency) uses directly, as well as for
runtime-only dependencies (element-call-embedded, sentry-android,
rustls-platform-verifier). Dependencies added by the convention plugin helpers
(composeDependencies, testCommonDependencies) are left untouched.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Allow displaying only unread notifications
Add `FeatureFlags.ShowAllActivityInRoomList` (enabled by default, to match the current behavior) that, when disabled, will display the unread messages of a room instead of unread notifications if no unread notifications are found
* Restore UI changes to the badges, `RoomSummaryRow` and `HorizontalFloatingToolbar`
* Fix `NavigationBarIcon`: use `BadgeBox` component.
* Adjust padding in `RoomSummaryRow` to 6.dp
* Fix other indicators not displaying a dot anymore
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
* Make `StrictMode` more lenient for network on main thread violations
`rustls-platform-verifier-android:0.2.0` performs the certification revocation checks in whatever thread called the Rust code that triggered the network request. In our codebase, sometimes this is the main thread through `MainScope`.
This would usually be fine if we were using suspend APIs, since it would just wait for a result and not block the main thread. However, the cert verification logic uses some `HttpUrlConnection` calls under the hood that are fully blocking and this triggers `NetworkOnMainThreadException`, which in turn make the revocation fail.
With these changes, we log those issues but don't make them break the certificate verification. That said, we should revert this logic once a fix lands in the verifier.
* Make the `StrictMode` weaker only for release and nightly builds
This way we can fix the issues when we detect them in debug builds.
* Fix `org.matrix.rustsdk.Android.init` not being called immediately in `PlatformInitializer`
This must be called before `platformService.init(tracingConfiguration)`
* Add `penaltyDialog`, to check if it's useful or just annoying
Replace the hand-written runningFold in RootFlowNode.observeRemovedSessions() with the existing withPreviousValue() helper, as done in IncomingVerificationRequestObserver.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Clear the listener when RustSessionVerificationService is destroyed.
- IncomingVerificationRequestObserver removes the listener of the clients which are not in the MatrixSessionCache anymore.
- Remove the sessions from the MatrixSessionCache when they are removed from the SessionStore (logout, account deactivation).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Add remove and clear history buttons to search
* Rename `SearchHistoryResultItem` to `SearchHistoryListItem` and `MessagesSearchResultItem` to `MessageSearchListItem`
Also, add some docs.
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
- Update .idea/kotlinc.xml to Kotlin 2.4.20.
- Remove `-Xannotation-default-target=first-only`: the new annotation
default target rule is stable in Kotlin 2.4, and disabling it now
produces a CLI_ARG_DISABLES_STABLE_FEATURE warning.
- Replace deprecated `KSNode.validate()` with
`validate(enableNewFeatures = false)` to keep the same behavior.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Paparazzi 2.0.0-alpha05.1 ships layoutlib 16.2.3, which fixes the errors this
rule was swallowing. All screenshot tests pass without it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Recording rewrote every screenshot unconditionally, so renderer noise far below
the verification tolerance produced large "Update screenshots" commits.
Use `app.cash.paparazzi.overwriteOnMaxPercentDifference` to keep screenshots that
are within tolerance. Existing screenshots are no longer deleted before recording;
obsolete ones are pruned afterwards, only when their preview produced no
screenshot at all.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Restore all the sessions with a valid token so that they sync and can receive
incoming verification requests, and listen to the requests of all of them with
IncomingVerificationRequestObserver.
Requests for the current account are still displayed by LoggedInFlowNode.
Requests for another account are displayed on top of the current account,
without switching, by OtherSessionIncomingVerificationNode which sets up the
Session graph of the account receiving the request.
Closes#7770
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Replace SessionDetailsView by AccountInfoCard with the DeviceVerification mode
on the incoming device verification screen, so that the account concerned by
the request is clearly indicated, with its avatar and Matrix ID.
The current user is built from the SessionStore, including the locally stored
avatar thumbnail, used as a fallback when the avatar cannot be loaded, for
instance for an account which is not the current one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Add an extension for room details sections
Provide a no-op rendering hook for build-specific room details sections and shared room audio labels. Keep the default room details behavior unchanged.
* Cover the default room details extension
Verify that the no-op extension renders no content and leaves surrounding UI unchanged through recomposition. Cover its constructor and both generated render paths.
* Refine the room details extension and link the enterprise picker
Expose ColumnScope to contributed room details sections and use room-details string keys. Pin the enterprise submodule to the reviewed picker implementation so integrated builds include it behind the default-off flag.
* Make the room details extension a functional interface
Declare the single composable rendering contract as a fun interface to satisfy the Sonar check while preserving its named dependency injection binding.
* Update localazy strings for the audio section in room details
* Bump enterprise commit
---------
Co-authored-by: Jorge Martín <jorgem@element.io>
Display the account which has been signed out, using the `AccountInfoCard`. The data comes
from the session database, including the stored avatar thumbnail, since there is no Matrix
client available to load the avatar. `SignedOutState.signedOutSession` is now a non-null
`MatrixUser`, defaulting to a user with only the session id.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Compose 1.13 test libraries start the test window in touch mode, where the editor is
not focusable in Robolectric, so `requestFocus()` was not giving it the focus anymore.
Use `requestFocusFromTouch()`, which leaves the touch mode first.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
material3 1.5.0-alpha29 brings Compose UI/foundation 1.13.0-alpha01, where clickable
nodes cannot be focused in touch mode anymore when requested from the test. Switch to
the keyboard input mode first, like a hardware keyboard would do, so that the key gets
the focus and receives the key events.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Many Material3 APIs are now stable, so the `ExperimentalMaterial3Api` and `ExperimentalMaterial3ExpressiveApi`
opt-ins are not required anymore at those places. Also remove the propagating `@ExperimentalMaterial3Api`
marker from `TopAppBarScrollBehaviorLayout`, which does not use experimental API anymore.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The overlay now draws with no controller too, so starting a call no longer resets the content's state, such as the composer draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
supportsPictureInPicture and smallestScreenSize have to be declared here, because a library cannot change a host Activity's manifest entry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A wrapper around the logged-in content rather than an overlay, because a minimized call takes a strip of height above it while a maximized one covers it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The notification's Answer action named ElementCallActivity outright, so answering always opened the WebView whatever FeatureFlags.NativeCall was set to.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found on a device: with no UI to answer the microphone permission or unset the maximized snapshot, the call parks forever and blanks the screen through the proximity wake lock.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Keyed by session id in app scope rather than session-scoped, because answering a call notification after a cold start has no session graph to reach a session-scoped stack from.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The factory lives in libraries/matrix/impl because that is the only module compiled against the Rust SDK, and is app-scoped because a call is started for whichever account it belongs to rather than the one on screen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The component and the matrix-rust-rtc core publish as GitHub release assets rather than to Maven, so two filtered ivy repositories resolve them; both become plain coordinates the day they reach Maven Central.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the seam a native MatrixRTC call stack plugs into, with nothing behind it
yet, so that the stack itself lands as a self-contained change.
`FeatureFlags.NativeCall` is off by default and unfinished.
`features/callnative/api` declares `NativeCallEntryPoint`, and
`features/callnative/impl` provides a default that logs and does nothing:
turning the flag on today gets no call and a line in the log. That is the
honest outcome, where falling back to the WebView would make the flag look
broken rather than unimplemented.
`DefaultElementCallEntryPoint` forks on the flag. The read is asynchronous
because the flag is storage-backed, and caching it in an eagerly started
StateFlow would race a call placed just after app start onto the default
value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`CurrentCallService` and `ActiveCallManager` are read by the whole app but
written only by whoever is actually running a call. Writing has so far been
reachable only from inside `features/call/impl`, because that is where both
implementations live.
Adds `CurrentCallTracker` and `RingingCallTracker` to `features/call/api`, so
that a call implementation in another module can report the call it is running
and answer a ring, without depending on `features/call/impl`.
No behaviour change: both interfaces are implemented by the existing classes,
and their methods are aliases of methods that already existed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The SearchBarState based replacement displays the expanded search bar in a dialog,
which would change the UX (in particular for the emoji picker bottom sheet).
Keep the current behavior for now and migrate later.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
material3 1.5.0-alpha28 removed the value-based Slider overload that accepts custom thumb and track.
Keep the public signature of our wrapper and remember a SliderState internally,
as the deprecated material3 overload does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This causes issues with F-Droid, since now Posthog unconditionally references some Firebase classes, which are detected by their checks.
Note these don't do anything if Firebase messaging isn't included, which is already the case for the F-droid variant of the app.
The R8 changes should remove any code referencing Firebase in Posthog, making the offending classes unused and removing them from the final APKs.
This is not the ideal fix, that would be Posthog actually extracting these classes to an optional dependency, but it's a workaround to be able to publish F-droid versions.
Remove the direct logout dialog from the settings and from the home screen, where it was never triggered.
The sign out screen will later display the account information (#7765).
The direct logout is still used in the FTUE session verification screen, so remove only the now unused `canDoDirectSignOut` field.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Upgrade to `rustls-platform-verifier:0.2.0`.
This re-enables certificate revocation checks, but v0.2.0 contains a list of CRL servers that can be accessed using clear text requests to check the validity of certain certificates, which should fix the issue that prevented us from using certificate revocation and at the same time strengthening the security of the client app.
Since we already had some network configuration in Element X, and `network_security_config.xml` files can't be merged, what we can do is manually download and merge the XML contents using `tools/dependencies/update_rustls_platform_verifier.py` like this:
```sh
./update_rustls_platform_verifier.py 0.2.0
```
It will automatically bump the Gradle dependency and merge the security config entries.
* Bump SDK bindings to `v26.09.28`, containing the rustls bump on the SDK side
* Fix unused resources caused by `app/src/main/res/xml/network_security_config_element.xml`
* Migrate `SqliteStoreBuilder.passphrase` usage to use `highEntropyPassphrase`
This is the first step in migrating existing sessions from using a passphrase that needs key derivation, slowing down opening SDK DBs to just a raw key using the already random keys of the 'passphrase'
* Remove no longer necessary `keyOfSize` extension function
Reduce the max concurrent requests and max requests per second to 5. We bumped it to 10 before because it seemed to work well, but we're seeing rate-limit issues again.
Add an optional thumbnail to `MatrixUser` and `AvatarData`, filled from the session
database for the other accounts in the room list header, the settings and the account
selection screen. `ImageAvatar` renders this thumbnail instead of the initial letter while
the avatar is loading or when it fails to load, for instance when the homeserver of the
current account is not federated with the one of the other account.
Fixes#5571
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add a `userAvatarData` column to the session database, containing the base64 encoded
thumbnail of the user avatar. It will allow rendering the avatar of an account without
having to use its Matrix client, for instance on the signed out screen, or for other
accounts when the homeservers are not federated.
The thumbnail is downloaded when the user profile is retrieved or updated, and only if
the avatar URL has changed. The profile is now also persisted when received from the
own profile subscription.
Part of #5571
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PreferencesFlowNode routed navigateToLinkNewDevice() to
callback.navigateToAddAccount(), so tapping "Link new device" under
Account settings opened the "Add an account" screen instead — the
opposite direction, signing an extra account into this device rather
than authorising a new one.
The correct destination was already implemented and simply unreachable:
LoggedInFlowNode handles navigateToLinkNewDevice() by pushing
NavTarget.LinkNewDevice. Call that instead.
Regression from 9b2577f1dd ("Split settings: app settings / account
settings"); the previous code called callback.navigateToLinkNewDevice().
It went unnoticed because FeatureFlags.QrCodeLogin defaults to false,
which hides the entry point on default builds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017y1etzTCPzowrNcPyRBdjm
* Don't show fading edge if the code block can't scroll. Use `canScrollForward || canScrollBackwards` for displaying the fading edges in the code blocks
* Don't add extra vertical padding to the mention pills. This should make them look like they did in the previous renderer
* Allow passing through touch events in `Modifier.linkTapHandler` if no url annotation is clicked or long clicked
`detectTapGestures` always consumes the touch events, no matter if they are handled or not, blocking these events from reaching parent components. Should fix the issue of `ParagraphView` blocking the long press action in the message bubbles
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
* Bump SDK bindings to `v26.09.26`
This version contains a fix for issues in `v26.09.25` related to certificate verification.
* Fix breaking API changes in the latest bindings
* Unify `FeatureFlags.RoomThreadList` with `FeatureFlags.Threads`
* Display the unread notification count only when needed
* Adjust `CounterAtom` and `UnreadIndicatorAtom` to use the official M3's `Badge` component, with a few tweaks to ensure single digit items are always displayed as a circle
* +99 is now 99+
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
The script logs in to MAS with the web login form (username and password),
then uses the MAS GraphQL API to end all the active compat, OAuth 2.0 and
browser sessions of the account. MAS pagination of browser sessions does not
always progress, so sessions are fetched and ended in batches until none are
left.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The item is removed from the account section of the root settings screen and
added at the bottom of the Moderation and safety screen. As before, it is only
shown when at least one user is blocked, with the number of blocked users.
ModerationAndSafetyPresenter now computes the number of blocked users, and
ModerationAndSafetyNode exposes a Callback to navigate to the blocked users
screen. Tests are moved accordingly.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Add headers (`h1-h6`) tag support for the new timeline renderer
* Make H1 and H2 use the same (smaller) style
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
* Add a script to install and launch an APK given a PR URL
This is easier than manually downloading, then installing the APK or scanning the QR code using the device
* Allow fetching the APK using the PR number too
* Add global search results history: keep the latest 25 items used in the global search, save them to an encrypted file store.
* Integrate the search history in the global search feature
* Add `MatrixClient.getRoomInfo` and use it in `GlobalSearchPresenter`
---------
Co-authored-by: ElementBot <android@element.io>
* Add linkification to the new timeline renderer
Reuse `AnnotatedString.linkify` for now, even though it creates `Spannable` instances for that: it's the closest one to what we already had.
Also, make sure we use the same URL annotation identifier in the renderer and the linkificator.
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
* Fix forwarding an event not working in threads
Add the `TimelineProvider` for the event to the navigation flow so the events can be fetched from the right timeline.
* Integrate the new renderer with the timeline UI
* Composable functions are now in `:libraries:htmlrenderer:api` so we don't need to add `impl` to other modules.
* Improve inline code span
* Return both the text and URL in `onLinkClick` and `onLinkLongClick` so we can check the URL is not misleading
* Remove `SelectionContainer` for now - it creates issues when long pressing
* Make `LocalComposeTimelineEventRenderer` contain a `ComposeLocalTimelineEventRendererData` instance. This checks if the new rendering is enabled and what is the current user id so it can be used for styling mention pills
* Add fading edges to code block to indicate scrolling is possible
* Long pressing in a code block copies its contents to the clipboard
* Fix alignment of ordered list item prefixes
* Don't use default values in State data classes
---------
Co-authored-by: ElementBot <android@element.io>
* Remove Roborazzi, use Paparazzi for Compound
The new screenshots will be recorded with the rest of the Paparazzi ones. Note the names changed, since Paparazzi enforces a `$packagename.$testname.$name.$extension` format
* Set a max width value for color previews
* Merge the `verifyPaparazziDebug` CI steps
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
* Add linear animation to voice message playback progress
Instead of animating the small `progress` value changes, calculate an animation for the range `maxProgress - progress`, with a linear interpolator so it's completely smooth. Previously, the progress was updated in 'steps' that were quite noticeable.
* Added HTML parser to abstract tree nodes
* Add HTML renderer using `HtmlMessageContent`
* Add custom rendering for inline code nodes besides the monospace font
* Support `<s>` and `<strike>` too
* Add `ContentAvoidingLayoutData` support
* Improve the last block node behaviour and its integration with `ContentAvoidingLayout`
* Move `ContentAvoidingLayout` to `:libraries:ui-common` so it can be shared
* Add missing `:libraries:htmlrenderer:impl` entry in `DependencyHandlerScope.allLibrariesImpl`
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
* Use a patched fork of Konsist
The patch contains code to ignore hidden folders in the root project: by default, Konsist scans *every single file* in the root directory except for a few exceptions, and hidden folders were not a part of that.
That means `.git`, `.github`, etc. would be scanned to for no good reason and even worse, hidden folders used for worktrees would get scanned too, so Konsist would end up scanning the same project with small variations several times and OOMing / taking ages to finish.
* Make `Immutable/Stable` annotation check more performant. Also, fix case that was previously not found
detekt's default configuration excludes test source sets from
`TooManyFunctions` and `StringLiteralDuplication`, but not from
`LargeClass`, so large test classes were reported and had to carry a
`@Suppress("LargeClass")` annotation.
Add the same kind of exclusion to the `LargeClass` rule in
`tools/detekt/detekt.yml` and remove the 24 now-redundant suppressions
from test files. The two remaining suppressions are in production code,
where the rule still applies.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Set `maxParallelForks = 1` for most tests, except for screenshot ones
It turns out the Robolectric setup penalty compounds the more parallel JVMs you spawn. Using 1 JVM vs 8 was up to 2.5x faster in local tests.
* Split the roborazzi tests tasks config and CI step
Point to the enterprise submodule rebased on main, which brings the X.509
feature, and resolves the MdmData conflict between the new UseX509 entry and
the split of AccountProvider into AccountProviderServerName,
AccountProviderManagedBaseUrl and AccountProviderManagedCanUseServerName.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both created a dedicated timeline for each operation performed in a thread, and
never closed it, leaking SDK resources. Apply the same treatment as PollRepository:
replace getTimeline() with a scoped withTimeline() helper, which closes the created
timeline once the operation completes, whether it succeeds, fails or gets cancelled.
In DefaultMediaSender.sendGallery(), waiting for the upload handler is now done
inside the block, so that the timeline stays open until the upload completes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The problem seemed to be sometimes the background work to switch from live timeline to a focused event timeline wasn't finished in time and an assertion failed from time to time.
By providing the coroutine to run this on we can pass a `TestScope` and force it to reliably run immediately instead in unit tests.
PollRepository created a dedicated timeline for each operation performed in a
thread, and never closed it, leaking SDK resources. Replace getTimelineProvider()
with a scoped withTimeline() helper, so that the created timeline is closed once
the operation completes, whether it succeeds, fails or gets cancelled.
The live timeline is left untouched, since it is owned by its room.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The script was relying on the external git-flow tool and on a local
`git config gitflow.*` initialisation, which made its behaviour depend on
the machine setup and hid what was actually done to the branches.
The release branch is now created with `git checkout -b`, and the release
is finished with explicit merge, tag, back-merge and branch deletion
commands. The merge and tag messages are provided explicitly so that the
generated history stays identical to the previous releases. Each step now
aborts the script with a clear message when it fails, instead of letting
the script continue.
Note that tools/release/release.sh (V1) still uses git flow.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Synapse deletes sliding sync connections that have been unused for 7 days. When
an OEM battery manager has kept the application frozen for longer than that, the
next sliding sync request is answered 400 M_UNKNOWN_POS.
The Rust SDK handles that: it resets `pos` and, in offline mode, restarts the
sync. When that recovery gives up, the sync service ends up in the Error or
Terminated state and stops for good, and it is then up to the application to
call start() again. Element X iOS does it, we do not: SyncOrchestrator only
restarts the sync service when its state is Idle, so the sync stays dead for the
rest of the process lifetime.
Nothing recovers from there. The sync never reaches Running, so
waitForE2eeInitializationTasks() never returns, the session verified status
stays Unknown, and the FTUE is stuck on WaitingForInitialState. Clearing the
application data is the only workaround.
Restart the sync service when it is in the Error or Terminated state, with an
exponential backoff from 1s to 30s to avoid hammering a failing homeserver. The
backoff is only reset once the sync service has been running for a while, since
startSync() moves the state to Running right away.
Fixes#7689
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
emptyNode() renders nothing at all, which is fine for a placeholder that is left
within a few frames, but makes the application look frozen when the wait can
last, or never end.
Add a delayBeforeShowingContent parameter to emptyNode(): when it is finite, a
progress indicator and a "Loading…" label fade in after that delay, reusing the
existing DelayedVisibility helper. It defaults to Duration.INFINITE, so the
splash screen keeps rendering an empty canvas.
Use a one second delay on the two placeholders which are not guaranteed to be
left: the FTUE one, rendered while waiting for the session verified status, and
the LoggedInFlowNode one, rendered while the FTUE state is unknown.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Try improving renovate issues with Maven Central rate limiting
Try not to make too many concurrent requests to Maven Central
The logs display rate limit responses followed by OK responses, which could mean we're hammering the Maven Central repo with too many concurrent requests
* Try not hitting Maven Central for `androidx` packages
sessionPaths keeps pointing at the directories of the account which has
just been logged in, and it was never reset. Adding another account
calls setHomeserver(), whose rotateSessionPath() starts by deleting the
directories of the previous sessionPaths, so the file and cache
directories of the first account were wiped.
finalizeClientCreation() now forgets the session paths once the login
succeeded. It is done there rather than in clear() on purpose: after a
failed attempt the directories are orphaned and must still be deleted by
the rotateSessionPath() of the next attempt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Since the login flows stopped handing their client over to
RustMatrixClientFactory and let finalizeClientCreation() build a fresh
one from the SessionData, two problems appeared.
The client built by loginWithQrCode() was never owned by anyone: it is
not assigned to currentClient, so clear() had nothing to close and the
Rust Client stayed open for the whole app lifetime, holding the session
stores of the freshly logged in account. It is now tracked in
currentClient, and also disposed of when the QR login fails, which is
safe because that flow always builds its own client.
The client of the session was also built while the login client was
still open. Both use the same session paths, so their state and crypto
SQLite stores, and the search index when message search is enabled, were
opened twice at the same time with no cross process lock to protect
them. clear() now runs before the final client is created.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Update dependency org.matrix.rustcomponents:sdk-android to v26.09.08
* Fix API breaks:
- There's no `SyncServiceBuilder.withProfilesExtension()` method anymore. This is now enabled unconditionally in the SDK.
- Timeline filters no longer use the `org.matrix.rustcomponents.sdk.TimelineEventFilter` FFI wrapper, but the `uniffi.matrix_sdk_ui.TimelineEventFilter` type directly. This also changes the underlying types used for inclusion/exclusion.
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jorge Martín <jorgem@element.io>
Fix API breaks:
- A new `Mentions` room list filter is available.
- `RoomListService.subscribeToRooms` is now `RoomListService.setRoomSubscriptions`.
- `enableAutomaticBackPagination` moved from `Client` to `ClientBuilder`.
Secrets are never exposed to pull_request runs from forks, so checkEnv.js
throws on MAESTRO_PASSWORD after a full APK build and emulator boot, and the
red check is indistinguishable from a real test failure. The existing guard
checked github.event.pull_request.fork, a field that does not exist on the
pull_request payload (it lives at head.repo.fork), so it was always true.
Gate both jobs at job level on head.repo.full_name == github.repository
(workflow_dispatch still runs), and drop the dead step-level condition.
Fixes#7653
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
With the default (queue: single), a run that finishes Build APK while
another run is already pending in the maestro-test group cancels that
pending run, which then never gets a Maestro verdict for its commit.
queue: max keeps the one-at-a-time constraint (no cancel-in-progress) and
lets up to 100 runs wait, at no runner cost.
Fixes#7654
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The PR metadata checks move to a new PR Checks workflow, and the lint,
ktlint and detekt reports are now surfaced as inline annotations from the
quality workflow. This removes the danger-js dependency, its ad-hoc npm
installs and the need for the ElementBot token, so the checks also run on
pull requests from forks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The -dontwarn is still required: firebase-messaging ships no consumer rules and
we exclude firebase-analytics from its dependencies. Only rename the file to
consumer-rules.keep and reword the comment, which embedded a local path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rule only kept the class, but ShowkaseBrowserActivity instantiates it with
Class.forName(...).getDeclaredConstructor().newInstance(), so R8 was free to
strip the constructor. Also drop the AGP template comments and rename the file
to .keep, for consistency with app/proguard.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The three rules came from a 2022 comment on Retrofit issue #3751. Retrofit
3.0.0 now ships META-INF/proguard/retrofit2.pro, which contains two of them
verbatim and covers retrofit2.Call through its conditional return-type rule.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This is now 7, which is higher than the max value (6) renovate adds by default, so they are *always* prioritised above the rest, even if renovate gets rate-limited.
* Keep the push foreground service alive while notifications are fetched
* Release the push foreground service as soon as results are handed over
---------
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
* Make sure `enforce_element_pro` is taken into account in FOSS
* Make sure we crate `RustTemporaryMatrixClient` instances with a proper `serverName` value
* Expose the timeline item gestures as TalkBack actions
Long pressing a timeline item opens the action list, and swiping it
starts a reply. Neither gesture is reachable with a screen reader
enabled, so those two actions were unavailable to TalkBack users.
Publish them as accessibility custom actions on the event row, reusing
the existing "Message actions" and "Reply" strings. The reply action is
only offered when the user is allowed to reply, matching the swipe.
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
Deciding whether a text message is emoji only compares the formatted body to
the plain one and runs a grapheme scan over the whole message. Nothing was
remembered, so both ran again on every recomposition of every visible message.
Remember the decision against the body and the formatted body. The preview
branch moves into the same computation, so the composable wrapper around
containsOnlyEmojis, which had a single caller and existed only to read
LocalInspectionMode, is no longer needed.
Ask the SDK to track receipts on message-like events only, for the live
timeline and for the focused and threaded ones, so all three agree. The
disabled case for the media and pinned-event timelines is unchanged.
This matches the behaviour on Element X iOS.
The SDK attaches the receipt to the preceding event rather than the next
one, which is the placement its API offers.
Add an interface `io.element.android.features.enterprise.api.AppStartupHook`,
which can be implemented in the enterprise build to perform certain actions on
startup.
It's a bit of a kludge for now since it doesn't block the rest of the app from
proceeding, but it will serve our purposes for now.
The dialog now reads "Confirm removal" over "Are you sure you wish to remove
this message?", with the reason field marked as optional beneath it, rather
than a bare title and a labelled field. TextFieldDialog gained a supporting
text so the field can carry that note.
The pending removal moves from a nullable timeline item to redactEventAction,
an AsyncAction whose Confirming state carries the event id, so it is modelled
the same way as every other action the user has to confirm.
`git flow release finish` opened the editor three times, and the content was
never edited: twice for the merge commits, where the message generated by git is
always kept, and once for the message of the annotated tag, where a placeholder
was typed by hand. The last tags carry the message "tag" for that reason.
GIT_MERGE_AUTOEDIT=no keeps the merge messages git already generates, and -m
provides the tag message. git flow appends the tag name to that message, so the
tag of the next release will read "Release v26.09.0".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
releaseV2.sh is a copy of release.sh in which the GitHub CLI is mandatory and
does what was previously done by hand: it finds and waits for the release.yml
run, downloads and extracts its artifacts, creates the pre-release with its 6
assets and the generated release notes, and prepends those notes to CHANGES.md.
It also compares the version code and version name of the signed artifacts to
the expected values instead of asking the user to check them, and no longer
needs ELEMENT_GITHUB_TOKEN, since the CLI uses its own credentials. release.sh
is left untouched, so that the next release can be done with releaseV2.sh while
keeping a way back if something goes wrong. The installation documentation is
also fixed, as it referred to an asset name that does not exist anymore.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The raw push rules returned by the SDK are a single long line of json, which is
hard to read on a phone. Re-encode the content with a pretty printer using a two
space indentation before handing it to the file viewer.
Parsing to a JsonElement and re-encoding preserves the order of the keys, so the
rules are still listed in the order the server returned them. Content which
cannot be parsed is passed through unchanged, so that a malformed payload remains
viewable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add a "Push rules" item in the Notifications section of the developer options,
above "Push history". It displays the raw push rules of the account, which were
only available as a rageshake attachment so far.
The content is read using NotificationSettingsService.getRawPushRules(), then
rendered by the new FileViewerEntryPoint, so that the screen looks like the media
viewer rendering a file, with Share and Download actions on a
push_rules<userId>.json file. The user id contains a colon, which is not a valid
file name character on all the file systems, so it is replaced by an underscore.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Introduce io.element.android.libraries.mediaviewer.api.FileViewerEntryPoint, a
reusable screen which renders content that is not a Matrix media the same way the
media viewer renders a file, with Share and Download actions.
The caller provides a filename, a mime type and the whole content as a String.
The node writes it to a temporary file in the cache directory, then reuses
LocalMediaView (so text content goes through TextFileViewer) and LocalMediaActions
for sharing and saving on disk. The node is contributed to AppScope so that the
screen can be used from any graph, since MediaViewerNode itself is bound to
RoomScope and depends on the timeline data sources.
Also route MimeTypes.Json to TextFileView in LocalMediaView, so that a json file
is rendered as text instead of falling back to the generic file card.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sharing from another app listed Element X once, with no way to pick a
conversation: the Direct Share row never showed any.
The app already publishes a long-lived, conversation-categorised shortcut per
room, which is most of what the sharing shortcuts API needs. The part that was
missing is the declaration that ties those shortcuts to the share intent: the
launcher activity had no `android.app.shortcuts` meta-data, so there was no
`<share-target>` for the system to match them against.
It now ships one, matching the activity that already receives ACTION_SEND, and
the shortcuts carry the category it names.
The system offers these from Android 10 onwards, where sharing shortcuts are
handled natively. Reaching further back would additionally need the
androidx.sharetarget library, which is not added here.
Part of #3835
Editing the caption of a media message dropped all of its formatting: bold,
links and the rest came back as plain text, and saving wrote that plain text
over the formatted caption.
The composer was being opened on the plain caption. Editing a message body
already starts from its HTML when rich text editing is on, and the caption
path was the only one that did not - it read `captionOrNull()` and never had
the caption's HTML to read, because the timeline content only kept the
already-parsed `formattedCaption`.
Media contents now carry the caption's HTML alongside the parsed one, the
same way text contents carry `htmlBody`, and editing a caption starts from it
when rich text editing is on. The save path already sent the composer's HTML,
so nothing changes there.
Part of #6730
Deleting a message that headed a thread took the thread with it: the "N
replies" summary under the message disappeared and there was no way back to
the replies, which are still there.
Every branch of the timeline content mapper passes `MsgLikeContent`'s thread
information on except the redacted one, which returned a bare
`RedactedContent` and dropped it. The thread summary lives on
`MsgLikeContent` next to `kind`, so it survives the redaction; only the app
was throwing it away. `RedactedContent` now carries the thread info like the
other contents do, and `EventTimelineItem.threadInfo()` reads it.
A deleted message that still heads a thread is also left out of the "N
removed messages" group, which would otherwise hide the summary again as soon
as three deletions ended up next to each other.
Part of #7397
Upgrading over an older install crashed before the login screen with
`SQLiteException: no such column: SessionData.position`. The session, cache
and push databases were opened at their new schema version but their tables
were never migrated.
`SqlCipherDriverFactory` overrides `AndroidSqliteDriver.Callback.onUpgrade` so
the database key can be replaced, and never called through to the base
implementation - which is the only thing that runs `schema.migrate`. Every
SQLDelight migration in the app was skipped as a result.
The override now calls `super.onUpgrade` after the re-key, so a database that
crosses the re-key version is still re-encrypted before its tables are
migrated. It moved out of an anonymous object into a named `MigrationCallback`
so the behaviour can be tested.
Part of #7509
The room list preview prefixes the sender's name unless the room is a
direct message. A room that was created as a group and later converted to
a direct message is not marked as one by the server, so its preview kept
naming the only other person in it on every message, while iOS did not.
The prefix now depends on how many people are actually in the room rather
than on the direct message flag, which is the behaviour asked for in the
issue: no name while only two people can be talking, the name back as
soon as a third joins.
Part of #1548
While the homeserver is unreachable but the device still has a working
network, the app showed no indication at all: the syncing chip is hidden
as soon as the SDK reports the session offline, and the offline banner
only appears when the device itself has no connectivity. A blocked or
down server was therefore indistinguishable from a quiet, healthy one.
The chip now has three states rather than a boolean, and the third says
"Server unreachable" for exactly that case. When the device has no
network the chip stays hidden, because the offline banner already says so
and two overlapping messages would be worse than one.
Part of #4099
A session whose recovery state is INCOMPLETE has not got the secrets it
would need to sign another device, so accepting an incoming verification
request from it can only fail. The screen offered "Start verification"
anyway, and the user learnt otherwise at the end of the flow.
Show the design's alert step instead — "Verification with this device is
currently unavailable" — with only the Ignore action, and leave the
request unacknowledged in every other respect.
Part of #5344.
The developer option passes its value through to the widget URL verbatim,
so it has to be the URL of the page that joins a room rather than the
deployment root. That is not obvious from the field, and hughns asked for
it to be written down in docs/ rather than expanded inline in the app,
because the path depends on how the deployment was hosted.
Part of #4475.
The gallery gave no indication of how old the screenshots on the page were,
which is one of the remaining items on the GitHub Pages improvement list.
The generator now writes the time it ran alongside the screenshot data, and the
page renders it in the footer next to the existing links.
The doc told the reader to add disableSslVerification() to
RustMatrixClientFactory.create(), which has not built the client for a long
time; getBaseClientBuilder() is where the chain lives, and it covers both the
login client and the restored session client.
It also only described the emulator's own proxy setting, while the app already
follows the device's global proxy through DefaultProxyProvider, which is the
only way to do this on a physical device.
The timeline renders a message with the sender's raw Matrix ID whenever the
SDK has no profile for them, even though the room member list the same screen
already holds usually knows their display name and avatar.
The timeline item factory now falls back to the room member for a sender whose
profile is not resolved, both when the item is first built and when the member
list arrives afterwards, so the name and the avatar settle on the display name
instead of staying on the ID.
The protocol carries a reason on a redaction and other clients let the sender
give one, but removing a message here sent the redaction straight away with no
reason and no confirmation.
Removing a message now opens a dialog with an optional reason, and the
redaction is only sent once it is confirmed. A blank reason is sent as none, so
confirming without typing anything behaves exactly as before. A message which
was never sent has nobody to give a reason to, so the send failure dialog's
Remove still redacts it immediately.
The member suggestions were emitted in whatever order the room member list
happened to be in, so a member who only matches somewhere in the middle of
their name could be offered above one whose name starts with the query.
Matches are now ordered by how well they match: display name prefix first,
then user ID localpart prefix, then any other match. The sort is stable, so
members of equal rank keep the order the member list gave them.
The @room suggestion was also matched case sensitively, so typing @Ro or
@ROOM dropped it while @ro kept it. It now matches the query the same way
the members do.
The HTML sanitiser in toHtmlDocument() drops every tag outside its safelist,
which does not include img. A formatted body whose only content is an image —
a custom emoji, or a bot posting an image inline — therefore cleans down to an
empty document, and because that document is not null the ?: body fallbacks in
the timeline item factory, the room list preview, the notification resolver and
plainText are all dead. The message renders as an empty bubble.
Return null from toHtmlDocument() when no text survives the sanitisation, so the
existing fallbacks take the plain text body, which for these messages carries the
alt text the sender provided.
Every @BindingContainer now lives in a di subpackage, in a file named after
it. Containers that were declared inside a shared file (SimplePlayer.kt,
LockScreenConfig.kt, VoiceMessagePresenter.kt, TimelineItemPresenterFactories.kt,
MediaItemPresenterFactories.kt) are extracted into their own file.
Method names are made consistent too:
- @Binds functions are named bind<ConcreteParameterType>, fixing three that
named something else (notably RoomAliasResolverBindingContainer, whose
provider was still called providesJoinRoomPresenterFactory).
- @Provides functions are named after what they provide, and all use the
provides prefix rather than a mix of provide and provides.
ANDROID_KEYSTORE moves back to the cryptography impl package, next to
KeyStoreSecretKeyRepository which uses it; it is not DI specific and would
otherwise be unreachable from there.
No behaviour change: Metro discovers containers via @ContributesTo and does
not care about method names, so the only call site to follow was in
DefaultVoiceRecorderTest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Metro's @BindingContainer replaced Dagger's @Module, but the declarations
kept the old Module suffix. Rename them (and their dedicated files) so the
type name matches the annotation, matching UserStatusBindingContainer which
already followed this convention.
No behaviour change: Metro discovers containers via @ContributesTo, so there
were only two call sites to update, both in tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The 174 commits develop gained since this branch was cut touch preview data
in three ways, and each is resolved so the flipped konsist rule still holds
across the merged tree.
Two screens the branch had renamed were deleted upstream by "Remove the
account provider search screen and homeserver resolver": the
changeaccountprovider and searchaccountprovider packages. Their deletion
wins, so ChangeAccountProviderStatePreviewParam, SearchAccountProviderStatePreviewParam
and the two views that used them go with them rather than coming back
under the new name.
Two preview classes develop added still carried the old suffix and are
renamed here, along with the `@PreviewParameter` reference in each view:
GlobalSearchStateProvider -> GlobalSearchStatePreviewParam and
SendFailureDialogStateProvider -> SendFailureDialogStatePreviewParam.
Everything else merged textually. No production class extending
PreviewParameterProvider is left with a `Provider` name, and no reference
to any of the 194 renamed names survives outside the CHANGES.md entry that
records a past pull request title.
* Fix warning about deprecated `keepRules.file` API
The proguard files have been moved to `app/proguard` in several directories for `common`, `foss`, and `enterprise` rules and it's those directories that are added through `keepRules.addStaticSourceDirectory(dir)`.
* Use `findPropery` instead of the deprecated `project.properties["key"]` API for `allWarningsAsErrors`
* Remove very legacy .gitignore rule and restore proguard `.keep` rules
* Make PIP react to camera orientation changes in the other participant's device
Adds a `controls.onPipMediaOrientationUpdate` callback for the WebView that will trigger a new `pipParamsBuilder.setAspectRatioFromOrientation(orientation)` method
* Fix warning about `PictureInPictureParams.Builder.setAspectRatio` only being available on API 26+
The fake was declared in RootFlowNodeTest, so other tests could not use
it. Move it to the testutils module, in the node package, and make its
documentation generic.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fake was declared in RootFlowNodeTest, so other tests could not use
it. Move it to the testutils module, in a new node package.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fake presenter was declared in RootFlowNodeTest, so other tests could
not use it. Move it to the testutils module, in a new presenter package,
along with MutablePresenter.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MatrixSessionCache and SyncOrchestrator were in the di package, which
does not describe what they do. Move them, along with their tests, to a
session package, and extract FakeSyncOrchestratorFactory from
RootFlowNodeTest so that other tests can use it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RootFlowNodeTest was declaring its own no-op fake, while the oauth test
module already provides one. Use it, and move it out of the customtab
package, since it is not specific to the custom tabs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fake was declared in RootFlowNodeTest, so other tests could not use
it. Move it next to the API it fakes, in a package mirroring the one of
the API, and let the result be provided by the caller, so that the case
where the account provider is not allowed can be tested too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fake was declared in RootFlowNodeTest, so other tests could not use
it. Move it next to the API it fakes, and let the cleared cache event
flow be provided by the caller, so that a test can emit events.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fakes were declared in RootFlowNodeTest, so other tests could not use
them. Move each of them next to the API it fakes, following the existing
convention of a createNodeResult lambda defaulting to lambdaError():
- FakeLoginEntryPoint in features/login/test
- FakeBugReportEntryPoint in features/rageshake/test
- FakeSignedOutEntryPoint in features/signedout/test (new module)
- FakeAccountSelectEntryPoint in libraries/accountselect/test (new module)
The login and rageshake test modules did not have Appyx on their
classpath, so add the architecture module to their dependencies.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
At cold start two writers set the root nav target of RootFlowNode: the
intent path (handleIntent -> onLoginLink -> NotLoggedInFlow(params)) and
the first nav state emission (NotLoggedIn -> NotLoggedInFlow(null)).
safeRoot() only dedupes on strict equality, so the two targets do not
cancel each other out and the last writer wins, silently dropping the
login params.
Keep the params coming from the intent as pending state on the node, and
let the nav state observer take them into account, so that the root is
derived from both the logged in state and the pending login params, and
both orderings converge on the same target. The pending params are
cleared when leaving the not logged in flow, so that a later sign out
does not resurrect a stale provisioning link.
Also log when switching to the not logged in flow, since the drop was
silent. The login hint can contain an email address, so only its
presence is logged.
Closes#7482
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Develop gained resolveImageMimeType, which reads the image header so a
share that declares a broad type is encoded as the format it really is.
Resolving the wildcard first would hand that helper a concrete image/jpeg
and make it return early, so a transparent PNG shared as image/* would be
flattened and an animated GIF would be recompressed. The header is now
read from the type the sender declared and the default subtype is applied
to whatever comes back, so the sniffing still runs and an undecodable file
no longer keeps a wildcard.
A non-image shared as image/* is therefore sent as image/jpeg rather than
image/*, which is the whole point of this branch, so the test covering
that case now expects the default subtype.
Tapping a room alias whose server is down left the resolver screen on a
spinner for as long as the screen stayed open, with no way forward other
than going back.
Bound the resolution with the ten second timeout already used when
joining a room by address, so the retry dialog is shown instead. The fake
client's resolve lambda becomes suspending so a server that never answers
can be simulated.
MainActivity is a singleTask activity that declares the elementx://open deep
link, so tapping a notification makes that deep link the base intent of the
task. When the user later brings the app back from the recents list, the
system replays that base intent with FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY, the
resolver turns it into a navigation again and the user is pushed back into the
room they had already left.
Ignore any intent the system replayed from the history, the same way the
launcher intent is already ignored.
* Keep the send button when the composer is emptied while editing
rememberEndButtonParams checked whether the composer had sendable text
before it checked whether the user was editing a message, so clearing
the text of a message being edited replaced the send button with the
voice recorder button. Starting a recording from there abandons the
edit.
Check the editing branch first, as the formatting composer already does,
and let it render a disabled send button when there is nothing to send.
* Update screenshots
* Request media audio focus when playing a voice message
Voice message playback asked for audio focus with
USAGE_VOICE_COMMUNICATION, the same bucket as Element Call and voice
message recording, while the player itself renders on the media stream.
Audio policy layers act on the declared usage, so a car head unit
switches to its call audio context and routes the playback away while
the player keeps running.
Declare USAGE_MEDIA (and STREAM_MUSIC on the legacy path) for playback,
which is what the player actually renders. Recording and Element Call
keep asking for voice communication focus.
* Keep playing a voice message through a short sound from another app
A notification, a navigation prompt or any other app taking focus for a
moment sends AUDIOFOCUS_LOSS_TRANSIENT or AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK,
and DefaultMediaPlayer pauses on it. The voice message stops part way
through and the user has to find it again and restart it.
Recording already ignores those two, because the capture path is not
affected by focus. Give voice message playback the same treatment, moving
the requester test into a named function rather than growing the inline
condition. A permanent AUDIOFOCUS_LOSS, a real phone call, still pauses,
and the media viewer is untouched.
* Model the fake voice recorder on a cancellable recording job
FakeVoiceRecorder pushed a whole recording synchronously from a single
suspending call, which made it both weaker and stricter than
DefaultVoiceRecorder.
Every level emission carried the same elapsed time and the emissions were
coalesced by Molecule, so no intermediate recording state was observable and
the level-limit test had to skip half the emissions to compensate. Each level
now advances the test time source and suspends for its share of the recording
duration, so the states arrive one at a time and that magic number is gone.
Call verification moved to the constructor lambdas the rest of the repo uses,
in place of hand-rolled counters and assertCalls. The counters discarded the
argument, so nothing pinned that cancelling a recording stops it with
cancelled = true; that is now asserted. Truth is no longer needed by the
fixture module.
A second startRecord() no longer raises an error where production only warns
and returns, and stopping or deleting terminates an in-flight emission loop
the way cancelling the real recording job does, so a stale recording state can
no longer arrive after the recording has finished.
* Keep each recording's level loop to its own recording
The level loop checked a single isRecording flag, which cannot tell "this
recording was cancelled" from "a later recording has started". After a cancel
followed by a new start, the first loop resumed, saw the flag set again, and
advanced the shared test time source, so the new recording reported the elapsed
time of both. Each recording now carries an id and its loop stops as soon as
that id is no longer the active one.
Declining an invite from the full screen invite left the user on that
same screen, so they had to press back themselves to get to the room
list. Both decline paths are affected: the plain decline handled by
AcceptDeclineInviteView, and the decline and block sub screen, which
navigated up into the join room flow instead of out of it.
Navigate up from the join room flow on a successful decline, and give
DeclineInviteAndBlockEntryPoint a callback so the sub screen can report
a successful decline separately from a back press. The room list flow
supplies the same callback, popping the sub screen once the invite has
been declined.
* Give the room name in the timeline top bar a 48dp tap target
The row carrying the room avatar and name is the control that opens the
room details, but it is only as tall as its content, which is under the
48dp minimum recommended for a touch target. Constrain it to a minimum
height of 48dp. The children stay centred inside the app bar, so nothing
moves visually.
* Update screenshots
The accessibility snapshot of the timeline is the only one the change
affects: the node which opens the room details now reports a 48dp tall
bounding box. The room name, the avatar and the icons stay where they
were.
* Rename `UrlContentFetcher` to `ClientUrlContentFetcher` and add missing base URL data (server name, homeserver URL)
* Avoid passing the `homeserverUrl/serverName` around.
Calculate the URL in `DefaultRemoteEnterpriseConfigRetriever` using the newly added `homserver` and `server` values as the possible base URLs, depending on the remote config source
* Make sure we don't perform blocking requests to fetch the remote config when restoring a session
Add a `needsRefresh` parameter to `RemoteEnterpriseConfigResult.NotFound` to handle this, and store a `no-config` value in the store to indicate an endpoint has been checked and no config was found
* Bump enterprise module
* Don't restart the sync service if the client is shutting down
This was causing issues when clearing the cache:
1. The client would ask the SDK to clear the caches.
2. The SDK would stop the sync service, clear the caches and set the `pos` value of sliding sync to 0 so the HS returns all the required state the next time the client is used.
3. However, between the sync service being stopped with the `pos=0` change and the client being destroyed, there was enough time for the `SyncOrchestrator` to decide the sync service was stopped for no good reason since there is network connectivity and the app is on foreground, so it restarted the sync, with `pos=0`.
4. This value was persisted, and the next time the new client did an 'initial sync' it had a `pos != 0`, which the HS uses to avoid returning some required state it already fetched.
5. The room list now lacks state events for lots of rooms and room names, avatars, members, etc. are now missing.
---------
Co-authored-by: Benoit Marty <benoitm@element.io>
Codecov reported 55.55% patch coverage on the previous commit: four of the nine
measured lines were only partially covered. No test paginates forwards in a
thread timeline, and the focus states Requested, Loading and Failure were never
live when a forward pagination arrived, so only the Success branch of
isPending() was ever evaluated both ways.
Add a test per uncovered state: a forward pagination is deferred while the focus
request is still being debounced and while the focused timeline is being
created, it is performed again once the request has failed, and it is dropped in
a thread timeline while a backward one still goes through.
The pagination recorder now also sits on the live timeline, and the presenter is
built on that same timeline. Before the detached timeline exists, a pagination
that escaped the gate would go to the live one, so without this the new guards
would pass against the unfixed presenter. The deferred cases end on the emitted
state rather than on cancelAndIgnoreRemainingEvents for the same reason: the
recorder throws from inside the presenter coroutine and cancelling swallows it.
Verified by reverting the gate: the three deferred-pagination tests then fail
with "This lambda should never be called."
* Refresh the room members when a mention is started
The mention suggestions read room.membersStateFlow, which is a cache filled
only by an explicit updateMembers() call. Nothing calls it while a room is
open, so a member who joined after the room was opened never appears in the
suggestion list until the room is left and entered again.
Call updateMembers() when a mention search starts. The trigger is
distinctUntilChanged on "the current suggestion is a mention", so it fires
once per mention rather than on every keystroke, and fetchRoomMembers already
no-ops while a fetch is in flight.
* Resolve the real image format before choosing the encoder
The upload pre-processor picks the encoder from the mime type it is
given. When an image is shared from another app that type is often
broader than the image really is, so a transparent PNG shared as a
wildcard image type is re-encoded as JPEG and its alpha channel is
flattened to black.
Read the format from the image header when the declared type is not
already a concrete PNG or JPEG, and use that to decide both whether the
image can be compressed at all and which encoder to use.
* Cover the mime type fallbacks the wildcard resolution introduced
resolveImageMimeType now decides whether an image is compressible, so two of
its outcomes had no test: a GIF shared with a wildcard type has to be
recognised and left uncompressed rather than recompressed into a still, and a
file the decoder cannot read at all has to fall back to the type it was shared
with.
The isMimeTypeImage() guard on the decoded type is dropped with them. The
BitmapFactory decoders only ever report an image type in outMimeType, so the
guard could not fire and there was no way to exercise it.
* Update dependency org.matrix.rustcomponents:sdk-android to v26.08.17
* Fix API breaks:
- `ClientBuildException.WellKnownLookupDisabled` should be handled now.
- `Client.sendReply` returns a `Result<SendHandle>` now. We can ignore it like we do for `Client.send`.
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jorge Martín <jorgem@element.io>
The `Provider` suffix reads as a dependency provider rather than as preview
data, and it collides with the real providers in the codebase — `StringProvider`,
`PickerProvider`, the login feature's `AccountProvider` — so the name says
nothing about which of the two a class is. `PreviewParam` says it.
195 classes are renamed, along with the 185 files named after them, the konsist
rule that enforces the convention, the presentation-class file templates and the
onboarding documentation.
This cannot be split. `Konsist.scopeFromProduction()` scans the whole project, so
flipping the assertion before every class has moved leaves the rule red, and a
rule that accepted either suffix during a transition would be exactly the kind of
compatibility layer this convention change is meant to remove.
Two judgement calls in the rule itself. The `name.endsWith("PreviewProvider")`
exclusion is dropped: its only job was to reject a redundant `Preview` before
`Provider`, and the new suffix already contains it; no class currently ends with
`PreviewProvider`, so removing it changes nothing today. And
`EditableAvatarViewUriProvider` is renamed rather than deleted from the exclusion
list even though no such class exists in this repository, since it presumably
comes from the modules overlaid for the enterprise build.
Nothing keyed off the old suffix: no snapshot filename contains a provider class
name, so no Paparazzi image changes, and the Kover variants match on `*Presenter`,
`*State` and `*ViewKt`, so coverage bounds are unaffected.
An intent carrying uris is now accepted before the mime type list is
reached, so the image check that every uri share used to exercise is only
covered by an intent which has nothing to send. Add one case per listed
mime type without a uri.
Cover the rest of the handler while it is under test: an intent with no
resolvable type, a send multiple without a stream, an action which is
neither send nor send multiple, an empty caption, a plain text intent
without text, the mime type reported by the content resolver for the uri,
and the explicit intent built for the application which will read it, both
when the read permission can be granted and when it cannot.
* Extract `IsEnterpriseBuild` use case
Having it inside `EnterpriseService` made having DI cycles with the `BuildMeta` provider too easy
* Rename `EnterpriseService.defaultHomeserverList()` to `homeserverAllowList()`
This seems more aligned with the way this list works
* Move most of the logic from `:libraries:wellknown` to `:enterprise:feature:enterprise`, both `shared` and `impl` modules
Ideally I would have added everything into `impl`, but some of the classes needed to be shared between the different `:enterprise:features` modules, and those are in the `shared` module, lacking a better name/strategy
* Add `EnterpriseService.isElementProEnforced`
With this, we don't need to provide the `RemoteEnterpriseConfig` anymore to any consumers, everything goes through `EnterpriseService` or other providers
* Remove `EnterpriseService.overriddenElementWellKnown`
This leaks the remote enterprise config/element well known contents to the app in a public interface.
* Create `CustomMapTilerConfigProvider` to avoid leaking the `RemoteEnterpriseConfig` in public APIs
This commit also starts moving some of the well-known related values to the enterprise modules
* Add missing docs to `EnterpriseService` and `SessionEnterpriseService`
* Fix tests
* Fix lint issues
* Bump enterprise module
Opening a room from a notification asks the timeline to focus the notified
event. When that event is outside the loaded live window the controller
creates a detached timeline for it, and a detached timeline always carries a
forward loading indicator as its last chronological item. The items list is
reversed for display, so that indicator is the bottom-most row and is composed
on first layout, which unconditionally emits a forward pagination request
nobody asked for. Because a notified event sits close to the live end, that one
page reaches the end, and the controller responds by closing the detached
timeline. The live window replaces the item list while it is still anchored at
the bottom, so the user lands on the newest message and the focus request is
discarded.
Defer automatic forward pagination while a focus request is still pending. Once
the focused event has been rendered the viewport has moved off the indicator
row, so only a real scroll back to the live end switches to the live timeline,
which is what the controller's existing behaviour is for. Backward pagination
and the explicit jump-to-live action are untouched.
* Explain a rate limiting push gateway in the notification troubleshooter
The push loop back test printed whatever HTTP status the gateway
answered, so a user throttled by the public ntfy.sh gateway was told
"Error: HTTP 507", which reads as a server fault rather than as a quota
they can wait out or move away from.
Map the two statuses that mean the notification was throttled onto a
typed failure and give the troubleshooter a message that says what to do.
* Import strings from localazy, remove `temporary.xml` in `:libraries:push:impl`
---------
Co-authored-by: Jorge Martín <jorgem@element.io>
The old name read as though the default subtype always replaced whatever the
receiver carried, when a mime type that already has a concrete subtype is
returned untouched.
ktlint reports the TestScope import as unused because the type only appears in
a context parameter, which the rule does not look at. The file carried a
file-level suppression for exactly that before this branch dropped it.
Images are downscaled against a 640px reference, which makes inSampleSize
halve the source one step further than the documented intent: the KDoc
says images should be sent at up to 1280x1280, but a 12000x3916 photo is
sent as 3006x979. Text in screenshots and detail in photos is lost at
that size.
Raise the reference to 1280. inSampleSize is a power of two, so this
moves each image one step up rather than to an exact size.
On shorter screens the software keyboard can cover the field the user is
typing into on the login and account provider screens, because nothing
scrolls the content back into the reduced viewport once the keyboard is
shown.
Apply the existing bringIntoViewOnImeVisible modifier to those fields,
the same way the recovery key screen already does.
The outgoing verification flow had no client side timeout: if the other
session never answered, the screen waited on "Waiting to accept request"
for as long as the user left it open, with no indication that the
request could no longer succeed.
Bound the wait to the ten minutes the Matrix specification gives a
verification request. Past that the other session can no longer accept
it, so cancel the request and move to the canceled state, which already
offers to start again.
Selecting a Light or Dark theme makes AppCompatDelegate rewrite the
activity configuration to the forced night mode. isSystemInDarkTheme
reads that same configuration, so switching back to System kept showing
the previously forced theme.
Read the night mode from the application configuration instead, which
AppCompat leaves untouched and itself uses to expand
MODE_NIGHT_FOLLOW_SYSTEM. The activity configuration is still used as
the recomposition key so a system-wide theme change is picked up.
Some apps share an image with the mime type image/* rather than a
concrete one. That value was carried straight into the uploaded event, so
the message reached the room with an unusable media type and bridges
rejected it as unsupported.
Resolve a wildcard subtype to a default for its top level type before
processing the upload, reusing the mapping the download path already
applies so the SDK can pick the right extension.
The settings badge was shown for every unknown backup state, so a session
whose key backup lives on the server but has not been reported by the SDK
kept a red dot next to Encryption while the Encryption screen itself
showed nothing to do.
Resolve an unknown backup state the way the Encryption screen already
does, by asking whether a backup exists on the server, and only badge the
entry when there is none.
A slash is valid in the local part of a user id, but in a permalink it
also separates the identifier from an event id, so the SDK could not tell
the two apart and the link fell back to opening matrix.to in a browser.
Only a user id can start with an @, so recover it before handing the URI
to the SDK, and only when the identifier really contains a slash so every
other permalink keeps taking the same path.
A m.key.verification.request sent from a client that supports in-chat
verification carries a plain text body for clients that do not. Element X
has no msgtype of its own for it, so it fell through to OtherMessageType
and the timeline printed that body, telling the user their client does
not support in-chat key verification right after a verification they had
just completed.
Drop the event when building the timeline items, as Element X iOS does.
fix-pg-map-id.py rewrites the pg_map_id of a dex file, but the dex
format changed and its regex no longer matches anything, so the step is
a no-op and the F-Droid build is reproducible without it. inplace-fix.py
only existed to drive it; none of its other commands are used here.
The removed call also re-ran zipalign -P 16 on each APK. AGP aligns the
release APKs at packaging time and apksigner is already invoked with
--alignment-preserved, so the signed output stays 16 KB aligned.
Adding or editing a media caption from the plain composer sent the
caption as text only: the composer has no HTML to pass, and the media
caption edit was built by hand with a null formatted body, so the
markdown was never rendered. Sending a caption with the same markdown
does render it, because the SDK builds the formatted body there.
Build the edit with the SDK's createCaptionEdit, which runs the caption
through the same markdown conversion the send path uses.
The sanitize() helper applied to the login, password and account deactivation
password fields promises to remove "any new line characters" but only removes
LF. A value pasted from a source with CRLF endings keeps its CR, which is
invisible in the field and is sent to the homeserver as part of the password,
so the login is rejected while typing the same characters by hand works.
Drop every line separator instead of LF only, in both copies of the helper.
Whitespace that can legitimately be part of a password, such as spaces and
tabs, is left untouched.
showLeaveRoomAlert ran its whole body inside client.getRoom(roomId)?.use { }.
When getRoom returned null, which happens for a room the client can no longer
resolve, the body was skipped without writing any state: leaveAction stayed
Uninitialized, so no confirmation dialog and no error was displayed and
Leave room appeared to do nothing at all.
Handle the missing room explicitly and surface it as a failure, which
AsyncActionView already renders with the generic error dialog. leaveRoom used
a !! for the same lookup, so it reported the miss as a NullPointerException;
it now throws the same named exception.
handleIncomingShareIntent only accepted an intent whose resolved mime type
started with image/, video/, audio/, application/, file/, text/ or */. An
ACTION_SEND carrying EXTRA_STREAM with any other top level type, such as the
message/rfc822 that some apps use when attaching a file, fell through to null
and the share was dropped before the room picker ever appeared, even though
the manifest filter accepts */*.
The uris are extracted before that check, so their presence is enough to know
the intent carries something to send. Accept the intent when it has uris and
keep the mime type list for the case where it does not.
Cover the last remaining interfaces across libraries/audio, libraries/cachestore,
libraries/indicator, libraries/slashcommands, libraries/voiceplayer and
services/apperror, with their members and parameters.
This completes the effort: all 222 in-scope interfaces and their 820 members in
the 60 api modules that hold any now carry documentation.
Notable points: CacheStore applies no expiry of its own and only records when each
entry was written, leaving staleness to the caller; SlashCommandService.parse
never throws, returning NotACommand for ordinary text and an error case for a
malformed command.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cover the last remaining interfaces across features/announcement, cachecleaner,
call, contentscanner, createroom, knockrequests, lockscreen, rolesandpermissions,
startchat, userprofile and viewfolder, with their members and parameters.
Grouped into one commit rather than eleven near-empty ones, per the AGENTS.md rule
about tiny commits.
This completes every features/* api module.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cover the remaining interfaces of features/login, features/logout,
features/preferences and features/share, along with their members and parameters.
Points recorded here: AccountProviderAccessControl returns true on a build with no
enterprise restriction, ExtraDeveloperOptionsRenderer is bound to a no-op in the
standard build, DirectLogoutView draws nothing until a logout event is sent, and
CacheService exists because clearing a cache destroys the client and the app has
to react.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document AcceptDeclineInviteEvents, AcceptDeclineInviteView and the
SeenInvitesStore interface.
The view doc explains why it is a shared component rather than part of one screen:
it is embedded everywhere an invitation can be acted on, so the confirmation
dialogs behave identically in the invite list and in a room preview.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document the members of SecretKeyRepository and EncryptionDecryptionService.
The caveat worth having in the API doc comes from KeyStoreSecretKeyRepository:
requiresUserAuthentication only takes effect when the key is created, so an
existing key keeps whatever protection it was created with.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document the members of AppNavigationStateService and AppForegroundStateService
that had none, and add the missing @param entries.
The recurring `owner` parameter is now explained, from
DefaultAppNavigationStateService: the navigation state is a stack, pushing a level
without its parent logs an error and does nothing, and a leave call whose owner
does not match the current level is silently ignored so that a node which has
already been replaced cannot pop someone else's state.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document RageshakeFeatureAvailability, LogFilesRemover and the BugReporter
interface, and complete the docs of its remaining members.
The BugReporter doc records that progress and the outcome arrive through the
listener passed to sendBugReport rather than through a return value or an
exception.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document LocationService, StaticMapUrlBuilder, TileServerStyleUriBuilder and the
members of ActiveLiveLocationShareManager that had none.
The two URL builders now say what happens when no map provider is configured for
the build, which is the case LocationService.isServiceAvailable exists to guard.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cover the remaining interfaces of libraries/troubleshoot, libraries/matrixmedia,
libraries/dateformatter, libraries/featureflag, libraries/workmanager and
libraries/mediaviewer, along with their members and parameters.
Behaviours worth recording: NotificationTroubleshootTest reports its outcome
through its state flow rather than by returning, and its quickFix throws unless
the test offers one; DateFormatter.format turns a null timestamp into an empty
string so callers need no null check; ImageLoaderHolder keeps one loader per
session because decrypting room media needs that session's client.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document PickerProvider and each of its register methods.
The interface doc carries the two rules that apply to all of them: they must be
called during composition rather than from a click handler, and a cancelled pick
arrives as a null uri or an empty list rather than as an error.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document PusherSubscriber and PushHandler at the interface level, document the
PushProvider members that had none, and add the missing @param entries.
Notable points: getCurrentDistributor also returns null when the stored
distributor is no longer installed, rotateToken throws unless canRotateToken is
true, and handleInvalid records the payload in the push history for diagnosis.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document SessionStore, SessionObserver and SessionListener at the interface level
and add the missing @param entries.
The store doc records that sessions are ordered by last usage, which is what makes
"the latest session" meaningful across the rest of the interface, and the listener
doc explains that wasLastSession is the signal to clean up app-wide data too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document AnalyticsProvider, AnalyticsTransaction, AnalyticsTracker and
ErrorTracker at the interface level, and add the missing @param entries.
The provider doc records that providers are driven by the analytics service and
only receive data once the user has consented, and that startTransaction returns
null on a provider with no performance support.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add the interface level docs that StringProvider, SystemClock and
BuildVersionSdkIntProvider were missing, document ExternalIntentLauncher.launch,
and add the remaining @param entries.
Each doc says why the abstraction exists rather than restating the signature:
these wrap Context, Build.VERSION and the clock so that presenters can be tested
without them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document OAuthIntentResolver and OAuthActionFlow along with their members.
OAuthActionFlow now explains why it exists rather than the result simply being
returned: the activity that receives the redirect and the login screen waiting for
it are not in the same navigation subtree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document UserRepository and UserListDataSource along with their members.
Two behaviours from MatrixUserRepository and MatrixUserListDataSource are recorded
because callers rely on them: the data source swallows a failed directory request
into an empty list, and a query that is a well-formed user id other than the
current user's yields that user even when the directory does not know them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document RoomLatestEventFormatter, TimelineEventFormatter and
PinnedMessagesBannerFormatter along with their members.
The formatters return a nullable CharSequence, so each one now states what null
means: the event is not rendered as text at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document DeeplinkParser, DeepLinkCreator and InviteFriendsUseCase, including that
DeepLinkCreator.create silently ignores the deeper arguments once an outer one is
null.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document RoomMemberModerationState, RoomMemberModerationEvents and
RoomMemberModerationRenderer along with their members and parameters.
The renderer doc states that it draws nothing until the host screen asks for a
member's actions, and that onSelectAction exists so the host can navigate for the
actions that need a screen of their own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document PollContentStateFactory, SendPollResponseAction and EndPollAction along
with their members and parameters.
The two action interfaces exist to wrap a plain timeline call, so their docs say
what they add: both capture an analytics event on success, which is the reason
callers should go through them rather than calling the timeline directly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document LeaveRoomState, LeaveRoomEvent and LeaveRoomRenderer.
The renderer doc records the two things a host screen needs to know, both taken
from LeaveRoomPresenter and LeaveRoomView: it draws nothing until a leave event is
sent, and onSelectNewOwners fires specifically when the user is the last owner of
a room that still has other members.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document InvitePeopleState and each of its properties, InvitePeopleEvents,
InvitePeopleRenderer and the presenter factory.
The state properties now say what drives them, in particular that
createRoomFromDmAction covers the case of inviting a third person into an
existing direct message, and that the factory accepts a null room for that flow.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document HtmlConverterProvider, MessageComposerContext,
PinnedEventsTimelineProvider, the MessagesEntryPoint node proxy and the voice
message composer factory.
HtmlConverterProvider carries the ordering constraint that matters: the converter
is rebuilt from the Compose theme, so Update has to be composed before provide is
called.
MessagesEntryPoint and its Callback are navigation plumbing and out of scope.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document EnterpriseService, SessionEnterpriseService and
RemoteEnterpriseConfigProvider along with all their members and parameters.
Two points that callers get wrong are stated explicitly: these services return
permissive defaults on a standard build, so there is no need to branch on the
build type, and SessionEnterpriseService only answers meaningfully once init has
run. The many nullable getters now say what null means in each case, which is
generally "no override, use the app default".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document PermissionsStore, PermissionStateProvider, LocalNetworkPermissionAdvisor
and the PermissionsPresenter factory, along with their members and parameters.
The store doc explains why the module exists: Android does not let an app
distinguish "never asked" from "asked and denied", so the app has to remember
that itself in order to decide whether it may show a rationale.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document UserPushStore, UserPushStoreFactory, PushClientSecret,
PushClientSecretStore and PushClientSecretFactory along with all their members.
The client secret interfaces now explain why they exist at all: a push gateway
delivers notifications without saying which account they belong to, so the secret
is what lets an incoming push be attributed back to a session.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document WellknownRetriever, ElementWellknownStore, ElementWellKnownParser and
their factories, along with every member.
The caching semantics are the part callers need and cannot see in the signatures,
so they are recorded here: the store reports entries older than a day as outdated
rather than missing, the retriever returns such a value immediately and refreshes
in the background, an enterprise build with a hardcoded configuration
short-circuits entirely, and the parser ignores unknown JSON fields.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document MediaSender and all its members, its two factories, MediaPreProcessor,
MaxUploadSizeProvider and MediaOptimizationConfigProvider.
The MediaSender doc explains the two-step shape of the API, which the individual
signatures do not convey: pre-processing and sending are separate so a preview
can be shown in between, and the temporary files that pre-processing creates are
only released by cleanUp, whether or not anything was ultimately sent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document AddRecentEmoji, GetRecentEmojis.invoke, the EmojiPickerRenderer
interface and the EmojiPickerPresenter factory, pointing at the no-op renderer
and the empty recent-emoji source so callers know how to opt out of each.
EmojiPickerPresenter itself is a Presenter and therefore out of scope.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document AnalyticsService and ScreenTracker at the interface level, document the
members of the cold start and room list watchers, and add the missing @param
entries across the analytics interfaces.
The AnalyticsService doc states the rule that applies to the whole interface:
nothing reaches the providers until the user has given their consent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document AppPreferencesStore, SessionPreferencesStore, their two factories, and
each of their 44 members.
For a preference store the useful thing to document is the fallback value, so
each getter states the default it uses when the preference has never been set,
taken from DefaultAppPreferencesStore and DefaultSessionPreferencesStore:
developer mode follows the build type, live location updates default to 10
metres, the log packs to an empty set, and the session toggles are all on by
default except session verification skipping.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document PushService, GetCurrentPushProvider, NotificationCleaner and
NotificationBitmapLoader along with their members, and add the missing @param
entries across the notification interfaces.
The NotificationCleaner docs record the behaviour shared by all its methods,
which is not visible in the signatures: each one also removes the grouping
summary notification once it has no children left.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document the permalink, widget, tracing, core, platform, server and analytics
interfaces along with their members, which completes libraries/matrix/api: all
69 in-scope interfaces and their 398 members now carry documentation.
Notable points recorded here: PermalinkParser reports an unrecognised uri as a
fallback link rather than failing, MatrixWidgetDriver.run is a no-op while it is
already running and send is silently ignored once closed, and every member of
CallAnalyticCredentialsProvider is null when the service is unconfigured or the
user has not consented to analytics.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document NotificationSettingsService and PushersService along with all their
members, and complete the NotificationService documentation.
Fix the documentation of notificationSettingsChangeFlow, which described a state
flow of room notification settings when the member is a SharedFlow<Unit> that
only signals that the push rules changed. That was the sole reference to
RoomNotificationSettingsState in the file, so its now unused import is removed.
The docs also explain why so many of these methods take isEncrypted and
isOneToOne: Matrix keeps a separate default push rule per combination.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add the interface level docs that MediaPreviewService, MatrixMediaLoader and
MxcTools were missing, document MediaFile.path and the nested
ContentScannerUrlProvider.Factory, and add the missing @param entries.
Most members in these packages already had accurate documentation and are left
untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document RoomDirectoryService, RoomDirectoryList, SpaceService, SpaceRoomList
and LeaveSpaceHandle along with their members, add the missing DynamicRoomList
page size doc and the missing @param entries, and give MessageSearchService the
interface level doc it was lacking.
The docs make the ownership rules explicit, since they differ across these
types: a SpaceRoomList and a LeaveSpaceHandle are owned by the caller and must be
destroyed or closed, while a RoomDirectoryList and a MessageSearch are released
when the scope they were created with completes.
RoomList, RoomListService and the search interfaces were already accurate and are
left untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document MatrixAuthenticationService, OAuthRedirectUrlProvider, the two QR code
login data interfaces, and the four device linking interfaces, along with their
members and parameters.
The docs record the ordering constraints a caller has to respect: setHomeserver
must precede any login attempt, the link handlers report their outcome through
their step flow rather than through a return value, and ContinuationMessageSender
expects exactly one of cancel or confirm.
The oauth package holds only a sealed interface, so it is out of scope here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document EncryptionService, SessionVerificationService and
SessionVerificationServiceListener along with their members, and add the missing
@param entries.
From RustEncryptionService: the backup and recovery state flows report a
waiting-for-sync value for as long as the session is not syncing, and
isLastDevice and hasDevicesToVerifyAgainst are polled rather than observed.
PasswordStrengthEstimator was already accurate and is left untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document Timeline and all of its members, TimelineProvider, and the three
lazy providers carried by EventTimelineItem.
Behaviours taken from RustTimeline: collecting membershipChangeEventReceived or
onSyncedEventReceived is what keeps the timeline subscribed to the SDK, paginate
fails with CannotPaginate when the matching status forbids it, the initial
hasMoreToLoad of each pagination status depends on the timeline mode,
timelineItems also carries virtual items such as date separators, and the media
send functions return a handler the caller must await.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document KnockRequest and ThreadsListService along with all their members, and
add the missing @param entries to the already documented RoomPermissions.
For ThreadsListService the docs record what the implementation does with the
subscriptions: subscribeToItemUpdates starts a shared subscription on the first
call and re-emits the whole list, subscribeToPaginationUpdates replays the
current state immediately, and destroy leaves the service unusable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document BaseRoom, JoinedRoom, NotJoinedRoom, RoomAliasHelper and JoinRoom,
along with their members, and add the missing @param entries.
Fix the documentation of BaseRoom.getUpdatedIsEncrypted, which described the
room directory visibility instead of the encryption state it actually returns.
Behaviours taken from the implementations: BaseRoom.predecessorRoom swallows
errors into null, updateMembers only reads the cache on its first call,
JoinedRoom.syncUpdateFlow is an opaque change counter, notification settings
start in an Unknown state until updateRoomNotificationSettings is called, and
JoinRoom maps a Forbidden answer to Failures.UnauthorizedJoin.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document the interface itself and all of its 60+ properties and functions.
Notable behaviours taken from RustMatrixClient that callers cannot guess from
the signatures: getRoom and getJoinedRoom build a new closeable instance on
every call, createRoom/joinRoom/knockRoom wait for the membership to come back
through sync and report a null RoomInfo rather than a failure when that wait
times out, getCacheSize excludes the crypto database, and clearCache destroys
the client while keeping the session.
Also fix the stale wording and the malformed KDoc block on resolveRoomAlias,
and add the missing @param entries on the functions that already had a summary.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Document MatrixClientProvider, SdkMetadata, HomeserverCapabilitiesProvider,
TemporaryMatrixClientFactory and SyncService, along with each of their members.
Behaviour described here was checked against the implementations
(MatrixSessionCache, RustSdkMetadata, RustHomeserverCapabilitiesProvider,
RustTemporaryMatrixClientFactory and RustSyncService), so the docs mention the
caching, idempotence and error-wrapping that callers cannot infer from the
signatures alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
We modify the existing `FeaturesProvider` interface so that there can be more
than one of them, and the Features provided by *all* of the instances are
shown. We can then make another instance in element-android-enterprise, which
exposes enterprise-only features.
Files.createTempDirectory needs API 26 and the fake lives in the main source
set of tests:testutils, so lint failed it against the minimum of 24.
File.createTempFile has been available since API 1 and still gives each
provider instance its own directory.
This was added as a temporary measure to allow matrix.org users to use Element X before it supported MAS, but it hasn't been needed for a very long time
The GITHUB_MAX_ISSUE_BODY_SIZE constant added with the description truncation
is a five-digit literal, above detekt's acceptableLength of 4, so
:features:rageshake:impl:detekt failed. Write it as 65_536, matching
MAX_LOG_LINES_SIZE in RageshakeConfig.
The timeline action sheet offered Forward on a live location share, but
forwarding one always failed: Mode.Live maps to the SDK's
MsgLikeKind.LiveLocation, which RoomContentForwarder rejects outright. So
the action was shown for exactly the set of events on which it could
never work.
canBeForwarded() now returns true for a location event only when its mode
is Static. Ended live shares keep Mode.Live and fail for the same reason,
so they are excluded too. ActionListPresenter needs no change: its check
is the function's only call site in the repo, so the single gate covers
the timeline sheet, the threaded timeline and the pinned messages list.
Hiding the action mirrors Element Web and the sticker exclusion already
present in the same function. Converting a live share into a static
m.location on forward would be a product decision and is out of scope.
Relates to https://github.com/element-hq/element-x-android/issues/7190
Also, allow instantiating it using a factory: with this we can always decide where to take the config from and how to do it
Do the same for `ElementWellknownStore` so we can store and restore the values for different sources (endpoints) independently
This is an abstraction over an actual unauthenticated `Client` from the SDK that implements `GetUrlResolver`, and is supposed to be short-lived.
With this, we can use the SDK network stack to perform arbitrary GET url operations.
The `AutoCloseable` implementation makes it easier to clean up any leftover local config after using this temporary client.
The whole crash call stack was appended to the bug report description, and the
bug report server copies that description verbatim into the GitHub issue it
opens. GitHub refuses an issue body over 65536 characters, so the server's save
step failed and answered "Could not save report". The stored crash data is only
cleared after a successful send, so every retry failed the same way.
Truncate the description before sending it. The user's own text and the GitHub
issue link come first, so only the tail of the crash stack is dropped. The
uncaught exception handler already writes the full stack through Timber, so it
stays available in the attached log files.
The 40 KB budget leaves room for the other reported fields, mainly the list of
omitted log file names, which is not otherwise bounded.
* Expand the emoji picker to full height when the keyboard is shown
The picker content is rendered with fillMaxSize, so the sheet measures itself to
the full window height. That makes the partially expanded anchor available, and
the modal sheet auto-shows at that anchor, half way down the screen.
The sheet reserves the IME inset through its content window insets, but applies
that padding at its own bottom edge, which is off-screen while it rests half way
down. The band that is both on-screen and above the keyboard is then only about
a tenth of the screen, which is why the matching emoji end up behind it.
Expand the sheet while the keyboard is visible so its bottom edge returns to the
screen edge and the inset it already reserves lands on top of the keyboard.
Modifier.imePadding on the sheet content is not an option: the sheet applies the
insets with windowInsetsPadding, which consumes them, so a nested imePadding
reads zero.
* Cover the keyboard driven sheet expansion with a test
The IME reaction moves into ExpandSheetWhenImeIsVisible so a Robolectric test can
drive it: dispatching window insets to the composition flips
WindowInsets.isImeVisible, and the sheet state is observable from the test. Both
the shown and the hidden case are asserted, which is the patch coverage this
change was missing.
* Do not leave a blank strip below the expanded emoji search
The 10dp of bottom padding on the search bar separates the collapsed
field from the category tabs underneath it. It stayed applied once the
search bar expanded to fill the picker, so the picker stopped 10dp short
of its own bottom edge.
That strip used to sit below the visible part of a half height sheet.
Now that the sheet expands against the keyboard it reads as a gap
between the results and the keyboard, so only pad while collapsed.
* Add default `OAUTH_CLIENT_URL_PATH` alternative
Previously this was `null`, but we want to use `apps/android` instead to identify Element X Android. Element Pro will use a different value, and other custom apps based on EP will use `null` or their own custom value, if they provided one
* Suppress lint check so the pro app can be built
heroes is populated for any room whose name is derived from its members,
not only for direct messages, so a two person room that is not a DM
showed a member's Matrix ID as its subtitle.
Carry isDm on SelectRoomInfo and read the hero only when it is set. The
canonical alias still takes precedence, so nothing changes for a room
that has one.
Restore the snapshot backed set the store started with and declare
allowedEventIds as SnapshotStateSet so the interface says out loud that
reads of it are observable.
TimelineProtectionPresenter reads the set inside derivedStateOf, so a
plain set silently stops the timeline from re-rendering when an event is
revealed. Naming the type keeps that requirement from being mistaken for
an implementation detail.
* Show a Message not found placeholder when a replied to message cannot be loaded
When the replied to event could not be fetched, the reply preview rendered the
raw SDK error string in critical styling, and tapping it emitted a focus
request that could never succeed, so the user got a dead end dialog.
Render the localised error_message_not_found string in the neutral italic style
already used for redacted and undecryptable replies, and drop the click handler
for that case. The existing key is reused rather than adding a new one; a "you
do not have access" wording was rejected because this state covers any failure
to load the event, not only a permission problem.
* Update screenshots
* Trigger CI after recording screenshots
---------
Co-authored-by: ElementBot <android@element.io>
DefaultFileSave had no test at all and AndroidLocalMediaActions only exercised the
failing path, so neither the uniquifying retry nor the new IOException on a null
insert was ever executed.
FakeMediaStoreContentProvider stands in for the media authority: it records the
display names an insert is attempted with, keeps whatever is copied into it, and can
either collide a name the way MediaStore does past 32 files or refuse to create
anything. Both call sites now go through it for the plain save, the retry and the
rejected insert.
Its name cannot follow the Fake<Interface> convention because it extends the
framework ContentProvider, so it joins the existing exceptions in
KonsistClassNameTest next to FakeFileSystem and FakeKeyStore.
The store no longer needs a mutableStateSetOf: an ordinary Set kept in a
mutableStateOf does the same job, and matches DefaultMediaPreviewConfigStateStore.
The Compose state itself has to stay. TimelineProtectionPresenter derives
ProtectionState from the store inside a derivedStateOf, so the set must be a
snapshot object for a reveal to invalidate it and reach the timeline. Backing it
with a plain mutableSetOf leaves the presenter with nothing to observe and it
stops emitting after ShowContent.
Review feedback: the previous pattern hardcoded http, https and ftp and
guessed at the address grammar. Candidates are now found by scheme and
handed to java.net.URI, which accepts any scheme and rejects an authority
that is not a real IPv6 literal, an unclosed bracket, or array syntax
such as values[0]:1. Also drops the explanatory comments.
Review feedback: the retry itself is short enough to read, and the
platform limit it works around belongs in the commit, not the source.
The contract of the name helper keeps its one-line KDoc.
The pinned events timeline is built with an exclusion filter rather than an
allowlist, so an event of a type the app has no renderer for reaches both
pinned surfaces. The banner falls through to the formatter catch-all and reads
"Unsupported event", and the list renders an unknown content tile.
Drop unknown content and message-like or state events that failed to parse from
both surfaces, along with any day divider left without an event to introduce,
and show the empty state when nothing renderable remains. The spec makes this
the client's call. Such an event can still be unpinned from the room timeline,
where it is still rendered.
Undecryptable and redacted events are untouched: they carry information the
user needs.
The set of revealed event ids lived on TimelineProtectionPresenter, which is
scoped to the room graph. That graph is rebuilt every time the room is opened,
so the set was empty on re-entry and every revealed image was hidden again.
Move it into a session scoped TimelineProtectionStore. The store is in memory
rather than persisted: revealing media is a privacy control and there is no
hide action today, so keeping the reveals for the session leaves restarting the
app as a way to undo one, and the set is dropped on logout.
The account provider search treated its whole input as a host, so a full user
ID was probed as https://@alice:example.org, which never resolves, and the
permissive URL fallback then offered that invalid address as a provider.
Recognise a complete user ID and resolve the homeserver from its server name,
preserving a port when there is one. Input that starts with @ but is not yet a
complete user ID resolves nothing, so no candidate is produced while the user
is still typing. Detection reuses MatrixPatterns.isUserId and UserId.domainName
rather than a new pattern.
When the replied to event could not be fetched, the reply preview rendered the
raw SDK error string in critical styling, and tapping it emitted a focus
request that could never succeed, so the user got a dead end dialog.
Render the localised error_message_not_found string in the neutral italic style
already used for redacted and undecryptable replies, and drop the click handler
for that case. The existing key is reused rather than adding a new one; a "you
do not have access" wording was rejected because this state covers any failure
to load the event, not only a permission problem.
MatrixClient had no way to read or write global account data although the
underlying client supports it.
Add getAccountData and setAccountData to the interface and implement them in
RustMatrixClient using the same withContext/runCatchingExceptions idiom as the
neighbouring methods. The content is the raw JSON string the SDK returns and
accepts: event types are open ended, so a typed wrapper belongs to whichever
caller needs one. Scope is limited to global account data; the room level
variant and the change notification APIs can follow when a feature needs them.
Generating a feature with the Generate Module from Template plugin produced a
module that could not build: features/<name>/api/build.gradle.kts was written
from the impl template, giving the api module a dependency on itself and an
impl namespace.
FeatureModule.json declared BUILD_GRADLE_API and BUILD_GRADLE_IMPL, both
defaulting to build.gradle.kts, and used them as fileTemplates keys. The plugin
resolves keys to filenames before matching, so both collapsed to one key and
the second overwrote the first. The same matching code falls back to a suffix
match on the path, so DefaultXEntryPoint also matched the key XEntryPoint and
was generated from the api template.
Key both by directory, which the plugin supports for exactly this case, and
drop the two now-unused placeholders. The file templates themselves were
correct and are unchanged. The onboarding doc gains a note that an already
imported template must be removed before re-importing, since the plugin keeps
its own copy.
A room notification is rebuilt from the one currently on screen: its messaging
style is extracted and the incoming events are appended. Every event was
appended unconditionally, so an event re-delivered while its notification was
still showing was added again, and the notification re-alerted each time.
Skip events whose id is already present in the existing messaging style, and
set onlyAlertOnce when the batch adds nothing new. Smart reply failures are
exempt, because that path deliberately reuses the same synthesised event id to
append the "message not sent" line. Filtering is per event rather than per
message so an image and its caption stay a unit.
This fixes the visible duplication, not the re-delivery that triggers it, and
does not help once the notification has been dismissed.
The blocked users list displayed each Matrix ID but offered no way to get it
out of the app, which is what someone who blocked a user for abuse needs in
order to report them onwards.
Long press now copies the Matrix ID and confirms with a snackbar, reusing the
ClipboardHelper and SnackbarDispatcher idiom from the user profile screen. A
short tap still opens the unblock confirmation. The row declares a long click
label so the action is announced by a screen reader. No new string is needed.
The forward and share room picker showed only a display name, so several
direct messages or rooms sharing a name were indistinguishable.
Render a secondary line: the canonical alias when the room has one, otherwise
the other person's Matrix ID for a direct message. No model change was needed,
because heroes is populated only for a direct message with exactly one hero,
so heroes.singleOrNull() is the peer.
After leaving a room listed in a space, the room stayed in the child list with
a Join button, even though it can only be entered with a new invitation. It
disappeared only after leaving and reopening the space, because the server
omits it from the refetched hierarchy.
Apply the same rule locally in the presenter. The filter keys on the LEFT
membership combined with an invite join rule rather than on the derived
visibility, which collapses knock and unknown join rules into private and
would wrongly remove the Join button from knockable rooms.
The ExoPlayer backing voice messages was built without a wake mode, so it
defaulted to C.WAKE_MODE_NONE and held no wake lock. The CPU was free to
suspend once the screen turned off, cutting playback short.
Build it with C.WAKE_MODE_LOCAL instead. The media is a local file, so no
WifiLock is needed, and android.permission.WAKE_LOCK is already in the merged
manifest. The media viewer players are left alone: they already pause on
ON_PAUSE, so a wake lock there would keep the CPU awake for a player the user
has intentionally backgrounded.
Two of the three modes on the room notification settings screen had
screen-scoped keys; the third reused common_mute, which also labels the Mute
action button on the room details screen. A translator could not fix one
occurrence without breaking the other.
Add screen_room_notification_settings_mode_mute and use it at both places on
that screen. The English value is unchanged, so nothing renders differently.
The action button keeps common_mute, which is the one context where the
generic verb is correct.
The media viewer is edge to edge and the loading progress indicator was the
top-most child of a container that only applied navigation bar padding, so it
was laid out at y=0, behind the status bar and any display cutout.
Give it safeDrawing horizontal and top padding, reusing the idiom already used
by MediaViewerBottomBar in the same file. The inset deliberately does not come
from the scaffold content padding, which collapses to zero when the user taps
to hide the overlay.
Android lint rejects the literal U+2068 and U+2069 characters embedded in the string template with BidiSpoofing, "String contains misleading Unicode bidirectional text", which fails the lint task for the module.
Naming the two code points and building the characters from them produces the same string and leaves no invisible characters in the source.
Opening an image from the timeline replaces it with a full-screen page
reading "error". Two things go wrong. The viewer already holds the item
that was tapped, but building the surrounding media timeline can fail and
the failure overwrites that cached item, so the picture the user asked for
disappears. And the failure page renders the exception message straight
from the SDK, which is developer text with nothing actionable in it.
Emit the failure only when there is nothing cached, so a viewer opened on
a specific event keeps showing that event and stays usable while the rest
of the gallery is unavailable. Where there really is nothing to show, the
page now uses the same alert icon, title and message as the other error
states in this screen, built from strings that already exist.
The trade-off is that the timeline error is no longer surfaced in the
cache case; it is still logged. Swapping it for a snackbar would need a
new surface in the viewer and is left out.
A voice message that arrives without amplitude data — bridged messages and
those sent by clients that omit it — is unusable. The waveform is empty, so
its measured width is zero: the progress overlay never draws, the cursor is
pinned to the left edge, and the touch handler rejects every event because
no x coordinate can fall inside a zero-width range. The message plays, but
there is no way to see where it is or to move it.
Fall back to a flat run of zero amplitudes sized to the canvas when there
is no waveform. Width, progress, cursor and seeking then all work exactly
as they do for a real waveform, and the existing minimum amplitude renders
those zeros as the same flat track a genuinely silent recording already
draws, so no new drawing code is needed.
Nothing changes for a message that has a waveform, and the model still
carries the empty list, which is the honest value. Computing a real
waveform from the audio is a separate piece of work.
With several accounts logged in, sending a message from one of them raises
a notification on every other account that shares the room. The device
notifies its owner about something the owner has just done, and the more
accounts share a room the more copies arrive.
Resolve the set of logged-in user ids once per push batch and drop an event
whose sender is one of the other accounts, using the filtered-out result
the notification pipeline already understands, so it is recorded in push
history rather than turned into a fallback notification.
The receiving session is removed from that set, which keeps the change a
strict no-op for a single account and protects the local echo that the
reply action pushes back through the same path. Filtering happens at the
one point that sees every kind of push, so messages, polls, call invites,
ringing calls and room invites are all covered.
Filtering is unconditional and has no setting; multiple accounts sit
behind an experimental flag that is off by default, so nothing changes for
a stock build.
A fediverse handle such as `@name@server.tld` contains a substring that
`Linkify.EMAIL_ADDRESSES` recognises as a valid address, so the timeline
renders `name@server.tld` as a tappable `mailto:` link. Tapping it opens a
mail composer addressed to an account that does not accept mail.
Drop a newly added `mailto:` span when the character immediately before it
is `@`. This mirrors what Android already does for web URLs, where
`Linkify.sUrlMatchFilter` rejects a match preceded by `@` — which is why
the handle currently produces exactly one stray mailto span and no partial
link is left behind once it is removed.
Author-supplied links are unaffected: spans that were already present
before linkification are excluded from the check and restored as before. A
genuine address written directly after an `@` — "mail me @support@element.io"
— is no longer linkified, which is the same trade-off Android already makes
for web URLs.
recordScreenshots.sh runs under `set -e`, and the `git commit -m "Update
screenshots"` at the end of it is unconditional. When a recording run
produces no diff — the common case once the snapshots are already up to
date — `git commit` exits 1 with "nothing to commit, working tree clean"
and takes the whole workflow down with it, reporting a red job for a
successful run.
Stage as before, then return early when the staged diff is empty. `set -e`
is left in place: it was added deliberately so that a failed Gradle run
cannot push a commit that deletes every snapshot. Using `git diff --cached
--quiet` as an `if` condition is `set -e`-safe, because its non-zero "there
are changes" status is consumed by the condition. The early return also
skips the push, which had nothing to send.
A redaction push whose redacts field the SDK could not map was reported as
an unknown error. The result processor only suppresses filtered-out and
redacted events; every other exception becomes a visible "you have N
notifications" fallback and also raises the battery optimisation banner.
So a deleted message could produce a notification about itself, which is
the opposite of what a redaction means, plus an unrelated banner.
The branch now throws EventFilteredOut, which the processor already drops
silently, and logs at warning level so the SDK gap still shows up in push
history.
Relates to https://github.com/element-hq/element-x-android/issues/5766
A permalink written as a matrix: URI stayed plain text, while the same
link in matrix.to form became a pill. The pillifier only scanned
Patterns.WEB_URL, which cannot match a URI with no web scheme, even though
the permalink parser already understands matrix: URIs.
Those URIs are now scanned for as well, before the web pass, since
Patterns.WEB_URL otherwise claims the bare domain inside the URI and
whichever pass runs first wins the range. Each match also gets a URLSpan:
the timeline only reports URLSpans as clicks, so without one the pill
would look right and do nothing.
The composer path is untouched, since it pillifies without permalinks.
Relates to https://github.com/element-hq/element-x-android/issues/5319
With a hardware keyboard attached, Enter inserted a newline and the only
way to send was to reach for the send button, which is the opposite of
what every desktop chat client does.
MarkdownEditText gains an optional Enter listener and consumes Enter when
it comes from a physical keyboard without Shift. Shift+Enter keeps
inserting a newline, and a key event from the on-screen keyboard is
identified by its VIRTUAL_KEYBOARD device id and left alone, so nothing
changes for touch users. The key release is consumed as well, so no
newline leaks out on key repeat.
TextComposer passes its existing send lambda through, guarded by the same
canSendTextMessage check the send button already uses, so Enter cannot
send an empty message.
Relates to https://github.com/element-hq/element-x-android/issues/860
A DM whose other member has left still offered the call button. Starting
that call put the user alone in a room nobody could join, with no way out
of the call screen.
The presenter now clears canStartCall for a DM with no other active
member. activeMembersCount counts joined and invited members, so a fresh
DM whose peer has been invited but has not joined yet can still start a
call. Joining an already ongoing call is untouched, so a call started
before the other member left stays reachable.
Relates to https://github.com/element-hq/element-x-android/issues/6189
The compression config computed a target bitrate purely from the output
resolution and frame rate, ignoring the bitrate already extracted from the
source. A clip that is small because it was efficiently encoded, rather
than because it is short, was therefore re-encoded upwards: the upload got
slower and the resulting file was larger than the original.
The target is now clamped to the source bitrate when that is known.
METADATA_KEY_BITRATE is the container total, video plus audio, so it is a
safe upper bound for the video track alone. An unknown or zero bitrate
still falls back to the calculated value.
Relates to https://github.com/element-hq/element-x-android/issues/4191
Patterns.WEB_URL, which LinkifyCompat uses to find links, has no
production for a bracketed IPv6 authority, so a URL such as
http://[2001:db8::1]:8008/path was never turned into a link. Worse, the
phone number matcher claimed the digit runs inside the address, leaving a
tel: link over part of it.
LinkifyHelper now runs its own pass for those URLs. It requires both a
scheme and the brackets, so array syntax like values[0]:1 is not matched,
and it runs after LinkifyCompat.addLinks, which strips any span set before
it, but before the pre-existing spans are restored, so real anchors and
pill spans keep their precedence. Any span Linkify left inside the address
is evicted, and the same trailing punctuation trimming as the other links
is applied.
Relates to https://github.com/element-hq/element-x-android/issues/2614
A room list preview is one paragraph, and its direction is resolved from
the first strong character. A sender whose display name is Arabic or
Hebrew therefore flipped the whole line: the message body moved to the
wrong side of the name and the separating colon jumped to the front.
The sender's display name is now wrapped in Unicode isolate characters
before it is used as a prefix, so its own direction cannot escape it. Both
places that interpolate the name are covered: the prefix path and the
emote path, which bypasses it.
prefixWith itself is deliberately left alone, since the pinned messages
banner shares it and only ever passes localised nouns. The "You" prefix is
localised rather than user-provided and is not isolated.
Relates to https://github.com/element-hq/element-x-android/issues/3338
Consecutive messages from the same sender were grouped into one bubble
block no matter how far apart they were sent, so a reply hours later
looked like part of the earlier burst and lost its own timestamp header.
computeGroupPosition now folds a five minute limit into the two
groupability flags it already computes. The delta is symmetric between a
pair of neighbours, so both items always agree on where the block splits
and every branch of the existing decision table keeps working unchanged.
Breaking a group on reactions, which the iOS client also does, is a
separate divergence and is not part of this.
Relates to https://github.com/element-hq/element-x-android/issues/4757
The composer emits Start whenever the voice message state is Idle, and the
state only leaves Idle once the recorder has produced its first audio
buffer. A second tap inside that window started a second recording, which
overwrote the output file, re-initialised the encoder while the first one
was still encoding, and orphaned the first job together with its
AudioReader.
The orphan kept the microphone open and kept emitting Recording for the
rest of the session, so Stop and Delete appeared to do nothing until the
room was left and re-entered, and re-initialising the encoder under an
active encode could crash natively.
DefaultVoiceRecorder.startRecord now runs under the mutex it already owns
and returns early when a recording job exists, so the second start is a
no-op: no file, no encoder re-init and no second AudioReader.
Relates to https://github.com/element-hq/element-x-android/issues/5780
A distributor such as ntfy or DAVx5 lists every registration it holds, but
Element registered each one with no message for the distributor, so an
account added from a second session was indistinguishable from the first.
RegisterUnifiedPushUseCase.execute now takes the session id and passes it
as messageForDistributor, which the connector has accepted since 3.x. The
value is the raw Matrix ID, which is not personal data by the project's
own logging rules, and no new string resource is needed.
UnifiedPushProvider already has the MatrixClient in hand at the only call
site, so the PushProvider interface is unchanged and nothing outside the
module moves.
Relates to https://github.com/element-hq/element-x-android/issues/6426
MediaStore de-duplicates the display name of a new file itself, but its
uniquifier gives up after 32 collisions and throws IllegalStateException.
Saving then failed with "Sorry, an error occurred" and wrote nothing, and
because image.png is the name most screenshots arrive with, a user who
saves media regularly could reach a state where that name never saved
again without deleting files from Downloads by hand.
A new saveWithUniqueFileName helper in androidutils retries the insert
once with a suffixed name when the platform reports it cannot build a
unique one; a second failure propagates so genuine errors still surface.
withFileNameSuffix builds that name by inserting the suffix before the
extension, and handles names without one.
Both call sites are fixed: AndroidLocalMediaActions and DefaultFileSave
carried byte-identical logic and therefore the identical defect. While in
AndroidLocalMediaActions, a null output uri from ContentResolver.insert
now throws instead of reporting success without writing anything.
The pre-Android-Q path is untouched: it overwrites rather than
uniquifying, so it never reaches this limit.
Relates to https://github.com/element-hq/element-x-android/issues/6371
Some room list filters are mutually exclusive, and the UI expresses that
by hiding the incompatible chips rather than disabling them. Nothing
enforced the rule on the write path, so two chips tapped in the same
frame, before the list could recompose, both applied. The room list then
matched nothing while the offending chip was hidden, leaving no way to
see or undo what was filtering the rooms.
DefaultFilterSelectionStrategy.select() now ignores a filter that is
incompatible with one already selected, so the same table buildFilters()
uses to hide chips also governs the selection itself. The currently
applied filters win and the late tap is dropped, which matches the
existing design where an incompatible chip is already on its way out of
the UI when it is tapped.
toggle(), deselect(), clear() and the FilterSelectionStrategy interface
are unchanged.
Relates to https://github.com/element-hq/element-x-android/issues/5383
When a homeserver publishes a map_style_url in its .well-known file, the
app appended its own MapTiler API key to it, producing a URL with two
query strings that the tile server rejects. The map then failed to load
for every user of that homeserver, with no way for the operator to opt
out.
MapTilerTileServerStyleUriBuilder.build() now appends ?key= only to the
URL it composes itself from the bundled base url and map ids. A non
blank customMapStyleUrl is returned verbatim, so the operator's own
credentials survive. This matches Element Web and the self-hosted tile
server documentation, both of which treat map_style_url as complete.
The two existing tests that pinned the old behaviour are updated, and
cases are added for a custom URL that already carries a key, one that
carries an unrelated query string, and a blank url falling back to the
built-in style.
Relates to https://github.com/element-hq/element-x-android/issues/7289
* Check if splitting the steps for testing and generating the reports improves performance and keeps the coverage stats
* Exclude Konsist tests and include `:app` ones
* Remove the extra dependency of `koverVerifyAll` on `:app:koverVerify{variant}`
* Add a message search abstraction over the Rust SDK's local index
Wraps the SDK's search service so the rest of the app never sees MatrixRustSDK
types, following the existing convention in libraries/matrix.
- MessageSearch, MessageSearchService, MessageSearchResult and
MessageSearchPaginationState describe the surface in the api module.
- RustMessageSearch and RustMessageSearchService implement it, with
MessageSearchResultMapper and MessageSearchResultsProcessor turning SDK results
into Kotlin data classes.
- TantivyQueryEscaper escapes the query so a user typing ":)" or "AND" searches
for that literal text rather than tripping the tantivy parser, and joins every
word as a Must clause.
- Fakes and unit tests cover the mapping, the processor and the escaper, the last
checked against a real tantivy 0.26.1 parser.
* Attach the encrypted search index behind a disabled feature flag
Gives the client an on-device tantivy index encrypted with the session's client
secret, so encrypted rooms can be searched at all.
- The index store is attached at ClientBuilder time via withSearchIndexStore, in
a search-index directory alongside the session, keyed on the client secret. A
session without one gets no index.
- MatrixClient exposes isMessageSearchAvailable so callers gate on the real
indexing capability rather than on the raw flag, and getCacheSize() counts the
index directory so it stays visible in the settings figures.
- FeatureFlags.MessageSearch (feature.message_search) is off by default and
isFinished = false. It is read once at ClientBuilder time, so develop stays
releasable and nothing reaches a user.
* Simplify search index wiring following review
Tie search availability to the MessageSearch feature flag alone and let
the index be created without a client secret, encrypting it only when
the session has one, matching the SDK's SQLite stores. Make
getBaseClientBuilder take the flag as a required parameter and return
ClientBuilder directly again, dropping the BaseClientBuilder wrapper
and the mid-restore snapshot parameter. Check the flag through
FeatureFlagService in RustMatrixAuthenticationService instead of
caching it in a field, and revert the extra matrix_sdk_search tracing
target, which belongs in the SDK's defaults.
It's sadly faster that way:
Every time we run a test class in parallel a new JVM is started, and the `KoScope` contents can't be shared between them.
This scope can take a few minutes to load, so having several instances of this happening at the same time is a terrible idea.
It's actually faster to run the tests sequentially, at least locally.
* Add a password strength estimator wrapping the SDK zxcvbn API
* Add tests for the password strength estimator SDK mapping
---------
Co-authored-by: ElementBot <android@element.io>
* Import Compound tokens from `v10.2.3`
* Remove manually added `bgSubtleTertiary` semantic color: it's in Compound now
* Bump enterprise module to a compatible commit
* Fix insets in several screens
UI components in the room list, room member list, etc. now have better insets based on `WindowInsets.safeDrawing`
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
* Make settings section separators visible in black theme
In the Black (OLED) theme the canvas is forced to pure black while the
section-separator divider keeps its standard-dark colour (outlineVariant,
~10% alpha white), leaving dividers in Settings and related screens almost
invisible. Add an OLED-aware listSectionDivider alias that lightens the
divider only when the black theme is active, and use it for the
ListSectionHeader divider. Light and standard-dark themes are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
* Set separatorSecondary for Black theme instead of a custom divider alias
Following review feedback: now that the global HorizontalDivider uses
separatorSecondary (#7054), give the Black/OLED theme a visible
separatorSecondary value in ElementTheme instead of carrying a bespoke
listSectionDivider alias. This fixes all separators on pure black, not
just section headers. The exact color is pending design confirmation.
* chore(theme): remove in-code design discussion comment
Move design discussion out of the code per review; the divider color
decision lives in the PR thread.
* Update screenshots
Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
---------
Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: ElementBot <android@element.io>
Co-authored-by: Jorge Martin Espinosa <jorgem@element.io>
* Try making `HeaderFooterView` more robust
Create `HeaderFooterLayout` to properly calculate the content size when the component is scrollable so it's at least the size of the available space.
* Make `DesktopNoticeView` scrollable
For this, `NumberedListOrganism` can't be a `LazyColumn`, since that's a lazy layout with no intrinsic height and Compose doesn't know how to size it.
* Fix the case where no header or content composables are supplied
* Fix insets in non-scrollable UI
* Remove `movableContentOf`, suppress the warning instead since the `isScrollable` parameter should never toggle this behavior
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
* Use the right insets in the home screen UI
That's `WindowInsets.statusBar` for the `Scaffold` component, then `WindowInsets.navigationBar` for the `LazyColum.contentPadding`
* Apply it to other screens with long lists of items too
* Apply the insets to `PreferencePage` too
* Apply inset changes to other screens with scrollable content
* Add helper insets/padding values based on review suggestions.
* Use the newly added insets/padding values in several scaffold and scrollable columns
---------
Co-authored-by: ElementBot <android@element.io>
* When no URL is provided to `ContentValidationState.collectMediaState`, assume it's valid
That way we can do:
`if (validationState.isValid() && state.collectMediaState(possiblyNullUrl).isValid()) ...`
* Remove the `eventId` param from `ContentScannerService.scan`
It's not really used, since we then provide a state that can be mutated, and fetching that is what the previous parameter was used for
* Make sure to always validate the medias in the viewer, both the actual media and the thumbnail
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
* Remove no longer temporary string
These are now in localazy and the sync strings CI flow was failing because we have 2 conflicting values in `temporary.xml` and `localazy.xml`
* Use the right placeholder value in `screen_local_network_opt_in_subtitle`
* Localazy: improve search, parallelize checks
The search for XML files has been narrowed down and it's several times faster now, and processing the files is now done in parallel
* Improve script to also find and delete previous files
Also add the option of having a `-v|--verbose` flag to print which files we're modifying
* Use `LocalBuildMeta` to get the app name inside `LocalNetworkPermissionDialogView`
* Allow finding all the forbidden terms in the strings and translations and then return an error exit code instead of a success one
---------
Co-authored-by: ElementBot <android@element.io>
* Remove no longer temporary string
These are now in localazy and the sync strings CI flow was failing because we have 2 conflicting values in `temporary.xml` and `localazy.xml`
* Use the right placeholder value in `screen_local_network_opt_in_subtitle`
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
The content scanner allows disabling certain mime types for files in the set up HS, but it turns out for the scan operation instead of returning an `"is_valid": false` result in that case, it returns an error with a `MCS_MIME_TYPE_FORBIDDEN` reason. We need to handle that case as if it had returned the false `isValid` value.
* Add `ExtraDeveloperOptionsRenderer` to display extra UI components in the developer settings screen. It defaults to not displaying anything, using `NoopExtraDeveloperOptionsRenderer`
* Add `EnterpriseService.overriddenElementWellKnown` method to fetch the current overridden element well known value, if any
* Modify the well known retrievers to use the overridden value if it's there. Also extract the JSON parsing to its own `ElementWellKnownParser` component.
* Bump enterprise repo reference.
* Announce expanded/collapsed state on the timeline group header
GroupHeaderView (the accordion-style header used for grouped state
events in the timeline) uses toggleable() combined with
clearAndSetSemantics { contentDescription = text }. The
clearAndSetSemantics block discards the state information that
toggleable() would otherwise expose, so TalkBack never announces
whether the group is currently expanded or collapsed.
Add an explicit stateDescription (two new a11y_state_expanded /
a11y_state_collapsed strings in temporary.xml) alongside the existing
contentDescription so the expanded/collapsed state is communicated to
assistive technology.
Internal note: this single-file/single-root-cause fix addresses three
separate BITV findings raised in the same accessibility audit against
this component's toggle semantics.
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
* Add content scanner support to media gallery and viewer
* Improve UI and previews
* Remove default content validation state in media content views
* Disable click for invalid media items in the gallery events
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
Pro-only PRs (labelled pr-element-pro) leaked into Element X release
notes, causing confusion. They now group into a single Element Pro
section instead of scattering through Features/Bugfixes. Granularity is
dropped on purpose for these changes.
* Fix#7181: Remove back arrow from device verified screen and use 'Done' text
* Update screenshots
* Fix failing Maestro test by tapping 'Done' instead of 'Continue'
---------
Co-authored-by: ElementBot <android@element.io>
Co-authored-by: thirumani-vihaan <your-github-email@example.com>
* Simplify how event content with captions are displayed
The caption component will now be added in `TimelineItemEventContentView` instead of separately in each `TimelineItem__View` component
* Make sure `CoilMediaFetcher` methods to fetch media throw errors
We need to catch them later to check if the content scanner marked some thumbnails or medias as invalid/unsafe in `AsyncImage` instances
* Add a `ContentScannerService` that allows us to manually scan a list of medias in an event and then update and store the results
The results take the form of `ContentValidationValue`, stored in a `ContentValidationState` instance tied to the event
* Create `EventContentValidationCache` to keep the `ContentValidationStates` for events in a room
* Expose `EventContentValidationCache` to Compose using `LocalEventContentValidationState`
Also add a couple of helper functions to avoid having to manually remember the states
* Use the new components to display invalid content in the timeline items
* Add `blurhash` to most media items so we can use it while validating the thumbnails/medias
* Fix custom layout issues with screenshot and UI tests
* Also check the validation state for replied-to events in the composer
* Fix and add tests
* Handle unrecoverable content validation errors too: display a 'not found' UI
* Improve the performance of `EqualWidthColumn` for the single item case
* Update screenshots
---------
Co-authored-by: ElementBot <android@element.io>
Several clickable elements on the login and onboarding flows (the
account provider list items, the search clear icon, the login clear
icon, and the hidden dev-mode version tap) use plain
Modifier.clickable without a semantics role. Screen readers announce
these as generic elements instead of buttons, so users relying on
TalkBack cannot tell they are interactive.
Add role = Role.Button to each clickable modifier, and reuse the
existing clear-icon content description as onClickLabel where one is
available.
FormattingOption used clearAndSetSemantics with only a contentDescription,
so the Bold/Italic/Underline toggle buttons never exposed whether they
were currently active to TalkBack. Add a stateDescription reflecting the
selected/unselected state alongside the existing contentDescription.
New a11y_active / a11y_inactive strings added to temporary.xml for
Localazy import.
The homeserver's new MSCxxxx knock push rule notifies members who can
act on a knock; the push resolver previously filtered every
m.room.member state event out, so the push produced no notification.
Split RoomMemberContent out of the filtered-out catch-all and render
membership=knock as "Requested to join" from the knocking user
(other membership changes stay filtered out).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace clickable(role = Role.Checkbox) with toggleable(value = checked, role =
Role.Checkbox) so TalkBack can announce the current state and state changes.
Set the inner Checkbox.onCheckedChange = null to make it a purely visual
indicator, avoiding duplicate accessibility focus on the same control.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
| `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:
description: Bring the HealthChat fork (release/healthchat) onto a newer element-hq/element-x-android release tag. Use when the user asks to rebase, sync, merge or update to upstream, take a new Element X version, or asks why the fork is behind upstream. Covers the patch-stack model, the rebase procedure, Git LFS, conflicts, verification, the force-push and the rewind.
---
# Syncing the HealthChat fork with upstream
`release/healthchat` = upstream release tag + HealthChat commits on top. Patch stack, same as
healthchat-web and element-x-ios. A sync replaces the stack's base and force-pushes the branch.
That is the one sanctioned history rewrite in this repo; everything else in `AGENTS.md` still
holds. We rebase, we never merge upstream into the branch.
First done v26.07.0 → v26.10.0 (2026-10-16 to 10-19). Numbers below come from that run.
## Before touching anything
- [ ]**Toolchain.** Upstream pins the JDK (`java-version` in `.github/workflows/*.yml`, 21),
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).
- Create previews for **all main states** of a Composable.
- Use `@PreviewsDayNight` for consistency.
- Use `PreviewParameterProvider` (e.g., `FooStateProvider`) to provide states.
- Use `PreviewParameterProvider` (e.g., `FooStatePreviewParam`) to provide states. Always add new states to existing providers at the end of the sequence.
- Wrap previews in `ElementPreview { ... }`.
- When writing tests, never try to record the screenshots, the CI will do it.
---
@@ -49,9 +50,10 @@ PRs must meet these rules.
- Title = changelog entry — descriptive, no "Fixes #…".
- Leave description template for the developer. Redirect them to the [contributing etiquette](CONTRIBUTING.md#etiquette).
- Screenshots/videos for visual changes.
- 500 additions max — split large changes.
- 500 additions max into production code. Test code can be larger — split large changes.
- Commits need a title and description; no tiny or massive commits.
- No history rewrites.
- When creating a pull request and if changes contain new or update(s) of the Composable Previews, add the label "Record-Screenshots" to the PR so that the CI will record the screenshots.
---
@@ -88,7 +90,7 @@ We use [Appyx](https://bumble-tech.github.io/appyx/) for navigation and [Molecul
| `FooView.kt` | Stateless Composable rendering the UI from `FooState`. |
| `FooState.kt` | Data class representing the immutable UI state. |
| `FooEvent.kt` | Sealed interface for UI actions sent to the Presenter. |
| `FooStateProvider.kt` | Provides sample states for Previews and Screenshot tests. |
| `FooStatePreviewParam.kt` | Provides sample states for Previews and Screenshot tests. |
| `FooPresenterTest.kt` | Unit tests for the Presenter logic using Turbine. |
<!-- Release notes generated using configuration in .github/release.yml at v26.09.3 -->
## What's Changed
### ✨ Features
* Integrate the new timeline renderer behind a feature flag by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7722
* Add headers (`h1-h6`) tag support for the new timeline renderer by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7754
### 🙌 Improvements
* Add smooth animation to voice messages play progress by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7698
* Add linkification to the new timeline renderer by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7731
### 🐛 Bugfixes
* Make global search use a `Joined` filter for room results by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7695
* Fix flaky test in `TimelinePresenterTest` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7702
* Close the threaded timelines created to create a poll, share a location or send media by @bmarty in https://github.com/element-hq/element-x-android/pull/7705
* Start sync manually after completing the auth flow by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7734
* Fix forwarding an event not working in threads by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7743
* Fix blank screen at launch when the homeserver expires the sliding sync session by @bmarty in https://github.com/element-hq/element-x-android/pull/7692
* Fix unread notification count in room list by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7749
### 🗣 Translations
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7699
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7736
### 🧱 Build
* Reduce parallel JVMs spawned for tests by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7701
* Replace git flow with regular git commands in the release script V2 by @bmarty in https://github.com/element-hq/element-x-android/pull/7707
* Remove Roborazzi, use Paparazzi for Compound by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7735
* Fix regex for invalid placeholders by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7738
* Add a script to install and launch an APK given a PR URL by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7752
### 🚧 In development 🚧
* New timeline event renderer: add new modules by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7721
* Add recent search results to global search by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7742
### Dependency upgrades
* Update dependency io.sentry:sentry-android to v8.55.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7685
* Update coil to v3.6.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7693
* Update kotlinpoet to v2.4.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7709
* Update plugin sonarqube to v7.5.0.8588 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7671
* Update roborazzi to v1.74.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7712
* Update dependency io.element.android:element-call-embedded to v0.26.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7715
* Use patched Konsist version (forked, 0.18.0) by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7719
* Update dependency net.zetetic:sqlcipher-android to v4.19.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7716
* Update metro to v1.4.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7711
* Update actions/setup-java action to v6.0.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7728
* Update dependency androidx.compose:compose-bom to v2026.09.00 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7730
* Update dependency androidx.sqlite:sqlite-ktx to v2.7.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7729
* Update dependencyAnalysis to v3.19.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7755
* Update media3 to v1.11.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7756
* Upgrade SDK bindings to `v26.09.26` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7780
### Others
* Let the user give a reason when removing a message by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7538
* Iterate on client creation by @bmarty in https://github.com/element-hq/element-x-android/pull/7591
* Iteration on Acount provider by @bmarty in https://github.com/element-hq/element-x-android/pull/7703
* Do not report detekt LargeClass issues on test classes by @bmarty in https://github.com/element-hq/element-x-android/pull/7713
* Let the user give an optional reason when removing a message by @bmarty in https://github.com/element-hq/element-x-android/pull/7679
* Make NotificationResultProcessor.emit wait until the batch is processed by @Johennes in https://github.com/element-hq/element-x-android/pull/7640
* Settings iteration: move user status and update multi-account section by @bmarty in https://github.com/element-hq/element-x-android/pull/7741
* Update remove message confirmation dialog. by @bmarty in https://github.com/element-hq/element-x-android/pull/7757
* Add a script to end all the sessions of a Matrix account using MAS by @bmarty in https://github.com/element-hq/element-x-android/pull/7762
* Settings iteration: split account settings and application settings. by @bmarty in https://github.com/element-hq/element-x-android/pull/7750
<!-- Release notes generated using configuration in .github/release.yml at v26.09.2 -->
## What's Changed
### 🙌 Improvements
* Keep the push foreground service alive while notifications are fetched by @Johennes in https://github.com/element-hq/element-x-android/pull/7639
* Stop clearing the remote config when clearing the cache by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7662
### 🐛 Bugfixes
* Fix fetched remote config being cached with the wrong key by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7626
* Make sure `enforce_element_pro` is taken into account in FOSS by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7631
### 🗣 Translations
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7628
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7657
### 🧱 Build
* Allow retrying finding the release assets in release script by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7619
* Improve detection of invalid placeholders in localazy strings by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7624
* Add `GRADLE_OPTS` for `sync-localazy.yml` flow by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7629
* Clean up ProGuard keep rules files by @bmarty in https://github.com/element-hq/element-x-android/pull/7647
* Replace Danger with actions/github-script by @bmarty in https://github.com/element-hq/element-x-android/pull/7649
* Remove Showkase and its in-app component browser by @bmarty in https://github.com/element-hq/element-x-android/pull/7678
* Improve Renovate configuration to fix issues with Maven Central by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7677
### Dependency upgrades
* Update dependency com.posthog:posthog-android to v3.60.7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7616
* Update dependency org.jsoup:jsoup to v1.23.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7620
* Update dependency io.element.android:element-call-embedded to v0.25.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7622
* Update dependencyAnalysis to v3.19.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7623
* Update wysiwyg to v2.42.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7627
* Update android.gradle.plugin to v9.3.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7603
* Update actions/setup-java action to v6 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7614
* Update danger/danger-js action to v14 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7602
* Update camera to v1.6.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7632
* Update coil to v3.6.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7630
* Update dependency io.sentry:sentry-android to v8.54.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7636
* Update dependency org.jetbrains.kotlinx:kotlinx-collections-immutable to v0.5.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7650
* Bump SDK bindings to `v26.09.3` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7637
* Update zizmorcore/zizmor-action action to v0.6.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7658
* Update coil to v3.6.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7664
* Update dependency org.matrix.rustcomponents:sdk-android to v26.09.08 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7668
* Update dependency org.matrix.rustcomponents:sdk-android to v26.09.9 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7683
### 💼 Element Pro
* Fix remote config getting refreshed with `Origin.Cache` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7666
### Others
* Move knock and gallery to labs by @bmarty in https://github.com/element-hq/element-x-android/pull/7625
* Say so when this session has no secrets to verify another one with by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7551
* Expose the timeline item long press and swipe to reply as TalkBack actions by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7458
* Bump PR priority for our dependencies in Renovate by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7642
* Import string from Localazy and remove temporary file. by @bmarty in https://github.com/element-hq/element-x-android/pull/7645
* Revert "Say so when this session has no secrets to verify another one with" by @bmarty in https://github.com/element-hq/element-x-android/pull/7644
* Maestro (local): queue pending runs in the maestro-test concurrency group instead of cancelling them by @andreBurnt in https://github.com/element-hq/element-x-android/pull/7656
* Maestro (local): skip the lane on pull requests from forks by @andreBurnt in https://github.com/element-hq/element-x-android/pull/7655
* Update maplibre compose to 0.15 by @ganfra in https://github.com/element-hq/element-x-android/pull/7663
* Try improving renovate issues with Maven Central rate limiting by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7667
## New Contributors
* @andreBurnt made their first contribution in https://github.com/element-hq/element-x-android/pull/7656
* Support creating polls with multiple answers and send response with multiple answers by @bmarty in https://github.com/element-hq/element-x-android/pull/7613
### 🙌 Improvements
* Only track read receipts on message-like events by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7361
* Stop rescanning message bodies for emoji only on every recomposition by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7531
### 🐛 Bugfixes
* Ensure that noisy notifications are still noisy when the app has a PIN code. by @bmarty in https://github.com/element-hq/element-x-android/pull/7584
* Ensure that sessionVerifiedStatus is updated when the crypto is ready. by @bmarty in https://github.com/element-hq/element-x-android/pull/7604
* Fix invalid placeholders in translations by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7612
### 🧱 Build
* Do not open the editor when finishing the release by @bmarty in https://github.com/element-hq/element-x-android/pull/7583
* [release] Check that there is no unmerged PR with the label "Z-NextRelease", else exit by @bmarty in https://github.com/element-hq/element-x-android/pull/7606
### Dependency upgrades
* Update dependency net.zetetic:sqlcipher-android to v4.18.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7552
* Update dependency com.google.firebase:firebase-bom to v34.18.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7581
* Update dependency io.github.sergio-sastre.ComposablePreviewScanner:android to v0.9.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7535
* Update dependency com.google.guava:guava to v33.7.1-android by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7534
* Update dependency org.unifiedpush.android:connector to v3.3.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7586
* Update dependency org.unifiedpush.android:connector to v3.3.5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7595
* Update roborazzi to v1.73.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7618
### 💼 Element Pro
* Implement a hook allowing enterprise modules to perform actions on startup by @richvdh in https://github.com/element-hq/element-x-android/pull/7585
### Others
* Don't hide the thread of a deleted message inside the removed messages group by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7571
* Name a sender the timeline has no profile for from the room member list by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7539
* Offer conversations in the system share sheet by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7574
* Rank the mention suggestions by how well they match what was typed by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7536
* Log the SDK git Sha at application startup by @bmarty in https://github.com/element-hq/element-x-android/pull/7589
* Limit usage of nullable callback. by @bmarty in https://github.com/element-hq/element-x-android/pull/7588
* Fix crash due to JSON.stringify on a cirular structure in injectes js. by @toger5 in https://github.com/element-hq/element-x-android/pull/7600
* Support multi-select polls by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6477
* Remove ExternalSession, fallback login with Element Web is gone. by @bmarty in https://github.com/element-hq/element-x-android/pull/7608
* Fall back to the message body when the formatted body has nothing to render by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7523
<!-- Release notes generated using configuration in .github/release.yml at v26.08.3 -->
## What's Changed
### ✨ Features
* Let users enter their full Matrix user ID on the account provider screen by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7500
* Enable user status feature and hide status text in the timeline by @bmarty in https://github.com/element-hq/element-x-android/pull/7517
### 🐛 Bugfixes
* Fix cold start with a mobile deep link by @bmarty in https://github.com/element-hq/element-x-android/pull/7488
* Make the account provider screen scrollable so its description is not clipped by the keyboard by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7521
### 🧱 Build
* Update Gradle Wrapper from 9.7.0 to 9.7.1 by @ElementBot in https://github.com/element-hq/element-x-android/pull/7485
* Fix flaky test in `DefaultVoiceMessageComposerPresenterTest` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7487
* Fix warning about deprecated Gradle `keepRules.file` API by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7498
* Update Enterprise Copyright in idea settings, so that new files get an expected header. by @bmarty in https://github.com/element-hq/element-x-android/pull/7501
* Remove workaround for Renovate. by @bmarty in https://github.com/element-hq/element-x-android/pull/7502
### 🚧 In development 🚧
* Make PIP react to camera orientation changes in the other participant's device by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6951
* Feature: global search by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7410
### Dependency upgrades
* Update dependency org.maplibre.gl:android-sdk to v13.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7486
* Update dependency androidx.webkit:webkit to v1.17.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7484
* Update dependency io.sentry:sentry-android to v8.53.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7481
* Update dependency io.element.android:element-call-embedded to v0.24.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7474
* Update roborazzi to v1.72.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7489
* Update metro to v1.4.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7493
* Update dependency com.squareup.okhttp3:okhttp-bom to v5.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7512
* Update dependency com.google.guava:guava to v33.7.0-android by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7516
* Update sdk to version 26.08.25 by @bmarty in https://github.com/element-hq/element-x-android/pull/7550
### 💼 Element Pro
* Add `ClientEnterpriseHook` and `ClientBuilderEnterpriseHook` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7495
* Remove dependency on enterprise module. by @bmarty in https://github.com/element-hq/element-x-android/pull/7505
### Others
* Show the right image when opening one that has not finished sending by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7359
* Move strings to the login module by @bmarty in https://github.com/element-hq/element-x-android/pull/7494
* Allow empty problem description if the GitHub issue number is present for rageshakes by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7497
* Apply the system theme when switching back to it from a forced light or dark theme by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7453
* Show dialog with reason when user clicks on a message with a failure by @bmarty in https://github.com/element-hq/element-x-android/pull/7490
* Keep a focused timeline focused until the event has been rendered by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7463
* Do not render in-chat key verification requests in the timeline by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7443
* Share a file whose mime type is not one of the supported ones by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7435
* Show reactions in the pinned messages list by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6676
* Send a concrete mime type for media shared with a wildcard one by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7449
* Rename module and ensure that naming convention is followed by @bmarty in https://github.com/element-hq/element-x-android/pull/7514
* Fix lint issue, ensure that PiP methods are not called on old devices not supporting them. by @bmarty in https://github.com/element-hq/element-x-android/pull/7515
* Rename PreviewParameterProvider subclasses to the PreviewParam suffix by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7468
* Update Ref. by @bmarty in https://github.com/element-hq/element-x-android/pull/7518
* Remove the reference to the decommissioned OIDC playground by @sandhose in https://github.com/element-hq/element-x-android/pull/7519
* Hide the encryption badge when key storage is set up on the server by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7447
* Rename remaining events sealed interface by @bmarty in https://github.com/element-hq/element-x-android/pull/7533
* Stop waiting on a verification request the other session never accepts by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7457
* Point the proxy debugging doc at the method that builds the clients by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7543
* Show the build date in the screenshot gallery footer by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7544
* Run the database migrations again when a database is upgraded by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7570
* Update fonts on the headings by @bmarty in https://github.com/element-hq/element-x-android/pull/7520
* Add a "Push rules" entry in the developer options by @bmarty in https://github.com/element-hq/element-x-android/pull/7577
## New Contributors
* @sandhose made their first contribution in https://github.com/element-hq/element-x-android/pull/7519
<!-- Release notes generated using configuration in .github/release.yml at v26.08.2 -->
**Note: this release in an internal release, it will not be pushed to the stores.**
## What's Changed
### ✨ Features
* Remove the legacy web-based account creation for matrix.org by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7412
### 🙌 Improvements
* Move the custom tile server URL config to the Element wellknown content by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7416
* Explain a rate limiting push gateway in the notification troubleshooter by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7448
* Move the `:libraries:wellknown` logic into `enterprise` modules by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7429
* Refresh the room members when a mention is started by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7438
* Close the invite screen once the invite has been declined by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7440
* Use Profiles sliding sync extension check for own profile subscription by @ganfra in https://github.com/element-hq/element-x-android/pull/7403
* Simplify choosing the account provider during sign in, with autocomplete of previously used ones by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7394
### 🐛 Bugfixes
* Fix wrong TextComposer placeholder displayed when in a thread. by @bmarty in https://github.com/element-hq/element-x-android/pull/7413
* Fix bug reports failing to send after the app has crashed by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7395
* Fix outgoing session verification stuck with incomplete recovery setup by @hardcore-sushi in https://github.com/element-hq/element-x-android/pull/7433
* Keep transparency when an image is shared into the app with an imprecise mime type by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7452
* Don't restart the sync service if the client is shutting down by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7472
* Normalize the applicationName used in user agent by @bmarty in https://github.com/element-hq/element-x-android/pull/7477
* Keep playing a voice message when another app plays a short sound by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7454
* Keep the send button when the composer is emptied while editing a message by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7456
* Do not open the room again when the app is resumed from the recents list by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7436
* Stop waiting for a room alias that the remote server never resolves by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7446
### 🧱 Build
* build-rust-sdk: print the command we run by @richvdh in https://github.com/element-hq/element-x-android/pull/7407
* Remove license check on Pro modules. by @bmarty in https://github.com/element-hq/element-x-android/pull/7418
* Improve build time config and update the Firebase configuration by @bmarty in https://github.com/element-hq/element-x-android/pull/7473
* Make the PUSHER_APP_ID configurable at build time. by @bmarty in https://github.com/element-hq/element-x-android/pull/7478
### 📄 Documentation
* Add KDoc to all the interfaces of the api modules by @bmarty in https://github.com/element-hq/element-x-android/pull/7426
### 🚧 In development 🚧
* [LinkNewDevice] Ensure the notice screen is also protected by the timeout. by @bmarty in https://github.com/element-hq/element-x-android/pull/7428
### Dependency upgrades
* Update kotlin to v2.3.11 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7396
* Update plugin sonarqube to v7.4.0.8496 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7415
* Update metro to v1.4.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7421
* Update roborazzi to v1.71.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7423
* Update dependency io.sentry:sentry-android to v8.52.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7414
* Update dependency org.matrix.rustcomponents:sdk-android to v26.08.13 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7420
* Update dependency io.github.sergio-sastre.ComposablePreviewScanner:android to v0.9.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7431
* Update dependency org.maplibre.compose:maplibre-compose to v0.14.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7432
* Update dependency org.matrix.rustcomponents:sdk-android to v26.08.17 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7460
* Update dependency org.matrix.rustcomponents:sdk-android to v26.08.19 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7479
* Update media3 to v1.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7427
* Update dependency androidx.compose:compose-bom to v2026.08.00 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7483
### 💼 Element Pro
* Add hooks to allow providing additional feature flags in EX pro by @richvdh in https://github.com/element-hq/element-x-android/pull/7419
* Use the right base URL for the ESS config/well known endpoints by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7469
### Others
* Do not display pinned events that the client cannot render by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7379
* Hide the forward action on live location shares by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7343
* Do not fail the screenshot recording job when there is nothing to commit by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7355
* Show the account a UnifiedPush registration belongs to in the distributor by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7345
* Stop repeating messages that are already shown in a room notification by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7370
* Turn a matrix: URI in a message into a pill by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7354
* Send a message with Enter on a physical keyboard, Shift+Enter for a new line by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7353
* Do not group timeline messages sent more than five minutes apart by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7349
* Linkify URLs whose host is a bracketed IPv6 address by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7347
* Add missing tests for `RustTemporaryMatrixClient` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7417
* Revert "Keep manually revealed media visible when re-entering a room" by @bmarty in https://github.com/element-hq/element-x-android/pull/7424
* Fix outdated documentation. by @bmarty in https://github.com/element-hq/element-x-android/pull/7430
* Sync strings - New language supported: Arabic by @bmarty in https://github.com/element-hq/element-x-android/pull/7461
* Keep the focused login field visible above the keyboard by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7455
* Relax image compression so photos and screenshots keep more detail by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7451
* Open the user profile of a user id that contains a slash by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7445
* Remove the F-Droid dex patching step from the release script by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7442
* Convert markdown to HTML when a caption is edited by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7441
* Exclude carriage returns from the login and password fields by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7439
* Show an error when the room to leave cannot be found by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7437
* Give the room name in the timeline top bar a 48dp tap target by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7450
* Model the fake voice recorder on a cancellable recording job by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7466
## New Contributors
* @hardcore-sushi made their first contribution in https://github.com/element-hq/element-x-android/pull/7433
<!-- Release notes generated using configuration in .github/release.yml at v26.08.1 -->
## What's Changed
### 🙌 Improvements
* Render push notifications for live location share starts (MSC4505) by @ara4n in https://github.com/element-hq/element-x-android/pull/7203
* [Task] Show active call participants in the people list #7199 by @Eklavya47 in https://github.com/element-hq/element-x-android/pull/7300
### 🐛 Bugfixes
* Fix overlap issue. by @bmarty in https://github.com/element-hq/element-x-android/pull/7331
* Announce checked/unchecked state for user rows in invite screen by @bmarty in https://github.com/element-hq/element-x-android/pull/7136
* Display 'add reactions' menu in the threads screen by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7388
* Expand the emoji picker to full height when the keyboard is shown by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7382
* UI fixes by @bmarty in https://github.com/element-hq/element-x-android/pull/7409
### 🗣 Translations
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7389
### 🧱 Build
* Don't check enterprise previews in `KonsistPreviewTest` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7297
* CI: split unit tests, screenshots and coverage in `tests.yml` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7296
* Update Gradle Wrapper from 9.6.1 to 9.7.0 by @ElementBot in https://github.com/element-hq/element-x-android/pull/7335
### 🚧 In development 🚧
* QR code login iteration by @bmarty in https://github.com/element-hq/element-x-android/pull/6719
* User status last pieces by @ganfra in https://github.com/element-hq/element-x-android/pull/7328
* [Link new device] Add timeout by @bmarty in https://github.com/element-hq/element-x-android/pull/7338
### Dependency upgrades
* Update dependency org.maplibre.compose:maplibre-compose to v0.13.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7291
* Update android.gradle.plugin to v9.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7248
* Update dependency io.sentry:sentry-android to v8.50.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7298
* Update zizmorcore/zizmor-action action to v0.6.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7306
* Update android.gradle.plugin to v9.3.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7304
* Update dependency com.posthog:posthog-android to v3.56.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7311
* Update dependencyAnalysis to v3.18.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7318
* Update actions/stale action to v11 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7317
* Update dependency io.github.zxing-cpp:android to v3.1.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7322
* Update dependency org.matrix.rustcomponents:sdk-android to v26.08.05 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7324
* Update dependency androidx.constraintlayout:constraintlayout to v2.2.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7329
* Update reactivecircus/android-emulator-runner action to v2.38.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7189
* Update dependency io.sentry:sentry-android to v8.51.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7301
* Update dependency org.maplibre.gl:android-sdk to v13.4.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7305
* Update dependency io.element.android:element-call-embedded to v0.23.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7323
* Update metro to v1.4.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7334
* Update dependency com.google.firebase:firebase-bom to v34.17.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7333
* Update dependency androidx.compose.material3:material3 to v1.5.0-alpha23 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7175
* Update dependency org.jsoup:jsoup to v1.23.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7332
* Update dependency androidx.constraintlayout:constraintlayout-compose to v1.1.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7330
* Update zizmorcore/zizmor-action action to v0.6.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7383
* Update actions/setup-java action to v5.7.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7339
* Update gradle/actions action to v6.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7384
* Update plugin dependencycheck to v13 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7386
* Update dependency org.matrix.rustcomponents:sdk-android to v26.08.11 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7402
### 💼 Element Pro
* Add support for checking the ESS config in Element Pro by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7302
### Others
* Add extra vertical padding to `ReplyToInvalidContent` and `ReplyToNotFoundContent` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7288
* Index messages locally in an encrypted on-device store so encrypted rooms can be searched at all by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7249
* Bump enterprise module to the latest commit by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7307
* Bump enterprise module to the latest commit by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7309
* Move deviceId and git details to developer settings by @bmarty in https://github.com/element-hq/element-x-android/pull/7241
* Show a Message not found placeholder when a replied to message cannot be loaded by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7376
* Update in reply to message when message is not found. by @bmarty in https://github.com/element-hq/element-x-android/pull/7387
* Add default `OAUTH_CLIENT_URL_PATH` alternative by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7385
* Fix the feature module template writing the impl build script into the api module by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7374
* Let users enter their full Matrix user ID when searching for a homeserver by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7375
* Keep manually revealed media visible when re-entering a room by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7378
* Copy a blocked user's Matrix ID by long pressing them by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7372
* Do not draw the media loading progress bar under the status bar by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7366
* Do not notify about an event sent by another account on the same device by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7360
* Keep the opened image on screen when the media gallery fails to load by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7362
* Do not linkify the server part of a fediverse handle as an email address by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7356
* Expose global account data get and set on MatrixClient by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7373
* Show the alias or the direct message user's Matrix ID in the room picker by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7371
* Use a dedicated string for the Mute room notification mode by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7367
* Keep playing a voice message when the screen turns off by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7365
* Let a voice message without a waveform be seeked by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7363
* Ignore a second voice recording start while one is already running by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7346
* Do not offer to start a call in a DM with no other member by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7352
* Do not notify about a redaction whose target event is unknown by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7350
* Ignore a room list filter that is incompatible with an already selected one by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7342
* Stop a right to left sender name from reordering a room list preview by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7348
* Do not append the application MapTiler key to a custom map style url by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7340
* Hide space children you have left that can only be joined by invitation by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7369
* Never re-encode a video at a higher bitrate than the source by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7351
* Display push notifications for knocks by @ara4n in https://github.com/element-hq/element-x-android/pull/7207
## New Contributors
* @hayaksi1 made their first contribution in https://github.com/element-hq/element-x-android/pull/7249
* @ara4n made their first contribution in https://github.com/element-hq/element-x-android/pull/7203
* @Eklavya47 made their first contribution in https://github.com/element-hq/element-x-android/pull/7300
<!-- Release notes generated using configuration in .github/release.yml at v26.08.0 -->
## What's Changed
### ✨ Features
* feat(call): Active call timeline rendering by @BillCarsonFr in https://github.com/element-hq/element-x-android/pull/7051
### 🙌 Improvements
* Announce expanded/collapsed state on the timeline group header by @unclejay80 in https://github.com/element-hq/element-x-android/pull/7226
* Allow adding extra elements to the developer settings from external modules by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7230
* Update keyboard visibility to track hasFocus by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7284
### 🐛 Bugfixes
* Live location sharing timeout by @ganfra in https://github.com/element-hq/element-x-android/pull/7184
* Fix logging via QR code when using MDM-configured homeserver by @mxandreas in https://github.com/element-hq/element-x-android/pull/7228
* Fix #7181: Remove back arrow from device verified screen and use 'Done' text by @thirumani-vihaan in https://github.com/element-hq/element-x-android/pull/7222
* Fix padding for captions of media in 'not found' state by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7247
* Fix banned mime type errors in `DefaultContentScannerService` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7255
* Fix `ContentErrorView` not calculating its text size synchronously by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7281
* Fix media viewer for medias not related to events displaying invalid state by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7280
* Try making `HeaderFooterView` more robust by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7267
* Fix insets in several screens by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7285
### ⚠️ API Changes
* Add a password strength estimator wrapping the SDK zxcvbn API by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7186
### 🗣 Translations
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/7277
### 🧱 Build
* CI: fix 'Update Gradle Wrapper' by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7238
* Update Gradle Wrapper from 9.5.1 to 9.6.1 by @ElementBot in https://github.com/element-hq/element-x-android/pull/7240
* Localazy: improve search, parallelize checks by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7261
* Run `Konsist` tests sequentially by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7293
### 📄 Documentation
* Group Element Pro changes into a dedicated release notes section by @manuroe in https://github.com/element-hq/element-x-android/pull/7236
### 🚧 In development 🚧
* Plug emoji picker for custom user status by @ganfra in https://github.com/element-hq/element-x-android/pull/7257
* WIP : display user status by @ganfra in https://github.com/element-hq/element-x-android/pull/7232
### Dependency upgrades
* Update roborazzi to v1.67.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7188
* Update dependency io.sentry:sentry-android to v8.48.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7217
* Update dependency com.google.crypto.tink:tink-android to v1.23.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7229
* Update kotlin to v2.3.10 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7234
* Update dependency net.zetetic:sqlcipher-android to v4.17.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7218
* Update dependency com.google.firebase:firebase-bom to v34.16.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7237
* Update dependencyAnalysis to v3.16.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7212
* Update actions/setup-java action to v5.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7214
* Update dependency io.element.android:element-call-embedded to v0.22.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7161
* Update kotlin to v2.4.10 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7244
* Update roborazzi to v1.68.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7245
* Update metro to v1.3.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7243
* Update dependency org.matrix.rustcomponents:sdk-android to v26.07.22 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7258
* Update zizmorcore/zizmor-action action to v0.6.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7256
* Update dependency org.matrix.rustcomponents:sdk-android to v26.07.23 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7262
* Update dependency io.sentry:sentry-android to v8.49.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7263
* Update dependencyAnalysis to v3.17.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7265
* Update actions/setup-java action to v5.6.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7268
* Update dependency org.jetbrains.kotlinx:kover-gradle-plugin to v0.9.9 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7273
* Update actions/setup-python action to v7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7278
* Update roborazzi to v1.70.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7274
* Update actions/checkout action to v7.0.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7282
* Import Compound tokens from `v10.2.3` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7272
* Update dependency org.matrix.rustcomponents:sdk-android to v26.07.28 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7287
* Update dependency org.maplibre.gl:android-sdk to v13.4.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7290
### 💼 Element Pro
* Content scanner (Element Pro): scan timeline events for unsafe content by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7159
* Add content scanner support to media gallery and viewer (Element Pro) by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7233
* Enforce HS disabling E2EE in DM creation too (Element Pro) by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7292
* Allow adding a path suffix to the OAuth client URI by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7286
### Others
* [A11y] Improve rendering on focused UI items by @bmarty in https://github.com/element-hq/element-x-android/pull/7182
* Add a button role to clickable icons and text on the login/onboarding screens by @unclejay80 in https://github.com/element-hq/element-x-android/pull/7225
* Expose formatting toggle state to TalkBack by @unclejay80 in https://github.com/element-hq/element-x-android/pull/7227
* Add warning to the 'Scan QR code' screen by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7264
* Refactor SearchBarResultState.Initial/NoResultsFound to data objects by @ganfra in https://github.com/element-hq/element-x-android/pull/7266
* Remove no longer temporary strings by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7260
* Use the right insets in the home screen UI by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7271
* Make settings section separators visible in black theme by @mvanhorn in https://github.com/element-hq/element-x-android/pull/7039
## New Contributors
* @thirumani-vihaan made their first contribution in https://github.com/element-hq/element-x-android/pull/7222
* @mvanhorn made their first contribution in https://github.com/element-hq/element-x-android/pull/7039
<!-- These are IANA recognized special use domain names, see https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml -->
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.