Skip to content

Integrate complementary Android TV player, reader and focus fixes - #1

Open
myasn1k wants to merge 17 commits into
daviddaco1:android-tv-dpad-supportfrom
myasn1k:codex/android-tv-shared
Open

myasn1k wants to merge 17 commits into
daviddaco1:android-tv-dpad-supportfrom
myasn1k:codex/android-tv-shared

Conversation

@myasn1k

@myasn1k myasn1k commented Sep 8, 2026 •

Copy link
Copy Markdown

Important: fully delegated to Codex

I used OpenAI Codex and delegated all of my implementation, integration, debugging, automated testing and this PR preparation to it because I do not have time to develop this myself. I do not understand the code well enough to independently review or vouch for it.

I have manually tried the app and reported problems while Codex made the changes. The flows I tried now seem to work, but that is not a production-readiness claim or a substitute for an experienced contributor's code review. I am opening this as a draft for review and collaboration, not as something that should be merged without review. This disclosure concerns my contribution; your original work remains credited to you.

Summary

Following your invitation to help with aniyomiorg#2377, this proposes merging my complementary TV work into your android-tv-dpad-support branch, rather than opening a competing upstream TV PR.

The integration preserves both histories. Merge 5414735ed has your PR head 078836194 as its first parent and my TV branch 02151998c as its second parent. The proposed head is c15fcfa0a.

  • Retain your Automatic/Always/Never TV mode, device-mode onboarding and existing focus coverage; use that same mode policy in the imported player, reader and navigation behavior.
  • Add the complementary remote-control work: player menu key ownership, seekbar navigation and control timeouts; manga reader menu navigation; per-episode/chapter download actions; explicit refresh actions and tab/content focus handling.
  • Refine the TV focus outline toward your style: a theme-colored outline with reduced fill.
  • Fix repository/store lists skipping their action buttons: keep TV list focus bounds below the toolbar and group the actions within each card.
  • Hide the touch-only Left/Right navigation guide in the reader when TV UI is enabled. Keep the existing guide behavior in mobile mode.
  • Preserve the original app name, launcher icons and application ID. No fork rebranding is included.

This is a sizeable integration (92 changed files relative to your current branch), not just the last three fixes. The imported history also includes an earlier upstream synchronization, including the existing torrent null-safety fix from aniyomiorg#2386.

Separately reviewable manga repository change

e01f1fc15 adds gzip/protobuf index.pb manga repository support, legacy JSON fallback, optional shortName metadata and regression tests. This is useful on mobile too and is not required for TV navigation. It is included in the combined test build, but should be reviewed separately and can be split out before any upstream TV merge.

Latest TV follow-ups are separate commits:

  • 6769af0b2: refine TV focus outlines.
  • 24aeb706b: restore repository/store D-pad navigation.
  • c15fcfa0a: hide the reader touch guide on TV, with regression tests.

Validation and limitations

The final local validation passed on 2026-09-08 with JDK 17:

./gradlew spotlessCheck :app:testDebugUnitTest \
  :presentation-core:testDebugUnitTest :domain:testDebugUnitTest \
  :app:assembleDebug

152 unit tests, zero failures/errors/skips. git diff --check also passes. These are debug build/unit results, not a claim of passing release CI; no CI run was present on my published branch when this PR was prepared.

Recent direct emulator checks on the combined build (Android TV API 36, x86_64, 1920x1080) included:

  • Repository actions reachable using D-pad, horizontal movement between icons, scrolling through six manga repositories, and returning to the toolbar in full-width and two-pane layouts.
  • Opening manga/anime repository deletion confirmations and cancelling them; no existing repository was deleted during these checks.
  • Reopening a manga without the Left/Right touch guide, opening reader controls with OK and closing them with Back.

I also manually tried earlier TV builds and a phone-emulator build. A complete mobile regression of this final combined branch, all base themes/tablet modes, physical TV hardware, release build/CI, and exhaustive extension/playback/dialog testing are still unverified. Please keep those as review/testing requirements. Codex inspected the code and diffs, but I have not performed an independent human code review.

Images

These are real emulator captures, not mockups. Images are hosted on a separate evidence branch in my fork so they do not add binary files to this code PR.

Device-mode onboarding retained from your TV implementation (reference capture from the emulator review, before the latest follow-up fixes):

Android TV selected during onboarding

Repository delete action reached by remote; Cancel selected in the confirmation after the focus fix:

Repository action and cancellation reachable with D-pad

Please feel free to request changes, keep only the useful parts, or ask for smaller contributions. I would appreciate your review before this is considered for the upstream PR.

myasn1k and others added 17 commits August 4, 2026 13:39
Integrate upstream 4b5b90a while preserving TV remote controls. Combine the upstream touch-seeking fix with TV timeline navigation and add position-selection regression tests.

Validation: spotlessCheck, app and presentation-core debug unit tests (67 passing), and assembleDebug. Upstream Kotlin metadata/toolchain warnings remain; release and emulator checks were not rerun for this merge.
Merge PR aniyomiorg#2377 with feat/android-tv-pr while preserving both histories. Unify the TV mode preference across remote input and shared UI, retain reader and player navigation fixes, and add mode-policy regression tests. Integration assisted by Codex.
@myasn1k
myasn1k marked this pull request as ready for review September 11, 2026 13:13
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Integrate Android TV controls, focus navigation, and repository support

✨ Enhancement 🐞 Bug fix 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Unifies TV mode across onboarding, navigation, settings, player, and reader experiences.
• Adds D-pad focus, remote seeking, download actions, refresh controls, and TV-safe menus.
• Supports protobuf manga repositories with JSON fallback and targeted regression coverage.
Diagram

graph TD
  A["Hardware and preference"] --> B["TV mode policy"] --> C["Shared UI primitives"] --> D["Home and browse"]
  C --> E["Player controls"]
  C --> F["Reader controls"]
  C --> G["Onboarding settings"]
  H["Repository indexes"] --> I["Extension API"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Split repository support from TV integration
  • ➕ Makes the protobuf index change independently reviewable and releasable
  • ➕ Reduces unrelated regression surface for the TV-focused review
  • ➕ Allows repository compatibility to merge without waiting for device testing
  • ➖ Requires branch surgery and duplicated integration validation
  • ➖ Adds coordination overhead across dependent review branches
2. Stage TV support by subsystem
  • ➕ Enables focused reviews for shared UI, player, reader, and onboarding behavior
  • ➕ Makes device-specific regressions easier to isolate and revert
  • ➖ Cross-subsystem focus policy may be temporarily inconsistent
  • ➖ Requires careful dependency ordering and repeated end-to-end testing

Recommendation: Split the protobuf repository work—and ideally the inherited torrent fix—into separate PRs, then review the TV changes in dependency-ordered slices if maintainers can absorb the coordination cost. The shared TV-mode policy should remain the foundation because it prevents player, reader, and Compose components from applying conflicting hardware rules.

Files changed (92) +2623 / -463

Enhancement (40) +1243 / -299
BrowseAnimeSourceScreen.ktRoute initial anime focus to the first entry +4/-5

Route initial anime focus to the first entry

• Replaces list-container focus injection with an optional requester passed to the initial anime item.

app/src/main/java/eu/kanade/presentation/browse/anime/BrowseAnimeSourceScreen.kt

BrowseMangaSourceScreen.ktPropagate initial manga result focus +4/-5

Propagate initial manga result focus

• Passes an optional initial focus requester through each manga source display mode.

app/src/main/java/eu/kanade/presentation/browse/manga/BrowseMangaSourceScreen.kt

MangaSourcesScreen.ktMake manga source actions remote navigable +15/-0

Make manga source actions remote navigable

• Focuses the first source option on TV and adds visible focus treatment to all dialog actions.

app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesScreen.kt

BrowseMangaSourceComfortableGrid.ktAttach focus to comfortable manga results +4/-2

Attach focus to comfortable manga results

• Accepts an initial requester and applies it to the comfortable-grid focus group.

app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceComfortableGrid.kt

BrowseMangaSourceCompactGrid.ktAttach focus to compact manga results +4/-2

Attach focus to compact manga results

• Accepts an initial requester and applies it to the compact-grid focus group.

app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceCompactGrid.kt

BrowseMangaSourceList.ktAttach focus to manga list results +4/-2

Attach focus to manga list results

• Allows source-screen controls to explicitly enter the manga result list.

app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceList.kt

AppBar.ktAllow focus styling on app-bar actions +3/-0

Allow focus styling on app-bar actions

• Adds a shared modifier parameter for direct actions and the overflow menu button.

app/src/main/java/eu/kanade/presentation/components/AppBar.kt

ItemDownloadIndicator.ktAdd TV navigation for item download actions +87/-14

Add TV navigation for item download actions

• Adds focus highlighting and right/left D-pad routing between an episode or chapter row and its download control.

app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt

AnimeEpisodeListItem.ktExpose episode downloads to the remote +22/-1

Expose episode downloads to the remote

• Adds paired focus requesters so right enters an episode's download action and left returns to the episode row.

app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt

MangaChapterListItem.ktExpose chapter downloads to the remote +11/-1

Expose chapter downloads to the remote

• Adds paired focus requesters for navigating between a chapter row and its download action.

app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt

CommonEntryItem.ktAccept focus modifiers on grid entries +5/-0

Accept focus modifiers on grid entries

• Allows compact and comfortable entry cards to receive caller-supplied focus requesters.

app/src/main/java/eu/kanade/presentation/library/components/CommonEntryItem.kt

OnboardingScreen.ktCoordinate onboarding content and acceptance focus +5/-2

Coordinate onboarding content and acceptance focus

• Shares an accept-button focus requester between the onboarding shell and theme step.

app/src/main/java/eu/kanade/presentation/more/onboarding/OnboardingScreen.kt

ThemeStep.ktRoute theme focus to onboarding acceptance +16/-1

Route theme focus to onboarding acceptance

• Directs downward D-pad movement from the theme chooser to the onboarding accept button.

app/src/main/java/eu/kanade/presentation/more/onboarding/ThemeStep.kt

SettingsMainScreen.ktInitialize settings focus and TV summaries +21/-2

Initialize settings focus and TV summaries

• Focuses the settings list on TV and presents a player summary that excludes touch-only gesture settings.

app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsMainScreen.kt

SettingsReaderScreen.ktHide touch and device-specific reader settings on TV +167/-99

Hide touch and device-specific reader settings on TV

• Filters orientation, tapping, fullscreen, cutout, E-Ink, volume-key, long-tap, and double-tap options when TV UI is active.

app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt

PlayerSettingsGesturesScreen.ktSuppress gesture settings on TV +3/-0

Suppress gesture settings on TV

• Returns no touch-gesture preferences while the shared TV UI mode is enabled.

app/src/main/java/eu/kanade/presentation/more/settings/screen/player/PlayerSettingsGesturesScreen.kt

PlayerSettingsMainScreen.ktRemove the TV gesture-settings destination +16/-3

Remove the TV gesture-settings destination

• Filters the gesture screen from player settings and provides TV-specific internal-player summaries.

app/src/main/java/eu/kanade/presentation/more/settings/screen/player/PlayerSettingsMainScreen.kt

PlayerSettingsPlayerScreen.ktFilter touch-only player preferences on TV +63/-33

Filter touch-only player preferences on TV

• Hides orientation, gestures, brightness, fullscreen, system-bar, and picture-in-picture controls that do not apply to TV playback.

app/src/main/java/eu/kanade/presentation/more/settings/screen/player/PlayerSettingsPlayerScreen.kt

SliderItem.ktSupport D-pad slider adjustment +43/-2

Support D-pad slider adjustment

• Adds TV focus treatment and left/right stepping for integer and floating-point player sliders.

app/src/main/java/eu/kanade/presentation/player/components/SliderItem.kt

TintedSliderItem.ktSupport D-pad tinted slider adjustment +19/-1

Support D-pad tinted slider adjustment

• Makes tinted integer sliders focus-visible and adjustable with left/right remote keys.

app/src/main/java/eu/kanade/presentation/player/components/TintedSliderItem.kt

BottomReaderBar.ktMake bottom reader controls TV navigable +26/-8

Make bottom reader controls TV navigable

• Adds initial focus and visible focus outlines while hiding the orientation control on TV.

app/src/main/java/eu/kanade/presentation/reader/appbars/BottomReaderBar.kt

ColorFilterPage.ktHide brightness controls on TV +10/-5

Hide brightness controls on TV

• Suppresses the touch-oriented custom brightness toggle and slider in the reader's TV settings sheet.

app/src/main/java/eu/kanade/presentation/reader/settings/ColorFilterPage.kt

GeneralSettingsPage.ktFilter touch-only reader actions in-session +16/-9

Filter touch-only reader actions in-session

• Hides fullscreen, display-cutout, and long-tap options from the reader settings sheet on TV.

app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt

ReadingModePage.ktAdapt reading-mode settings for remote use +48/-31

Adapt reading-mode settings for remote use

• Removes orientation, tap-zone, inversion, and double-tap zoom controls from TV reader settings.

app/src/main/java/eu/kanade/presentation/reader/settings/ReadingModePage.kt

TachiyomiTheme.ktProvide shared TV mode and focus ripple styling +28/-4

Provide shared TV mode and focus ripple styling

• Publishes the resolved TV state to presentation-core and applies a reduced-fill, theme-aware ripple configuration on TV.

app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt

MangaExtensionApi.ktLoad protobuf manga extension indexes +120/-1

Load protobuf manga extension indexes

• Fetches and decodes gzip-compressed protobuf indexes before falling back to legacy JSON. It maps resources, sources, warnings, compatible library versions, and absolute APK URLs.

app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt

HomeScreen.ktCoordinate navigation rail and content focus +52/-4

Coordinate navigation rail and content focus

• Initializes TV focus on the active rail item and provides explicit right/left transitions between navigation and page content.

app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt

PlayerActivity.ktDispatch TV remote actions through the player +55/-18

Dispatch TV remote actions through the player

• Maps remote and media keys to controls, seeking, playback, and episode navigation while preserving modal focus ownership. It also forces TV landscape playback and fixes reverse-landscape orientation.

app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt

TvRemoteAction.ktDefine TV player remote key policy +81/-0

Define TV player remote key policy

• Introduces centralized remote-action mapping, modal navigation exclusions, and matching key-up consumption.

app/src/main/java/eu/kanade/tachiyomi/ui/player/TvRemoteAction.kt

BottomLeftPlayerControls.ktHide mobile-only player controls on TV +11/-8

Hide mobile-only player controls on TV

• Conditionally removes lock and screen-rotation actions from the TV player control row.

app/src/main/java/eu/kanade/tachiyomi/ui/player/controls/BottomLeftPlayerControls.kt

BottomRightPlayerControls.ktHide aspect controls on TV +9/-4

Hide aspect controls on TV

• Makes aspect-ratio control visibility explicit and suppresses it for television playback.

app/src/main/java/eu/kanade/tachiyomi/ui/player/controls/BottomRightPlayerControls.kt

SeekBar.ktAdd remote timeline navigation and seeking +113/-10

Add remote timeline navigation and seeking

• Makes the timeline focusable on TV, supports stepped left/right seeking, commits on release or escape, and preserves the correct local seek position.

app/src/main/java/eu/kanade/tachiyomi/ui/player/controls/components/SeekBar.kt

ReaderActivity.ktRoute TV remote input into reader menus +39/-0

Route TV remote input into reader menus

• Uses select keys to reveal hidden controls, gives visible menus directional ownership, and lets Back dismiss controls before leaving the reader.

app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt

DisplayExtensions.ktSelect navigation-rail layouts automatically on TV +25/-10

Select navigation-rail layouts automatically on TV

• Extracts tablet-layout resolution and treats television hardware as wide UI in Automatic mode while respecting explicit overrides.

app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt

CreateMangaExtensionRepo.ktAccept protobuf manga repository links +4/-2

Accept protobuf manga repository links

• Validates both index.pb and index.min.json URLs and derives the same repository base URL from either format.

domain/src/main/java/mihon/domain/extensionrepo/manga/interactor/CreateMangaExtensionRepo.kt

PullRefresh.ktProvide an explicit TV refresh action +31/-1

Provide an explicit TV refresh action

• Disables pull gestures in TV mode and exposes a focus-highlighted refresh button instead.

presentation-core/src/main/java/tachiyomi/presentation/core/components/material/PullRefresh.kt

Surface.ktHighlight focused clickable surfaces +2/-0

Highlight focused clickable surfaces

• Applies the shared TV focus outline to clickable presentation surfaces.

presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Surface.kt

InfoScreen.ktConnect informational content to its accept action +23/-0

Connect informational content to its accept action

• Adds explicit TV focus transitions between scrollable onboarding content and the bottom acceptance button.

presentation-core/src/main/java/tachiyomi/presentation/core/screens/InfoScreen.kt

Modifier.ktStandardize TV focus outlines +28/-9

Standardize TV focus outlines

• Introduces a reusable TV-only focus highlighter and increases the theme-colored outline width while preserving mobile indications.

presentation-core/src/main/java/tachiyomi/presentation/core/util/Modifier.kt

TvUiMode.ktExpose TV mode to shared Compose components +6/-0

Expose TV mode to shared Compose components

• Adds a composition local populated by the app theme so presentation-core follows the same resolved mode policy.

presentation-core/src/main/java/tachiyomi/presentation/core/util/TvUiMode.kt

Bug fix (26) +475 / -137
BrowseAnimeSourceComfortableGrid.ktFocus the first comfortable-grid anime item +10/-3

Focus the first comfortable-grid anime item

• Attaches the initial focus requester directly to the first rendered anime card instead of the grid container.

app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceComfortableGrid.kt

BrowseAnimeSourceCompactGrid.ktFocus the first compact-grid anime item +10/-3

Focus the first compact-grid anime item

• Allows the first compact anime card to receive explicit focus when entering source results.

app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceCompactGrid.kt

BrowseAnimeSourceList.ktFocus the first anime list item +10/-3

Focus the first anime list item

• Moves initial focus from the lazy list container to its first available anime row.

app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceList.kt

MangaExtensionsScreen.ktFocus the TV extension trust action +15/-1

Focus the TV extension trust action

• Automatically focuses the confirmation action when an untrusted-extension dialog opens in TV mode.

app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionsScreen.kt

TabbedDialog.ktFocus the first tab in TV dialogs +15/-0

Focus the first tab in TV dialogs

• Requests focus on the first tab when a tabbed adaptive dialog opens in TV mode.

app/src/main/java/eu/kanade/presentation/components/TabbedDialog.kt

TabbedScreen.ktIsolate TV tab content and focus +61/-15

Isolate TV tab content and focus

• Uses a dedicated saved TV page instead of a horizontal pager, preventing adjacent pages from entering D-pad focus search while preserving per-page state.

app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt

StorageStep.ktRequest usable storage access on TV +54/-5

Request usable storage access on TV

• Requests all-files access where required, creates the default TV folder, and prevents onboarding completion until storage is accessible.

app/src/main/java/eu/kanade/presentation/more/onboarding/StorageStep.kt

AnimeExtensionStoresContent.ktKeep TV store actions within focus bounds +7/-4

Keep TV store actions within focus bounds

• Moves TV list padding outside the lazy content, adds stable keys, and groups each store card's actions.

app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/anime/AnimeExtensionStoresContent.kt

MangaExtensionReposContent.ktKeep TV repository actions within focus bounds +7/-4

Keep TV repository actions within focus bounds

• Prevents repository cards from overlapping toolbar focus and groups actions within each keyed card.

app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/manga/MangaExtensionReposContent.kt

ExposedTextDropDownMenu.ktStabilize TV dropdown focus navigation +22/-0

Stabilize TV dropdown focus navigation

• Adds focus highlighting and explicitly moves focus vertically when a closed dropdown receives D-pad input.

app/src/main/java/eu/kanade/presentation/player/components/ExposedTextDropDownMenu.kt

PlayerSheet.ktGive TV player sheets modal focus ownership +34/-13

Give TV player sheets modal focus ownership

• Groups and initializes sheet focus, prevents touch event propagation, and disables drag behavior on TV.

app/src/main/java/eu/kanade/presentation/player/components/PlayerSheet.kt

ReaderAppBars.ktOwn and initialize reader menu focus +38/-4

Own and initialize reader menu focus

• Groups top-bar controls, makes title and actions focus-visible, and focuses the first bottom action when the TV menu opens.

app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderAppBars.kt

BrowseTab.ktUse stable TV browse-page selection +14/-2

Use stable TV browse-page selection

• Maintains a separate saved page for TV tab switching while retaining pager navigation on touch devices.

app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt

BrowseAnimeSourceScreen.ktRoute anime filters into the first result +9/-6

Route anime filters into the first result

• Directs downward focus from listing chips to the first anime item and avoids touch scrolling behavior on TV.

app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt

BrowseMangaSourceScreen.ktRoute manga filters into available results +17/-7

Route manga filters into available results

• Caches paging items and sends downward TV focus from listing chips into results only when entries exist.

app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreen.kt

MainActivity.ktPreserve HomeScreen focus ownership +1/-1

Preserve HomeScreen focus ownership

• Skips the generic post-navigation focus nudge on HomeScreen so its TV rail can select the intended target.

app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

AniyomiMPVView.ktAvoid green frames on Android emulators +23/-1

Avoid green frames on Android emulators

• Uses hardware-decoder copy mode on goldfish and ranchu emulators while retaining normal hardware decoding elsewhere.

app/src/main/java/eu/kanade/tachiyomi/ui/player/AniyomiMPVView.kt

MiddlePlayerControls.ktStabilize play-button focus timing +10/-7

Stabilize play-button focus timing

• Shares the play/pause requester with the parent and requests focus only while the actual button is composed.

app/src/main/java/eu/kanade/tachiyomi/ui/player/controls/MiddlePlayerControls.kt

PlayerControls.ktCoordinate TV player focus and control timeouts +45/-12

Coordinate TV player focus and control timeouts

• Resets auto-hide on remote input, pauses timeouts during seeking or modal UI, and applies TV-specific control visibility. It also commits the final seek position consistently.

app/src/main/java/eu/kanade/tachiyomi/ui/player/controls/PlayerControls.kt

PlayerPanels.ktMake player panels focus-owning modals +22/-1

Make player panels focus-owning modals

• Adds back handling, grouped focus, and automatic entry into the first panel control.

app/src/main/java/eu/kanade/tachiyomi/ui/player/controls/PlayerPanels.kt

ControlsButton.ktUse one focusable filled control button +12/-19

Use one focusable filled control button

• Replaces an overlaid click target with a single long-click-capable button, eliminating duplicate focus semantics.

app/src/main/java/eu/kanade/tachiyomi/ui/player/controls/components/ControlsButton.kt

MoreSheet.ktExecute custom player sheet buttons +1/-1

Execute custom player sheet buttons

• Connects custom filter-chip clicks to their configured player command.

app/src/main/java/eu/kanade/tachiyomi/ui/player/controls/components/sheets/MoreSheet.kt

ReaderNavigationOverlayView.ktHide touch navigation guides on TV +23/-1

Hide touch navigation guides on TV

• Suppresses and cancels the touch-region overlay whenever TV UI is enabled while retaining existing handheld behavior.

app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderNavigationOverlayView.kt

ExtensionRepoDto.ktMake repository short names optional +1/-1

Make repository short names optional

• Defaults missing shortName metadata to null for compatibility with repository manifests that omit it.

domain/src/main/java/mihon/domain/extensionrepo/service/ExtensionRepoDto.kt

AdaptiveSheet.ktPrevent adaptive sheets from stealing mobile focus +13/-22

Prevent adaptive sheets from stealing mobile focus

• Requests initial sheet focus only on TV and replaces focusable click blockers with pointer-only tap handling.

presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt

TorrentUtils.ktHandle missing torrent sizes safely +1/-1

Handle missing torrent sizes safely

• Uses -1 when torrent metadata omits its size instead of throwing a null-pointer exception.

source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/torrentutils/TorrentUtils.kt

Refactor (4) +47 / -24
DeviceModeStep.ktUse resolved TV mode during onboarding +1/-12

Use resolved TV mode during onboarding

• Derives the selected device mode from the shared TV policy rather than maintaining a separate detected state.

app/src/main/java/eu/kanade/presentation/more/onboarding/DeviceModeStep.kt

AppThemePreferenceWidget.ktExpose theme-list focus customization +4/-0

Expose theme-list focus customization

• Propagates a caller modifier to the horizontal theme list for explicit onboarding focus routing.

app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt

WindowSize.ktReactively resolve the TV UI policy +7/-8

Reactively resolve the TV UI policy

• Observes TV preference changes and delegates Automatic, Always, and Never resolution to the shared policy helper.

app/src/main/java/eu/kanade/presentation/util/WindowSize.kt

TvUtils.ktCentralize TV detection and mode resolution +35/-4

Centralize TV detection and mode resolution

• Separates strict television detection from storage-box heuristics and exposes one preference-aware TV UI policy.

app/src/main/java/eu/kanade/tachiyomi/util/system/TvUtils.kt

Tests (19) +851 / -0
TvDownloadNavigationTest.ktTest TV download focus shortcuts +37/-0

Test TV download focus shortcuts

• Covers rightward entry into download actions, leftward return, and non-TV or key-up exclusions.

app/src/test/java/eu/kanade/presentation/components/TvDownloadNavigationTest.kt

TvStorageStepTest.ktTest TV storage readiness +41/-0

Test TV storage readiness

• Verifies that TV onboarding requires both a configured folder and storage access without changing handheld SAF behavior.

app/src/test/java/eu/kanade/presentation/more/onboarding/TvStorageStepTest.kt

TvReaderSettingsVisibilityTest.ktTest TV reader preference filtering +20/-0

Test TV reader preference filtering

• Confirms E-Ink and volume-key settings remain on handhelds but are hidden on TV.

app/src/test/java/eu/kanade/presentation/more/settings/screen/TvReaderSettingsVisibilityTest.kt

TvSliderInputTest.ktTest remote slider stepping +33/-0

Test remote slider stepping

• Covers horizontal key mapping, vertical focus escape, discrete endpoint spacing, and continuous increments.

app/src/test/java/eu/kanade/presentation/player/components/TvSliderInputTest.kt

MangaExtensionProtobufIndexTest.ktTest protobuf extension-index decoding +59/-0

Test protobuf extension-index decoding

• Builds a compressed protobuf fixture and verifies extension resources, source metadata, NSFW mapping, and absolute APK URLs.

app/src/test/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionProtobufIndexTest.kt

TvManifestTest.ktTest handheld and TV manifest compatibility +50/-0

Test handheld and TV manifest compatibility

• Checks both launcher categories, optional Leanback and touchscreen features, and the TV banner resource.

app/src/test/java/eu/kanade/tachiyomi/tv/TvManifestTest.kt

TvHomeFocusTest.ktTest HomeScreen focus exits +42/-0

Test HomeScreen focus exits

• Verifies leftward content exits target an available navigation rail while other directions retain spatial focus.

app/src/test/java/eu/kanade/tachiyomi/ui/home/TvHomeFocusTest.kt

AniyomiMPVViewTest.ktTest emulator decoder selection +27/-0

Test emulator decoder selection

• Confirms copy mode on ranchu and goldfish, normal hardware decoding on devices, and respected decoder disablement.

app/src/test/java/eu/kanade/tachiyomi/ui/player/AniyomiMPVViewTest.kt

TvRemoteInputTest.ktTest TV player remote ownership +126/-0

Test TV player remote ownership

• Covers hidden and visible controls, modal overlays, media buttons, channel keys, seeking, Back behavior, and key-up consumption.

app/src/test/java/eu/kanade/tachiyomi/ui/player/TvRemoteInputTest.kt

PlayerControlsTest.ktTest player timeout and TV visibility policy +68/-0

Test player timeout and TV visibility policy

• Verifies auto-hide conditions, remote timeout resets, modal retention, and TV aspect-control suppression.

app/src/test/java/eu/kanade/tachiyomi/ui/player/controls/PlayerControlsTest.kt

SeekBarPositionTest.ktTest seek-position precedence +31/-0

Test seek-position precedence

• Confirms gesture, local slider or remote, and playback positions are selected in the intended order.

app/src/test/java/eu/kanade/tachiyomi/ui/player/controls/components/SeekBarPositionTest.kt

TvSeekBarInputTest.ktTest TV timeline input behavior +48/-0

Test TV timeline input behavior

• Covers initial and repeated seek distances, vertical escape, select-key suppression, and release-time commits.

app/src/test/java/eu/kanade/tachiyomi/ui/player/controls/components/TvSeekBarInputTest.kt

ReaderNavigationOverlayTest.ktTest reader guide visibility policy +39/-0

Test reader guide visibility policy

• Verifies TV always hides touch regions while handheld startup and navigation previews retain existing behavior.

app/src/test/java/eu/kanade/tachiyomi/ui/reader/ReaderNavigationOverlayTest.kt

TvReaderInputTest.ktTest TV reader key routing +37/-0

Test TV reader key routing

• Covers menu activation keys, viewer-owned directional input, and visible-menu navigation ownership.

app/src/test/java/eu/kanade/tachiyomi/ui/reader/TvReaderInputTest.kt

DisplayExtensionsTest.ktTest TV adaptive-layout selection +46/-0

Test TV adaptive-layout selection

• Verifies Automatic mode selects rail layouts on TV and explicit Never mode remains an override.

app/src/test/java/eu/kanade/tachiyomi/util/system/DisplayExtensionsTest.kt

TvUiModeTest.ktTest shared TV mode resolution +34/-0

Test shared TV mode resolution

• Covers Automatic hardware detection, Leanback fallback, forced TV mode, and explicit disablement.

app/src/test/java/eu/kanade/tachiyomi/util/system/TvUiModeTest.kt

TvUtilsTest.ktTest strict television detection +24/-0

Test strict television detection

• Verifies UI mode, Leanback capability, and ordinary touch-device classification.

app/src/test/java/eu/kanade/tachiyomi/util/system/TvUtilsTest.kt

CreateMangaExtensionRepoTest.ktTest manga repository URL compatibility +65/-0

Test manga repository URL compatibility

• Covers protobuf and JSON links, invalid URL rejection, repository persistence, and omitted short-name metadata.

domain/src/test/java/mihon/domain/extensionrepo/manga/interactor/CreateMangaExtensionRepoTest.kt

TvPullRefreshTest.ktTest TV refresh gesture replacement +24/-0

Test TV refresh gesture replacement

• Verifies pull-to-refresh remains available on handhelds, is replaced on TV, and respects disabled state.

presentation-core/src/test/java/tachiyomi/presentation/core/components/material/TvPullRefreshTest.kt

Documentation (1) +2 / -0
strings.xmlAdd TV-specific player setting summaries +2/-0

Add TV-specific player setting summaries

• Adds summaries that omit gesture and orientation categories hidden from television users.

i18n-aniyomi/src/commonMain/moko-resources/base/strings.xml

Other (2) +5 / -3
AndroidManifest.xmlClarify dual handheld and TV compatibility +3/-3

Clarify dual handheld and TV compatibility

• Reorders the optional Leanback and touchscreen declarations and documents that one APK supports both device classes.

app/src/main/AndroidManifest.xml

build.gradle.ktsEnable presentation-core unit testing +2/-0

Enable presentation-core unit testing

• Adds the shared test dependency bundle required by TV presentation regression tests.

presentation-core/build.gradle.kts

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (10) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. TV category shortcuts open anime tab 🐞 Bug ≡ Correctness
Description
TabbedScreen derives the rendered TV tab exclusively from tvPage, leaving the supplied
PagerState independent and ignoring its updates. Because CategoriesTab does not supply a
television page state and showMangaCategory() only calls state.scrollToPage(1), its TV shortcut
continues to render the default anime categories instead of manga.
Code

app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt[R60-61]

+    val isTelevision = LocalContext.current.isTvUiEnabled()
+    val selectedPage = if (isTelevision) tvPage.value else state.currentPage
Evidence
The new TV branch selects and renders exclusively from tvPage, while the existing category
navigation channel changes only the separate pager state through scrollToPage(1). Unlike
BrowseTab, CategoriesTab does not pass a television page state, so its programmatic request
cannot affect the tab rendered in TV mode.

app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt[45-69]
app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoriesTab.kt[46-74]
app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt[89-111]
app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt[57-69]
app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt[121-165]

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

## Issue description
TV rendering uses a separate `tvPage` state and ignores programmatic tab updates made through the supplied `PagerState`. Since `CategoriesTab.showMangaCategory()` updates only the pager state, the manga-category shortcut does not display tab 1 in TV mode.

## Fix Focus Areas
- app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt[50-69]
- app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt[144-155]
- app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoriesTab.kt[64-74]

## Recommended Fix
Use one authoritative, externally controllable selected-page state for both television and mobile rendering. Alternatively, synchronize `tvPage` whenever the supplied pager state changes and update `CategoriesTab` to provide and mutate that same television page state when invoking the manga-category shortcut; verify that `CategoriesTab.showMangaCategory()` displays tab 1 in TV mode.

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


2. Right-to-left download navigation breaks 🐞 Bug ≡ Correctness
Description
focusDownloadOnTvRight and downloadFocusTarget hard-code right to enter the download action and
left to return to the row. In a right-to-left layout the row places that trailing action on the
visual left, so remote movement is forced opposite to the displayed direction for every episode and
chapter.
Code

app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt[R52-55]

+): Boolean = isTelevision &&
+    itemFocused &&
+    action == KeyEvent.ACTION_DOWN &&
+    keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
Evidence
Both key predicates use fixed physical key codes, and the new modifiers are attached to all episode
and chapter rows. The manifest explicitly enables right-to-left layout mirroring, under which the
trailing download indicator is displayed on the opposite side.

