[PR #2963] feat(tv): make the D-pad focus indicator visible on Android TV #3031

Open
opened 2026-10-14 21:07:00 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/amnezia-vpn/amnezia-client/pull/2963
Author: @WkdXeqtr
Created: 8/9/2026
Status: 🔄 Open

Base: dev ← Head: feat/tv-focus-indicator


📝 Commits (3)

  • c929a1c feat(tv): make the focus indicator readable on Android TV
  • 33f4ccb fix(tv): focus the connect button when the home page opens
  • 94b9614 fix(tv): skip the outdated container icon in the focus chain

📊 Changes

23 files changed (+219 additions, -64 deletions)

View changed files

📝 client/ui/qml/Components/AdLabel.qml (+4 -5)
📝 client/ui/qml/Components/ConnectButton.qml (+3 -3)
📝 client/ui/qml/Components/ServersListView.qml (+4 -0)
📝 client/ui/qml/Controls2/BasicButtonType.qml (+7 -6)
📝 client/ui/qml/Controls2/CaptchaDialogType.qml (+0 -1)
📝 client/ui/qml/Controls2/CardType.qml (+8 -2)
📝 client/ui/qml/Controls2/CheckBoxType.qml (+6 -3)
📝 client/ui/qml/Controls2/DropDownType.qml (+6 -7)
➕ client/ui/qml/Controls2/FocusIndicatorType.qml (+36 -0)
📝 client/ui/qml/Controls2/HorizontalRadioButton.qml (+8 -2)
📝 client/ui/qml/Controls2/ImageButtonType.qml (+7 -7)
📝 client/ui/qml/Controls2/LabelWithButtonType.qml (+12 -7)
📝 client/ui/qml/Controls2/SwitcherType.qml (+23 -5)
📝 client/ui/qml/Controls2/TabButtonType.qml (+7 -4)
📝 client/ui/qml/Controls2/TabImageButtonType.qml (+4 -5)
📝 client/ui/qml/Controls2/TextAreaType.qml (+1 -1)
📝 client/ui/qml/Controls2/TextAreaWithFooterType.qml (+1 -1)
📝 client/ui/qml/Controls2/TextFieldWithHeaderType.qml (+1 -1)
📝 client/ui/qml/Controls2/VerticalRadioButton.qml (+14 -4)
📝 client/ui/qml/Modules/Style/AmneziaStyle.qml (+24 -0)

...and 3 more files

📄 Description

On Android TV the focus indicator is a 1px paleGray outline. On the light buttons that is the same colour as the button itself, so it is invisible, and on dark rows it is a hairline you cannot see from the couch. Settings rows are worse: a row with a chevron is not focusable itself (isFocusable in LabelWithButtonType), so the focus target is the 40x40 icon and nothing shows which row you are on.

#946 asked for exactly this and was closed once arrow key handling landed, but the visual side was never done. Related: #1282.

Changes:

  • new Controls2/FocusIndicatorType.qml and an AmneziaStyle.focus group holding the indicator colour, width, fill and padding
  • on a TV the indicator becomes a 3px goldenApricot outline with a faint fill of the same colour, and controls whose focus target is a small child of a wider row (settings rows, the killswitch, radio buttons) light up as a whole
  • SwitcherType also gets the padding the list rows already have, its label sits flush against the control edge and the outline would run across it

The focus chain is unchanged, Tab and the arrow keys visit the same items in the same order. TV is detected with the existing SettingsController.isOnTv(). Off a TV every control renders as before.

While testing this I noticed the app opens with nothing focused at all, so on a TV the first press of the remote only enters the focus chain and the connect button lights up on the second. Fixed here too: PageHome now starts with the focus on the connect button, the same way PageSetupWizardStart already does for TV.

One more dead stop: the outdated container icon in a server row has no action, but ImageButtonType is focusable by default, so the D-pad stopped on it on the way to the settings button. It is skipped now. This one is not limited to a TV, it also removes the stop from keyboard navigation on desktop.

Before After
subscription key row, before subscription key row, after
protocol row, before protocol row, after
light button, before light button, after
killswitch row, before killswitch row, after
server row, before server row, after
settings button in a server row, before settings button in a server row, after
connect button, before connect button, after
tab bar, before tab bar, after

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/amnezia-vpn/amnezia-client/pull/2963 **Author:** [@WkdXeqtr](https://github.com/WkdXeqtr) **Created:** 8/9/2026 **Status:** 🔄 Open **Base:** `dev` ← **Head:** `feat/tv-focus-indicator` --- ### 📝 Commits (3) - [`c929a1c`](https://github.com/amnezia-vpn/amnezia-client/commit/c929a1c098ce56b4a26e04b38105deaab99ea29b) feat(tv): make the focus indicator readable on Android TV - [`33f4ccb`](https://github.com/amnezia-vpn/amnezia-client/commit/33f4ccbdab3e1adf1241a77e08c7ad68d395e550) fix(tv): focus the connect button when the home page opens - [`94b9614`](https://github.com/amnezia-vpn/amnezia-client/commit/94b9614219ea4d96f4683f829c59e5e8afdcfbe9) fix(tv): skip the outdated container icon in the focus chain ### 📊 Changes **23 files changed** (+219 additions, -64 deletions) <details> <summary>View changed files</summary> 📝 `client/ui/qml/Components/AdLabel.qml` (+4 -5) 📝 `client/ui/qml/Components/ConnectButton.qml` (+3 -3) 📝 `client/ui/qml/Components/ServersListView.qml` (+4 -0) 📝 `client/ui/qml/Controls2/BasicButtonType.qml` (+7 -6) 📝 `client/ui/qml/Controls2/CaptchaDialogType.qml` (+0 -1) 📝 `client/ui/qml/Controls2/CardType.qml` (+8 -2) 📝 `client/ui/qml/Controls2/CheckBoxType.qml` (+6 -3) 📝 `client/ui/qml/Controls2/DropDownType.qml` (+6 -7) ➕ `client/ui/qml/Controls2/FocusIndicatorType.qml` (+36 -0) 📝 `client/ui/qml/Controls2/HorizontalRadioButton.qml` (+8 -2) 📝 `client/ui/qml/Controls2/ImageButtonType.qml` (+7 -7) 📝 `client/ui/qml/Controls2/LabelWithButtonType.qml` (+12 -7) 📝 `client/ui/qml/Controls2/SwitcherType.qml` (+23 -5) 📝 `client/ui/qml/Controls2/TabButtonType.qml` (+7 -4) 📝 `client/ui/qml/Controls2/TabImageButtonType.qml` (+4 -5) 📝 `client/ui/qml/Controls2/TextAreaType.qml` (+1 -1) 📝 `client/ui/qml/Controls2/TextAreaWithFooterType.qml` (+1 -1) 📝 `client/ui/qml/Controls2/TextFieldWithHeaderType.qml` (+1 -1) 📝 `client/ui/qml/Controls2/VerticalRadioButton.qml` (+14 -4) 📝 `client/ui/qml/Modules/Style/AmneziaStyle.qml` (+24 -0) _...and 3 more files_ </details> ### 📄 Description On Android TV the focus indicator is a 1px paleGray outline. On the light buttons that is the same colour as the button itself, so it is invisible, and on dark rows it is a hairline you cannot see from the couch. Settings rows are worse: a row with a chevron is not focusable itself (isFocusable in LabelWithButtonType), so the focus target is the 40x40 icon and nothing shows which row you are on. #946 asked for exactly this and was closed once arrow key handling landed, but the visual side was never done. Related: #1282. Changes: - new Controls2/FocusIndicatorType.qml and an AmneziaStyle.focus group holding the indicator colour, width, fill and padding - on a TV the indicator becomes a 3px goldenApricot outline with a faint fill of the same colour, and controls whose focus target is a small child of a wider row (settings rows, the killswitch, radio buttons) light up as a whole - SwitcherType also gets the padding the list rows already have, its label sits flush against the control edge and the outline would run across it The focus chain is unchanged, Tab and the arrow keys visit the same items in the same order. TV is detected with the existing SettingsController.isOnTv(). Off a TV every control renders as before. While testing this I noticed the app opens with nothing focused at all, so on a TV the first press of the remote only enters the focus chain and the connect button lights up on the second. Fixed here too: PageHome now starts with the focus on the connect button, the same way PageSetupWizardStart already does for TV. One more dead stop: the outdated container icon in a server row has no action, but ImageButtonType is focusable by default, so the D-pad stopped on it on the way to the settings button. It is skipped now. This one is not limited to a TV, it also removes the stop from keyboard navigation on desktop. | Before | After | | --- | --- | | ![subscription key row, before](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/pr_Premium_2_before.png) | ![subscription key row, after](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/pr_Premium_2_after.png) | | ![protocol row, before](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/pr_SelfHosted_4_before.png) | ![protocol row, after](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/pr_SelfHosted_4_after.png) | | ![light button, before](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/pr_SelfHosted_9_before.png) | ![light button, after](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/pr_SelfHosted_9_after.png) | | ![killswitch row, before](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/pr_SelfHosted_7_before.png) | ![killswitch row, after](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/pr_SelfHosted_7_after.png) | | ![server row, before](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/tv_row_before.png) | ![server row, after](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/tv_row_after.png) | | ![settings button in a server row, before](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/tv_gear_before.png) | ![settings button in a server row, after](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/tv_gear_after.png) | | ![connect button, before](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/tv_ring_before.png) | ![connect button, after](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/tv_ring_after.png) | | ![tab bar, before](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/tv_tab_before.png) | ![tab bar, after](https://raw.githubusercontent.com/WkdXeqtr/amnezia-client/pr-screens-tv-focus/screens/tv_tab_after.png) | --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amnezia-client#3031
No description provided.