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