app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt[47-106]
app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt[111-123]
app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt[198-207]
app/src/main/AndroidManifest.xml[51-59]

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

## Issue description
Episode and chapter download focus navigation assumes a left-to-right layout even though the application supports right-to-left layouts.

## Fix Focus Areas
- app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt[47-63]
- app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt[65-106]

## Recommended Fix
Read `LocalLayoutDirection` and map entry and return key directions accordingly, using right-to-enter and left-to-return only for left-to-right layouts and reversing them for right-to-left layouts.

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


3. TV remote arrows can change episodes 🐞 Bug ≡ Correctness
Description
PlayerActivity.onKeyDown() maps SeekBackward and SeekForward to handleLeftDoubleTap() and
handleRightDoubleTap() instead of the dedicated seek methods. Those handlers honor persisted
double-tap gesture preferences, so a TV user whose prior setting is Switch, PlayPause, Custom, or
None gets that action when pressing D-pad Left or Right while controls are hidden.
Code

app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt[R879-880]

+                    TvRemoteAction.SeekBackward -> viewModel.handleLeftDoubleTap()
+                    TvRemoteAction.SeekForward -> viewModel.handleRightDoubleTap()
Evidence
The new mapping explicitly calls gesture-configurable handlers, while the view model shows that
those handlers execute several non-seek actions depending on stored preferences; the direct seek
methods already exist separately.

