Commit Graph

6045 Commits

Author SHA1 Message Date
Benoit Marty c19dfd1db6 Merge pull request #7901 from element-hq/feature/bma/mainThreadBlocking
Avoid blocking the main thread in several places
2026-10-14 16:39:14 +02:00
Jorge Martin Espinosa 3ef59a8910 Don't log OkHttp binary bodies in debug builds (#7919)
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.
2026-10-14 12:04:07 +00:00
Jorge Martin Espinosa 566eb1f4f3 Use local SDK library for EC native too (#7916)
When a local SDK AAR file is present, ignore the version needed by the Element Call Matrix library.

This allows us to test local SDK builds.
2026-10-14 11:14:38 +02:00
Jorge Martin Espinosa 241f8e67bd Fix side-effects when tapping on a link in the new renderer (#7906)
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.
2026-10-14 11:13:59 +02:00
Jorge Martin Espinosa 8a5c50d2b8 Fix File.getSizeOfFiles() double counting some files (#7907)
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.
2026-10-14 11:13:14 +02:00
Jorge Martin Espinosa 213ed22cad Bump SDK bindings to 26.10.12-nightly (#7909)
* Bump SDK bindings to `26.10.12-nightly`

* Fix API breaks in SDK bindings

* Fix doc comment
2026-10-13 22:28:22 +02:00
Jorge Martin Espinosa 9cc5cf9a01 Add total unread notification count to home screen (#7891)
* 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>
2026-10-13 10:58:07 +00:00
Benoit Marty ffe8f4a14e Merge pull request #7900 from element-hq/feature/bma/settingsMainThreadJank
Fix animation glitches when opening the settings
2026-10-13 11:30:36 +02:00
ElementBot 092cded211 Update Matrix Rust SDK to 26.10.07-nightly (#7880)
* 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>
2026-10-13 11:21:21 +02:00
Benoit Marty 3170ff4422 Remove useless Unit type. 2026-10-13 10:42:05 +02:00
Benoit Marty d5780507c9 Make storeUserProfile main-safe instead of wrapping getUserProfile 2026-10-13 09:28:49 +02:00
Benoit Marty 6efb10df11 Write the push history off the main thread
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>
2026-10-12 14:33:17 +02:00
Benoit Marty 9be2a31c5f Schedule the background search backfill off the main thread
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>
2026-10-12 14:33:16 +02:00
Benoit Marty 149dedcc03 Store the user profile off the main thread when it is updated by the SDK
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>
2026-10-12 14:33:16 +02:00
Benoit Marty 404c26c62e Fetch and map the thread list items off the main thread
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>
2026-10-12 14:33:15 +02:00
Benoit Marty 23a79ad987 Merge pull request #7896 from element-hq/sync-localazy
Sync Strings
2026-10-12 13:56:43 +02:00
Benoit Marty 80578d799f Merge pull request #7882 from element-hq/feature/bma/multiAccountShareAtRoot
Keep the incoming share displayed when the current account is signed out
2026-10-12 11:19:49 +02:00
Benoit Marty 8912341439 Avoid database access on the main thread when opening the settings
`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>
2026-10-12 10:42:19 +02:00
bmarty 8100fa6555 Sync Strings from Localazy 2026-10-12 00:37:35 +00:00
Jorge Martin Espinosa f12b9c77d5 Enable global search feature flag (#7890)
Enable the global search feature by default for all variants of the app
2026-10-09 15:24:29 +00:00
Benoit Marty d29e4e5f0c Merge remote-tracking branch 'origin/develop' into feature/bma/multiAccountShareAtRoot
# Conflicts:
#	libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt
2026-10-09 17:11:58 +02:00
Benoit Marty 00dab131dd Ask for session verification before sharing with an unverified account
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>
2026-10-09 17:00:48 +02:00
Benoit Marty 5d498ee37b Merge pull request #7883 from element-hq/feature/bma/roomSelectSearchField
Use a search field to filter the rooms in the room select screen
2026-10-09 15:09:50 +02:00
Benoit Marty 85224988af Room select: address review remarks
- 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>
2026-10-09 14:34:45 +02:00
Benoit Marty 10afe7c18c Release room list entries resources even if onControllerCreated is cancelled
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>
2026-10-09 14:28:17 +02:00
Benoit Marty 9bb03f7cc4 Merge pull request #7885 from element-hq/feature/bma/selectedItemFocus
Draw the keyboard focus indicator around the whole selected item
2026-10-09 11:44:48 +02:00
Jorge Martin Espinosa d8a072755f Add edit and redaction send state support (#7840)
* 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>
2026-10-09 10:30:52 +02:00
Benoit Marty b62999d4c0 Draw the focus indicator around the whole SelectedItem
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>
2026-10-09 09:31:15 +02:00
Benoit Marty 9429bd9420 Clean up 2026-10-09 09:04:41 +02:00
Benoit Marty 2177a62ff1 Apply the room list filter requested before the controller is created
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>
2026-10-09 09:00:48 +02:00
Benoit Marty 0c6da39acb Room select: update empty search UI and behavior. 2026-10-09 09:00:47 +02:00
Benoit Marty 59bc387c7d Replace the SearchBar by a SearchField in the room select screen
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>
2026-10-09 09:00:46 +02:00
Jorge Martin Espinosa 1564de9b6e Iterate on unread counts again (#7869)
* 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>
2026-10-09 08:30:43 +02:00
ganfra 512bab05a6 Merge branch 'develop' into feature/fga/timeline-multi-forward 2026-10-08 17:38:14 +02:00
Benoit Marty 9bc7e751c4 Keep the incoming share displayed when the current account is signed out
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>
2026-10-08 09:45:57 +02:00
Benoit Marty 920e37d097 Scroll the rooms header with the search results in room select screen
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>
2026-10-08 09:41:05 +02:00
Benoit Marty 7ad1277faf Fix room list not scrollable in room select screen when the account switch is hidden
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>
2026-10-08 09:32:39 +02:00
Benoit Marty 54b1a909bc Display an error in the room select screen if the selected account has an invalid token
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>
2026-10-07 16:01:14 +02:00
Benoit Marty 3c4632ee9d Render an error in the room select screen if the rooms cannot be loaded
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>
2026-10-07 15:50:11 +02:00
Benoit Marty 6f1033ee67 RoomSelectView: improve scroll behavior
Ref: https://www.figma.com/design/kEAcfun9iSpszeUDvdKZ6b/ER-351--Multi-account-in-EX?node-id=195-40292
2026-10-07 15:50:10 +02:00
Benoit Marty 30b71cf90b Use derivedStateOf for selectedAccount too 2026-10-07 15:50:09 +02:00
Benoit Marty 3c212ae9a3 Move SessionGraphFactory back to the appnav module
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>
2026-10-07 15:50:09 +02:00
Benoit Marty 72e3a7d92b Introduce ShareDataSender so that SharePresenter is not session scoped
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>
2026-10-07 15:50:08 +02:00
Benoit Marty ed2a553736 Fix flaky test. 2026-10-07 15:50:07 +02:00
Benoit Marty 792e0adde3 Let the user switch account from the room select screen when sharing
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>
2026-10-07 15:50:06 +02:00
Benoit Marty dc031dd0e6 Move SessionData.toMatrixUser() extension to a common place, then use it across the codebase. 2026-10-07 15:50:05 +02:00
Benoit Marty def8f7f348 Merge pull request #7877 from element-hq/bugfix/bma/sessionDelegatePerClient
Fix the wrong account being signed out when the session of another account is revoked
2026-10-07 15:29:35 +02:00
Jorge Martin Espinosa a9d7e7e5ab Fix multi-line split inline code background drawing (#7875)
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.
2026-10-07 15:10:19 +02:00
Benoit Marty 6e810f2ffd Use one RustClientSessionDelegate per client, so that an auth error only affects the session it belongs to
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>
2026-10-07 14:26:47 +02:00
Jorge Martin Espinosa 3fe0e09e3c Add global search backfill worker (#7860)
* 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
2026-10-07 10:41:19 +00:00