* 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>