app/src/main/java/eu/kanade/tachiyomi/ui/player/TvRemoteAction.kt[41-42]
app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt[865-898]
app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerViewModel.kt[1002-1018]
app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerViewModel.kt[1051-1064]
app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerViewModel.kt[1081-1094]

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

## Issue description
TV D-pad Left and Right are classified as seek actions but are dispatched through configurable touch double-tap handlers. Persisted gesture preferences can therefore pause playback, change episodes, run a custom command, or do nothing instead of seeking.

## Fix Focus Areas
- app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt[879-880]

## Recommended Fix
Dispatch `TvRemoteAction.SeekBackward` to `viewModel.leftSeek()` and `TvRemoteAction.SeekForward` to `viewModel.rightSeek()`. Keep the gesture-preference handlers exclusively for touch gesture paths.

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



Remediation recommended

4. Unknown protobuf hides legacy index 🐞 Bug ☼ Reliability
Description
decodeMangaExtensionIndex converts a missing protobuf extension-list field into a successful empty
list rather than the null fallback signal used by getExtensions. When a repository serves a valid
but unsupported protobuf envelope alongside a working JSON index, all of its extensions disappear
without attempting the legacy index.
Code

app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[R179-182]

+        .extensionList
+        ?.extensions
+        ?.toProtobufExtensions(repoUrl)
+        .orEmpty()
Evidence
getExtensions requests JSON only when the protobuf helper returns null, but .orEmpty() makes an
absent field return a non-null empty list. The protobuf model defaults field 101 to null, so unknown
or incompatible envelopes take this path.

