Skip to content

Android: keep the D-pad focus inside mobile dialogs - #106

Merged
keyldev merged 1 commit into
mainfrom
fix/dialog-dpad-focus
Nov 3, 2026
Merged

keyldev merged 1 commit into
mainfrom
fix/dialog-dpad-focus

Conversation

@compot-dev

Copy link
Copy Markdown
Collaborator

Arrows in a mobile dialog now move the focus only within its card (Directional, so the focus ring shows), and Tab cycles inside it. A dialog opened from a key starts on its first control; one opened by a tap only takes the focus off the page, so the soft keyboard does not pop up on its own. If the focused control disappears (the discovery sheet's "Scan again"), the next key starts again from the dialog, not from the page behind it. Closing a dialog from a key gives the focus back to where it was. Desktop dialogs are real windows and are not affected.

Checked on an Android TV 16 emulator with D-pad input only: Welcome, Add camera and the discovery sheet on top of it; desktop build and tests as in CI pass.

Fixes #102

Mobile dialogs (OverlayDialogPresenter) sit in the TopLevel's OverlayLayer
over the page, but nothing kept the keyboard focus in them. A TV remote's
XY focus navigation searches the whole TopLevel, so the arrows walked off
the sheet onto the page under the dim: on first start, Down from the
Welcome sheet landed in the Library search box and opened the keyboard.

- Arrows that reach a dialog's dim unhandled are searched within its card
  only (FindNextElement with SearchRoot) and focused as Directional, so
  the focus ring shows; at the card's edge the focus stays put. Tab cycles
  inside the dialog. Controls that use arrows themselves (a TextBox caret)
  handle them first, as before.
- A dialog opened from a key starts with its first control focused; one
  opened by a tap only takes the focus off the page (the card holds it, no
  ring, no keyboard), and the first arrow then goes to its first control.
- If the focused control goes away while a dialog is open (the discovery
  sheet's "Scan again" button is replaced by the scan), the next navigation
  key starts again from the topmost dialog's first control instead of the
  page behind it.
- When a dialog closed from a key, the focus goes back to where it was
  (posted, as the button that opened it stays disabled until its command,
  which awaits the dialog, ends).

Desktop dialogs are real windows and do not go through this path.

Checked on an Android TV 16 emulator with D-pad input only: Welcome, Add
camera and the discovery sheet opened on top of it keep the focus, the
focus returns to the toolbar Add button after closing, and "Scan again"
no longer lets the arrows reach the page. Desktop build /warnaserror and
tests as in CI: 0 warnings, all pass.

Fixes #102
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Keep Android D-pad focus inside mobile dialogs

🐞 Bug fix 🕐 20-40 Minutes

Grey Divider

AI Description

• Keep arrow and Tab navigation inside mobile dialogs, away from controls behind them.
• Set initial focus based on key or touch input to avoid opening the soft keyboard unexpectedly.
• Recover focus when dialog controls disappear and restore it after keyboard-driven dismissal.
Diagram

graph TD
  I["TopLevel input"] --> O{"Opening input?"} -->|key| F["First control"] --> N["Scoped navigation"] -->|key close| R["Previous focus"]
  O -->|tap| C["Focus holding card"] --> N
  N -->|focus lost| F
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Disable background focus while overlays are open
  • ➕ Could let default directional navigation operate without searching page controls.
  • ➖ Requires tracking and restoring background control states, particularly with stacked dialogs.
  • ➖ Does not directly address touch-sensitive initial focus or restoration after dismissal.

Recommendation: Keep focus containment in the mobile presenter. It scopes navigation to the active card without changing page control states, while explicitly handling initial focus, disappearing controls, and dismissal.

Files changed (1) +105 / -2

Bug fix (1) +105 / -2
OverlayDialogPresenter.csContain mobile dialog focus and restore it on dismissal +105/-2

Contain mobile dialog focus and restore it on dismissal

• Tracks key versus pointer input to focus the first dialog control for remote users or the card itself after a tap. Cycles Tab within the overlay, scopes unhandled arrow navigation to the card, and recovers focus when a control disappears. After keyboard-driven dismissal, posts restoration of the previously focused element if it remains visible and enabled.

src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Dialog arrows seek the video underneath 🐞 Bug ≡ Correctness
Description
KeepArrowFocusInside runs on the dialog's bubbling key route, after the recording player's
top-level tunnel handler has already handled Left and Right. When the recording player opens its
delete confirmation, those keys seek the recording instead of moving focus within the dialog.
Code

src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[R294-295]

+        dim.AddHandler(InputElement.KeyDownEvent, (object? _, KeyEventArgs e) => KeepArrowFocusInside(card, e),
+            RoutingStrategies.Bubble);
Evidence
The player registers a top-level tunnel handler and handles Left/Right by seeking; its view model
opens a confirmation dialog without leaving the page. The new dialog handler is registered on the
dim in the later bubble route, so it cannot contain keys the player has already handled.

src/OpenIPC.Viewer.App/Views/Pages/RecordingPlayerPage.axaml.cs[25-33]
src/OpenIPC.Viewer.App/Views/Pages/RecordingPlayerPage.axaml.cs[58-98]
src/OpenIPC.Viewer.App/ViewModels/RecordingPlayerPageViewModel.cs[575-584]
src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[289-295]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The recording player's top-level tunnel handler consumes arrow keys before the new dialog-local bubble handler can contain them.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/Views/Pages/RecordingPlayerPage.axaml.cs[58-98]
- src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[289-295]
## Recommended Fix
Prevent the recording player's window-level shortcuts from acting while a mobile overlay is open, so unhandled dialog arrows reach the dialog's focus handler.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Remote terminal input loses focus on open 🐞 Bug ≡ Correctness
Description
ShowAsync focuses the first focusable descendant at background priority, which is the SSH
terminal's Close button rather than its input surface. On a key-opened terminal, this overrides the
terminal content's earlier posted input focus, leaving remote keystrokes directed away from the
terminal.
Code

src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[R334-336]

+            var first = _lastInputWasKey ? FirstFocusable(card) : null;
+            if (first is not null)
+                first.Focus(NavigationMethod.Directional);
Evidence
The SSH terminal uses the mobile overlay, places a focusable Close button before the terminal, and
posts AutoFocusInput at Loaded priority. The presenter subsequently posts its first-descendant
focus at Background priority, replacing that input focus with the Close button on key-originated
opens.

src/OpenIPC.Viewer.App/Services/DialogService.cs[280-287]
src/OpenIPC.Viewer.App/Views/Dialogs/SshTerminalContent.axaml[34-47]
src/OpenIPC.Viewer.App/Views/Dialogs/SshTerminalContent.axaml.cs[203-229]
src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[329-339]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The presenter's generic first-control focus overrides the SSH terminal's own initial input focus and selects Close.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs[329-339]
- src/OpenIPC.Viewer.App/Views/Dialogs/SshTerminalContent.axaml.cs[203-229]
## Recommended Fix
Allow content with an explicit initial focus target to supply it, and avoid replacing focus already established inside that content. Keep the terminal's existing soft-keyboard preference when selecting its input surface.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can tweak Display settings with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@keyldev
keyldev merged commit ee0eee2 into main Nov 3, 2026
6 checks passed
@compot-dev
compot-dev deleted the fix/dialog-dpad-focus branch November 4, 2026 10:10
keyldev added a commit that referenced this pull request Nov 4, 2026
Follow-ups to #104, #106 and #107: CI test order, dialog focus, TV keyboard
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android / TV: dialogs do not keep the D-pad focus

2 participants