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