app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[57-75]
app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[172-182]
app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[213-223]

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

## Issue description
A protobuf document without the expected extension-list field is treated as an authoritative empty repository and suppresses legacy JSON fallback.

## Fix Focus Areas
- app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[57-75]
- app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[172-182]

## Recommended Fix
Return `null` or throw a parse exception when the expected protobuf extension-list field is absent, while preserving an explicitly present empty list as a valid empty repository.

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


5. Repository gzip can exhaust app memory 🐞 Bug ⛨ Security
Description
decodeMangaExtensionIndex expands the complete gzip stream with unbounded readBytes() after the
compressed response has already been materialized in memory. Refreshing any configured repository
can therefore exhaust memory or CPU when its endpoint returns a highly expanding gzip payload.
Code

app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[177]

+    val decoded = GZIPInputStream(ByteArrayInputStream(compressed)).use { it.readBytes() }
Evidence
The response is first copied into a compressed byte array and then fully expanded without a bound.
Repository creation accepts arbitrary HTTPS index hosts, and every configured repository is fetched
concurrently by findExtensions.

app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[48-69]
app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[172-178]
domain/src/main/java/mihon/domain/extensionrepo/manga/interactor/CreateMangaExtensionRepo.kt[15-26]

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 new repository parser fully expands arbitrary gzip responses without enforcing a decompressed-size limit.

