reis-mobile 0.3.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +24 -0
  4. package/README.md +175 -168
  5. package/THIRD_PARTY_NOTICES.md +224 -4
  6. package/agents/android-architect.md +89 -0
  7. package/agents/android-performance-engineer.md +92 -0
  8. package/agents/android-test-engineer.md +97 -0
  9. package/agents/flutter-test-engineer.md +2 -0
  10. package/agents/ios-architect.md +93 -0
  11. package/agents/ios-performance-engineer.md +92 -0
  12. package/agents/ios-test-engineer.md +95 -0
  13. package/agents/lead-mobile.md +10 -1
  14. package/agents/mobile-staff-engineer.md +1 -1
  15. package/agents/rn-architect.md +94 -0
  16. package/agents/rn-performance-engineer.md +92 -0
  17. package/agents/rn-test-engineer.md +98 -0
  18. package/bin/reis-mobile.mjs +59 -17
  19. package/commands/debate.md +10 -1
  20. package/commands/debug.md +60 -0
  21. package/commands/reis-mobile.md +7 -2
  22. package/core/config/project.mjs +59 -0
  23. package/core/registry/frontmatter.mjs +15 -4
  24. package/core/registry/registry.mjs +21 -1
  25. package/core/router/intents.mjs +2 -0
  26. package/core/router/router.mjs +10 -5
  27. package/package.json +1 -1
  28. package/skills/android-adaptive/SKILL.md +304 -0
  29. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/container-behavior.md +112 -0
  30. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/get-started.md +69 -0
  31. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/index.md +81 -0
  32. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/item-behavior.md +170 -0
  33. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/container-properties.md +415 -0
  34. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/get-started.md +51 -0
  35. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/index.md +73 -0
  36. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/item-properties.md +170 -0
  37. package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/mediaquery/index.md +333 -0
  38. package/skills/android-adaptive/references/android/develop/ui/compose/tooling/debug.md +114 -0
  39. package/skills/android-adaptive/references/android/guide/navigation/navigation-3/recipes/material-listdetail.md +141 -0
  40. package/skills/android-agp-9-upgrade/SKILL.md +106 -0
  41. package/skills/android-agp-9-upgrade/references/android/build/migrate-to-built-in-kotlin.md +416 -0
  42. package/skills/android-agp-9-upgrade/references/android/build/releases/agp-9-0-0-release-notes.md +554 -0
  43. package/skills/android-agp-9-upgrade/references/buildconfig.md +54 -0
  44. package/skills/android-agp-9-upgrade/references/ksp-kapt.md +39 -0
  45. package/skills/android-agp-9-upgrade/references/paparazzi-gradle-9.md +30 -0
  46. package/skills/android-agp-9-upgrade/references/recipes.md +62 -0
  47. package/skills/android-appfunctions/SKILL.md +74 -0
  48. package/skills/android-appfunctions/references/adb-interaction-testing.md +95 -0
  49. package/skills/android-appfunctions/references/context.md +26 -0
  50. package/skills/android-appfunctions/references/feature-discovery-analysis.md +71 -0
  51. package/skills/android-appfunctions/references/implementation-configuration.md +282 -0
  52. package/skills/android-appfunctions/references/kdoc-refinement-optimization.md +75 -0
  53. package/skills/android-appfunctions/references/migrate-to-service-entry-point.md +226 -0
  54. package/skills/android-camerax/SKILL.md +135 -0
  55. package/skills/android-camerax/references/camera1-to-camerax.md +198 -0
  56. package/skills/android-camerax/references/camera2-to-camerax.md +193 -0
  57. package/skills/android-camerax/references/expert-blueprints.md +58 -0
  58. package/skills/android-camerax/references/foldables.md +83 -0
  59. package/skills/android-camerax/references/immutability.md +60 -0
  60. package/skills/android-camerax/references/low-light.md +124 -0
  61. package/skills/android-camerax/references/mlkit-spatial.md +75 -0
  62. package/skills/android-camerax/references/modern-apis.md +42 -0
  63. package/skills/android-camerax/references/testing.md +69 -0
  64. package/skills/android-camerax/references/thermals.md +85 -0
  65. package/skills/android-camerax/references/wear-os.md +76 -0
  66. package/skills/android-camerax/references/xr.md +54 -0
  67. package/skills/android-cli/SKILL.md +474 -0
  68. package/skills/android-cli/references/interact.md +113 -0
  69. package/skills/android-cli/references/journeys.md +120 -0
  70. package/skills/android-code-review/SKILL.md +58 -0
  71. package/skills/android-display-glasses-with-jetpack-compose-glimmer/SKILL.md +340 -0
  72. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/design/ui/ai-glasses/guides/interaction/inputs.md +67 -0
  73. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/access-hardware-projected-context.md +388 -0
  74. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/buttons.md +139 -0
  75. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/cards.md +148 -0
  76. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/focus.md +107 -0
  77. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/icons.md +90 -0
  78. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/text.md +69 -0
  79. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/title-chips.md +82 -0
  80. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/request-hardware-permissions.md +213 -0
  81. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/button-samples-source.md +176 -0
  82. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/button-source.md +203 -0
  83. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/card-samples-source.md +296 -0
  84. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/card-source.md +515 -0
  85. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/deptheffect-source.md +224 -0
  86. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/deptheffectlevels-source.md +105 -0
  87. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/glimmersansflextypography-source.md +270 -0
  88. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/glimmertheme-source.md +182 -0
  89. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/icon-source.md +388 -0
  90. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/iconbutton-source.md +126 -0
  91. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/iconsizes-source.md +78 -0
  92. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/list-source.md +27 -0
  93. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/listitem-source.md +272 -0
  94. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/liststate-source.md +427 -0
  95. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/material-hct-source.md +217 -0
  96. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/projectedcontext-source.md +265 -0
  97. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/stack-source.md +368 -0
  98. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/stackitemscope-source.md +440 -0
  99. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/stackstate-source.md +325 -0
  100. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/surface-source.md +1211 -0
  101. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/titlechip-samples-source.md +101 -0
  102. package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/titlechip-source.md +131 -0
  103. package/skills/android-edge-to-edge/SKILL.md +429 -0
  104. package/skills/android-engage-sdk-integration/SKILL.md +130 -0
  105. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/faq.md +439 -0
  106. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/food.md +1149 -0
  107. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/healthandfitness.md +1055 -0
  108. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/listen.md +1050 -0
  109. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/otherverticals.md +1301 -0
  110. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/read.md +910 -0
  111. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/shopping.md +1224 -0
  112. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/social.md +719 -0
  113. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/travel.md +1413 -0
  114. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/tv/continue-watching/index.md +19 -0
  115. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/tv/entitlements.md +353 -0
  116. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/tv/getting-started.md +202 -0
  117. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/tv/recommendations.md +471 -0
  118. package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/watch.md +1081 -0
  119. package/skills/android-engage-sdk-integration/references/clusters.md +154 -0
  120. package/skills/android-engage-sdk-integration/references/common.md +519 -0
  121. package/skills/android-engage-sdk-integration/references/patterns.md +527 -0
  122. package/skills/android-engage-sdk-integration/references/requests.md +260 -0
  123. package/skills/android-engage-sdk-integration/references/schemas/food.md +509 -0
  124. package/skills/android-engage-sdk-integration/references/schemas/listen.md +842 -0
  125. package/skills/android-engage-sdk-integration/references/schemas/other.md +220 -0
  126. package/skills/android-engage-sdk-integration/references/schemas/read.md +412 -0
  127. package/skills/android-engage-sdk-integration/references/schemas/shopping.md +357 -0
  128. package/skills/android-engage-sdk-integration/references/schemas/social.md +228 -0
  129. package/skills/android-engage-sdk-integration/references/schemas/travel.md +923 -0
  130. package/skills/android-engage-sdk-integration/references/schemas/tv.md +1226 -0
  131. package/skills/android-engage-sdk-integration/references/schemas/watch.md +1137 -0
  132. package/skills/android-engage-sdk-integration/references/troubleshooting.md +39 -0
  133. package/skills/android-gradle-build-debug/SKILL.md +71 -0
  134. package/skills/android-intent-security/SKILL.md +550 -0
  135. package/skills/android-leanback-to-compose-tv-migration/SKILL.md +743 -0
  136. package/skills/android-leanback-to-compose-tv-migration/references/android/training/tv/get-started/navigation.md +372 -0
  137. package/skills/android-leanback-to-compose-tv-migration/references/android/training/tv/playback/compose/browse.md +243 -0
  138. package/skills/android-leanback-to-compose-tv-migration/references/android/training/tv/playback/compose/index.md +153 -0
  139. package/skills/android-media3-cast-integration/SKILL.md +236 -0
  140. package/skills/android-media3-cast-integration/references/android/media/media3/cast/create-castplayer.md +390 -0
  141. package/skills/android-media3-cast-integration/references/android/media/media3/cast/customize-castoptions.md +93 -0
  142. package/skills/android-media3-cast-integration/references/android/media/media3/cast/index.md +21 -0
  143. package/skills/android-migrate-xml-views-to-jetpack-compose/SKILL.md +127 -0
  144. package/skills/android-migrate-xml-views-to-jetpack-compose/references/analysis-of-the-project-and-layout.md +42 -0
  145. package/skills/android-migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/designsystems/migrate-xml-theme-to-compose.md +171 -0
  146. package/skills/android-migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/compose-in-views.md +299 -0
  147. package/skills/android-migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/views-in-compose.md +286 -0
  148. package/skills/android-migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/setup-compose-dependencies-and-compiler.md +197 -0
  149. package/skills/android-migrate-xml-views-to-jetpack-compose/references/identify-optimal-xml-candidate.md +31 -0
  150. package/skills/android-migrate-xml-views-to-jetpack-compose/references/xml-layout-migration.md +86 -0
  151. package/skills/android-ml-kit-genai-prompt-api/SKILL.md +124 -0
  152. package/skills/android-ml-kit-genai-prompt-api/references/get-started.md +272 -0
  153. package/skills/android-ml-kit-genai-prompt-api/references/prefix-caching.md +146 -0
  154. package/skills/android-ml-kit-genai-prompt-api/references/prompt-design.md +55 -0
  155. package/skills/android-ml-kit-genai-prompt-api/references/prompt-optimization.md +74 -0
  156. package/skills/android-ml-kit-genai-prompt-api/references/structured-output.md +219 -0
  157. package/skills/android-ml-kit-genai-prompt-api/references/system-instructions.md +71 -0
  158. package/skills/android-navigation-3/SKILL.md +122 -0
  159. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/index.md +51 -0
  160. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/migration-guide.md +670 -0
  161. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/animations.md +147 -0
  162. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/basic.md +89 -0
  163. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/basicdsl.md +85 -0
  164. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/basicsaveable.md +90 -0
  165. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/bottomsheet.md +195 -0
  166. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/common-ui.md +200 -0
  167. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/conditional-transitions.md +182 -0
  168. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/conditional.md +230 -0
  169. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/deeplinks-custommatcher.md +176 -0
  170. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/deeplinks-staticuri.md +140 -0
  171. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/deeplinks-syntheticbackstack.md +184 -0
  172. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/dialog.md +107 -0
  173. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/lifecycle-owner.md +185 -0
  174. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/material-listdetail.md +141 -0
  175. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/material-supportingpane.md +145 -0
  176. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/modular-hilt.md +283 -0
  177. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/modular-koin.md +287 -0
  178. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/multiple-backstacks.md +468 -0
  179. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/passingarguments.md +371 -0
  180. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/results-event.md +273 -0
  181. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/results-state.md +266 -0
  182. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/scenes-listdetail.md +435 -0
  183. package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/scenes-twopane.md +244 -0
  184. package/skills/android-navigation-3/references/android/guide/navigation/type-safe-destinations.md +129 -0
  185. package/skills/android-navigation-event/SKILL.md +312 -0
  186. package/skills/android-navigation-event/references/android/guide/navigation/navigation-event/dispatcher.md +134 -0
  187. package/skills/android-navigation-event/references/android/guide/navigation/navigation-event/handle-back.md +217 -0
  188. package/skills/android-navigation-event/references/android/guide/navigation/navigation-event/index.md +83 -0
  189. package/skills/android-navigation-event/references/android/guide/navigation/navigation-event/setup.md +32 -0
  190. package/skills/android-play-billing-library-version-upgrade/SKILL.md +96 -0
  191. package/skills/android-play-billing-library-version-upgrade/references/android/google/play/billing/release-notes.md +1240 -0
  192. package/skills/android-play-billing-library-version-upgrade/references/migration-logic.md +78 -0
  193. package/skills/android-play-billing-library-version-upgrade/references/version-checklist.md +54 -0
  194. package/skills/android-play-policy-insights/SKILL.md +172 -0
  195. package/skills/android-play-policy-insights/resources/common_mandates.md +100 -0
  196. package/skills/android-play-policy-insights/resources/compliance_report_template.md +59 -0
  197. package/skills/android-play-policy-insights/resources/critic.md +86 -0
  198. package/skills/android-play-policy-insights/resources/goal_data_safety.md +212 -0
  199. package/skills/android-play-policy-insights/resources/goal_permissions_and_apis.md +459 -0
  200. package/skills/android-play-policy-insights/resources/goal_user_account.md +106 -0
  201. package/skills/android-play-policy-insights/resources/policies.json +321 -0
  202. package/skills/android-play-policy-insights/resources/scanner_config.json +843 -0
  203. package/skills/android-play-policy-insights/scripts/generate_report.py +860 -0
  204. package/skills/android-play-policy-insights/scripts/orchestrator.py +1313 -0
  205. package/skills/android-play-policy-insights/scripts/play_store_scraper.py +410 -0
  206. package/skills/android-play-policy-insights/scripts/scanner.py +261 -0
  207. package/skills/android-play-policy-insights/scripts/template_engine.py +243 -0
  208. package/skills/android-profiler/SKILL.md +60 -0
  209. package/skills/android-profiler/analysis/analysis_orchestrator.md +32 -0
  210. package/skills/android-profiler/analysis/workflows/perfetto-sql/perfetto_sql.md +28 -0
  211. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/perfetto_trace_analysis.md +121 -0
  212. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/guiding_principles.md +51 -0
  213. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/domain_hints_template.md +7 -0
  214. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_cpu.md +56 -0
  215. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_graphics.md +51 -0
  216. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_io.md +46 -0
  217. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_ipc.md +41 -0
  218. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_memory.md +55 -0
  219. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_power.md +33 -0
  220. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/per_candidate_analysis.md +172 -0
  221. package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/triage.md +94 -0
  222. package/skills/android-profiler/recording/recording_orchestrator.md +36 -0
  223. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/perfetto_trace_recording.md +148 -0
  224. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/app_jank.pftxt +43 -0
  225. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/cpu_profile.pftxt +35 -0
  226. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/java_heap_dump.pftxt +21 -0
  227. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/long_background.pftxt +37 -0
  228. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/memory_counters.pftxt +45 -0
  229. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/native_heap.pftxt +21 -0
  230. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/sched_cpu.pftxt +33 -0
  231. package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/trace_config_reference.md +102 -0
  232. package/skills/android-profiler/references/env_setup.md +15 -0
  233. package/skills/android-profiler/references/perfetto/setup.md +29 -0
  234. package/skills/android-profiler/references/perfetto/sql.md +327 -0
  235. package/skills/android-r8-analyzer/SKILL.md +72 -0
  236. package/skills/android-r8-analyzer/references/CONFIGURATION-ANALYZER.md +287 -0
  237. package/skills/android-r8-analyzer/references/CONFIGURATION.md +44 -0
  238. package/skills/android-r8-analyzer/references/KEEP-RULES-IMPACT-HIERARCHY.md +83 -0
  239. package/skills/android-r8-analyzer/references/REDUNDANT-RULES.md +222 -0
  240. package/skills/android-r8-analyzer/references/REFLECTION-GUIDE.md +139 -0
  241. package/skills/android-r8-analyzer/references/REPORT_FORMAT.md +51 -0
  242. package/skills/android-r8-analyzer/references/android/topic/performance/app-optimization/enable-app-optimization.md +252 -0
  243. package/skills/android-r8-analyzer/references/android/training/testing/other-components/ui-automator.md +312 -0
  244. package/skills/android-restore-credentials/SKILL.md +359 -0
  245. package/skills/android-restore-credentials/references/android/guide/topics/manifest/application-element.md +832 -0
  246. package/skills/android-restore-credentials/references/android/identity/passkeys/create-passkeys.md +327 -0
  247. package/skills/android-restore-credentials/references/android/identity/passkeys/sign-in-with-passkeys.md +219 -0
  248. package/skills/android-styles/SKILL.md +229 -0
  249. package/skills/android-styles/references/android/develop/ui/compose/designsystems/custom.md +459 -0
  250. package/skills/android-styles/references/android/develop/ui/compose/styles/fundamentals.md +421 -0
  251. package/skills/android-styles/references/android/develop/ui/compose/styles/state-animations.md +461 -0
  252. package/skills/android-styles/references/android/develop/ui/compose/styles/styles-vs-modifiers.md +48 -0
  253. package/skills/android-styles/references/android/develop/ui/compose/styles/theming.md +269 -0
  254. package/skills/android-verified-email/SKILL.md +432 -0
  255. package/skills/android-verified-email/references/android/identity/credential-manager/index.md +67 -0
  256. package/skills/android-verified-email/references/android/identity/digital-credentials/credential-verifier.md +166 -0
  257. package/skills/android-verified-email/references/android/identity/digital-credentials/email-verification-implementation.md +313 -0
  258. package/skills/android-verified-email/references/android/identity/digital-credentials/email-verification.md +158 -0
  259. package/skills/android-verified-email/references/android/identity/digital-credentials/index.md +112 -0
  260. package/skills/android-verified-email/references/android/identity/passkeys/create-passkeys.md +327 -0
  261. package/skills/android-verified-email/references/android/identity/sign-in/credential-manager-webview.md +84 -0
  262. package/skills/android-wear-compose-m3/SKILL.md +365 -0
  263. package/skills/android-wear-compose-m3/references/android/training/wearables/compose/migrate-to-material3.md +677 -0
  264. package/skills/android-wear-compose-m3/references/foundation/AmbientModeSample.kt.md +123 -0
  265. package/skills/android-wear-compose-m3/references/foundation/AmbientModeSample.kt.md.txt +123 -0
  266. package/skills/android-wear-compose-m3/references/foundation/CurvedWorldSample.kt.md +430 -0
  267. package/skills/android-wear-compose-m3/references/foundation/CurvedWorldSample.kt.md.txt +430 -0
  268. package/skills/android-wear-compose-m3/references/foundation/ExpandableSample.kt.md +99 -0
  269. package/skills/android-wear-compose-m3/references/foundation/ExpandableSample.kt.md.txt +99 -0
  270. package/skills/android-wear-compose-m3/references/foundation/HierarchicalFocusSample.kt.md +141 -0
  271. package/skills/android-wear-compose-m3/references/foundation/HierarchicalFocusSample.kt.md.txt +141 -0
  272. package/skills/android-wear-compose-m3/references/foundation/PagerSamples.kt.md +56 -0
  273. package/skills/android-wear-compose-m3/references/foundation/PagerSamples.kt.md.txt +56 -0
  274. package/skills/android-wear-compose-m3/references/foundation/RotarySamples.kt.md +153 -0
  275. package/skills/android-wear-compose-m3/references/foundation/RotarySamples.kt.md.txt +153 -0
  276. package/skills/android-wear-compose-m3/references/foundation/ScalingLazyColumnSample.kt.md +131 -0
  277. package/skills/android-wear-compose-m3/references/foundation/ScalingLazyColumnSample.kt.md.txt +131 -0
  278. package/skills/android-wear-compose-m3/references/foundation/SwipeToDismissBoxSample.kt.md +169 -0
  279. package/skills/android-wear-compose-m3/references/foundation/SwipeToDismissBoxSample.kt.md.txt +169 -0
  280. package/skills/android-wear-compose-m3/references/foundation/SwipeToRevealSample.kt.md +282 -0
  281. package/skills/android-wear-compose-m3/references/foundation/SwipeToRevealSample.kt.md.txt +282 -0
  282. package/skills/android-wear-compose-m3/references/foundation/TransformingLazyColumnSample.kt.md +335 -0
  283. package/skills/android-wear-compose-m3/references/foundation/TransformingLazyColumnSample.kt.md.txt +335 -0
  284. package/skills/android-wear-compose-m3/references/material3/AlertDialogSample.kt.md +377 -0
  285. package/skills/android-wear-compose-m3/references/material3/AlertDialogSample.kt.md.txt +377 -0
  286. package/skills/android-wear-compose-m3/references/material3/AnimatedTextSample.kt.md +165 -0
  287. package/skills/android-wear-compose-m3/references/material3/AnimatedTextSample.kt.md.txt +165 -0
  288. package/skills/android-wear-compose-m3/references/material3/ButtonGroupSample.kt.md +91 -0
  289. package/skills/android-wear-compose-m3/references/material3/ButtonGroupSample.kt.md.txt +91 -0
  290. package/skills/android-wear-compose-m3/references/material3/ButtonSample.kt.md +496 -0
  291. package/skills/android-wear-compose-m3/references/material3/ButtonSample.kt.md.txt +496 -0
  292. package/skills/android-wear-compose-m3/references/material3/CardSample.kt.md +470 -0
  293. package/skills/android-wear-compose-m3/references/material3/CardSample.kt.md.txt +470 -0
  294. package/skills/android-wear-compose-m3/references/material3/CheckboxButtonSample.kt.md +67 -0
  295. package/skills/android-wear-compose-m3/references/material3/CheckboxButtonSample.kt.md.txt +67 -0
  296. package/skills/android-wear-compose-m3/references/material3/ConfirmationDialogSample.kt.md +162 -0
  297. package/skills/android-wear-compose-m3/references/material3/ConfirmationDialogSample.kt.md.txt +162 -0
  298. package/skills/android-wear-compose-m3/references/material3/CurvedTextSamples.kt.md +78 -0
  299. package/skills/android-wear-compose-m3/references/material3/CurvedTextSamples.kt.md.txt +78 -0
  300. package/skills/android-wear-compose-m3/references/material3/DatePickerSample.kt.md +131 -0
  301. package/skills/android-wear-compose-m3/references/material3/DatePickerSample.kt.md.txt +131 -0
  302. package/skills/android-wear-compose-m3/references/material3/DynamicColorSchemeSample.kt.md +74 -0
  303. package/skills/android-wear-compose-m3/references/material3/DynamicColorSchemeSample.kt.md.txt +74 -0
  304. package/skills/android-wear-compose-m3/references/material3/EdgeButtonSample.kt.md +63 -0
  305. package/skills/android-wear-compose-m3/references/material3/EdgeButtonSample.kt.md.txt +63 -0
  306. package/skills/android-wear-compose-m3/references/material3/FadingExpandingLabelSample.kt.md +60 -0
  307. package/skills/android-wear-compose-m3/references/material3/FadingExpandingLabelSample.kt.md.txt +60 -0
  308. package/skills/android-wear-compose-m3/references/material3/IconButtonSample.kt.md +125 -0
  309. package/skills/android-wear-compose-m3/references/material3/IconButtonSample.kt.md.txt +125 -0
  310. package/skills/android-wear-compose-m3/references/material3/IconToggleButtonSample.kt.md +116 -0
  311. package/skills/android-wear-compose-m3/references/material3/IconToggleButtonSample.kt.md.txt +116 -0
  312. package/skills/android-wear-compose-m3/references/material3/LevelIndicatorSample.kt.md +68 -0
  313. package/skills/android-wear-compose-m3/references/material3/LevelIndicatorSample.kt.md.txt +68 -0
  314. package/skills/android-wear-compose-m3/references/material3/LinearProgressIndicatorSample.kt.md +50 -0
  315. package/skills/android-wear-compose-m3/references/material3/LinearProgressIndicatorSample.kt.md.txt +50 -0
  316. package/skills/android-wear-compose-m3/references/material3/ListHeaderSample.kt.md +177 -0
  317. package/skills/android-wear-compose-m3/references/material3/ListHeaderSample.kt.md.txt +177 -0
  318. package/skills/android-wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md +651 -0
  319. package/skills/android-wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md.txt +651 -0
  320. package/skills/android-wear-compose-m3/references/material3/OpenOnPhoneDialogSample.kt.md +57 -0
  321. package/skills/android-wear-compose-m3/references/material3/OpenOnPhoneDialogSample.kt.md.txt +57 -0
  322. package/skills/android-wear-compose-m3/references/material3/PageIndicatorSample.kt.md +110 -0
  323. package/skills/android-wear-compose-m3/references/material3/PageIndicatorSample.kt.md.txt +110 -0
  324. package/skills/android-wear-compose-m3/references/material3/PagerScaffoldSample.kt.md +204 -0
  325. package/skills/android-wear-compose-m3/references/material3/PagerScaffoldSample.kt.md.txt +204 -0
  326. package/skills/android-wear-compose-m3/references/material3/PickerGroupSample.kt.md +139 -0
  327. package/skills/android-wear-compose-m3/references/material3/PickerGroupSample.kt.md.txt +139 -0
  328. package/skills/android-wear-compose-m3/references/material3/PickerSample.kt.md +111 -0
  329. package/skills/android-wear-compose-m3/references/material3/PickerSample.kt.md.txt +111 -0
  330. package/skills/android-wear-compose-m3/references/material3/PlaceholderSample.kt.md +184 -0
  331. package/skills/android-wear-compose-m3/references/material3/PlaceholderSample.kt.md.txt +184 -0
  332. package/skills/android-wear-compose-m3/references/material3/ProgressIndicatorSample.kt.md +263 -0
  333. package/skills/android-wear-compose-m3/references/material3/ProgressIndicatorSample.kt.md.txt +263 -0
  334. package/skills/android-wear-compose-m3/references/material3/RadioButtonSample.kt.md +105 -0
  335. package/skills/android-wear-compose-m3/references/material3/RadioButtonSample.kt.md.txt +105 -0
  336. package/skills/android-wear-compose-m3/references/material3/ScaffoldSample.kt.md +146 -0
  337. package/skills/android-wear-compose-m3/references/material3/ScaffoldSample.kt.md.txt +146 -0
  338. package/skills/android-wear-compose-m3/references/material3/ScrollAwaySample.kt.md +97 -0
  339. package/skills/android-wear-compose-m3/references/material3/ScrollAwaySample.kt.md.txt +97 -0
  340. package/skills/android-wear-compose-m3/references/material3/ScrollIndicatorSample.kt.md +66 -0
  341. package/skills/android-wear-compose-m3/references/material3/ScrollIndicatorSample.kt.md.txt +66 -0
  342. package/skills/android-wear-compose-m3/references/material3/SliderSample.kt.md +86 -0
  343. package/skills/android-wear-compose-m3/references/material3/SliderSample.kt.md.txt +86 -0
  344. package/skills/android-wear-compose-m3/references/material3/StepperSample.kt.md +148 -0
  345. package/skills/android-wear-compose-m3/references/material3/StepperSample.kt.md.txt +148 -0
  346. package/skills/android-wear-compose-m3/references/material3/SurfaceTransformationSample.kt.md +166 -0
  347. package/skills/android-wear-compose-m3/references/material3/SurfaceTransformationSample.kt.md.txt +166 -0
  348. package/skills/android-wear-compose-m3/references/material3/SwipeToDismissBoxSample.kt.md +172 -0
  349. package/skills/android-wear-compose-m3/references/material3/SwipeToDismissBoxSample.kt.md.txt +172 -0
  350. package/skills/android-wear-compose-m3/references/material3/SwipeToRevealSample.kt.md +367 -0
  351. package/skills/android-wear-compose-m3/references/material3/SwipeToRevealSample.kt.md.txt +367 -0
  352. package/skills/android-wear-compose-m3/references/material3/SwitchButtonSample.kt.md +66 -0
  353. package/skills/android-wear-compose-m3/references/material3/SwitchButtonSample.kt.md.txt +66 -0
  354. package/skills/android-wear-compose-m3/references/material3/TextButtonSample.kt.md +115 -0
  355. package/skills/android-wear-compose-m3/references/material3/TextButtonSample.kt.md.txt +115 -0
  356. package/skills/android-wear-compose-m3/references/material3/TextToggleButtonSample.kt.md +71 -0
  357. package/skills/android-wear-compose-m3/references/material3/TextToggleButtonSample.kt.md.txt +71 -0
  358. package/skills/android-wear-compose-m3/references/material3/TimePickerSample.kt.md +151 -0
  359. package/skills/android-wear-compose-m3/references/material3/TimePickerSample.kt.md.txt +151 -0
  360. package/skills/android-wear-compose-m3/references/material3/TimeTextSample.kt.md +65 -0
  361. package/skills/android-wear-compose-m3/references/material3/TimeTextSample.kt.md.txt +65 -0
  362. package/skills/android-wear-compose-m3/references/material3/TransformationSpecSample.kt.md +246 -0
  363. package/skills/android-wear-compose-m3/references/material3/TransformationSpecSample.kt.md.txt +246 -0
  364. package/skills/android-wear-compose-m3/references/material3/TransformingLazyColumnNotificationsSample.kt.md +89 -0
  365. package/skills/android-wear-compose-m3/references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt +89 -0
  366. package/skills/android-wear-compose-m3/references/material3/TransformingLazyColumnSample.kt.md +309 -0
  367. package/skills/android-wear-compose-m3/references/material3/TransformingLazyColumnSample.kt.md.txt +309 -0
  368. package/skills/android-wear-compose-m3/references/navigation3/SwipeDismissableSceneStrategySample.kt.md +205 -0
  369. package/skills/android-wear-compose-m3/references/navigation3/SwipeDismissableSceneStrategySample.kt.md.txt +205 -0
  370. package/skills/{architecture-feature-first → flutter-architecture-feature-first}/SKILL.md +1 -1
  371. package/skills/{bloc → flutter-bloc}/SKILL.md +1 -1
  372. package/skills/flutter-build-debug/SKILL.md +66 -0
  373. package/skills/{code-review → flutter-code-review}/SKILL.md +1 -1
  374. package/skills/{dart-3-updates → flutter-dart-3-updates}/SKILL.md +1 -1
  375. package/skills/{dart-fix-runtime-errors → flutter-dart-fix-runtime-errors}/SKILL.md +1 -1
  376. package/skills/{dart-resolve-package-conflicts → flutter-dart-resolve-package-conflicts}/SKILL.md +1 -1
  377. package/skills/{dart-run-static-analysis → flutter-dart-run-static-analysis}/SKILL.md +1 -1
  378. package/skills/{dart-use-pattern-matching → flutter-dart-use-pattern-matching}/SKILL.md +1 -1
  379. package/skills/{effective-dart → flutter-effective-dart}/SKILL.md +1 -1
  380. package/skills/{provider → flutter-provider}/SKILL.md +1 -1
  381. package/skills/{riverpod → flutter-riverpod}/SKILL.md +1 -1
  382. package/skills/ios-cocoapods-debug/SKILL.md +65 -0
  383. package/skills/ios-code-review/SKILL.md +56 -0
  384. package/skills/ios-xcode-build-debug/SKILL.md +66 -0
  385. package/skills/{detect-mobile-stack → mobile-detect-stack}/SKILL.md +1 -1
  386. package/skills/mobile-firebase/SKILL.md +54 -0
  387. package/skills/mobile-firebase/references/android/ai-logic.md +31 -0
  388. package/skills/mobile-firebase/references/android/analytics.md +33 -0
  389. package/skills/mobile-firebase/references/android/app-check.md +36 -0
  390. package/skills/mobile-firebase/references/android/auth.md +44 -0
  391. package/skills/mobile-firebase/references/android/crashlytics.md +31 -0
  392. package/skills/mobile-firebase/references/android/firestore.md +44 -0
  393. package/skills/mobile-firebase/references/android/functions.md +33 -0
  394. package/skills/mobile-firebase/references/android/in-app-messaging.md +28 -0
  395. package/skills/mobile-firebase/references/android/messaging.md +41 -0
  396. package/skills/mobile-firebase/references/android/realtime-database.md +50 -0
  397. package/skills/mobile-firebase/references/android/remote-config.md +43 -0
  398. package/skills/mobile-firebase/references/android/sql-connect.md +31 -0
  399. package/skills/mobile-firebase/references/android/storage.md +39 -0
  400. package/skills/mobile-firebase/references/android.md +30 -0
  401. package/skills/{firebase-ai/SKILL.md → mobile-firebase/references/flutter/ai-logic.md} +2 -8
  402. package/skills/{firebase-analytics/SKILL.md → mobile-firebase/references/flutter/analytics.md} +2 -8
  403. package/skills/{firebase-app-check/SKILL.md → mobile-firebase/references/flutter/app-check.md} +2 -8
  404. package/skills/{firebase-auth/SKILL.md → mobile-firebase/references/flutter/auth.md} +2 -8
  405. package/skills/{firebase-crashlytics/SKILL.md → mobile-firebase/references/flutter/crashlytics.md} +2 -8
  406. package/skills/{firebase-cloud-firestore/SKILL.md → mobile-firebase/references/flutter/firestore.md} +2 -8
  407. package/skills/{firebase-cloud-functions/SKILL.md → mobile-firebase/references/flutter/functions.md} +2 -8
  408. package/skills/{firebase-in-app-messaging/SKILL.md → mobile-firebase/references/flutter/in-app-messaging.md} +2 -8
  409. package/skills/{firebase-messaging/SKILL.md → mobile-firebase/references/flutter/messaging.md} +2 -8
  410. package/skills/{firebase-database/SKILL.md → mobile-firebase/references/flutter/realtime-database.md} +2 -8
  411. package/skills/{firebase-remote-config/SKILL.md → mobile-firebase/references/flutter/remote-config.md} +2 -8
  412. package/skills/{flutterfire-configure/SKILL.md → mobile-firebase/references/flutter/setup-flutterfire.md} +1 -8
  413. package/skills/{firebase-data-connect/SKILL.md → mobile-firebase/references/flutter/sql-connect.md} +2 -8
  414. package/skills/{firebase-storage/SKILL.md → mobile-firebase/references/flutter/storage.md} +2 -8
  415. package/skills/mobile-firebase/references/flutter.md +36 -0
  416. package/skills/mobile-firebase/references/ios/ai-logic.md +30 -0
  417. package/skills/mobile-firebase/references/ios/analytics.md +33 -0
  418. package/skills/mobile-firebase/references/ios/app-check.md +36 -0
  419. package/skills/mobile-firebase/references/ios/auth.md +48 -0
  420. package/skills/mobile-firebase/references/ios/crashlytics.md +34 -0
  421. package/skills/mobile-firebase/references/ios/firestore.md +53 -0
  422. package/skills/mobile-firebase/references/ios/functions.md +35 -0
  423. package/skills/mobile-firebase/references/ios/in-app-messaging.md +26 -0
  424. package/skills/mobile-firebase/references/ios/messaging.md +45 -0
  425. package/skills/mobile-firebase/references/ios/realtime-database.md +49 -0
  426. package/skills/mobile-firebase/references/ios/remote-config.md +44 -0
  427. package/skills/mobile-firebase/references/ios/sql-connect.md +33 -0
  428. package/skills/mobile-firebase/references/ios/storage.md +36 -0
  429. package/skills/mobile-firebase/references/ios.md +28 -0
  430. package/skills/mobile-firebase/references/react-native/ai-logic.md +32 -0
  431. package/skills/mobile-firebase/references/react-native/analytics.md +43 -0
  432. package/skills/mobile-firebase/references/react-native/app-check.md +35 -0
  433. package/skills/mobile-firebase/references/react-native/auth.md +50 -0
  434. package/skills/mobile-firebase/references/react-native/crashlytics.md +36 -0
  435. package/skills/mobile-firebase/references/react-native/firestore.md +45 -0
  436. package/skills/mobile-firebase/references/react-native/functions.md +28 -0
  437. package/skills/mobile-firebase/references/react-native/in-app-messaging.md +27 -0
  438. package/skills/mobile-firebase/references/react-native/messaging.md +46 -0
  439. package/skills/mobile-firebase/references/react-native/realtime-database.md +46 -0
  440. package/skills/mobile-firebase/references/react-native/remote-config.md +32 -0
  441. package/skills/mobile-firebase/references/react-native/sql-connect.md +11 -0
  442. package/skills/mobile-firebase/references/react-native/storage.md +30 -0
  443. package/skills/mobile-firebase/references/react-native.md +29 -0
  444. package/skills/mobile-test/SKILL.md +63 -0
  445. package/skills/mobile-test/references/android/setup/GUIDE.md +183 -0
  446. package/skills/mobile-test/references/android/setup/references/android/develop/ui/compose/testing/common-patterns.md +181 -0
  447. package/skills/mobile-test/references/android/setup/references/android/studio/preview/compose-screenshot-testing.md +255 -0
  448. package/skills/mobile-test/references/android/setup/references/android/training/dependency-injection/hilt-testing.md +447 -0
  449. package/skills/mobile-test/references/android.md +83 -0
  450. package/skills/{dart-collect-coverage/SKILL.md → mobile-test/references/flutter/coverage.md} +2 -11
  451. package/skills/{dart-generate-test-mocks/SKILL.md → mobile-test/references/flutter/generate-mocks.md} +2 -11
  452. package/skills/{flutter-add-integration-test/SKILL.md → mobile-test/references/flutter/integration-tests.md} +2 -11
  453. package/skills/{dart-migrate-to-checks-package/SKILL.md → mobile-test/references/flutter/migrate-to-checks.md} +2 -13
  454. package/skills/{mockito/SKILL.md → mobile-test/references/flutter/mockito.md} +1 -8
  455. package/skills/{mocktail/SKILL.md → mobile-test/references/flutter/mocktail.md} +1 -8
  456. package/skills/{patrol-e2e-testing/SKILL.md → mobile-test/references/flutter/patrol.md} +1 -8
  457. package/skills/{testing/SKILL.md → mobile-test/references/flutter/testing-guidelines.md} +1 -8
  458. package/skills/{dart-add-unit-test/SKILL.md → mobile-test/references/flutter/unit-tests.md} +2 -11
  459. package/skills/{flutter-add-widget-test/SKILL.md → mobile-test/references/flutter/widget-tests.md} +2 -11
  460. package/skills/mobile-test/references/flutter.md +44 -0
  461. package/skills/mobile-test/references/ios.md +79 -0
  462. package/skills/mobile-test/references/react-native.md +62 -0
  463. /package/skills/{dart-use-pattern-matching → flutter-dart-use-pattern-matching}/examples/json_patterns.dart +0 -0
@@ -0,0 +1,743 @@
1
+ ---
2
+ name: android-leanback-to-compose-tv-migration
3
+ description: Provides instructions and architectural patterns for migrating Android
4
+ TV applications from legacy Leanback UI Toolkit, Android Views, or Support Fragments
5
+ to Jetpack Compose for TV (androidx.tv). Use this skill for Leanback to Compose
6
+ migrations, including browse screen, settings screen, authentication screen, login
7
+ screen, or video playback screen migrations, or when replacing BrowseSupportFragment,
8
+ LeanbackSettingsFragment, PreferenceFragment, BaseLeanbackPreferenceFragmentCompat,
9
+ VideoSupportFragment, GuidedStepSupportFragment, SearchSupportFragment, VerticalGridSupportFragment,
10
+ Presenter, ArrayObjectAdapter, or CursorMapper with modern Compose equivalents,
11
+ implementing immersive carousels with focus memory, Media3 video playback with PlayerSurface,
12
+ or custom 10-foot hero layouts.
13
+ license: Apache-2.0
14
+ source: https://github.com/android/skills
15
+ routing: manual
16
+ stacks: [android]
17
+ metadata:
18
+ author: Google LLC
19
+ last-updated: '2026-09-03'
20
+ keywords:
21
+ - Android TV
22
+ - Jetpack Compose
23
+ - Leanback
24
+ - migration
25
+ - androidx.tv
26
+ - Carousel
27
+ - 10-foot UI
28
+ - PlayerSurface
29
+ - Media3
30
+ - focusRestorer
31
+ - FocusRequester
32
+ - TV development
33
+ - Android Views
34
+ - LeanbackSettingsFragment
35
+ - PreferenceFragment
36
+ - BrowseSupportFragment
37
+ ---
38
+
39
+ ## The 10-foot UI
40
+
41
+ A "10-foot UI" is a design paradigm for televisions that tailors an interface
42
+ for viewing from approximately 3 meters (10 feet) away. When designing for this
43
+ experience, account for these key characteristics:
44
+
45
+ - **Viewing distance**: Viewers are sitting far from the screen, "leaning back". Screen layouts are uncluttered, with text and UI elements that are large enough to be comfortably readable from a distance, without dense blocks of text.
46
+ - **Color contrast**: To avoid washed out colors on TV displays with low contrast ratios, the design uses high-contrast palettes and distinct visual indicators so focused states remain visible across different TV panels.
47
+ - **D-pad navigation** : Interaction relies on a directional remote control with limited 4-way navigation (`Up`, `Down`, `Left`, `Right`) with components organized into clear spatial grids and carousels without focus traps.
48
+
49
+ ## Core architecture and library selection
50
+
51
+ When migrating an Android TV application to Jetpack Compose, you must use
52
+ `androidx.tv` libraries and follow these 10-foot UI patterns:
53
+
54
+ - **UI modernization**: Focus on custom, cinematic layouts over legacy direct 1:1 templates. You must use Jetpack Compose for TV features like dynamic gradient hero backdrops, custom focus animations, custom navigation drawers, and custom layouts.
55
+ - **Primary design system** : You must always use `androidx.tv.material3.*` (`androidx.tv:tv-material`) over mobile `androidx.compose.material3.*`. TV Material 3 provides built-in D-Pad focus handling, focus zoom scaling, and TV-optimized typography and shapes. To set up Compose for TV dependencies, follow [Compose for TV setup](references/android/training/tv/playback/compose/index.md).
56
+ - **Focus zoom animation** : For interactive cards, you must use `CompactCard`, `ClassicCard`, or `WideCardContainer` with `scale =
57
+ CardDefaults.scale(focusedScale = 1.1f)` to provide standard TV focus animation.
58
+ - **Coil image loading** : To use declarative `AsyncImage(model,
59
+ contentDescription, ...)` without passing an explicit `ImageLoader` parameter, you must include `io.coil-kt:coil-compose` in your Gradle dependencies.
60
+ - **Explicit imports** : You must always import TV Material 3 classes explicitly (for example, `import androidx.tv.material3.Surface`, `import
61
+ androidx.tv.material3.ListItem`) instead of using wildcard imports (`import
62
+ androidx.tv.material3.*`).
63
+ - **File naming conventions** : You must name Composable screen files after the screen (for example, name `BrowseScreen` as `BrowseScreen.kt`, `PlaybackScreen` as `PlaybackScreen.kt`, and `AuthenticationScreen` as `AuthenticationScreen.kt`). Don't use generic prefixes like `Main`.
64
+ - **Overscan and bezels** : You must apply horizontal padding (for example, `horizontal = 48.dp` or `32.dp`, `vertical = 24.dp`) to root containers, carousels, and top bars to prevent clipping.
65
+ - **Reading width constrainment** : You must constrain reading width using `Modifier.widthIn(max = 600.dp)` on text columns for long-form text.
66
+ - **Media3 Compose dependencies** : Include `androidx.media3:media3-ui-compose` in `app/build.gradle` when modernizing media playback screens.
67
+ - **Prohibition of legacy AndroidView wrappers** : Don't use legacy `AndroidView` wrappers to embed View-based components into Jetpack Compose screens. All migrated screens must use Compose components or Media3 Compose surfaces (`PlayerSurface`).
68
+
69
+ ## D-pad focus handling and navigation
70
+
71
+ Jetpack Compose for TV (`androidx.tv.material3`) requires explicit focus
72
+ management, as components don't receive initial focus automatically and
73
+ navigation uses 2D spatial coordinates. To configure TV D-pad navigation, follow
74
+ instructions in [TV Navigation guide](references/android/training/tv/get-started/navigation.md).
75
+
76
+ ### Initial focus
77
+
78
+ You must assign initial focus to the primary interactive element on every screen
79
+ (such as the first action button, card, or `ListItem`) using `FocusRequester`
80
+ when entering a screen. Define `val focusRequester = remember { FocusRequester()
81
+ }`, attach `Modifier.focusRequester(focusRequester)` to the primary element, and
82
+ request focus inside `LaunchedEffect(Unit) { focusRequester.requestFocus() }`:
83
+
84
+
85
+ ```kotlin
86
+ val focusRequester = remember { FocusRequester() }
87
+ val focusManager = LocalFocusManager.current
88
+
89
+ LaunchedEffect(Unit) {
90
+ focusRequester.requestFocus()
91
+ }
92
+ ```
93
+
94
+ <br />
95
+
96
+ *Note: For screens with dynamic state or pagers (like `OnboardingScreen` using
97
+ `HorizontalPager`), you must pass the state key to `LaunchedEffect` (for example
98
+ `LaunchedEffect(pagerState.currentPage)`) so that focus is re-applied when the
99
+ page changes.*
100
+
101
+ ### Bidirectional focus routing and avoiding focus traps
102
+
103
+ When interactive elements sit on opposite sides of the display, standard 2D
104
+ spatial navigation fails to find targets across them. This creates focus traps
105
+ where users are unable to navigate out of an area using the D-pad.
106
+
107
+ For symmetrical, bidirectional D-pad navigation without focus traps, you must
108
+ rely on Compose's 2D spatial focus engine whenever possible. When connecting
109
+ adjacent UI elements across scrollable containers (like `LazyColumn` or
110
+ `LazyRow`), don't set directional overrides (`up = ...`, `down = ...`) targeting
111
+ individual items inside lazy lists. When an item scrolls off-screen during
112
+ vertical navigation, its `FocusRequester` becomes uninitialized, throwing
113
+ `IllegalStateException` during focus searches:
114
+
115
+
116
+ ```kotlin
117
+ Row(
118
+ modifier = Modifier
119
+ .fillMaxWidth()
120
+ .padding(horizontal = 48.dp, vertical = 16.dp),
121
+ horizontalArrangement = Arrangement.End
122
+ ) {
123
+ Button(
124
+ onClick = { /* Search */ },
125
+ modifier = Modifier.focusRequester(topBarFocusRequester)
126
+ ) { Text("Search") }
127
+ }
128
+ ```
129
+
130
+ <br />
131
+
132
+ ### Row focus recollection (`Modifier.focusRestorer`)
133
+
134
+ When navigating vertically between horizontal carousels (`LazyRow`), Compose's
135
+ default 2D spatial focus engine searches along the X coordinate of the focused
136
+ item. If a user scrolls right in Row 1 (for example to Item 4 at X=800dp) and
137
+ presses DOWN to navigate to Row 2, spatial routing focuses whatever item sits at
138
+ X=800dp in Row 2.
139
+
140
+ To make every row maintain its own recollection of card focus (restoring focus
141
+ to the previously visited item when revisited), you must attach
142
+ **`Modifier.focusRestorer`** (with no arguments) directly to the `LazyRow`.
143
+ Don't pass custom fallback `FocusRequester` lambdas in lazy containers, as
144
+ calling `requestFocus` on an unattached or off-screen item during rapid D-pad
145
+ scrolling throws `IllegalStateException`.
146
+
147
+
148
+ ```kotlin
149
+ LazyRow(
150
+ modifier = Modifier.focusRestorer(),
151
+ contentPadding = PaddingValues(horizontal = 48.dp),
152
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
153
+ ) {
154
+ itemsIndexed(videos) { vidIndex, video ->
155
+ CompactCard(
156
+ onClick = { onVideoClick(video) },
157
+ image = {
158
+ AsyncImage(
159
+ model = video.cardImageUrl,
160
+ contentDescription = video.title,
161
+ contentScale = ContentScale.Crop,
162
+ modifier = Modifier.fillMaxSize()
163
+ )
164
+ },
165
+ title = { Text(video.title) },
166
+ modifier = Modifier
167
+ .then(
168
+ if (catIndex == 0 && vidIndex == 0) {
169
+ Modifier.focusRequester(firstCardFocusRequester)
170
+ } else {
171
+ Modifier
172
+ }
173
+ )
174
+ .onFocusChanged { focusState ->
175
+ if (focusState.isFocused) {
176
+ focusedVideo = video
177
+ focusedCategoryIndex = catIndex
178
+ }
179
+ }
180
+ )
181
+ }
182
+ }
183
+ ```
184
+
185
+ <br />
186
+
187
+ ### Text input, hardware keyboard enter interception, and IME focus chaining
188
+
189
+ When migrating search bars or login forms from Leanback
190
+ (`SearchSupportFragment`, `GuidedStepSupportFragment`), don't use bare
191
+ `BasicTextField` containers or empty `Surface(onClick = {})` wrappers, as they
192
+ prevent D-pad CENTER from attaching the virtual keyboard (IME).
193
+
194
+ 1. **Clickable TV surface wrapper with Back-key interception
195
+ (`onPreviewKeyEvent`)** : Wrap standard M3 `TextField` inside a focusable TV `Surface(onClick = { focusRequester.requestFocus() }, scale =
196
+ ClickableSurfaceDefaults.scale(focusedScale = 1.01f), border =
197
+ ClickableSurfaceDefaults.border(focusedBorder = Border(BorderStroke(2.dp,
198
+ Color.White))))` to provide a focused border outline and D-pad focus scaling. You must attach `Modifier.onPreviewKeyEvent` on the text field or wrapper to intercept `Key.Back` and `Key.Escape` so the user's able to remove focus from the input field without exiting the screen.
199
+ 2. **Why Back-key interception is mandatory** : When editing a text field on Android TV, pressing the D-pad **Back** button normally navigates back and exits the screen. By intercepting `Key.Back` and `Key.Escape` on `KeyUp` in `onPreviewKeyEvent` to stop editing (clearing focus), the user's able to return to D-pad form navigation without being trapped in the text field or accidentally exiting the screen.
200
+ 3. **IME focus chaining** : For multi-field forms (such as Username and Password in authentication screens), attach `KeyboardActions(onNext = {
201
+ focusManager.moveFocus(FocusDirection.Down) })` with `ImeAction.Next` on top fields to route focus down to the next input box, and `ImeAction.Done` on the bottom field to route focus directly to the submit button:
202
+
203
+
204
+ ```kotlin
205
+ Surface(
206
+ onClick = { focusRequester.requestFocus() },
207
+ border = ClickableSurfaceDefaults.border(focusedBorder = Border(border = BorderStroke(2.dp, Color.White))),
208
+ modifier = Modifier
209
+ .fillMaxWidth()
210
+ .focusRequester(focusRequester)
211
+ ) {
212
+ OutlinedTextField(
213
+ value = username,
214
+ onValueChange = { username = it },
215
+ label = { Text("Username") },
216
+ singleLine = true,
217
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
218
+ keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Down) }),
219
+ modifier = Modifier
220
+ .fillMaxWidth()
221
+ .onPreviewKeyEvent { event ->
222
+ if (event.type == KeyEventType.KeyDown && (event.key == Key.Enter || event.key == Key.NumPadEnter)) {
223
+ focusManager.moveFocus(FocusDirection.Down)
224
+ true
225
+ } else false
226
+ }
227
+ )
228
+ }
229
+
230
+ Surface(
231
+ onClick = {},
232
+ border = ClickableSurfaceDefaults.border(focusedBorder = Border(border = BorderStroke(2.dp, Color.White))),
233
+ modifier = Modifier.fillMaxWidth()
234
+ ) {
235
+ OutlinedTextField(
236
+ value = password,
237
+ onValueChange = { password = it },
238
+ label = { Text("Password") },
239
+ singleLine = true,
240
+ visualTransformation = PasswordVisualTransformation(),
241
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
242
+ keyboardActions = KeyboardActions(onDone = { onLoginSuccess() }),
243
+ modifier = Modifier
244
+ .fillMaxWidth()
245
+ .onPreviewKeyEvent { event ->
246
+ if (event.type == KeyEventType.KeyDown && (event.key == Key.Enter || event.key == Key.NumPadEnter)) {
247
+ onLoginSuccess()
248
+ true
249
+ } else false
250
+ }
251
+ )
252
+ }
253
+ ```
254
+
255
+ <br />
256
+
257
+ ## Lazy containers
258
+
259
+ When implementing scrollable lists or grids in Jetpack Compose for TV, you must
260
+ use standard `LazyColumn`, `LazyRow`, and `LazyVerticalGrid` from
261
+ `androidx.compose.foundation.lazy` and `androidx.compose.foundation.lazy.grid`.
262
+ For catalog browsing layouts, follow instructions in [Catalog Browser guide](references/android/training/tv/playback/compose/browse.md).
263
+
264
+ 1. **Pivot scrolling with `BringIntoViewSpec`** : When defining a custom pivot scroll line for catalog rows using `BringIntoViewSpec` and `CompositionLocalProvider(LocalBringIntoViewSpec provides ...)`, you must ensure your project compiles against Compose Foundation 1.7.0+ by adding `implementation platform('androidx.compose:compose-bom:2024.06.00')` (or newer) or `implementation 'androidx.compose.foundation:foundation:1.7.0'` in `app/build.gradle`. Without Compose Foundation 1.7.0+, `import
265
+ androidx.compose.foundation.gestures.LocalBringIntoViewSpec` will fail with `Unresolved reference: LocalBringIntoViewSpec`.
266
+ 2. **Row focus recollection (`Modifier.focusRestorer`)** : Annotate your composable with `@OptIn(ExperimentalFocusRestorerApi::class,
267
+ ExperimentalComposeUiApi::class)` and attach `Modifier.focusRestorer` on every category `LazyRow` to remember and restore the last focused card when navigating vertically across catalog rows.
268
+
269
+ When populated with focusable TV Material 3 components (`CompactCard`,
270
+ `ListItem`, `Button`), standard Compose lazy containers handle 2D D-Pad focus
271
+ routing, focus memory, and edge scrolling automatically:
272
+
273
+
274
+ ```kotlin
275
+ @Composable
276
+ fun CatalogBrowser(
277
+ featuredContentList: List<Movie>,
278
+ sectionList: List<Section>,
279
+ modifier: Modifier = Modifier,
280
+ onItemSelected: (Movie) -> Unit = {},
281
+ ) {
282
+ LazyColumn(
283
+ modifier = modifier.fillMaxSize(),
284
+ verticalArrangement = Arrangement.spacedBy(16.dp)
285
+ ) {
286
+ items(sectionList.size) { index ->
287
+ val section = sectionList[index]
288
+ SectionRow(section, onItemSelected = onItemSelected)
289
+ }
290
+ }
291
+ }
292
+ ```
293
+
294
+ <br />
295
+
296
+ ## Media3 video playback and transport controls
297
+
298
+ When migrating legacy Leanback video playback (`VideoSupportFragment` /
299
+ `PlaybackGlue`), use Compose Media3 `PlayerSurface`
300
+ (`androidx.media3.ui.compose.PlayerSurface`) combined with a translucent
301
+ transport controls overlay:
302
+
303
+ 1. **Mandatory Media3 transport control buttons (`PlayPauseButton`)** : Over the `PlayerSurface`, you must layer a translucent bottom controls bar (`Box(modifier = Modifier.align(Alignment.BottomCenter))`) containing explicit Media3 UI Compose buttons: at minimum `PlayPauseButton`, `SeekBackButton`, and `SeekForwardButton`. Never leave the transport controls overlay empty. To use these composables, you must add `implementation 'androidx.media3:media3-ui-compose-material3:1.6.0'` alongside `androidx.media3:media3-ui-compose` in your `app/build.gradle` dependencies.
304
+ 2. **D-pad directional seeking (`onPreviewKeyEvent`)** : To support seeking backward and forward with the remote control D-pad, attach `Modifier.onPreviewKeyEvent` on the container or controls overlay and intercept Compose `Key.DirectionLeft` and `Key.DirectionRight` (for example, `keyEvent.key == Key.DirectionLeft`) to seek backward and forward by 10 seconds (`exoPlayer.seekTo(exoPlayer.currentPosition - 10000)`). Never use legacy Android View keycodes (`KeyEvent.KEYCODE_DPAD_LEFT` or `nativeKeyEvent.keyCode`).
305
+ 3. **Prohibition of legacy AndroidView wrappers** : Don't wrap legacy `PlayerView` or `StyledPlayerView` in `AndroidView { ... }`. You must use `PlayerSurface` (`androidx.media3.ui.compose.PlayerSurface`) with `ExoPlayer` for video rendering in Compose for TV.
306
+
307
+ ## Phased migration strategy
308
+
309
+ To migrate an app cleanly without breaking compilation or introducing circular
310
+ dependencies, execute in five distinct phases:
311
+
312
+ - **Phase 1**: Foundation and design system
313
+ - **Phase 2**: Leaf and standalone screens
314
+ - **Phase 3**: Core browsing and discovery screens
315
+ - **Phase 4**: Details and media playback
316
+ - **Phase 5**: Final unification and cleanup
317
+
318
+ ### Phase 1: Foundation and design system
319
+
320
+ - Create `TvTheme.kt` wrapping `TvMaterialTheme` with custom `ColorScheme`, `Typography`, and `Shapes`.
321
+ - Build atomic reusable components: `MovieCard`, `SectionHeader`, `LoadingIndicator`, `ErrorState`.
322
+
323
+ ### Phase 2: Leaf and standalone screens
324
+
325
+ - Migrate screens with no outbound navigation first, such as error messages, onboarding screens, or settings screens, by replacing them with `ErrorDialog`, `OnboardingScreen`, and `SettingsScreen`.
326
+ - Replace anything using `BaseLeanbackPreferenceFragmentCompat`, `BaseLeanbackPreferenceFragment`, or `LeanbackSettingsFragment` to use Compose, for example by using `ListItem` + `Switch` bound directly to `SharedPreferences` and replacing the `findPreference` implementation.
327
+ - Ensure screens receive initial D-pad focus on the first or main component of that screen using `FocusRequester`, for example on the first `ListItem`.
328
+ - Replace legacy `Fragment` classes with activities of type `ComponentActivity` that declaratively use components in Compose.
329
+ - Clean up legacy style and theme references in `res/values/styles.xml` and `res/values/themes.xml` (such as removing `preferenceTheme` that points to `@style/PreferenceThemeOverlay.v14.Leanback`) that aren't supported once leanback dependencies are removed:
330
+
331
+
332
+ ```kotlin
333
+ @Composable
334
+ fun TvSettingsScreen(
335
+ modifier: Modifier = Modifier
336
+ ) {
337
+ var autoPlayNext by remember { mutableStateOf(true) }
338
+ var highQualityAudio by remember { mutableStateOf(false) }
339
+
340
+ Column(
341
+ modifier = modifier
342
+ .fillMaxSize()
343
+ .padding(48.dp)
344
+ ) {
345
+ Text(
346
+ text = "Settings",
347
+ style = MaterialTheme.typography.headlineMedium,
348
+ modifier = Modifier.padding(bottom = 24.dp)
349
+ )
350
+
351
+ LazyColumn(
352
+ verticalArrangement = Arrangement.spacedBy(12.dp),
353
+ contentPadding = PaddingValues(vertical = 8.dp)
354
+ ) {
355
+ item {
356
+ ListItem(
357
+ selected = false,
358
+ onClick = { autoPlayNext = !autoPlayNext },
359
+ headlineContent = { Text("Autoplay Next Video") },
360
+ supportingContent = { Text("Automatically start playing next item in queue") },
361
+ trailingContent = {
362
+ Switch(
363
+ checked = autoPlayNext,
364
+ onCheckedChange = null
365
+ )
366
+ }
367
+ )
368
+ }
369
+
370
+ item {
371
+ ListItem(
372
+ selected = false,
373
+ onClick = { highQualityAudio = !highQualityAudio },
374
+ headlineContent = { Text("High Quality Audio") },
375
+ supportingContent = { Text("Use spatial audio and multi-channel output when available") },
376
+ trailingContent = {
377
+ Switch(
378
+ checked = highQualityAudio,
379
+ onCheckedChange = null
380
+ )
381
+ }
382
+ )
383
+ }
384
+ }
385
+ }
386
+ }
387
+ ```
388
+
389
+ <br />
390
+
391
+ ### Phase 3: Core browsing and discovery screens
392
+
393
+ - Migrate `VerticalGridScreen` (`LazyVerticalGrid`), `SearchScreen` (`BasicTextField` with live list filtering), and `BrowseScreen` (`LazyColumn` of `LazyRow`s).
394
+ - Eradicate legacy `ArrayObjectAdapter`, `ListRowPresenter`, `CardPresenter`, and `HeaderItem` classes.
395
+
396
+ ### Phase 4: Details and media playback
397
+
398
+ - Migrate `VideoDetailsScreen` and `GuidedStepScreen`.
399
+ - Migrate `PlaybackScreen` using Compose Media3 `PlayerSurface` (`androidx.media3.ui.compose.PlayerSurface`) paired with a translucent bottom overlay containing Media3 Compose transport controls (such as `PlayPauseButton`, `SeekBackButton`, `SeekForwardButton`).
400
+
401
+ ### Phase 5: Final unification and cleanup
402
+
403
+ - Remove all remaining legacy `.java` activities, fragments, presenters, and XML layout files.
404
+ - Ensure all activities extend `ComponentActivity` or `FragmentActivity` calling `setContent { ... }`.
405
+ - Completely remove legacy Leanback themes and style declarations from `res/values/styles.xml` and `res/values/themes.xml` (for example, any styles inheriting from `Theme.Leanback` or referencing `lb_` styles).
406
+
407
+ ## Component and class mapping guide
408
+
409
+ | Legacy Leanback / View Class | Modern Jetpack Compose Equivalent |
410
+ |---|---|
411
+ | `BrowseSupportFragment` / `MainFragment` | `BrowseScreen` (`LazyColumn` containing categorized `LazyRow`s + Hero Banner) |
412
+ | `DetailsSupportFragment` | `VideoDetailsScreen` (Poster image, text column, action buttons, related `LazyRow`) |
413
+ | `VideoSupportFragment` / `PlaybackGlue` | `PlaybackScreen` (Media3 `ExoPlayer` + Compose `PlayerSurface`) |
414
+ | `GuidedStepSupportFragment` | `GuidedStepScreen` (Split-screen layout: 40% left guidance pane, 60% right actions pane) |
415
+ | `SearchSupportFragment` | `SearchScreen` (`BasicTextField` + live filtering + `LazyVerticalGrid`) |
416
+ | `VerticalGridSupportFragment` | `VerticalGridScreen` (`LazyVerticalGrid(columns = GridCells.Fixed(5))`) |
417
+ | `ArrayObjectAdapter` / `Presenter` | Declarative `@Composable` functions observing immutable `State<List<T>>` |
418
+ | `CursorMapper` / `LoaderManager` / `CursorLoader` | Kotlin Coroutines / `withContext(Dispatchers.IO)` in a Repository object |
419
+ | `OnboardingSupportFragment` | `OnboardingScreen` (`HorizontalPager` + D-Pad navigation buttons) |
420
+ | `LeanbackSettingsFragment` / `PreferenceFragment` | `SettingsScreen` (`FocusRequester` on first `ListItem` + trailing `Switch` bound to `SharedPreferences`) |
421
+
422
+ ## Reference implementations and battle-tested patterns
423
+
424
+ ### Modern TV immersive list architecture (`BrowseScreen`)
425
+
426
+ When building a 10-foot TV browse screen or Immersive List, don't place a hero
427
+ banner before or outside a scrolling list, and don't fight Compose's automatic
428
+ `BringIntoView` system with programmatic `animateScrollToItem` calls.
429
+
430
+ Instead, use `BringIntoViewSpec` with `LocalBringIntoViewSpec` from Compose
431
+ Foundation to define exact TV pivot scrolling (for example, pivoting active rows
432
+ at 35% from the top edge of the display). Combine this with a reshaping
433
+ immersive row: when lower rows are focused (`focusedCategoryIndex > 0`), hide
434
+ the hero text and display a normal section header on Row 0:
435
+
436
+
437
+ ```kotlin
438
+ @Composable
439
+ fun ImmersiveBrowseScreen(
440
+ categories: Map<String, List<Video>>,
441
+ onVideoClick: (Video) -> Unit
442
+ ) {
443
+ var focusedVideo by remember { mutableStateOf<Video?>(null) }
444
+ var focusedCategoryIndex by remember { mutableStateOf(0) }
445
+ val listState = rememberLazyListState()
446
+ val topBarFocusRequester = remember { FocusRequester() }
447
+ val firstCardFocusRequester = remember { FocusRequester() }
448
+
449
+ Box(modifier = Modifier.fillMaxSize()) {
450
+ AsyncImage(
451
+ model = focusedVideo?.bgImageUrl,
452
+ contentDescription = null,
453
+ contentScale = ContentScale.Crop,
454
+ modifier = Modifier.fillMaxSize()
455
+ )
456
+
457
+ PositionFocusedItemInLazyLayout(parentFraction = 0.35f, childFraction = 0.5f) {
458
+ LazyColumn(
459
+ state = listState,
460
+ contentPadding = PaddingValues(top = 36.dp, bottom = 64.dp),
461
+ verticalArrangement = Arrangement.spacedBy(28.dp),
462
+ modifier = Modifier.fillMaxSize()
463
+ ) {
464
+ item {
465
+ Row(
466
+ modifier = Modifier
467
+ .fillMaxWidth()
468
+ .padding(horizontal = 48.dp, vertical = 16.dp),
469
+ horizontalArrangement = Arrangement.End
470
+ ) {
471
+ Button(
472
+ onClick = { /* Search */ },
473
+ modifier = Modifier.focusRequester(topBarFocusRequester)
474
+ ) { Text("Search") }
475
+ }
476
+ }
477
+
478
+ itemsIndexed(categories.entries.toList()) { catIndex, (categoryName, videos) ->
479
+ Column {
480
+ if (catIndex == 0 && focusedCategoryIndex == 0) {
481
+ Column(
482
+ modifier = Modifier
483
+ .heightIn(min = 200.dp)
484
+ .padding(horizontal = 48.dp)
485
+ ) {
486
+ Text(
487
+ text = focusedVideo?.title ?: "",
488
+ style = MaterialTheme.typography.displayMedium
489
+ )
490
+ Text(
491
+ text = focusedVideo?.description ?: "",
492
+ style = MaterialTheme.typography.bodyLarge
493
+ )
494
+ }
495
+ } else {
496
+ Text(
497
+ text = categoryName,
498
+ style = MaterialTheme.typography.titleMedium,
499
+ modifier = Modifier.padding(horizontal = 48.dp, vertical = 8.dp)
500
+ )
501
+ }
502
+
503
+ LazyRow(
504
+ modifier = Modifier.focusRestorer(),
505
+ contentPadding = PaddingValues(horizontal = 48.dp),
506
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
507
+ ) {
508
+ itemsIndexed(videos) { vidIndex, video ->
509
+ CompactCard(
510
+ onClick = { onVideoClick(video) },
511
+ image = {
512
+ AsyncImage(
513
+ model = video.cardImageUrl,
514
+ contentDescription = video.title,
515
+ contentScale = ContentScale.Crop,
516
+ modifier = Modifier.fillMaxSize()
517
+ )
518
+ },
519
+ title = { Text(video.title) },
520
+ modifier = Modifier
521
+ .then(
522
+ if (catIndex == 0 && vidIndex == 0) {
523
+ Modifier.focusRequester(firstCardFocusRequester)
524
+ } else {
525
+ Modifier
526
+ }
527
+ )
528
+ .onFocusChanged { focusState ->
529
+ if (focusState.isFocused) {
530
+ focusedVideo = video
531
+ focusedCategoryIndex = catIndex
532
+ }
533
+ }
534
+ )
535
+ }
536
+ }
537
+ }
538
+ }
539
+ }
540
+ }
541
+ }
542
+ }
543
+
544
+ @OptIn(ExperimentalFoundationApi::class)
545
+ @Composable
546
+ private fun PositionFocusedItemInLazyLayout(
547
+ parentFraction: Float = 0.35f,
548
+ childFraction: Float = 0.5f,
549
+ content: @Composable () -> Unit,
550
+ ) {
551
+ val bringIntoViewSpec = remember(parentFraction, childFraction) {
552
+ object : BringIntoViewSpec {
553
+ override fun calculateScrollDistance(
554
+ offset: Float,
555
+ size: Float,
556
+ containerSize: Float
557
+ ): Float {
558
+ if (offset >= 0f && offset <= containerSize * 0.45f) {
559
+ return 0f
560
+ }
561
+ val initialTargetForLeadingEdge = parentFraction * containerSize - (childFraction * size)
562
+ val targetForLeadingEdge = if (size <= containerSize && (containerSize - initialTargetForLeadingEdge) < size) {
563
+ containerSize - size
564
+ } else {
565
+ initialTargetForLeadingEdge
566
+ }
567
+ return offset - targetForLeadingEdge
568
+ }
569
+ }
570
+ }
571
+ CompositionLocalProvider(LocalBringIntoViewSpec provides bringIntoViewSpec, content = content)
572
+ }
573
+ ```
574
+
575
+ <br />
576
+
577
+ ### Media3 playback in Compose TV (`PlaybackScreen`)
578
+
579
+ Implement a custom playback screen using
580
+ `androidx.media3.ui.compose.PlayerSurface` as the video rendering canvas. Layer
581
+ Material3 transport controls (`SeekBackButton`, `PlayPauseButton`,
582
+ `SeekForwardButton` from `androidx.media3:media3-ui-compose-material3`) over the
583
+ surface in a translucent bottom overlay, and handle D-pad remote key events with
584
+ an auto-hide timeout:
585
+
586
+
587
+ ```kotlin
588
+ @OptIn(UnstableApi::class)
589
+ @Composable
590
+ fun Media3PlaybackScreen(
591
+ video: Video,
592
+ onFinish: () -> Unit,
593
+ modifier: Modifier = Modifier
594
+ ) {
595
+ val context = LocalContext.current
596
+ val exoPlayer = remember(context) { ExoPlayer.Builder(context).build() }
597
+ var showControls by remember { mutableStateOf(true) }
598
+ val focusRequester = remember { FocusRequester() }
599
+ val coroutineScope = rememberCoroutineScope()
600
+ var autoHideJob by remember { mutableStateOf<Job?>(null) }
601
+
602
+ fun scheduleAutoHide() {
603
+ autoHideJob?.cancel()
604
+ autoHideJob = coroutineScope.launch {
605
+ delay(5000)
606
+ showControls = false
607
+ }
608
+ }
609
+
610
+ DisposableEffect(exoPlayer) {
611
+ val listener = object : Player.Listener {
612
+ override fun onPlaybackStateChanged(playbackState: Int) {
613
+ if (playbackState == Player.STATE_ENDED) {
614
+ onFinish()
615
+ }
616
+ }
617
+ }
618
+ exoPlayer.addListener(listener)
619
+ onDispose {
620
+ autoHideJob?.cancel()
621
+ exoPlayer.removeListener(listener)
622
+ exoPlayer.release()
623
+ }
624
+ }
625
+
626
+ LaunchedEffect(video) {
627
+ focusRequester.requestFocus()
628
+ scheduleAutoHide()
629
+
630
+ val mediaItem = MediaItem.fromUri(video.videoUrl)
631
+ exoPlayer.setMediaItem(mediaItem)
632
+ exoPlayer.prepare()
633
+ exoPlayer.playWhenReady = true
634
+ }
635
+
636
+ Box(
637
+ modifier = modifier
638
+ .fillMaxSize()
639
+ .background(Color.Black)
640
+ .focusRequester(focusRequester)
641
+ .focusable()
642
+ .onPreviewKeyEvent { event ->
643
+ if (event.type == KeyEventType.KeyDown) {
644
+ when (event.key) {
645
+ Key.DirectionCenter, Key.Enter, Key.NumPadEnter, Key.MediaPlayPause, Key.Spacebar -> {
646
+ if (exoPlayer.isPlaying) exoPlayer.pause() else exoPlayer.play()
647
+ showControls = true
648
+ scheduleAutoHide()
649
+ true
650
+ }
651
+ Key.DirectionLeft, Key.MediaRewind -> {
652
+ val newPos = (exoPlayer.currentPosition - 10_000L).coerceAtLeast(0L)
653
+ exoPlayer.seekTo(newPos)
654
+ showControls = true
655
+ scheduleAutoHide()
656
+ true
657
+ }
658
+ Key.DirectionRight, Key.MediaFastForward -> {
659
+ val duration = exoPlayer.duration.coerceAtLeast(0L)
660
+ val newPos = (exoPlayer.currentPosition + 10_000L).coerceAtMost(duration)
661
+ exoPlayer.seekTo(newPos)
662
+ showControls = true
663
+ scheduleAutoHide()
664
+ true
665
+ }
666
+ Key.DirectionUp, Key.DirectionDown -> {
667
+ showControls = !showControls
668
+ if (showControls) scheduleAutoHide() else autoHideJob?.cancel()
669
+ true
670
+ }
671
+ else -> false
672
+ }
673
+ } else false
674
+ },
675
+ contentAlignment = Alignment.Center
676
+ ) {
677
+ PlayerSurface(
678
+ player = exoPlayer,
679
+ modifier = Modifier.fillMaxSize()
680
+ )
681
+
682
+ if (showControls) {
683
+ Box(
684
+ modifier = Modifier
685
+ .fillMaxSize()
686
+ .background(Color(0x80000000))
687
+ .padding(48.dp),
688
+ contentAlignment = Alignment.BottomCenter
689
+ ) {
690
+ Row(
691
+ horizontalArrangement = Arrangement.spacedBy(24.dp),
692
+ verticalAlignment = Alignment.CenterVertically
693
+ ) {
694
+ SeekBackButton(player = exoPlayer)
695
+ PlayPauseButton(player = exoPlayer)
696
+ SeekForwardButton(player = exoPlayer)
697
+ }
698
+ }
699
+ }
700
+ }
701
+ }
702
+ ```
703
+
704
+ <br />
705
+
706
+ ### Replacing CursorLoader with reactive coroutine flow
707
+
708
+ Replace legacy `LoaderManager.LoaderCallbacks<Cursor>` and `CursorObjectAdapter`
709
+ with a repository returning a `Flow` that observes database changes and triggers
710
+ asynchronous fetching when empty:
711
+
712
+
713
+ ```kotlin
714
+ object VideoFlowRepository {
715
+ fun getVideosFlow(context: Context, contentUri: Uri): Flow<List<Video>> = callbackFlow {
716
+ val contentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
717
+ override fun onChange(selfChange: Boolean) {
718
+ trySend(queryVideos(context, contentUri))
719
+ }
720
+ }
721
+
722
+ context.contentResolver.registerContentObserver(
723
+ contentUri,
724
+ true,
725
+ contentObserver
726
+ )
727
+
728
+ val initialVideos = queryVideos(context, contentUri)
729
+ trySend(initialVideos)
730
+
731
+ awaitClose {
732
+ context.contentResolver.unregisterContentObserver(contentObserver)
733
+ }
734
+ }.flowOn(Dispatchers.IO)
735
+
736
+ private fun queryVideos(context: Context, uri: Uri): List<Video> {
737
+ // Query database or ContentProvider
738
+ return emptyList()
739
+ }
740
+ }
741
+ ```
742
+
743
+ <br />