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,1211 @@
1
+ When using surfaces to build a custom component, refer to the following source
2
+ code in `Surface.kt`:
3
+
4
+
5
+ ```kotlin
6
+ /*
7
+ * Copyright 2025 The Android Open Source Project
8
+ *
9
+ * Licensed under the Apache License, Version 2.0 (the "License");
10
+ * you may not use this file except in compliance with the License.
11
+ * You may obtain a copy of the License at
12
+ *
13
+ * http://www.apache.org/licenses/LICENSE-2.0
14
+ *
15
+ * Unless required by applicable law or agreed to in writing, software
16
+ * distributed under the License is distributed on an "AS IS" BASIS,
17
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ * See the License for the specific language governing permissions and
19
+ * limitations under the License.
20
+ */
21
+
22
+ package androidx.xr.glimmer
23
+
24
+ import android.graphics.RenderEffect
25
+ import android.graphics.RuntimeShader
26
+ import android.os.Build
27
+ import androidx.annotation.RequiresApi
28
+ import androidx.compose.animation.core.Animatable
29
+ import androidx.compose.animation.core.AnimationSpec
30
+ import androidx.compose.animation.core.AnimationVector1D
31
+ import androidx.compose.animation.core.LinearEasing
32
+ import androidx.compose.animation.core.LinearOutSlowInEasing
33
+ import androidx.compose.animation.core.spring
34
+ import androidx.compose.animation.core.tween
35
+ import androidx.compose.foundation.interaction.FocusInteraction
36
+ import androidx.compose.foundation.interaction.Interaction
37
+ import androidx.compose.foundation.interaction.InteractionSource
38
+ import androidx.compose.foundation.interaction.PressInteraction
39
+ import androidx.compose.runtime.Composable
40
+ import androidx.compose.runtime.Immutable
41
+ import androidx.compose.runtime.LaunchedEffect
42
+ import androidx.compose.runtime.derivedStateOf
43
+ import androidx.compose.runtime.getValue
44
+ import androidx.compose.runtime.remember
45
+ import androidx.compose.ui.Modifier
46
+ import androidx.compose.ui.draw.clip
47
+ import androidx.compose.ui.geometry.Size
48
+ import androidx.compose.ui.graphics.Brush
49
+ import androidx.compose.ui.graphics.Color
50
+ import androidx.compose.ui.graphics.Outline
51
+ import androidx.compose.ui.graphics.ShaderBrush
52
+ import androidx.compose.ui.graphics.Shape
53
+ import androidx.compose.ui.graphics.SolidColor
54
+ import androidx.compose.ui.graphics.asComposeRenderEffect
55
+ import androidx.compose.ui.graphics.compositeOver
56
+ import androidx.compose.ui.graphics.drawOutline
57
+ import androidx.compose.ui.graphics.drawscope.ContentDrawScope
58
+ import androidx.compose.ui.graphics.drawscope.DrawScope
59
+ import androidx.compose.ui.graphics.layer.GraphicsLayer
60
+ import androidx.compose.ui.graphics.layer.drawLayer
61
+ import androidx.compose.ui.graphics.lerp
62
+ import androidx.compose.ui.layout.layout
63
+ import androidx.compose.ui.node.DelegatingNode
64
+ import androidx.compose.ui.node.DrawModifierNode
65
+ import androidx.compose.ui.node.ModifierNodeElement
66
+ import androidx.compose.ui.node.invalidateDraw
67
+ import androidx.compose.ui.node.requireGraphicsContext
68
+ import androidx.compose.ui.platform.InspectorInfo
69
+ import androidx.compose.ui.unit.Density
70
+ import androidx.compose.ui.unit.Dp
71
+ import androidx.compose.ui.unit.IntSize
72
+ import androidx.compose.ui.unit.dp
73
+ import androidx.compose.ui.unit.lerp
74
+ import androidx.xr.glimmer.internal.color.withTone
75
+ import kotlin.math.ceil
76
+ import kotlinx.coroutines.CoroutineStart
77
+ import kotlinx.coroutines.Job
78
+ import kotlinx.coroutines.delay
79
+ import kotlinx.coroutines.isActive
80
+ import kotlinx.coroutines.launch
81
+ import org.intellij.lang.annotations.Language
82
+
83
+ /**
84
+ * A surface is a fundamental building block in Glimmer. A surface represents a distinct visual area
85
+ * or 'physical' boundary for components such as buttons and cards. A [surface] implements shared
86
+ * visual decoration for Jetpack Compose Glimmer components:
87
+ * 1) Clipping: a surface clips its children to the shape specified by [shape]
88
+ * 2) Border: a surface draws an inner border to emphasize the boundary of the component. When
89
+ * focused, a surface draws a wider border with a focused highlight on top to indicate the focus
90
+ * state.
91
+ * 3) Background: a surface has a background color of [color] (and [focusedColor] when focused).
92
+ * 4) Depth effect: a surface can have different [DepthEffect] shadows for different states, as
93
+ * specified by [depthEffect].
94
+ * 5) Content color: a surface provides a [contentColor] (and [focusedContentColor] when focused)
95
+ * for text and icons inside the surface. By default this is calculated from the provided
96
+ * background color.
97
+ * 6) Interaction states: when focused, a surface displays draws a wider border with a focused
98
+ * highlight on top. When pressed, a surface draws a pressed overlay. This happens for
99
+ * interactions emitted from [interactionSource].
100
+ *
101
+ * Use surface on its own for decorative elements that cannot be interacted with by a user:
102
+ *
103
+ * @sample androidx.xr.glimmer.samples.SurfaceSample
104
+ *
105
+ * In most cases surfaces should be interactive, to allow users to consistently move focus and
106
+ * navigate between components. You can use [androidx.compose.foundation.focusable] for focus-only
107
+ * surfaces, or [androidx.compose.foundation.clickable] and other modifiers for surfaces with
108
+ * actions. To ensure the surface correctly reflects the interaction states, provide the same
109
+ * [InteractionSource] to all modifiers.
110
+ *
111
+ * For example, to create a clickable surface:
112
+ *
113
+ * @sample androidx.xr.glimmer.samples.ClickableSurfaceSample
114
+ *
115
+ * Similarly, to create a focusable surface:
116
+ *
117
+ * @sample androidx.xr.glimmer.samples.FocusableSurfaceSample
118
+ *
119
+ * To create a surface with custom colors:
120
+ *
121
+ * @sample androidx.xr.glimmer.samples.CustomFocusedColorSurfaceSample
122
+ * @param enabled controls the enabled state of this surface. When `false`, a disabled overlay
123
+ * visual will be drawn on top of the surface. Note that this only affects the visual decoration;
124
+ * it does not intercept input or block interaction states (such as focus or press) from the
125
+ * [interactionSource].
126
+ * @param shape the [Shape] used to clip this surface, and also used to draw the background and
127
+ * border
128
+ * @param color the background [Color] for this surface. When providing a custom color, ensure it is
129
+ * suitable for a surface background or use [SurfaceDefaults.color] to adapt it.
130
+ * @param focusedColor the background [Color] for this surface when focused. When providing a custom
131
+ * color, ensure it is suitable for a focused surface background or use
132
+ * [SurfaceDefaults.focusedColor] to adapt it.
133
+ * @param contentColor the [Color] for content inside this surface
134
+ * @param focusedContentColor the [Color] for content inside this surface when focused
135
+ * @param depthEffect the [SurfaceDepthEffect] for this surface, representing the [DepthEffect]
136
+ * shadows rendered in different states.
137
+ * @param interactionSource the [InteractionSource] that emits [Interaction]s for this surface. For
138
+ * interactive surfaces, the [InteractionSource] instance provided to this surface must be shared
139
+ * with the modifier responsible for emitting [Interaction]s, such as
140
+ * [androidx.compose.foundation.focusable] or [androidx.compose.foundation.clickable].
141
+ */
142
+ @Composable
143
+ public fun Modifier.surface(
144
+ enabled: Boolean = true,
145
+ shape: Shape = GlimmerTheme.shapes.medium,
146
+ color: Color = GlimmerTheme.colors.surface,
147
+ focusedColor: Color = SurfaceDefaults.focusedColor(color),
148
+ contentColor: Color = calculateContentColor(color),
149
+ focusedContentColor: Color = calculateContentColor(focusedColor),
150
+ depthEffect: SurfaceDepthEffect? = null,
151
+ interactionSource: InteractionSource? = null,
152
+ ): Modifier =
153
+ this.surfaceDepthEffect(depthEffect, shape, interactionSource)
154
+ .clip(shape)
155
+ .then(
156
+ SurfaceNodeElement(
157
+ enabled = enabled,
158
+ color = color,
159
+ focusedColor = focusedColor,
160
+ contentColor = contentColor,
161
+ focusedContentColor = focusedContentColor,
162
+ shape = shape,
163
+ interactionSource = interactionSource,
164
+ )
165
+ )
166
+
167
+ /** Contains default values used by [surface]. */
168
+ public object SurfaceDefaults {
169
+
170
+ /**
171
+ * Returns the background [Color] for a surface derived from the provided [baseColor].
172
+ *
173
+ * Adjusts the provided [baseColor] so that it is suitable for use as a surface background.
174
+ *
175
+ * @param baseColor the base [Color] of the surface
176
+ * @return the surface background [Color], adjusted to improve content contrast
177
+ */
178
+ @Composable
179
+ public fun color(baseColor: Color = GlimmerTheme.colors.surface): Color =
180
+ baseColor.withTone(newTone = SurfaceColorTone)
181
+
182
+ /**
183
+ * Returns the focused background [Color] for a surface derived from the provided [baseColor].
184
+ *
185
+ * Adjusts the provided [baseColor] so that it is suitable for use as a focused surface
186
+ * background.
187
+ *
188
+ * @param baseColor the base [Color] of the surface
189
+ * @return the focused surface background [Color], adjusted to improve content contrast
190
+ */
191
+ @Composable
192
+ public fun focusedColor(baseColor: Color = GlimmerTheme.colors.surface): Color =
193
+ baseColor.withTone(newTone = FocusedSurfaceColorTone)
194
+ }
195
+
196
+ /**
197
+ * Represents the [DepthEffect] used by a [surface] in different states.
198
+ *
199
+ * Focused [surface]s with a [focusedDepthEffect] will have a higher zIndex set so they can draw
200
+ * their focused depth effect over siblings.
201
+ *
202
+ * @property [depthEffect] the [DepthEffect] used when the [surface] is in its default state (no
203
+ * other interactions are ongoing)
204
+ * @property [focusedDepthEffect] the [DepthEffect] used when the [surface] is focused
205
+ */
206
+ @Immutable
207
+ public class SurfaceDepthEffect(
208
+ public val depthEffect: DepthEffect?,
209
+ public val focusedDepthEffect: DepthEffect?,
210
+ ) {
211
+ override fun equals(other: Any?): Boolean {
212
+ if (this === other) return true
213
+ if (other !is SurfaceDepthEffect) return false
214
+
215
+ if (depthEffect != other.depthEffect) return false
216
+ if (focusedDepthEffect != other.focusedDepthEffect) return false
217
+
218
+ return true
219
+ }
220
+
221
+ override fun hashCode(): Int {
222
+ var result = depthEffect?.hashCode() ?: 0
223
+ result = 31 * result + (focusedDepthEffect?.hashCode() ?: 0)
224
+ return result
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Surface node responsible for drawing the border, focused border and highlight, and pressed
230
+ * overlay.
231
+ */
232
+ private class SurfaceNodeElement(
233
+ private val enabled: Boolean,
234
+ private val color: Color,
235
+ private val focusedColor: Color,
236
+ private val contentColor: Color,
237
+ private val focusedContentColor: Color,
238
+ private val shape: Shape,
239
+ private val interactionSource: InteractionSource?,
240
+ ) : ModifierNodeElement<SurfaceNode>() {
241
+ override fun create(): SurfaceNode =
242
+ SurfaceNode(
243
+ enabled = enabled,
244
+ color = color,
245
+ focusedColor = focusedColor,
246
+ contentColor = contentColor,
247
+ focusedContentColor = focusedContentColor,
248
+ shape = shape,
249
+ interactionSource = interactionSource,
250
+ )
251
+
252
+ override fun update(node: SurfaceNode) =
253
+ node.update(
254
+ enabled = enabled,
255
+ color = color,
256
+ focusedColor = focusedColor,
257
+ contentColor = contentColor,
258
+ focusedContentColor = focusedContentColor,
259
+ shape = shape,
260
+ interactionSource = interactionSource,
261
+ )
262
+
263
+ override fun equals(other: Any?): Boolean {
264
+ if (this === other) return true
265
+ if (other !is SurfaceNodeElement) return false
266
+
267
+ if (enabled != other.enabled) return false
268
+ if (color != other.color) return false
269
+ if (focusedColor != other.focusedColor) return false
270
+ if (contentColor != other.contentColor) return false
271
+ if (focusedContentColor != other.focusedContentColor) return false
272
+ if (shape != other.shape) return false
273
+ if (interactionSource != other.interactionSource) return false
274
+
275
+ return true
276
+ }
277
+
278
+ override fun hashCode(): Int {
279
+ var result = enabled.hashCode()
280
+ result = 31 * result + color.hashCode()
281
+ result = 31 * result + focusedColor.hashCode()
282
+ result = 31 * result + contentColor.hashCode()
283
+ result = 31 * result + focusedContentColor.hashCode()
284
+ result = 31 * result + shape.hashCode()
285
+ result = 31 * result + (interactionSource?.hashCode() ?: 0)
286
+ return result
287
+ }
288
+
289
+ override fun InspectorInfo.inspectableProperties() {
290
+ name = "surface"
291
+ properties["enabled"] = enabled
292
+ properties["color"] = color
293
+ properties["focusedColor"] = focusedColor
294
+ properties["contentColor"] = contentColor
295
+ properties["focusedContentColor"] = focusedContentColor
296
+ properties["shape"] = shape
297
+ properties["interactionSource"] = interactionSource
298
+ }
299
+ }
300
+
301
+ private class SurfaceNode(
302
+ private var enabled: Boolean,
303
+ private var color: Color,
304
+ private var focusedColor: Color,
305
+ private var contentColor: Color,
306
+ private var focusedContentColor: Color,
307
+ private var shape: Shape,
308
+ private var interactionSource: InteractionSource?,
309
+ ) : DrawModifierNode, DelegatingNode() {
310
+
311
+ override val shouldAutoInvalidate = false
312
+
313
+ var isFocused = false
314
+ set(value) {
315
+ if (field != value) {
316
+ field = value
317
+ if (value) {
318
+ contentColorNode.update(focusedContentColor)
319
+ startFocusAnimation()
320
+ } else {
321
+ contentColorNode.update(contentColor)
322
+ stopFocusAnimation()
323
+ }
324
+ // No need to invalidate the border cache - we build it ahead of time to account for
325
+ // focus changes. Just invalidate draw so we can switch to drawing the correct
326
+ // border.
327
+ invalidateDraw()
328
+ }
329
+ }
330
+
331
+ private val contentColorNode = delegate(ContentColorProviderNode(contentColor))
332
+
333
+ // Cache borders and highlight for unfocused and focused states. This means we
334
+ // can avoid recreating these for a given surface, if the border and shape never
335
+ // change. Changing between unfocused and focus states only requires a draw
336
+ // invalidation, as the borders are already cached.
337
+
338
+ // Focused border - this consists of two layers. A 'base' layer (which is the
339
+ // unfocused border with a different size) and the highlight we draw on top of
340
+ // this base layer. We need to increase the size of the underlying border to
341
+ // make sure that the highlight area fully matches the underlying border, to
342
+ // avoid inconsistent areas of coverage due to the transparency of the
343
+ // highlight.
344
+ private var borderLogic: BorderLogic? = null
345
+
346
+ // Border shader / brush
347
+ var borderShader: RuntimeShader? = null
348
+ var borderShaderBrush: Brush? = null
349
+
350
+ // Graphics Layer
351
+ private var blurGraphicsLayer: GraphicsLayer? = null
352
+
353
+ private var borderLayer: GraphicsLayer? = null
354
+ private var borderLayerProvider: (() -> GraphicsLayer)? = null
355
+
356
+ private var interactionCollectionJob: Job? = null
357
+ private var ambientMotionJob: Job? = null
358
+
359
+ private var _focusProgress: Animatable<Float, AnimationVector1D>? = null
360
+ private val focusProgress
361
+ get() = _focusProgress?.value ?: 0f
362
+
363
+ private var _ambientProgress: Animatable<Float, AnimationVector1D>? = null
364
+ private val ambientProgress
365
+ get() = _ambientProgress?.value ?: 0f
366
+
367
+ private var _pressedProgress: Animatable<Float, AnimationVector1D>? = null
368
+ private val pressedProgress
369
+ get() = _pressedProgress?.value ?: 0f
370
+
371
+ // Job that runs for a minimum duration to make sure quick presses are still visible
372
+ private var minimumPressDuration: Job? = null
373
+ private var pressReleaseAnimation: Job? = null
374
+
375
+ private var horizontalShader: RuntimeShader? = null
376
+ private var verticalShader: RuntimeShader? = null
377
+
378
+ private val cornerRadii = FloatArray(4)
379
+
380
+ private val focusedBorderColor0: Color = Color.White
381
+
382
+ private var focusedBorderColor1: Color = focusedColor.withTone(newTone = 85f)
383
+
384
+ private var focusedBorderColor2: Color = focusedColor.withTone(newTone = 69f)
385
+
386
+ private var focusedBorderColor3: Color = focusedColor.withTone(newTone = 77f)
387
+
388
+ fun update(
389
+ enabled: Boolean,
390
+ color: Color,
391
+ focusedColor: Color,
392
+ contentColor: Color,
393
+ focusedContentColor: Color,
394
+ shape: Shape,
395
+ interactionSource: InteractionSource?,
396
+ ) {
397
+ if (this.enabled != enabled) {
398
+ this.enabled = enabled
399
+ invalidateDraw()
400
+ }
401
+ if (this.color != color) {
402
+ this.color = color
403
+ invalidateDraw()
404
+ }
405
+ if (this.focusedColor != focusedColor) {
406
+ this.focusedColor = focusedColor
407
+ updateFocusedBorderColors(focusedColor)
408
+ invalidateDraw()
409
+ }
410
+ if (this.contentColor != contentColor) {
411
+ this.contentColor = contentColor
412
+ if (!isFocused) contentColorNode.update(contentColor)
413
+ }
414
+ if (this.focusedContentColor != focusedContentColor) {
415
+ this.focusedContentColor = focusedContentColor
416
+ if (isFocused) contentColorNode.update(focusedContentColor)
417
+ }
418
+ if (this.shape != shape) {
419
+ this.shape = shape
420
+ invalidateDraw()
421
+ }
422
+ if (this.interactionSource != interactionSource) {
423
+ this.interactionSource = interactionSource
424
+ observeInteractions()
425
+ }
426
+ }
427
+
428
+ override fun onAttach() {
429
+ observeInteractions()
430
+ }
431
+
432
+ var isPressed = false
433
+ set(value) {
434
+ if (field != value) {
435
+ field = value
436
+ if (value) {
437
+ _pressedProgress = _pressedProgress ?: Animatable(0f)
438
+ pressReleaseAnimation?.cancel()
439
+ minimumPressDuration?.cancel()
440
+
441
+ minimumPressDuration =
442
+ coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
443
+ delay(PressedMinimumDurationMillis)
444
+ }
445
+ coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
446
+ _pressedProgress?.animateTo(1f, PressedEnterAnimationSpec)
447
+ }
448
+ } else {
449
+ _pressedProgress?.let { progress ->
450
+ pressReleaseAnimation =
451
+ coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
452
+ minimumPressDuration?.join()
453
+ progress.animateTo(0f, PressedExitAnimationSpec)
454
+ }
455
+ }
456
+ }
457
+ invalidateDraw()
458
+ }
459
+ }
460
+
461
+ private fun observeInteractions() {
462
+ interactionCollectionJob?.cancel()
463
+ interactionCollectionJob = null
464
+ isFocused = false
465
+ isPressed = false
466
+ interactionSource?.let { source ->
467
+ interactionCollectionJob =
468
+ coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
469
+ var focusCount = 0
470
+ var pressCount = 0
471
+ source.interactions.collect { interaction ->
472
+ when (interaction) {
473
+ is FocusInteraction.Focus -> focusCount++
474
+ is FocusInteraction.Unfocus -> focusCount--
475
+ is PressInteraction.Press -> pressCount++
476
+ is PressInteraction.Release -> pressCount--
477
+ is PressInteraction.Cancel -> pressCount--
478
+ }
479
+ isFocused = focusCount > 0
480
+ isPressed = pressCount > 0
481
+ }
482
+ }
483
+ }
484
+ }
485
+
486
+ private fun startFocusAnimation() {
487
+ _focusProgress = _focusProgress ?: Animatable(0f)
488
+ _ambientProgress = _ambientProgress ?: Animatable(0f)
489
+
490
+ coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
491
+ _focusProgress?.animateTo(targetValue = 1f, animationSpec = FocusedEnterAnimationSpec)
492
+ }
493
+
494
+ ambientMotionJob?.cancel()
495
+ ambientMotionJob =
496
+ coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
497
+ // Delay after focus enter starts
498
+ delay(AmbientInitialDelayMillis)
499
+ while (isActive) {
500
+ _ambientProgress?.animateTo(
501
+ targetValue = 1f,
502
+ animationSpec = AmbientAnimationSpec,
503
+ )
504
+ _ambientProgress?.snapTo(0f)
505
+ delay(AmbientDelayMillis)
506
+ }
507
+ }
508
+ }
509
+
510
+ private fun stopFocusAnimation() {
511
+ ambientMotionJob?.cancel()
512
+ ambientMotionJob = null
513
+
514
+ coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
515
+ _ambientProgress?.animateTo(targetValue = 0f, animationSpec = FocusedExitAnimationSpec)
516
+ }
517
+
518
+ coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
519
+ _focusProgress?.animateTo(targetValue = 0f, animationSpec = FocusedExitAnimationSpec)
520
+ }
521
+ }
522
+
523
+ override fun ContentDrawScope.draw() {
524
+ val outline = shape.createOutline(size, layoutDirection, this)
525
+ val focusProgress = focusProgress
526
+ val ambientProgress = ambientProgress
527
+ val pressedProgress = pressedProgress
528
+
529
+ // RuntimeShader used for progressive blur requires at least API 33.
530
+ // Fall back to drawing a solid background and border on older API levels.
531
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
532
+ drawBlurredBackgroundAndBorder(outline, focusProgress, ambientProgress, pressedProgress)
533
+ } else {
534
+ drawBackgroundAndBorder(outline, focusProgress, pressedProgress)
535
+ }
536
+
537
+ drawContent()
538
+
539
+ if (!enabled) {
540
+ drawOutline(outline, color = DisabledOverlayColor)
541
+ }
542
+ }
543
+
544
+ /**
545
+ * Draws background, pressed overlay, and solid border for devices below API 33
546
+ * ([Build.VERSION_CODES.TIRAMISU]).
547
+ */
548
+ private fun DrawScope.drawBackgroundAndBorder(
549
+ outline: Outline,
550
+ focusProgress: Float,
551
+ pressedProgress: Float,
552
+ ) {
553
+ val backgroundColor = lerp(color, focusedColor, focusProgress)
554
+
555
+ // Pressed overlay transition: #FFFFFF with 16% opacity when pressed
556
+ val compositeBackground =
557
+ if (pressedProgress > 0f) {
558
+ val pressedOverlayColor =
559
+ PressedOverlayColor.copy(alpha = PressedOverlayAlpha * pressedProgress)
560
+ pressedOverlayColor.compositeOver(backgroundColor)
561
+ } else {
562
+ backgroundColor
563
+ }
564
+
565
+ drawOutline(outline, color = compositeBackground)
566
+
567
+ val borderLogic = borderLogic ?: BorderLogic().also { borderLogic = it }
568
+ val borderLayerProvider =
569
+ borderLayerProvider
570
+ ?: {
571
+ borderLayer
572
+ ?: requireGraphicsContext().createGraphicsLayer().also {
573
+ borderLayer = it
574
+ }
575
+ }
576
+ .also { borderLayerProvider = it }
577
+ val borderColor = lerp(DefaultSolidBorderIdleColor, focusedBorderColor1, focusProgress)
578
+
579
+ borderLogic.drawBorder(
580
+ this,
581
+ calculateSolidBorderWidth(),
582
+ SolidColor(borderColor),
583
+ borderLayerProvider,
584
+ outline,
585
+ )
586
+ }
587
+
588
+ /** Calculates solid border width for devices below API 33 ([Build.VERSION_CODES.TIRAMISU]). */
589
+ private fun DrawScope.calculateSolidBorderWidth(): Dp =
590
+ lerp(DefaultSurfaceBorderWidth, FocusedSurfaceBorderWidth, this@SurfaceNode.focusProgress)
591
+
592
+ /**
593
+ * Calculates AGSL shader border width for API 33 ([Build.VERSION_CODES.TIRAMISU]) and higher.
594
+ */
595
+ private fun DrawScope.calculateBorderWidth(): Dp =
596
+ lerp(
597
+ lerp(
598
+ DefaultSurfaceBorderWidth,
599
+ FocusedSurfaceBorderWidth,
600
+ this@SurfaceNode.focusProgress,
601
+ ),
602
+ (size.minDimension / 8f).toDp(),
603
+ this@SurfaceNode.pressedProgress,
604
+ )
605
+
606
+ /**
607
+ * Records the background + border into a [GraphicsLayer] and applies two-pass (horizontal then
608
+ * vertical) Gaussian blur.
609
+ */
610
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
611
+ private fun DrawScope.drawBlurredBackgroundAndBorder(
612
+ outline: Outline,
613
+ focusProgress: Float,
614
+ ambientProgress: Float,
615
+ pressedProgress: Float,
616
+ ) {
617
+ val width = size.width
618
+ val height = size.height
619
+
620
+ val idleBlurRadiusStartPx = IdleBlurRadiusStart.toPx()
621
+ val idleBlurRadiusEndPx = IdleBlurRadiusEnd.toPx()
622
+ val focusedBlurRadiusStartPx = FocusedBlurRadiusStart.toPx()
623
+ val focusedBlurRadiusEndPx = FocusedBlurRadiusEnd.toPx()
624
+ val ambientBlurRadiusMaxStartPx = AmbientBlurRadiusMaxStart.toPx()
625
+ val ambientBlurRadiusMaxEndPx = AmbientBlurRadiusMaxEnd.toPx()
626
+
627
+ if (outline is Outline.Rounded) {
628
+ val roundRect = outline.roundRect
629
+ cornerRadii[0] = roundRect.bottomRightCornerRadius.x
630
+ cornerRadii[1] = roundRect.topRightCornerRadius.x
631
+ cornerRadii[2] = roundRect.bottomLeftCornerRadius.x
632
+ cornerRadii[3] = roundRect.topLeftCornerRadius.x
633
+ } else {
634
+ cornerRadii[0] = 0f
635
+ cornerRadii[1] = 0f
636
+ cornerRadii[2] = 0f
637
+ cornerRadii[3] = 0f
638
+ }
639
+
640
+ val horizontalShader =
641
+ BlurShaderHelper.configureShader(
642
+ shader = this@SurfaceNode.horizontalShader,
643
+ isVertical = false,
644
+ width = width,
645
+ height = height,
646
+ cornerRadii = cornerRadii,
647
+ focusProgress = focusProgress,
648
+ ambientProgress = ambientProgress,
649
+ pressedProgress = pressedProgress,
650
+ idleBlurRadiusStartPx = idleBlurRadiusStartPx,
651
+ idleBlurRadiusEndPx = idleBlurRadiusEndPx,
652
+ focusedBlurRadiusStartPx = focusedBlurRadiusStartPx,
653
+ focusedBlurRadiusEndPx = focusedBlurRadiusEndPx,
654
+ ambientBlurRadiusMaxStartPx = ambientBlurRadiusMaxStartPx,
655
+ ambientBlurRadiusMaxEndPx = ambientBlurRadiusMaxEndPx,
656
+ )
657
+ .also { this@SurfaceNode.horizontalShader = it }
658
+
659
+ val verticalShader =
660
+ BlurShaderHelper.configureShader(
661
+ shader = this@SurfaceNode.verticalShader,
662
+ isVertical = true,
663
+ width = width,
664
+ height = height,
665
+ cornerRadii = cornerRadii,
666
+ focusProgress = focusProgress,
667
+ ambientProgress = ambientProgress,
668
+ pressedProgress = pressedProgress,
669
+ idleBlurRadiusStartPx = idleBlurRadiusStartPx,
670
+ idleBlurRadiusEndPx = idleBlurRadiusEndPx,
671
+ focusedBlurRadiusStartPx = focusedBlurRadiusStartPx,
672
+ focusedBlurRadiusEndPx = focusedBlurRadiusEndPx,
673
+ ambientBlurRadiusMaxStartPx = ambientBlurRadiusMaxStartPx,
674
+ ambientBlurRadiusMaxEndPx = ambientBlurRadiusMaxEndPx,
675
+ )
676
+ .also { this@SurfaceNode.verticalShader = it }
677
+
678
+ val horizontalEffect =
679
+ RenderEffect.createRuntimeShaderEffect(horizontalShader, UniformContent)
680
+ val verticalEffect = RenderEffect.createRuntimeShaderEffect(verticalShader, UniformContent)
681
+ val renderEffect = RenderEffect.createChainEffect(verticalEffect, horizontalEffect)
682
+
683
+ val blurLayer =
684
+ blurGraphicsLayer
685
+ ?: requireGraphicsContext().createGraphicsLayer().also { blurGraphicsLayer = it }
686
+
687
+ blurLayer.renderEffect = renderEffect.asComposeRenderEffect()
688
+ blurLayer.record(
689
+ density = Density(density, fontScale),
690
+ layoutDirection = layoutDirection,
691
+ size = IntSize(ceil(size.width).toInt(), ceil(size.height).toInt()),
692
+ ) {
693
+ drawShaderBackgroundAndBorder(outline, focusProgress, ambientProgress)
694
+ }
695
+
696
+ drawLayer(blurLayer)
697
+ }
698
+
699
+ /** Draws the background and the AGSL shader border for API 33+ */
700
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
701
+ private fun DrawScope.drawShaderBackgroundAndBorder(
702
+ outline: Outline,
703
+ focusProgress: Float,
704
+ ambientProgress: Float,
705
+ ) {
706
+ val backgroundColor = lerp(color, focusedColor, focusProgress)
707
+ drawOutline(outline, backgroundColor)
708
+
709
+ borderShader =
710
+ BorderShaderHelper.configureShader(
711
+ shader = borderShader,
712
+ size = size,
713
+ focusProgress = focusProgress,
714
+ ambientProgress = ambientProgress,
715
+ focusedBorderColor0 = focusedBorderColor0,
716
+ focusedBorderColor1 = focusedBorderColor1,
717
+ focusedBorderColor2 = focusedBorderColor2,
718
+ focusedBorderColor3 = focusedBorderColor3,
719
+ )
720
+ val borderShaderBrush =
721
+ borderShaderBrush ?: ShaderBrush(borderShader!!).also { borderShaderBrush = it }
722
+ val borderLogic = borderLogic ?: BorderLogic().also { borderLogic = it }
723
+ val borderLayerProvider =
724
+ borderLayerProvider
725
+ ?: {
726
+ borderLayer
727
+ ?: requireGraphicsContext().createGraphicsLayer().also {
728
+ borderLayer = it
729
+ }
730
+ }
731
+ .also { borderLayerProvider = it }
732
+ borderLogic.drawBorder(
733
+ this,
734
+ calculateBorderWidth(),
735
+ borderShaderBrush,
736
+ borderLayerProvider,
737
+ outline,
738
+ )
739
+ }
740
+
741
+ override fun onDetach() {
742
+ _focusProgress = null
743
+ _ambientProgress = null
744
+ _pressedProgress = null
745
+ ambientMotionJob?.cancel()
746
+ ambientMotionJob = null
747
+
748
+ val gContext = requireGraphicsContext()
749
+
750
+ borderLayerProvider = null
751
+ borderLayer?.let {
752
+ gContext.releaseGraphicsLayer(it)
753
+ borderLayer = null
754
+ }
755
+
756
+ blurGraphicsLayer?.let {
757
+ gContext.releaseGraphicsLayer(it)
758
+ blurGraphicsLayer = null
759
+ }
760
+
761
+ horizontalShader = null
762
+ verticalShader = null
763
+ }
764
+
765
+ private fun updateFocusedBorderColors(focusedColor: Color) {
766
+ focusedBorderColor1 = focusedColor.withTone(newTone = 85f)
767
+ focusedBorderColor2 = focusedColor.withTone(newTone = 69f)
768
+ focusedBorderColor3 = focusedColor.withTone(newTone = 77f)
769
+ }
770
+ }
771
+
772
+ /**
773
+ * Renders and animates a [surface]'s [depthEffect] for a given [shape], by observing
774
+ * [interactionSource].
775
+ */
776
+ @Composable
777
+ private fun Modifier.surfaceDepthEffect(
778
+ depthEffect: SurfaceDepthEffect?,
779
+ shape: Shape,
780
+ interactionSource: InteractionSource?,
781
+ ): Modifier {
782
+ if (depthEffect == null) return this
783
+ val focusedProgress = remember { Animatable(0f) }
784
+ // If focused and there is focused depth effect, we need to draw the surface on top of
785
+ // other siblings to make sure the depth effect occludes siblings.
786
+ val zIndex by remember {
787
+ // Derived to avoid invalidating layout each frame of the animation
788
+ derivedStateOf {
789
+ if (depthEffect.focusedDepthEffect != null && focusedProgress.value >= 0.5f) 1f else 0f
790
+ }
791
+ }
792
+ if (interactionSource != null) {
793
+ LaunchedEffect(interactionSource) {
794
+ interactionSource.interactions.collect { interaction ->
795
+ when (interaction) {
796
+ is FocusInteraction.Focus ->
797
+ launch(start = CoroutineStart.UNDISPATCHED) {
798
+ focusedProgress.animateTo(1f, FocusedEnterAnimationSpec)
799
+ }
800
+
801
+ is FocusInteraction.Unfocus ->
802
+ launch(start = CoroutineStart.UNDISPATCHED) {
803
+ focusedProgress.animateTo(0f, FocusedExitAnimationSpec)
804
+ }
805
+ }
806
+ }
807
+ }
808
+ }
809
+
810
+ return layout { measurable, constraints ->
811
+ val placeable = measurable.measure(constraints)
812
+ layout(placeable.width, placeable.height) { placeable.place(0, 0, zIndex = zIndex) }
813
+ }
814
+ .depthEffect(
815
+ from = depthEffect.depthEffect,
816
+ to = depthEffect.focusedDepthEffect,
817
+ shape = shape,
818
+ progress = { focusedProgress.value },
819
+ )
820
+ }
821
+
822
+ /** String keys map directly to uniform parameter names inside the AGSL runtime shaders. */
823
+ private const val UniformResolution = "uResolution"
824
+ private const val UniformCornerRadii = "uCornerRadii"
825
+ private const val UniformFocusProgress = "uFocusProgress"
826
+ private const val UniformAmbientProgress = "uAmbientProgress"
827
+ private const val UniformPressedProgress = "uPressedProgress"
828
+ private const val UniformIdleStartRadius = "uIdleRadiusStart"
829
+ private const val UniformIdleEndRadius = "uIdleRadiusEnd"
830
+ private const val UniformFocusedStartRadius = "uFocusedRadiusStart"
831
+ private const val UniformFocusedEndRadius = "uFocusedRadiusEnd"
832
+ private const val UniformAmbientMaxStartRadius = "uAmbientMaxRadiusStart"
833
+ private const val UniformAmbientMaxEndRadius = "uAmbientMaxRadiusEnd"
834
+ private const val UniformContent = "uContent"
835
+
836
+ private const val UniformBorderFocusedColor0 = "uBorderFocusedColor0"
837
+ private const val UniformBorderFocusedColor1 = "uBorderFocusedColor1"
838
+ private const val UniformBorderFocusedColor2 = "uBorderFocusedColor2"
839
+ private const val UniformBorderFocusedColor3 = "uBorderFocusedColor3"
840
+
841
+ private const val SurfaceColorTone = 20f
842
+ private const val FocusedSurfaceColorTone = 34f
843
+
844
+ /** Default border width for a [surface]. */
845
+ private val DefaultSurfaceBorderWidth = 1.5.dp
846
+
847
+ /** Focused border width for a [surface]. */
848
+ private val FocusedSurfaceBorderWidth = 2.dp
849
+
850
+ /** Blur radii at the start / end of the blur gradient while the surface is idle (unfocused). */
851
+ private val IdleBlurRadiusStart = 2.dp
852
+ private val IdleBlurRadiusEnd = 8.dp
853
+
854
+ /** Blur radii at the start / end of the blur gradient while the surface is focused. */
855
+ private val FocusedBlurRadiusStart = 1.dp
856
+ private val FocusedBlurRadiusEnd = 3.dp
857
+
858
+ /** Maximum blur radii reached at the peak of the ambient motion */
859
+ private val AmbientBlurRadiusMaxStart = 5.3.dp
860
+ private val AmbientBlurRadiusMaxEnd = 15.9.dp
861
+
862
+ private val DefaultSolidBorderIdleColor = Color(0.25f, 0.25f, 0.25f, 0.5f)
863
+
864
+ /** Enter animation for focus highlight and depth effect */
865
+ private val FocusedEnterAnimationSpec: AnimationSpec<Float> =
866
+ tween(durationMillis = 800, easing = LinearOutSlowInEasing)
867
+
868
+ /** Exit animation for focus highlight and depth effect */
869
+ private val FocusedExitAnimationSpec: AnimationSpec<Float> =
870
+ tween(durationMillis = 500, easing = LinearOutSlowInEasing)
871
+
872
+ private val AmbientAnimationSpec: AnimationSpec<Float> =
873
+ tween(durationMillis = 2000, easing = LinearEasing)
874
+
875
+ private const val AmbientInitialDelayMillis = 1800L
876
+ private const val AmbientDelayMillis = 4000L
877
+
878
+ internal val DisabledOverlayColor = Color(0x8F191919)
879
+
880
+ private val PressedOverlayColor = Color.White
881
+ private const val PressedOverlayAlpha = 0.16f
882
+
883
+ private val PressedEnterAnimationSpec: AnimationSpec<Float> =
884
+ spring(dampingRatio = 0.84f, stiffness = 8000f)
885
+
886
+ private val PressedExitAnimationSpec: AnimationSpec<Float> =
887
+ spring(dampingRatio = 0.85f, stiffness = 50f)
888
+
889
+ private const val PressedMinimumDurationMillis = 300L
890
+
891
+ @Language(value = "AGSL")
892
+ private const val BorderShader =
893
+ """
894
+ uniform float2 uResolution;
895
+ uniform float uFocusProgress;
896
+ uniform float uAmbientProgress;
897
+
898
+ const half4 uBorderIdleColor0 = half4(0.81, 0.81, 0.81, 0.9); // Top-Left
899
+ const half4 uBorderIdleColor1 = half4(0.25, 0.25, 0.25, 0.5); // Top-Right
900
+ const half4 uBorderIdleColor2 = half4(0.16, 0.16, 0.16, 0.4); // Bottom-Right
901
+ const half4 uBorderIdleColor3 = half4(0.49, 0.49, 0.49, 0.7); // Bottom-Left
902
+
903
+ uniform half4 uBorderFocusedColor0;
904
+ uniform half4 uBorderFocusedColor1;
905
+ uniform half4 uBorderFocusedColor2;
906
+ uniform half4 uBorderFocusedColor3;
907
+
908
+ const half4 uAnimationPeakColor = half4(1.0, 1.0, 1.0, 1.0); // White
909
+
910
+ const float PI = 3.14159265;
911
+
912
+ half4 main(float2 fragCoord) {
913
+ // Compute ambient pulse
914
+ // Phase 1: Increase Ambient Pulse
915
+ // Linearly scales 0.0 -> 0.2835 onto a 0.0 -> 1.0 range.
916
+ float t1 = clamp(uAmbientProgress / 0.2835, 0.0, 1.0);
917
+
918
+ // Phase 2: Steady and then Ramp-Down
919
+ // Delays the fade-out until progress passes 0.375
920
+ // Uses the remaining 0.625 of the duration to smoothly decline back to 0.0.
921
+ float t2 = clamp(1.0 - (uAmbientProgress - 0.375) / 0.625, 0.0, 1.0);
922
+
923
+ // Uses t1 (ramp-up/hold) for the first 37.5%, then switches to t2 (ramp-down).
924
+ float ambientPulse = mix(t1, t2, step(0.375, uAmbientProgress));
925
+
926
+ // Compute focus pulse peaking animation peak color at exactly 0.5
927
+ float focusPulse = clamp(1.0 - 2.0 * abs(uFocusProgress - 0.5), 0.0, 1.0);
928
+
929
+ // Use maximum of focus and ambient pulses
930
+ float maxAnimationPeakPulse = max(ambientPulse, focusPulse);
931
+
932
+ half4 colors[4];
933
+ colors[0] = mix(mix(uBorderIdleColor0, uBorderFocusedColor0, uFocusProgress), uAnimationPeakColor, maxAnimationPeakPulse);
934
+ colors[1] = mix(mix(uBorderIdleColor1, uBorderFocusedColor1, uFocusProgress), uAnimationPeakColor, maxAnimationPeakPulse);
935
+ colors[2] = mix(mix(uBorderIdleColor2, uBorderFocusedColor2, uFocusProgress), uAnimationPeakColor, maxAnimationPeakPulse);
936
+ colors[3] = mix(mix(uBorderIdleColor3, uBorderFocusedColor3, uFocusProgress), uAnimationPeakColor, maxAnimationPeakPulse);
937
+
938
+ float w = uResolution.x;
939
+ float h = uResolution.y;
940
+
941
+ // Normalize coordinates to [-1, 1] relative to center
942
+ float nx = 2.0 * fragCoord.x / max(w, 1.0) - 1.0;
943
+ float ny = 2.0 * fragCoord.y / max(h, 1.0) - 1.0;
944
+
945
+ // Compute the angle of the current pixel relative to the center in radians [-PI, PI].
946
+ float theta = atan(ny, nx);
947
+
948
+ // Map the angle from [-PI, PI] to a clean [0.0, 1.0] percentage circle.
949
+ // The "+ 0.75 * PI" offsets the starting point (0.0) to the Top-Left corner.
950
+ float d_cw = fract((theta + 0.75 * PI) / (2.0 * PI));
951
+
952
+ // As focus progresses from 0 to 1, the gradient start position shifts from top left to top right
953
+ float d_start = mix(0.0, 0.25, uFocusProgress);
954
+
955
+ // Apply the rotation shift to the current pixel's position.
956
+ float d_rotated = mod(d_cw - d_start + 1.0, 1.0);
957
+ float dist = min(d_rotated, 1.0 - d_rotated);
958
+
959
+ // Branchless color interpolation using chained mixes
960
+ float factor0 = smoothstep(0.0, 0.1, dist);
961
+ float factor1 = smoothstep(0.1, 0.25, dist);
962
+ float factor2 = smoothstep(0.25, 0.4, dist);
963
+
964
+ half4 color = mix(colors[0], colors[1], factor0);
965
+ color = mix(color, colors[2], factor1);
966
+ color = mix(color, colors[3], factor2);
967
+
968
+ return color;
969
+ }
970
+ """
971
+
972
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
973
+ private object BlurShaderHelper {
974
+ @JvmStatic
975
+ fun configureShader(
976
+ shader: RuntimeShader?,
977
+ isVertical: Boolean,
978
+ width: Float,
979
+ height: Float,
980
+ cornerRadii: FloatArray,
981
+ focusProgress: Float,
982
+ ambientProgress: Float,
983
+ pressedProgress: Float,
984
+ idleBlurRadiusStartPx: Float,
985
+ idleBlurRadiusEndPx: Float,
986
+ focusedBlurRadiusStartPx: Float,
987
+ focusedBlurRadiusEndPx: Float,
988
+ ambientBlurRadiusMaxStartPx: Float,
989
+ ambientBlurRadiusMaxEndPx: Float,
990
+ ): RuntimeShader {
991
+ val runtimeShader = shader ?: RuntimeShader(getBlurShader(isVertical))
992
+ runtimeShader.setFloatUniform(UniformResolution, width, height)
993
+ runtimeShader.setFloatUniform(UniformCornerRadii, cornerRadii)
994
+ runtimeShader.setFloatUniform(UniformFocusProgress, focusProgress)
995
+ runtimeShader.setFloatUniform(UniformAmbientProgress, ambientProgress)
996
+ runtimeShader.setFloatUniform(UniformPressedProgress, pressedProgress)
997
+ runtimeShader.setFloatUniform(UniformIdleStartRadius, idleBlurRadiusStartPx)
998
+ runtimeShader.setFloatUniform(UniformIdleEndRadius, idleBlurRadiusEndPx)
999
+ runtimeShader.setFloatUniform(UniformFocusedStartRadius, focusedBlurRadiusStartPx)
1000
+ runtimeShader.setFloatUniform(UniformFocusedEndRadius, focusedBlurRadiusEndPx)
1001
+ runtimeShader.setFloatUniform(UniformAmbientMaxStartRadius, ambientBlurRadiusMaxStartPx)
1002
+ runtimeShader.setFloatUniform(UniformAmbientMaxEndRadius, ambientBlurRadiusMaxEndPx)
1003
+ return runtimeShader
1004
+ }
1005
+ }
1006
+
1007
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
1008
+ private object BorderShaderHelper {
1009
+ @JvmStatic
1010
+ fun configureShader(
1011
+ shader: RuntimeShader?,
1012
+ size: Size,
1013
+ focusProgress: Float,
1014
+ ambientProgress: Float,
1015
+ focusedBorderColor0: Color,
1016
+ focusedBorderColor1: Color,
1017
+ focusedBorderColor2: Color,
1018
+ focusedBorderColor3: Color,
1019
+ ): RuntimeShader {
1020
+ val runtimeShader = shader ?: RuntimeShader(BorderShader)
1021
+ runtimeShader.setFloatUniform(UniformResolution, size.width, size.height)
1022
+ runtimeShader.setFloatUniform(UniformFocusProgress, focusProgress)
1023
+ runtimeShader.setFloatUniform(UniformAmbientProgress, ambientProgress)
1024
+
1025
+ runtimeShader.setFloatUniform(
1026
+ UniformBorderFocusedColor0,
1027
+ focusedBorderColor0.red,
1028
+ focusedBorderColor0.green,
1029
+ focusedBorderColor0.blue,
1030
+ focusedBorderColor0.alpha,
1031
+ )
1032
+
1033
+ runtimeShader.setFloatUniform(
1034
+ UniformBorderFocusedColor1,
1035
+ focusedBorderColor1.red,
1036
+ focusedBorderColor1.green,
1037
+ focusedBorderColor1.blue,
1038
+ focusedBorderColor1.alpha,
1039
+ )
1040
+
1041
+ runtimeShader.setFloatUniform(
1042
+ UniformBorderFocusedColor2,
1043
+ focusedBorderColor2.red,
1044
+ focusedBorderColor2.green,
1045
+ focusedBorderColor2.blue,
1046
+ focusedBorderColor2.alpha,
1047
+ )
1048
+
1049
+ runtimeShader.setFloatUniform(
1050
+ UniformBorderFocusedColor3,
1051
+ focusedBorderColor3.red,
1052
+ focusedBorderColor3.green,
1053
+ focusedBorderColor3.blue,
1054
+ focusedBorderColor3.alpha,
1055
+ )
1056
+
1057
+ return runtimeShader
1058
+ }
1059
+ }
1060
+
1061
+ @Language(value = "AGSL")
1062
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
1063
+ private fun getBlurShader(isVertical: Boolean): String {
1064
+ val loopOffset =
1065
+ if (isVertical) "float2(0.0, i + weightH / weight)" else "float2(i + weightH / weight, 0.0)"
1066
+ val oddOffset = if (isVertical) "float2(0.0, r)" else "float2(r, 0.0)"
1067
+ return """
1068
+ uniform shader uContent;
1069
+
1070
+ uniform float2 uResolution;
1071
+ uniform float4 uCornerRadii;
1072
+ uniform float uFocusProgress;
1073
+ uniform float uAmbientProgress;
1074
+ uniform float uPressedProgress;
1075
+
1076
+ uniform float uIdleRadiusStart;
1077
+ uniform float uIdleRadiusEnd;
1078
+ uniform float uFocusedRadiusStart;
1079
+ uniform float uFocusedRadiusEnd;
1080
+ uniform float uAmbientMaxRadiusStart;
1081
+ uniform float uAmbientMaxRadiusEnd;
1082
+
1083
+ const float MAX_RADIUS = 120.0;
1084
+ const float PI = 3.14159265;
1085
+
1086
+ float sdRoundedBox(float2 p, float2 b, float4 r) {
1087
+ float2 sign_p = step(0.0, p);
1088
+ float2 r2 = mix(r.zw, r.xy, sign_p.x);
1089
+ float rad = mix(r2.y, r2.x, sign_p.y);
1090
+
1091
+ float2 q = abs(p) - b + rad;
1092
+ return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - rad;
1093
+ }
1094
+
1095
+ float gaussian(float x, float sigma) { return exp(-(x * x) / (2.0 * sigma * sigma)); }
1096
+
1097
+ float radiusAt(float2 coord, float2 start, float2 end, float r0, float r1) {
1098
+ float2 axis = end - start;
1099
+ float t = clamp(dot(coord - start, axis) / max(dot(axis, axis), 1.0), 0.0, 1.0);
1100
+ return mix(r0, r1, t);
1101
+ }
1102
+
1103
+ float4 blur(float2 coord, float radius) {
1104
+ float r = floor(radius);
1105
+ if (r < 1.0) return uContent.eval(coord);
1106
+ float sigma = max(radius / 2.0, 1.0);
1107
+ float2 center = uResolution / 2.0;
1108
+ float2 halfSize = uResolution / 2.0;
1109
+ float weightSum = 1.0;
1110
+ float limit = min(sigma * 3, MAX_RADIUS);
1111
+ float4 result = uContent.eval(coord);
1112
+ for (float i = 1.0; i < MAX_RADIUS; i += 2.0) {
1113
+ if (i >= limit) { break; }
1114
+ float weightL = gaussian(i, sigma);
1115
+ float weightH = gaussian(i + 1.0, sigma);
1116
+ float weight = weightL + weightH;
1117
+ float2 off = $loopOffset;
1118
+
1119
+ float2 c1 = coord - off;
1120
+ float d1 = sdRoundedBox(c1 - center, halfSize, uCornerRadii);
1121
+ float w1 = mix(weight, 0.0, step(0.0, d1));
1122
+ if (w1 > 0.0) {
1123
+ result += w1 * uContent.eval(c1);
1124
+ weightSum += w1;
1125
+ }
1126
+
1127
+ float2 c2 = coord + off;
1128
+ float d2 = sdRoundedBox(c2 - center, halfSize, uCornerRadii);
1129
+ float w2 = mix(weight, 0.0, step(0.0, d2));
1130
+ if (w2 > 0.0) {
1131
+ result += w2 * uContent.eval(c2);
1132
+ weightSum += w2;
1133
+ }
1134
+ }
1135
+ float oddMask = fract(r * 0.5) * 2.0 * (1.0 - step(MAX_RADIUS, r));
1136
+ float ow = gaussian(r, sigma) * oddMask;
1137
+
1138
+ if (ow > 0.0) {
1139
+ float2 oo = $oddOffset;
1140
+ float2 o1 = coord - oo;
1141
+ float2 o2 = coord + oo;
1142
+ result += ow * uContent.eval(o1) + ow * uContent.eval(o2);
1143
+ weightSum += 2.0 * ow;
1144
+ }
1145
+ return result / weightSum;
1146
+ }
1147
+
1148
+ half4 main(float2 coord) {
1149
+ // Compute ambient pulse
1150
+ // Phase 1: Increase Ambient Pulse
1151
+ // Linearly scales 0.0 -> 0.2835 onto a 0.0 -> 1.0 range.
1152
+ float t1 = clamp(uAmbientProgress / 0.2835, 0.0, 1.0);
1153
+
1154
+ // Phase 2: Steady and then Ramp-Down
1155
+ // Delays the fade-out until progress passes 0.375
1156
+ // Uses the remaining 0.625 of the duration to smoothly decline back to 0.0.
1157
+ float t2 = clamp(1.0 - (uAmbientProgress - 0.375) / 0.625, 0.0, 1.0);
1158
+
1159
+ // Uses t1 (ramp-up/hold) for the first 37.5%, then switches to t2 (ramp-down).
1160
+ float pulse = mix(t1, t2, step(0.375, uAmbientProgress));
1161
+
1162
+ // Mix focused and ambient blurs based on the pulse
1163
+ float focusedBlurStart = mix(uFocusedRadiusStart, uAmbientMaxRadiusStart, pulse);
1164
+ float focusedBlurEnd = mix(uFocusedRadiusEnd, uAmbientMaxRadiusEnd, pulse);
1165
+
1166
+ // Interpolate between idle and focused/ambient blur ranges
1167
+ float blur0 = mix(uIdleRadiusStart, focusedBlurStart, uFocusProgress);
1168
+ float blur1 = mix(uIdleRadiusEnd, focusedBlurEnd, uFocusProgress);
1169
+
1170
+ // Enforce massive uniform during press interaction
1171
+ float minDimension = min(uResolution.x, uResolution.y);
1172
+ float pressedBlur = minDimension / 3.0;
1173
+ blur0 = mix(blur0, pressedBlur, uPressedProgress);
1174
+ blur1 = mix(blur1, pressedBlur, uPressedProgress);
1175
+
1176
+ float centerX = uResolution.x / 2.0;
1177
+ float centerY = uResolution.y / 2.0;
1178
+
1179
+ // Start position of blur shifts across the width based on focus progress
1180
+ float startX = uFocusProgress * uResolution.x;
1181
+ float startY = 0.0;
1182
+ float endX = uResolution.x / 2.0;
1183
+ float endY = uResolution.y;
1184
+
1185
+ // Anchor coordinates for rotational animation phase
1186
+ float baseStartX = uResolution.x;
1187
+ float baseStartY = 0.0;
1188
+ float baseEndX = uResolution.x / 2.0;
1189
+ float baseEndY = uResolution.y;
1190
+
1191
+ // Drive a full circle around the center for ambient motion
1192
+ float angle = uAmbientProgress * PI * 2;
1193
+ float cosA = cos(angle);
1194
+ float sinA = sin(angle);
1195
+
1196
+ // Calculate start and end rotation X and Y
1197
+ float rotStartX = centerX + (baseStartX - centerX) * cosA - (baseStartY - centerY) * sinA;
1198
+ float rotStartY = centerY + (baseStartX - centerX) * sinA + (baseStartY - centerY) * cosA;
1199
+ float rotEndX = centerX + (baseEndX - centerX) * cosA - (baseEndY - centerY) * sinA;
1200
+ float rotEndY = centerY + (baseEndX - centerX) * sinA + (baseEndY - centerY) * cosA;
1201
+
1202
+ float2 finalStart = mix(float2(startX, startY), float2(rotStartX, rotStartY), uFocusProgress);
1203
+ float2 finalEnd = mix(float2(endX, endY), float2(rotEndX, rotEndY), uFocusProgress);
1204
+
1205
+ return blur(coord, radiusAt(coord, finalStart, finalEnd, blur0, blur1));
1206
+ }
1207
+ """
1208
+ }
1209
+ ```
1210
+
1211
+ <br />