Skip to content

feat: Add Android TV D-pad navigation support with tvOptions - #2173

Open
TheNoumanDev wants to merge 156 commits into
mainfrom
android_TV_implementation
Open

TheNoumanDev wants to merge 156 commits into
mainfrom
android_TV_implementation

Conversation

@TheNoumanDev

@TheNoumanDev TheNoumanDev commented Mar 6, 2026 •

Copy link
Copy Markdown
Member

Summary

Add D-pad focus navigation system for Android TV with coordinate-based tvOptions in YAML. All TV code gated behind Device().isTV check.

Key Changes

Core TV Framework

  • tv_focus_widget.dart - Built-in D-pad navigation with grid-based focus traversal
  • tv_focus_provider.dart - Interface for host app focus integration
  • tv_focus_order.dart - Coordinate system (row, order, isRowEntryPoint)
  • tv_focus_theme.dart - Focus styling resolution (theme > provider > primary color)
  • tv_scrollbar_widget.dart - Focusable scrollbar for ListView on TV

Widget Support

  • box_wrapper.dart - TV focus for BoxController widgets (~30 widgets)
  • form_helper.dart + input_wrapper.dart - TV focus for form widgets (~10 widgets)
  • controllers.dart - TVOptionsComposite with focus styling

Layout Support

  • list_view.dart - Focusable scrollbar (scrollbarOptions) with fallback (direct) focus when the list has no focusable items
  • carousel.dart - delegateHorizontalNavigation, interceptHorizontalNav
  • tab_bar.dart + base_tab_bar.dart - TV tab navigation with tabWidget support, plus focusIndicatorColor / focusTabColor / tvRow
  • grid_view.dart - TV focus coordinates
  • bracket - TV D-pad navigation for tournaments

Theme Integration

  • theme_loader.dart - Parse Tokens.TV.* for focus styling
  • device.dart - isTV getter for Android TV detection

Focus Styling Properties under tvOptions

row, order, isRowEntryPoint, rememberRowPosition, focusGroup, edges, focusBorderColor, focusBorderWidth, focusBorderRadius, scale, elevation, opacity

Example

Button:
  label: Play
  styles:
    tvOptions:
      row: 1
      order: 0
      isRowEntryPoint: true
      scale: 1.05

Developer docs

Comprehensive developer guide: modules/ensemble/doc/tv_developer_guide.md

Non-TV Changes (this branch)

Cross-platform changes not behind the TV flag:

New properties:

  • Image & Avatar — allowRedirect to block HTTP redirects (new RedirectBlockingHttpClient, redirect-keyed cache)
  • Carousel — indicatorLeadingWidget / indicatorTrailingWidget
  • Button — autofocus: true" and "default false, so existing buttons are unchanged"; base-widget autofocus + ${widget.hasFocus} getter
  • Bracket — scale layout knob, opt-in tab borders (borderColor / borderWidth / selectedBorderColor / selectedBorderWidth), roundIndex / matchIndex template bindings

Improvements & fixes:

  • Bracket — connector length now computed from card spacing so lines connect at any depth/scale (was a fixed value), shouldRepaint returns real conditions, crash guards (_scrollToSelectedTab, _onPageChanged), default match scope-name typo march → match
  • GridView & TabBar — null-safety crash guards
  • Framework — theme-override plumbing (tokensOverrides), controller id propagation, external-navigation theming

Demo

Video: https://drive.google.com/file/d/1mZfg8hNFv9m0QkUy0S3P1C4Xkt287tfc/view?usp=sharing

To test the KPN Android TV app, download this APK and install on the Tv emulator or real device: https://dev.azure.com/kpn/_apis/resources/Containers/28001799/android_apk?itemPath=android_apk%2Fapp-kpnandroidtvstaging-release.apk

@TheNoumanDev
TheNoumanDev force-pushed the android_TV_implementation branch from 0ec7c68 to 30b28fa Compare April 6, 2026 08:46
@TheNoumanDev
TheNoumanDev force-pushed the android_TV_implementation branch from 9761dfb to 59a05bc Compare April 6, 2026 09:35
@TheNoumanDev
TheNoumanDev force-pushed the android_TV_implementation branch from 93bef64 to 7fd5f77 Compare April 6, 2026 09:43
usmanvrtx and others added 30 commits September 11, 2026 02:05
The focus-state copy in view_util carries hasFocus onto a rebuilt
controller, but it is one-directional: when the rebuild produces a new
element whose FocusNode is not actually focused, nothing clears the
copied true and the ${id.hasFocus} binding stays true permanently.

Re-assert the controller against the live focus node in both TV focus
wrapper states (initState post-frame for inherited state, didUpdateWidget
on controller swap), deferring the ModelChangeEvent dispatch to post-frame.

Also scope the view_util copy to ids registered in the current scope so
an ancestor-scope hit with the same id (a collision, not the widget being
rebuilt) cannot transfer focus onto the rebuilt widget.
feat(border): support decimal border widths and radii
feat(tab-bar): add overlay indicator option
…nctionality

- Introduced TVTooltipPopover for displaying tooltips on TV devices, allowing for focusable content.
- Implemented TVPopoverRegistry to manage open tooltip popovers and enable programmatic dismissal.
- Enhanced EnsembleUtils with dismissPopover method to close the top-most tooltip.
- Updated TooltipData to support new options for tooltip positioning, alignment, and animations.
- Added tests for tooltip behavior, including focus management, dismissal, and content rendering.
…Popover

- Updated EnsembleUtils to dismiss TV tooltips instead of popovers.
- Renamed TooltipPopoverOptions to TooltipOptions and adjusted related code.
- Introduced TVTooltip widget for TV-specific tooltip functionality.
- Modified tests to reflect changes from popover to tooltip terminology and functionality.
- Ensured tooltip animations and positioning are correctly handled in the new implementation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants