Show every Listen error to the user
Categories
(Firefox for Android :: Contextual AI, task, P2)
Tracking
()
People
(Reporter: bclark, Unassigned)
References
(Blocks 3 open bugs)
Details
(Whiteboard: [fxdroid][android-core])
Four things can go wrong. Show each one to the user and let them get rid of it.
| Error | When it happens | Show | Player on screen? |
|---|---|---|---|
| ContentUnavailable | the page gives back no usable text | snackbar | no |
| NoOfflineVoice | the user taps Listen and the language has no usable | dialog | no |
| offline voice | |
SynthesisFailed | making the audio failed part-way through | dialog | yes
PlaybackFailed | the player failed part-way through | dialog | yes
One ticket, because all four share the same field, the same clearing action and the same strings, and
because the four messages have to read as a set.
WHY A SNACKBAR FOR ONE AND A DIALOG FOR THREE
ContentUnavailable may well work on the next article, so a glance is proportionate. The other three
are worth acknowledging: two of them interrupt audio that was playing, and the voice one is a
permanent fact about that language.
No error has an action beyond dismiss. Dismissing is not a retry: playback stays paused and the user
can press play again.
WHERE EACH SURFACE LIVES
The snackbar is in the Fenix integration, which is registered for the whole browser screen's
lifetime, so it can show one with no player present. Use a snackbar, not a Toast: snackbars are
themed, they respect the toolbar, and the player's id is already in SnackbarBehavior.dependenciesIds.
The dialog composable is in the module, and the host must compose it beside the player, not inside
it, because two of the four errors fire with no session:
setContent {
AcornTheme {
if (state.isActive && state.tabId == selectedTabId) {
ListenPlayer(state = state, onAction = store::dispatch)
}
// Not inside the condition above: two of the four errors fire with no session.
if (state.tabId == selectedTabId) {
ListenErrorDialog(error = state.error, onDismiss = { store.dispatch(ErrorDismissed) })
}
}
}
CLEAR THE ERROR AS SOON AS YOU SHOW IT
error is state and both surfaces are one-shot events, so dispatch ErrorDismissed immediately after
showing a snackbar, and when the user dismisses a dialog. Anything that leaves the field set shows it
again on the next recomposition.
THERE IS NO DESIGN FOR ANY OF THIS YET
Placeholder styling on purpose. Do not polish it, and do not review it against a mockup that does not
exist.
TECHNICAL NOTES
- Add: ui/ListenErrorDialog.kt in the module
- Update: ListenSheetIntegration.kt in Fenix for the snackbar
- Update: ListenAction.kt and ListenReducer.kt to add ErrorDismissed and its one-line branch
BLOCKED BY A FIELD OR ACTION THAT DOES NOT EXIST YET? ADD IT, DO NOT WAIT.
Give a new field a default so no existing call site changes, and give a new action a "-> this"
reducer branch whose comment names the ticket that will implement it. Use exactly the declarations
below, so that whoever adds them second finds them already right. Note on this bug what you added.
// in ListenState
val error: ListenError? = null
sealed interface ListenError {
data object NoOfflineVoice : ListenError
data object ContentUnavailable : ListenError
data object SynthesisFailed : ListenError
data object PlaybackFailed : ListenError
}
STRINGS
While the Figma designs are not final, every new user-visible string goes in the module's own
static_strings.xml, or Fenix's static_strings.xml. Once the designs are final the strings move
into strings.xml. The polish pass ticket owns that move.
ACCEPTANCE CRITERIA
- [ ] Each of the four errors appears on its own surface, per the table above.
- [ ] Each appears once and only once. A recomposition does not show it again.
- [ ] Dismissing clears the error field.
- [ ] ContentUnavailable and NoOfflineVoice show with no player behind them.
- [ ] Dismissing does not retry. Playback stays paused and play works afterwards.
- [ ] The four messages read as a set.
Updated•15 days ago
|
Description
•