## Fix Focus Areas
- app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[63-69]
- app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[172-178]

## Recommended Fix
Stream decompression through a bounded reader, reject indexes exceeding a conservative maximum compressed or decompressed size, and allow the existing JSON fallback to handle that rejection.

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


6. Anime result focus can escape the list 🐞 Bug ≡ Correctness
Description
The anime comfortable grid, compact grid, and list remove their collection-level focusGroup while
adding only a requester to the first result item. On TV this allows spatial focus search to select
controls outside the result collection before all nearer result children, unlike the equivalent
manga result layouts that retain grouping.
Code

app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceComfortableGrid.kt[35]

-        modifier = modifier.focusGroup(),
Evidence
The PR explicitly deletes focusGroup() from all three anime collections, while their result items
remain independently focusable. The corresponding manga grid and list implementations still wrap
their collections in focusGroup(), confirming that grouping remains part of the intended TV
traversal model.

app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceComfortableGrid.kt[35-58]
app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceCompactGrid.kt[35-58]
app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceList.kt[41-62]
app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceComfortableGrid.kt[36-55]

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

## Issue description
All anime source result layouts lose collection-level focus grouping while their manga equivalents retain it.

## Fix Focus Areas
- app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceComfortableGrid.kt[35-55]
- app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceCompactGrid.kt[35-55]
- app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceList.kt[41-59]

## Recommended Fix
Restore `focusGroup()` on each anime result collection while retaining the new first-item requester used as the filter-chip destination.

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


View review recommended (4)
7. Empty anime results trap remote focus 🐞 Bug ≡ Correctness
Description
Each anime listing chip assigns its Down destination to initialItemFocusRequester, although that
requester is attached only to result item index zero. During loading or with an empty result set,
pressing Down targets an unattached requester instead of following normal focus traversal; the
parallel manga implementation avoids this by requiring TV mode and itemCount > 0.
Code

app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt[R163-165]

                        FilterChip(
+                            modifier = Modifier.focusProperties { down = initialItemFocusRequester },
                            selected = state.listing == Listing.Popular,
Evidence
All three anime chip modifiers unconditionally point Down at initialItemFocusRequester, while the
result grid binds that requester only to the item at index zero. The parallel manga source screen
demonstrates the intended guard by assigning the explicit destination only in TV mode when the
paging list has at least one item.

app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt[134-165]
app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt[182-205]
app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceComfortableGrid.kt[47-55]
app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreen.kt[157-167]
app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt[132-136]
app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt[156-205]
app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceComfortableGrid.kt[47-58]
app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreen.kt[157-212]

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

## Issue description
Anime filter chips unconditionally direct D-pad Down to `initialItemFocusRequester`, which is attached only to the first result item. During loading or when there are no results, that destination is unbound and focus cannot reliably leave the filter row through normal traversal.

## Fix Focus Areas
- app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt[134-165]
- app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt[182-205]
- app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceComfortableGrid.kt[47-58]

## Recommended Fix
Collect the anime paging items where the filter chips are composed, then apply the explicit Down focus requester only when TV UI is active and `itemCount > 0`, matching the manga source implementation. Apply this guard consistently to every anime filter chip.

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


8. Some TV users cannot finish onboarding 🐞 Bug ≡ Correctness
Description
isTvStorageReady() marks TV storage incomplete when storagePref is set but all-files access is
absent, while StorageStep hides the sole permission-settings button once that preference is set.
Reopening onboarding after the permission is missing or revoked therefore reaches a
configured-but-incomplete state with no corrective action.
Code

app/src/main/java/eu/kanade/presentation/more/onboarding/StorageStep.kt[R173-178]

+internal fun isTvStorageReady(
+    isTvBox: Boolean,
+    storagePreferenceSet: Boolean,
+    hasExternalStorageManager: Boolean,
+): Boolean {
+    return storagePreferenceSet && (!isTvBox || hasExternalStorageManager)
Evidence
The TV completion predicate requires both a configured storage preference and storage-manager
access, but lines 100-122 render the sole permission launcher only when !storagePref.isSet().
Because users can reopen onboarding from advanced settings after permission revocation, the
configured-but-revoked state is reachable and cannot be resolved through this step.

app/src/main/java/eu/kanade/presentation/more/onboarding/StorageStep.kt[77-84]
app/src/main/java/eu/kanade/presentation/more/onboarding/StorageStep.kt[100-122]
app/src/main/java/eu/kanade/presentation/more/onboarding/StorageStep.kt[160-178]
core/common/src/main/java/tachiyomi/core/common/preference/AndroidPreference.kt[60-65]
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt[120-127]

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 TV completion predicate requires all-files access even when the storage preference is already configured, but the permission launcher is hidden once that preference is set. If the permission is missing or revoked, reopening onboarding leaves the storage step incomplete without an available corrective action.

## Fix Focus Areas
- app/src/main/java/eu/kanade/presentation/more/onboarding/StorageStep.kt[100-122]
- app/src/main/java/eu/kanade/presentation/more/onboarding/StorageStep.kt[160-178]

## Recommended Fix
Render the storage recovery action whenever either the preference is unset or required all-files access is missing. Reuse the existing permission flow, request access before validating or creating the configured directory, and re-check completion when the user returns; alternatively, do not mark the step incomplete when an existing storage selection remains valid.

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


9. Disabled download controls block navigation 🐞 Bug ≡ Correctness
Description
focusDownloadOnTvRight() consumes D-pad Right after requesting the download focus target without
checking whether that indicator is enabled or has a download callback. Episode and chapter rows
attach this handler even while multi-selection disables downloads, so focus is directed to an
unavailable control rather than continuing normal navigation.
Code

app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt[R72-80]

+            if (shouldFocusDownloadOnTvRight(
+                    isTelevision = isTelevision,
+                    itemFocused = itemFocused.value,
+                    keyCode = event.nativeKeyEvent.keyCode,
+                    action = event.nativeKeyEvent.action,
+                )
+            ) {
+                downloadFocusRequester.requestFocus()
+                true
Evidence
The new handler has no enabled/actionable predicate and always consumes Right after requesting
focus, while the entry screens intentionally disable indicators during selection and the indicator
implements that state with a disabled clickable.

app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt[47-105]
app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt[133-161]
app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt[111-115]
app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt[1108-1134]
app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt[809-832]
app/src/main/java/eu/kanade/presentation/entries/anime/components/EpisodeDownloadIndicator.kt[84-108]

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

## Issue description
TV D-pad Right is always intercepted by a focused entry row and sent to its download requester. The download indicator can be disabled during multi-selection or when no download callback is available, so this intercept prevents useful focus movement without exposing a working action.

## Fix Focus Areas
- app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt[47-105]
- app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt[133-161]
- app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt[111-115]

## Recommended Fix
Pass download availability into the TV focus modifier and consume Right only when the download indicator is enabled and actionable. Otherwise return false and allow Compose focus navigation to choose the next target.

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


10. Media remotes let controls time out 🐞 Bug ☼ Reliability
Description
The new timeout reset changes local Compose state only from onPreviewKeyEvent, but media-session
callbacks invoke the player view model directly without delivering that event. Skip and playback
commands received through the active media session therefore leave an already-running
visible-controls timeout unchanged.
Code

app/src/main/java/eu/kanade/tachiyomi/ui/player/controls/PlayerControls.kt[R176-179]

+                    .onPreviewKeyEvent { event ->
+                        if (shouldResetTvControlsAutoHide(isTelevision, event.nativeKeyEvent.action)) {
+                            resetControls = !resetControls
+                        }
Evidence
The timeout effect is restarted by resetControls, which the added handler toggles only for Compose
key input; active media-session callbacks call the view model directly and do not change that state.

app/src/main/java/eu/kanade/tachiyomi/ui/player/controls/PlayerControls.kt[140-152]
app/src/main/java/eu/kanade/tachiyomi/ui/player/controls/PlayerControls.kt[173-180]
app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt[945-980]
app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt[982-1014]

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 TV controls timeout is reset only by Compose key events, but media-session controls bypass Compose and call the view model directly. Remote media interaction can therefore continue while the existing auto-hide delay expires.

## Fix Focus Areas
- app/src/main/java/eu/kanade/tachiyomi/ui/player/controls/PlayerControls.kt[140-180]
- app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt[945-1014]

## Recommended Fix
Move the auto-hide reset into a shared view-model event or timeout generation state, and invoke it from both Compose input/button paths and each media-session callback that performs a player action.

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


Grey Divider

Context sources
Review mode: 🧠 Deep: This is a broad, high-density integration spanning player, reader, focus/navigation, onboarding, extension parsing, shared UI, and manifest behavior, with 92 files and 310 independent hunks creating many plausible independent defects.

Grey Divider

Tip of the day
💡 Did you know, you can switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +60 to +61
val isTelevision = LocalContext.current.isTvUiEnabled()
val selectedPage = if (isTelevision) tvPage.value else state.currentPage

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Tv category shortcuts open anime tab 🐞 Bug ≡ Correctness

TabbedScreen derives the rendered TV tab exclusively from tvPage, leaving the supplied
PagerState independent and ignoring its updates. Because CategoriesTab does not supply a
television page state and showMangaCategory() only calls state.scrollToPage(1), its TV shortcut
continues to render the default anime categories instead of manga.
Agent Prompt
## Issue description
TV rendering uses a separate `tvPage` state and ignores programmatic tab updates made through the supplied `PagerState`. Since `CategoriesTab.showMangaCategory()` updates only the pager state, the manga-category shortcut does not display tab 1 in TV mode.

## Fix Focus Areas
- app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt[50-69]
- app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt[144-155]
- app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoriesTab.kt[64-74]

## Recommended Fix
Use one authoritative, externally controllable selected-page state for both television and mobile rendering. Alternatively, synchronize `tvPage` whenever the supplied pager state changes and update `CategoriesTab` to provide and mutate that same television page state when invoking the manga-category shortcut; verify that `CategoriesTab.showMangaCategory()` displays tab 1 in TV mode.

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

Comment on lines +179 to +182
.extensionList
?.extensions
?.toProtobufExtensions(repoUrl)
.orEmpty()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. Unknown protobuf hides legacy index 🐞 Bug ☼ Reliability

decodeMangaExtensionIndex converts a missing protobuf extension-list field into a successful empty
list rather than the null fallback signal used by getExtensions. When a repository serves a valid
but unsupported protobuf envelope alongside a working JSON index, all of its extensions disappear
without attempting the legacy index.
Agent Prompt
## Issue description
A protobuf document without the expected extension-list field is treated as an authoritative empty repository and suppresses legacy JSON fallback.

## Fix Focus Areas
- app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[57-75]
- app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[172-182]

## Recommended Fix
Return `null` or throw a parse exception when the expected protobuf extension-list field is absent, while preserving an explicitly present empty list as a valid empty repository.

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

compressed: ByteArray,
repoUrl: String,
): List<MangaExtension.Available> {
val decoded = GZIPInputStream(ByteArrayInputStream(compressed)).use { it.readBytes() }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

5. Repository gzip can exhaust app memory 🐞 Bug ⛨ Security

decodeMangaExtensionIndex expands the complete gzip stream with unbounded readBytes() after the
compressed response has already been materialized in memory. Refreshing any configured repository
can therefore exhaust memory or CPU when its endpoint returns a highly expanding gzip payload.
Agent Prompt
## Issue description
The new repository parser fully expands arbitrary gzip responses without enforcing a decompressed-size limit.

## Fix Focus Areas
- app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[63-69]
- app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionApi.kt[172-178]

## Recommended Fix
Stream decompression through a bounded reader, reject indexes exceeding a conservative maximum compressed or decompressed size, and allow the existing JSON fallback to handle that rejection.

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

Comment on lines +52 to +55
): Boolean = isTelevision &&
itemFocused &&
action == KeyEvent.ACTION_DOWN &&
keyCode == KeyEvent.KEYCODE_DPAD_RIGHT

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Right-to-left download navigation breaks 🐞 Bug ≡ Correctness

focusDownloadOnTvRight and downloadFocusTarget hard-code right to enter the download action and
left to return to the row. In a right-to-left layout the row places that trailing action on the
visual left, so remote movement is forced opposite to the displayed direction for every episode and
chapter.
Agent Prompt
## Issue description
Episode and chapter download focus navigation assumes a left-to-right layout even though the application supports right-to-left layouts.

## Fix Focus Areas
- app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt[47-63]
- app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt[65-106]

## Recommended Fix
Read `LocalLayoutDirection` and map entry and return key directions accordingly, using right-to-enter and left-to-return only for left-to-right layouts and reversing them for right-to-left layouts.

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

initialItemFocusRequester: FocusRequester?,
) {
LazyVerticalGrid(
modifier = modifier.focusGroup(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

6. Anime result focus can escape the list 🐞 Bug ≡ Correctness

The anime comfortable grid, compact grid, and list remove their collection-level focusGroup while
adding only a requester to the first result item. On TV this allows spatial focus search to select
controls outside the result collection before all nearer result children, unlike the equivalent
manga result layouts that retain grouping.
Agent Prompt
## Issue description
All anime source result layouts lose collection-level focus grouping while their manga equivalents retain it.

## Fix Focus Areas
- app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceComfortableGrid.kt[35-55]
- app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceCompactGrid.kt[35-55]
- app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceList.kt[41-59]

## Recommended Fix
Restore `focusGroup()` on each anime result collection while retaining the new first-item requester used as the filter-chip destination.

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

Comment on lines 163 to 165
FilterChip(
modifier = Modifier.focusProperties { down = initialItemFocusRequester },
selected = state.listing == Listing.Popular,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

7. Empty anime results trap remote focus 🐞 Bug ≡ Correctness

Each anime listing chip assigns its Down destination to initialItemFocusRequester, although that
requester is attached only to result item index zero. During loading or with an empty result set,
pressing Down targets an unattached requester instead of following normal focus traversal; the
parallel manga implementation avoids this by requiring TV mode and itemCount > 0.
Agent Prompt
## Issue description
Anime filter chips unconditionally direct D-pad Down to `initialItemFocusRequester`, which is attached only to the first result item. During loading or when there are no results, that destination is unbound and focus cannot reliably leave the filter row through normal traversal.

## Fix Focus Areas
- app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt[134-165]
- app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt[182-205]
- app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceComfortableGrid.kt[47-58]

## Recommended Fix
Collect the anime paging items where the filter chips are composed, then apply the explicit Down focus requester only when TV UI is active and `itemCount > 0`, matching the manga source implementation. Apply this guard consistently to every anime filter chip.

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

Comment on lines +879 to +880
TvRemoteAction.SeekBackward -> viewModel.handleLeftDoubleTap()
TvRemoteAction.SeekForward -> viewModel.handleRightDoubleTap()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Tv remote arrows can change episodes 🐞 Bug ≡ Correctness

PlayerActivity.onKeyDown() maps SeekBackward and SeekForward to handleLeftDoubleTap() and
handleRightDoubleTap() instead of the dedicated seek methods. Those handlers honor persisted
double-tap gesture preferences, so a TV user whose prior setting is Switch, PlayPause, Custom, or
None gets that action when pressing D-pad Left or Right while controls are hidden.
Agent Prompt
## Issue description
TV D-pad Left and Right are classified as seek actions but are dispatched through configurable touch double-tap handlers. Persisted gesture preferences can therefore pause playback, change episodes, run a custom command, or do nothing instead of seeking.

## Fix Focus Areas
- app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt[879-880]

## Recommended Fix
Dispatch `TvRemoteAction.SeekBackward` to `viewModel.leftSeek()` and `TvRemoteAction.SeekForward` to `viewModel.rightSeek()`. Keep the gesture-preference handlers exclusively for touch gesture paths.

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

Comment on lines +173 to +178
internal fun isTvStorageReady(
isTvBox: Boolean,
storagePreferenceSet: Boolean,
hasExternalStorageManager: Boolean,
): Boolean {
return storagePreferenceSet && (!isTvBox || hasExternalStorageManager)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

8. Some tv users cannot finish onboarding 🐞 Bug ≡ Correctness

isTvStorageReady() marks TV storage incomplete when storagePref is set but all-files access is
absent, while StorageStep hides the sole permission-settings button once that preference is set.
Reopening onboarding after the permission is missing or revoked therefore reaches a
configured-but-incomplete state with no corrective action.
Agent Prompt
## Issue description
The TV completion predicate requires all-files access even when the storage preference is already configured, but the permission launcher is hidden once that preference is set. If the permission is missing or revoked, reopening onboarding leaves the storage step incomplete without an available corrective action.

## Fix Focus Areas
- app/src/main/java/eu/kanade/presentation/more/onboarding/StorageStep.kt[100-122]
- app/src/main/java/eu/kanade/presentation/more/onboarding/StorageStep.kt[160-178]

## Recommended Fix
Render the storage recovery action whenever either the preference is unset or required all-files access is missing. Reuse the existing permission flow, request access before validating or creating the configured directory, and re-check completion when the user returns; alternatively, do not mark the step incomplete when an existing storage selection remains valid.

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

Comment on lines +72 to +80
if (shouldFocusDownloadOnTvRight(
isTelevision = isTelevision,
itemFocused = itemFocused.value,
keyCode = event.nativeKeyEvent.keyCode,
action = event.nativeKeyEvent.action,
)
) {
downloadFocusRequester.requestFocus()
true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

9. Disabled download controls block navigation 🐞 Bug ≡ Correctness

focusDownloadOnTvRight() consumes D-pad Right after requesting the download focus target without
checking whether that indicator is enabled or has a download callback. Episode and chapter rows
attach this handler even while multi-selection disables downloads, so focus is directed to an
unavailable control rather than continuing normal navigation.
Agent Prompt
## Issue description
TV D-pad Right is always intercepted by a focused entry row and sent to its download requester. The download indicator can be disabled during multi-selection or when no download callback is available, so this intercept prevents useful focus movement without exposing a working action.

## Fix Focus Areas
- app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt[47-105]
- app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt[133-161]
- app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt[111-115]

## Recommended Fix
Pass download availability into the TV focus modifier and consume Right only when the download indicator is enabled and actionable. Otherwise return false and allow Compose focus navigation to choose the next target.

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

Comment on lines +176 to +179
.onPreviewKeyEvent { event ->
if (shouldResetTvControlsAutoHide(isTelevision, event.nativeKeyEvent.action)) {
resetControls = !resetControls
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

10. Media remotes let controls time out 🐞 Bug ☼ Reliability

The new timeout reset changes local Compose state only from onPreviewKeyEvent, but media-session
callbacks invoke the player view model directly without delivering that event. Skip and playback
commands received through the active media session therefore leave an already-running
visible-controls timeout unchanged.
Agent Prompt
## Issue description
The TV controls timeout is reset only by Compose key events, but media-session controls bypass Compose and call the view model directly. Remote media interaction can therefore continue while the existing auto-hide delay expires.

## Fix Focus Areas
- app/src/main/java/eu/kanade/tachiyomi/ui/player/controls/PlayerControls.kt[140-180]
- app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt[945-1014]

## Recommended Fix
Move the auto-hide reset into a shared view-model event or timeout generation state, and invoke it from both Compose input/button paths and each media-session callback that performs a player action.

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

@aoba-seragakii

aoba-seragakii commented Sep 12, 2026 •

Copy link
Copy Markdown

Hi. I saw this pr and got interested. Would it be possible for you to upload the .apk for me to try it out?

Edit: was able to build it myself but it unfortunately doesnt go past the storage step in the onboarding screen in fire tv. Anikku had fixed this issue so you guys could take a look at how it was done

@myasn1k

myasn1k commented Sep 14, 2026

Copy link
Copy Markdown
Author

hey @aoba-seragakii , could you link the commit? Thanks a lot for the feedback!

@aoba-seragakii

Copy link
Copy Markdown

hey @aoba-seragakii , could you link the commit? Thanks a lot for the feedback!

https://github.com/komikku-app/anikku/commits/b29fb99e4530a4464ab62731d25febd6ad9bae4f/domain/src/main/java/tachiyomi/domain/storage/service/StorageManager.kt this file history should have all of them
image

Also I wasn't able to grant the install apps permission in fire tv, while im able in anikku. I unfortunately couldn't find the commit though

@myasn1k

myasn1k commented Sep 30, 2026

Copy link
Copy Markdown
Author

Guys i switched to anikku as its supported and works with android tv, so i dont think i will work on this.
Sorry.

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.

3 participants