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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +24 -0
- package/README.md +175 -168
- package/THIRD_PARTY_NOTICES.md +224 -4
- package/agents/android-architect.md +89 -0
- package/agents/android-performance-engineer.md +92 -0
- package/agents/android-test-engineer.md +97 -0
- package/agents/flutter-test-engineer.md +2 -0
- package/agents/ios-architect.md +93 -0
- package/agents/ios-performance-engineer.md +92 -0
- package/agents/ios-test-engineer.md +95 -0
- package/agents/lead-mobile.md +10 -1
- package/agents/mobile-staff-engineer.md +1 -1
- package/agents/rn-architect.md +94 -0
- package/agents/rn-performance-engineer.md +92 -0
- package/agents/rn-test-engineer.md +98 -0
- package/bin/reis-mobile.mjs +59 -17
- package/commands/debate.md +10 -1
- package/commands/debug.md +60 -0
- package/commands/reis-mobile.md +7 -2
- package/core/config/project.mjs +59 -0
- package/core/registry/frontmatter.mjs +15 -4
- package/core/registry/registry.mjs +21 -1
- package/core/router/intents.mjs +2 -0
- package/core/router/router.mjs +10 -5
- package/package.json +1 -1
- package/skills/android-adaptive/SKILL.md +304 -0
- package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/container-behavior.md +112 -0
- package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/get-started.md +69 -0
- package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/index.md +81 -0
- package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/flexbox/item-behavior.md +170 -0
- package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/container-properties.md +415 -0
- package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/get-started.md +51 -0
- package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/index.md +73 -0
- package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/grid/item-properties.md +170 -0
- package/skills/android-adaptive/references/android/develop/ui/compose/layouts/adaptive/mediaquery/index.md +333 -0
- package/skills/android-adaptive/references/android/develop/ui/compose/tooling/debug.md +114 -0
- package/skills/android-adaptive/references/android/guide/navigation/navigation-3/recipes/material-listdetail.md +141 -0
- package/skills/android-agp-9-upgrade/SKILL.md +106 -0
- package/skills/android-agp-9-upgrade/references/android/build/migrate-to-built-in-kotlin.md +416 -0
- package/skills/android-agp-9-upgrade/references/android/build/releases/agp-9-0-0-release-notes.md +554 -0
- package/skills/android-agp-9-upgrade/references/buildconfig.md +54 -0
- package/skills/android-agp-9-upgrade/references/ksp-kapt.md +39 -0
- package/skills/android-agp-9-upgrade/references/paparazzi-gradle-9.md +30 -0
- package/skills/android-agp-9-upgrade/references/recipes.md +62 -0
- package/skills/android-appfunctions/SKILL.md +74 -0
- package/skills/android-appfunctions/references/adb-interaction-testing.md +95 -0
- package/skills/android-appfunctions/references/context.md +26 -0
- package/skills/android-appfunctions/references/feature-discovery-analysis.md +71 -0
- package/skills/android-appfunctions/references/implementation-configuration.md +282 -0
- package/skills/android-appfunctions/references/kdoc-refinement-optimization.md +75 -0
- package/skills/android-appfunctions/references/migrate-to-service-entry-point.md +226 -0
- package/skills/android-camerax/SKILL.md +135 -0
- package/skills/android-camerax/references/camera1-to-camerax.md +198 -0
- package/skills/android-camerax/references/camera2-to-camerax.md +193 -0
- package/skills/android-camerax/references/expert-blueprints.md +58 -0
- package/skills/android-camerax/references/foldables.md +83 -0
- package/skills/android-camerax/references/immutability.md +60 -0
- package/skills/android-camerax/references/low-light.md +124 -0
- package/skills/android-camerax/references/mlkit-spatial.md +75 -0
- package/skills/android-camerax/references/modern-apis.md +42 -0
- package/skills/android-camerax/references/testing.md +69 -0
- package/skills/android-camerax/references/thermals.md +85 -0
- package/skills/android-camerax/references/wear-os.md +76 -0
- package/skills/android-camerax/references/xr.md +54 -0
- package/skills/android-cli/SKILL.md +474 -0
- package/skills/android-cli/references/interact.md +113 -0
- package/skills/android-cli/references/journeys.md +120 -0
- package/skills/android-code-review/SKILL.md +58 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/SKILL.md +340 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/design/ui/ai-glasses/guides/interaction/inputs.md +67 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/access-hardware-projected-context.md +388 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/buttons.md +139 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/cards.md +148 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/focus.md +107 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/icons.md +90 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/text.md +69 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/jetpack-compose-glimmer/title-chips.md +82 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/android/develop/xr/jetpack-xr-sdk/request-hardware-permissions.md +213 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/button-samples-source.md +176 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/button-source.md +203 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/card-samples-source.md +296 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/card-source.md +515 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/deptheffect-source.md +224 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/deptheffectlevels-source.md +105 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/glimmersansflextypography-source.md +270 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/glimmertheme-source.md +182 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/icon-source.md +388 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/iconbutton-source.md +126 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/iconsizes-source.md +78 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/list-source.md +27 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/listitem-source.md +272 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/liststate-source.md +427 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/material-hct-source.md +217 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/projectedcontext-source.md +265 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/stack-source.md +368 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/stackitemscope-source.md +440 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/stackstate-source.md +325 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/surface-source.md +1211 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/titlechip-samples-source.md +101 -0
- package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/titlechip-source.md +131 -0
- package/skills/android-edge-to-edge/SKILL.md +429 -0
- package/skills/android-engage-sdk-integration/SKILL.md +130 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/faq.md +439 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/food.md +1149 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/healthandfitness.md +1055 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/listen.md +1050 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/otherverticals.md +1301 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/read.md +910 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/shopping.md +1224 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/social.md +719 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/travel.md +1413 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/tv/continue-watching/index.md +19 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/tv/entitlements.md +353 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/tv/getting-started.md +202 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/tv/recommendations.md +471 -0
- package/skills/android-engage-sdk-integration/references/android/guide/playcore/engage/watch.md +1081 -0
- package/skills/android-engage-sdk-integration/references/clusters.md +154 -0
- package/skills/android-engage-sdk-integration/references/common.md +519 -0
- package/skills/android-engage-sdk-integration/references/patterns.md +527 -0
- package/skills/android-engage-sdk-integration/references/requests.md +260 -0
- package/skills/android-engage-sdk-integration/references/schemas/food.md +509 -0
- package/skills/android-engage-sdk-integration/references/schemas/listen.md +842 -0
- package/skills/android-engage-sdk-integration/references/schemas/other.md +220 -0
- package/skills/android-engage-sdk-integration/references/schemas/read.md +412 -0
- package/skills/android-engage-sdk-integration/references/schemas/shopping.md +357 -0
- package/skills/android-engage-sdk-integration/references/schemas/social.md +228 -0
- package/skills/android-engage-sdk-integration/references/schemas/travel.md +923 -0
- package/skills/android-engage-sdk-integration/references/schemas/tv.md +1226 -0
- package/skills/android-engage-sdk-integration/references/schemas/watch.md +1137 -0
- package/skills/android-engage-sdk-integration/references/troubleshooting.md +39 -0
- package/skills/android-gradle-build-debug/SKILL.md +71 -0
- package/skills/android-intent-security/SKILL.md +550 -0
- package/skills/android-leanback-to-compose-tv-migration/SKILL.md +743 -0
- package/skills/android-leanback-to-compose-tv-migration/references/android/training/tv/get-started/navigation.md +372 -0
- package/skills/android-leanback-to-compose-tv-migration/references/android/training/tv/playback/compose/browse.md +243 -0
- package/skills/android-leanback-to-compose-tv-migration/references/android/training/tv/playback/compose/index.md +153 -0
- package/skills/android-media3-cast-integration/SKILL.md +236 -0
- package/skills/android-media3-cast-integration/references/android/media/media3/cast/create-castplayer.md +390 -0
- package/skills/android-media3-cast-integration/references/android/media/media3/cast/customize-castoptions.md +93 -0
- package/skills/android-media3-cast-integration/references/android/media/media3/cast/index.md +21 -0
- package/skills/android-migrate-xml-views-to-jetpack-compose/SKILL.md +127 -0
- package/skills/android-migrate-xml-views-to-jetpack-compose/references/analysis-of-the-project-and-layout.md +42 -0
- package/skills/android-migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/designsystems/migrate-xml-theme-to-compose.md +171 -0
- package/skills/android-migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/compose-in-views.md +299 -0
- package/skills/android-migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/migrate/interoperability-apis/views-in-compose.md +286 -0
- package/skills/android-migrate-xml-views-to-jetpack-compose/references/android/develop/ui/compose/setup-compose-dependencies-and-compiler.md +197 -0
- package/skills/android-migrate-xml-views-to-jetpack-compose/references/identify-optimal-xml-candidate.md +31 -0
- package/skills/android-migrate-xml-views-to-jetpack-compose/references/xml-layout-migration.md +86 -0
- package/skills/android-ml-kit-genai-prompt-api/SKILL.md +124 -0
- package/skills/android-ml-kit-genai-prompt-api/references/get-started.md +272 -0
- package/skills/android-ml-kit-genai-prompt-api/references/prefix-caching.md +146 -0
- package/skills/android-ml-kit-genai-prompt-api/references/prompt-design.md +55 -0
- package/skills/android-ml-kit-genai-prompt-api/references/prompt-optimization.md +74 -0
- package/skills/android-ml-kit-genai-prompt-api/references/structured-output.md +219 -0
- package/skills/android-ml-kit-genai-prompt-api/references/system-instructions.md +71 -0
- package/skills/android-navigation-3/SKILL.md +122 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/index.md +51 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/migration-guide.md +670 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/animations.md +147 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/basic.md +89 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/basicdsl.md +85 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/basicsaveable.md +90 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/bottomsheet.md +195 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/common-ui.md +200 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/conditional-transitions.md +182 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/conditional.md +230 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/deeplinks-custommatcher.md +176 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/deeplinks-staticuri.md +140 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/deeplinks-syntheticbackstack.md +184 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/dialog.md +107 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/lifecycle-owner.md +185 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/material-listdetail.md +141 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/material-supportingpane.md +145 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/modular-hilt.md +283 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/modular-koin.md +287 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/multiple-backstacks.md +468 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/passingarguments.md +371 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/results-event.md +273 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/results-state.md +266 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/scenes-listdetail.md +435 -0
- package/skills/android-navigation-3/references/android/guide/navigation/navigation-3/recipes/scenes-twopane.md +244 -0
- package/skills/android-navigation-3/references/android/guide/navigation/type-safe-destinations.md +129 -0
- package/skills/android-navigation-event/SKILL.md +312 -0
- package/skills/android-navigation-event/references/android/guide/navigation/navigation-event/dispatcher.md +134 -0
- package/skills/android-navigation-event/references/android/guide/navigation/navigation-event/handle-back.md +217 -0
- package/skills/android-navigation-event/references/android/guide/navigation/navigation-event/index.md +83 -0
- package/skills/android-navigation-event/references/android/guide/navigation/navigation-event/setup.md +32 -0
- package/skills/android-play-billing-library-version-upgrade/SKILL.md +96 -0
- package/skills/android-play-billing-library-version-upgrade/references/android/google/play/billing/release-notes.md +1240 -0
- package/skills/android-play-billing-library-version-upgrade/references/migration-logic.md +78 -0
- package/skills/android-play-billing-library-version-upgrade/references/version-checklist.md +54 -0
- package/skills/android-play-policy-insights/SKILL.md +172 -0
- package/skills/android-play-policy-insights/resources/common_mandates.md +100 -0
- package/skills/android-play-policy-insights/resources/compliance_report_template.md +59 -0
- package/skills/android-play-policy-insights/resources/critic.md +86 -0
- package/skills/android-play-policy-insights/resources/goal_data_safety.md +212 -0
- package/skills/android-play-policy-insights/resources/goal_permissions_and_apis.md +459 -0
- package/skills/android-play-policy-insights/resources/goal_user_account.md +106 -0
- package/skills/android-play-policy-insights/resources/policies.json +321 -0
- package/skills/android-play-policy-insights/resources/scanner_config.json +843 -0
- package/skills/android-play-policy-insights/scripts/generate_report.py +860 -0
- package/skills/android-play-policy-insights/scripts/orchestrator.py +1313 -0
- package/skills/android-play-policy-insights/scripts/play_store_scraper.py +410 -0
- package/skills/android-play-policy-insights/scripts/scanner.py +261 -0
- package/skills/android-play-policy-insights/scripts/template_engine.py +243 -0
- package/skills/android-profiler/SKILL.md +60 -0
- package/skills/android-profiler/analysis/analysis_orchestrator.md +32 -0
- package/skills/android-profiler/analysis/workflows/perfetto-sql/perfetto_sql.md +28 -0
- package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/perfetto_trace_analysis.md +121 -0
- package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/guiding_principles.md +51 -0
- package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/domain_hints_template.md +7 -0
- package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_cpu.md +56 -0
- package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_graphics.md +51 -0
- package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_io.md +46 -0
- package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_ipc.md +41 -0
- package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_memory.md +55 -0
- package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/hints/subsystems/hints_power.md +33 -0
- package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/per_candidate_analysis.md +172 -0
- package/skills/android-profiler/analysis/workflows/perfetto-trace-analysis/references/triage.md +94 -0
- package/skills/android-profiler/recording/recording_orchestrator.md +36 -0
- package/skills/android-profiler/recording/workflows/perfetto-trace-recording/perfetto_trace_recording.md +148 -0
- package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/app_jank.pftxt +43 -0
- package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/cpu_profile.pftxt +35 -0
- package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/java_heap_dump.pftxt +21 -0
- package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/long_background.pftxt +37 -0
- package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/memory_counters.pftxt +45 -0
- package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/native_heap.pftxt +21 -0
- package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/example-configs/sched_cpu.pftxt +33 -0
- package/skills/android-profiler/recording/workflows/perfetto-trace-recording/references/trace_config_reference.md +102 -0
- package/skills/android-profiler/references/env_setup.md +15 -0
- package/skills/android-profiler/references/perfetto/setup.md +29 -0
- package/skills/android-profiler/references/perfetto/sql.md +327 -0
- package/skills/android-r8-analyzer/SKILL.md +72 -0
- package/skills/android-r8-analyzer/references/CONFIGURATION-ANALYZER.md +287 -0
- package/skills/android-r8-analyzer/references/CONFIGURATION.md +44 -0
- package/skills/android-r8-analyzer/references/KEEP-RULES-IMPACT-HIERARCHY.md +83 -0
- package/skills/android-r8-analyzer/references/REDUNDANT-RULES.md +222 -0
- package/skills/android-r8-analyzer/references/REFLECTION-GUIDE.md +139 -0
- package/skills/android-r8-analyzer/references/REPORT_FORMAT.md +51 -0
- package/skills/android-r8-analyzer/references/android/topic/performance/app-optimization/enable-app-optimization.md +252 -0
- package/skills/android-r8-analyzer/references/android/training/testing/other-components/ui-automator.md +312 -0
- package/skills/android-restore-credentials/SKILL.md +359 -0
- package/skills/android-restore-credentials/references/android/guide/topics/manifest/application-element.md +832 -0
- package/skills/android-restore-credentials/references/android/identity/passkeys/create-passkeys.md +327 -0
- package/skills/android-restore-credentials/references/android/identity/passkeys/sign-in-with-passkeys.md +219 -0
- package/skills/android-styles/SKILL.md +229 -0
- package/skills/android-styles/references/android/develop/ui/compose/designsystems/custom.md +459 -0
- package/skills/android-styles/references/android/develop/ui/compose/styles/fundamentals.md +421 -0
- package/skills/android-styles/references/android/develop/ui/compose/styles/state-animations.md +461 -0
- package/skills/android-styles/references/android/develop/ui/compose/styles/styles-vs-modifiers.md +48 -0
- package/skills/android-styles/references/android/develop/ui/compose/styles/theming.md +269 -0
- package/skills/android-verified-email/SKILL.md +432 -0
- package/skills/android-verified-email/references/android/identity/credential-manager/index.md +67 -0
- package/skills/android-verified-email/references/android/identity/digital-credentials/credential-verifier.md +166 -0
- package/skills/android-verified-email/references/android/identity/digital-credentials/email-verification-implementation.md +313 -0
- package/skills/android-verified-email/references/android/identity/digital-credentials/email-verification.md +158 -0
- package/skills/android-verified-email/references/android/identity/digital-credentials/index.md +112 -0
- package/skills/android-verified-email/references/android/identity/passkeys/create-passkeys.md +327 -0
- package/skills/android-verified-email/references/android/identity/sign-in/credential-manager-webview.md +84 -0
- package/skills/android-wear-compose-m3/SKILL.md +365 -0
- package/skills/android-wear-compose-m3/references/android/training/wearables/compose/migrate-to-material3.md +677 -0
- package/skills/android-wear-compose-m3/references/foundation/AmbientModeSample.kt.md +123 -0
- package/skills/android-wear-compose-m3/references/foundation/AmbientModeSample.kt.md.txt +123 -0
- package/skills/android-wear-compose-m3/references/foundation/CurvedWorldSample.kt.md +430 -0
- package/skills/android-wear-compose-m3/references/foundation/CurvedWorldSample.kt.md.txt +430 -0
- package/skills/android-wear-compose-m3/references/foundation/ExpandableSample.kt.md +99 -0
- package/skills/android-wear-compose-m3/references/foundation/ExpandableSample.kt.md.txt +99 -0
- package/skills/android-wear-compose-m3/references/foundation/HierarchicalFocusSample.kt.md +141 -0
- package/skills/android-wear-compose-m3/references/foundation/HierarchicalFocusSample.kt.md.txt +141 -0
- package/skills/android-wear-compose-m3/references/foundation/PagerSamples.kt.md +56 -0
- package/skills/android-wear-compose-m3/references/foundation/PagerSamples.kt.md.txt +56 -0
- package/skills/android-wear-compose-m3/references/foundation/RotarySamples.kt.md +153 -0
- package/skills/android-wear-compose-m3/references/foundation/RotarySamples.kt.md.txt +153 -0
- package/skills/android-wear-compose-m3/references/foundation/ScalingLazyColumnSample.kt.md +131 -0
- package/skills/android-wear-compose-m3/references/foundation/ScalingLazyColumnSample.kt.md.txt +131 -0
- package/skills/android-wear-compose-m3/references/foundation/SwipeToDismissBoxSample.kt.md +169 -0
- package/skills/android-wear-compose-m3/references/foundation/SwipeToDismissBoxSample.kt.md.txt +169 -0
- package/skills/android-wear-compose-m3/references/foundation/SwipeToRevealSample.kt.md +282 -0
- package/skills/android-wear-compose-m3/references/foundation/SwipeToRevealSample.kt.md.txt +282 -0
- package/skills/android-wear-compose-m3/references/foundation/TransformingLazyColumnSample.kt.md +335 -0
- package/skills/android-wear-compose-m3/references/foundation/TransformingLazyColumnSample.kt.md.txt +335 -0
- package/skills/android-wear-compose-m3/references/material3/AlertDialogSample.kt.md +377 -0
- package/skills/android-wear-compose-m3/references/material3/AlertDialogSample.kt.md.txt +377 -0
- package/skills/android-wear-compose-m3/references/material3/AnimatedTextSample.kt.md +165 -0
- package/skills/android-wear-compose-m3/references/material3/AnimatedTextSample.kt.md.txt +165 -0
- package/skills/android-wear-compose-m3/references/material3/ButtonGroupSample.kt.md +91 -0
- package/skills/android-wear-compose-m3/references/material3/ButtonGroupSample.kt.md.txt +91 -0
- package/skills/android-wear-compose-m3/references/material3/ButtonSample.kt.md +496 -0
- package/skills/android-wear-compose-m3/references/material3/ButtonSample.kt.md.txt +496 -0
- package/skills/android-wear-compose-m3/references/material3/CardSample.kt.md +470 -0
- package/skills/android-wear-compose-m3/references/material3/CardSample.kt.md.txt +470 -0
- package/skills/android-wear-compose-m3/references/material3/CheckboxButtonSample.kt.md +67 -0
- package/skills/android-wear-compose-m3/references/material3/CheckboxButtonSample.kt.md.txt +67 -0
- package/skills/android-wear-compose-m3/references/material3/ConfirmationDialogSample.kt.md +162 -0
- package/skills/android-wear-compose-m3/references/material3/ConfirmationDialogSample.kt.md.txt +162 -0
- package/skills/android-wear-compose-m3/references/material3/CurvedTextSamples.kt.md +78 -0
- package/skills/android-wear-compose-m3/references/material3/CurvedTextSamples.kt.md.txt +78 -0
- package/skills/android-wear-compose-m3/references/material3/DatePickerSample.kt.md +131 -0
- package/skills/android-wear-compose-m3/references/material3/DatePickerSample.kt.md.txt +131 -0
- package/skills/android-wear-compose-m3/references/material3/DynamicColorSchemeSample.kt.md +74 -0
- package/skills/android-wear-compose-m3/references/material3/DynamicColorSchemeSample.kt.md.txt +74 -0
- package/skills/android-wear-compose-m3/references/material3/EdgeButtonSample.kt.md +63 -0
- package/skills/android-wear-compose-m3/references/material3/EdgeButtonSample.kt.md.txt +63 -0
- package/skills/android-wear-compose-m3/references/material3/FadingExpandingLabelSample.kt.md +60 -0
- package/skills/android-wear-compose-m3/references/material3/FadingExpandingLabelSample.kt.md.txt +60 -0
- package/skills/android-wear-compose-m3/references/material3/IconButtonSample.kt.md +125 -0
- package/skills/android-wear-compose-m3/references/material3/IconButtonSample.kt.md.txt +125 -0
- package/skills/android-wear-compose-m3/references/material3/IconToggleButtonSample.kt.md +116 -0
- package/skills/android-wear-compose-m3/references/material3/IconToggleButtonSample.kt.md.txt +116 -0
- package/skills/android-wear-compose-m3/references/material3/LevelIndicatorSample.kt.md +68 -0
- package/skills/android-wear-compose-m3/references/material3/LevelIndicatorSample.kt.md.txt +68 -0
- package/skills/android-wear-compose-m3/references/material3/LinearProgressIndicatorSample.kt.md +50 -0
- package/skills/android-wear-compose-m3/references/material3/LinearProgressIndicatorSample.kt.md.txt +50 -0
- package/skills/android-wear-compose-m3/references/material3/ListHeaderSample.kt.md +177 -0
- package/skills/android-wear-compose-m3/references/material3/ListHeaderSample.kt.md.txt +177 -0
- package/skills/android-wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md +651 -0
- package/skills/android-wear-compose-m3/references/material3/OneHandedGestureSamples.kt.md.txt +651 -0
- package/skills/android-wear-compose-m3/references/material3/OpenOnPhoneDialogSample.kt.md +57 -0
- package/skills/android-wear-compose-m3/references/material3/OpenOnPhoneDialogSample.kt.md.txt +57 -0
- package/skills/android-wear-compose-m3/references/material3/PageIndicatorSample.kt.md +110 -0
- package/skills/android-wear-compose-m3/references/material3/PageIndicatorSample.kt.md.txt +110 -0
- package/skills/android-wear-compose-m3/references/material3/PagerScaffoldSample.kt.md +204 -0
- package/skills/android-wear-compose-m3/references/material3/PagerScaffoldSample.kt.md.txt +204 -0
- package/skills/android-wear-compose-m3/references/material3/PickerGroupSample.kt.md +139 -0
- package/skills/android-wear-compose-m3/references/material3/PickerGroupSample.kt.md.txt +139 -0
- package/skills/android-wear-compose-m3/references/material3/PickerSample.kt.md +111 -0
- package/skills/android-wear-compose-m3/references/material3/PickerSample.kt.md.txt +111 -0
- package/skills/android-wear-compose-m3/references/material3/PlaceholderSample.kt.md +184 -0
- package/skills/android-wear-compose-m3/references/material3/PlaceholderSample.kt.md.txt +184 -0
- package/skills/android-wear-compose-m3/references/material3/ProgressIndicatorSample.kt.md +263 -0
- package/skills/android-wear-compose-m3/references/material3/ProgressIndicatorSample.kt.md.txt +263 -0
- package/skills/android-wear-compose-m3/references/material3/RadioButtonSample.kt.md +105 -0
- package/skills/android-wear-compose-m3/references/material3/RadioButtonSample.kt.md.txt +105 -0
- package/skills/android-wear-compose-m3/references/material3/ScaffoldSample.kt.md +146 -0
- package/skills/android-wear-compose-m3/references/material3/ScaffoldSample.kt.md.txt +146 -0
- package/skills/android-wear-compose-m3/references/material3/ScrollAwaySample.kt.md +97 -0
- package/skills/android-wear-compose-m3/references/material3/ScrollAwaySample.kt.md.txt +97 -0
- package/skills/android-wear-compose-m3/references/material3/ScrollIndicatorSample.kt.md +66 -0
- package/skills/android-wear-compose-m3/references/material3/ScrollIndicatorSample.kt.md.txt +66 -0
- package/skills/android-wear-compose-m3/references/material3/SliderSample.kt.md +86 -0
- package/skills/android-wear-compose-m3/references/material3/SliderSample.kt.md.txt +86 -0
- package/skills/android-wear-compose-m3/references/material3/StepperSample.kt.md +148 -0
- package/skills/android-wear-compose-m3/references/material3/StepperSample.kt.md.txt +148 -0
- package/skills/android-wear-compose-m3/references/material3/SurfaceTransformationSample.kt.md +166 -0
- package/skills/android-wear-compose-m3/references/material3/SurfaceTransformationSample.kt.md.txt +166 -0
- package/skills/android-wear-compose-m3/references/material3/SwipeToDismissBoxSample.kt.md +172 -0
- package/skills/android-wear-compose-m3/references/material3/SwipeToDismissBoxSample.kt.md.txt +172 -0
- package/skills/android-wear-compose-m3/references/material3/SwipeToRevealSample.kt.md +367 -0
- package/skills/android-wear-compose-m3/references/material3/SwipeToRevealSample.kt.md.txt +367 -0
- package/skills/android-wear-compose-m3/references/material3/SwitchButtonSample.kt.md +66 -0
- package/skills/android-wear-compose-m3/references/material3/SwitchButtonSample.kt.md.txt +66 -0
- package/skills/android-wear-compose-m3/references/material3/TextButtonSample.kt.md +115 -0
- package/skills/android-wear-compose-m3/references/material3/TextButtonSample.kt.md.txt +115 -0
- package/skills/android-wear-compose-m3/references/material3/TextToggleButtonSample.kt.md +71 -0
- package/skills/android-wear-compose-m3/references/material3/TextToggleButtonSample.kt.md.txt +71 -0
- package/skills/android-wear-compose-m3/references/material3/TimePickerSample.kt.md +151 -0
- package/skills/android-wear-compose-m3/references/material3/TimePickerSample.kt.md.txt +151 -0
- package/skills/android-wear-compose-m3/references/material3/TimeTextSample.kt.md +65 -0
- package/skills/android-wear-compose-m3/references/material3/TimeTextSample.kt.md.txt +65 -0
- package/skills/android-wear-compose-m3/references/material3/TransformationSpecSample.kt.md +246 -0
- package/skills/android-wear-compose-m3/references/material3/TransformationSpecSample.kt.md.txt +246 -0
- package/skills/android-wear-compose-m3/references/material3/TransformingLazyColumnNotificationsSample.kt.md +89 -0
- package/skills/android-wear-compose-m3/references/material3/TransformingLazyColumnNotificationsSample.kt.md.txt +89 -0
- package/skills/android-wear-compose-m3/references/material3/TransformingLazyColumnSample.kt.md +309 -0
- package/skills/android-wear-compose-m3/references/material3/TransformingLazyColumnSample.kt.md.txt +309 -0
- package/skills/android-wear-compose-m3/references/navigation3/SwipeDismissableSceneStrategySample.kt.md +205 -0
- package/skills/android-wear-compose-m3/references/navigation3/SwipeDismissableSceneStrategySample.kt.md.txt +205 -0
- package/skills/{architecture-feature-first → flutter-architecture-feature-first}/SKILL.md +1 -1
- package/skills/{bloc → flutter-bloc}/SKILL.md +1 -1
- package/skills/flutter-build-debug/SKILL.md +66 -0
- package/skills/{code-review → flutter-code-review}/SKILL.md +1 -1
- package/skills/{dart-3-updates → flutter-dart-3-updates}/SKILL.md +1 -1
- package/skills/{dart-fix-runtime-errors → flutter-dart-fix-runtime-errors}/SKILL.md +1 -1
- package/skills/{dart-resolve-package-conflicts → flutter-dart-resolve-package-conflicts}/SKILL.md +1 -1
- package/skills/{dart-run-static-analysis → flutter-dart-run-static-analysis}/SKILL.md +1 -1
- package/skills/{dart-use-pattern-matching → flutter-dart-use-pattern-matching}/SKILL.md +1 -1
- package/skills/{effective-dart → flutter-effective-dart}/SKILL.md +1 -1
- package/skills/{provider → flutter-provider}/SKILL.md +1 -1
- package/skills/{riverpod → flutter-riverpod}/SKILL.md +1 -1
- package/skills/ios-cocoapods-debug/SKILL.md +65 -0
- package/skills/ios-code-review/SKILL.md +56 -0
- package/skills/ios-xcode-build-debug/SKILL.md +66 -0
- package/skills/{detect-mobile-stack → mobile-detect-stack}/SKILL.md +1 -1
- package/skills/mobile-firebase/SKILL.md +54 -0
- package/skills/mobile-firebase/references/android/ai-logic.md +31 -0
- package/skills/mobile-firebase/references/android/analytics.md +33 -0
- package/skills/mobile-firebase/references/android/app-check.md +36 -0
- package/skills/mobile-firebase/references/android/auth.md +44 -0
- package/skills/mobile-firebase/references/android/crashlytics.md +31 -0
- package/skills/mobile-firebase/references/android/firestore.md +44 -0
- package/skills/mobile-firebase/references/android/functions.md +33 -0
- package/skills/mobile-firebase/references/android/in-app-messaging.md +28 -0
- package/skills/mobile-firebase/references/android/messaging.md +41 -0
- package/skills/mobile-firebase/references/android/realtime-database.md +50 -0
- package/skills/mobile-firebase/references/android/remote-config.md +43 -0
- package/skills/mobile-firebase/references/android/sql-connect.md +31 -0
- package/skills/mobile-firebase/references/android/storage.md +39 -0
- package/skills/mobile-firebase/references/android.md +30 -0
- package/skills/{firebase-ai/SKILL.md → mobile-firebase/references/flutter/ai-logic.md} +2 -8
- package/skills/{firebase-analytics/SKILL.md → mobile-firebase/references/flutter/analytics.md} +2 -8
- package/skills/{firebase-app-check/SKILL.md → mobile-firebase/references/flutter/app-check.md} +2 -8
- package/skills/{firebase-auth/SKILL.md → mobile-firebase/references/flutter/auth.md} +2 -8
- package/skills/{firebase-crashlytics/SKILL.md → mobile-firebase/references/flutter/crashlytics.md} +2 -8
- package/skills/{firebase-cloud-firestore/SKILL.md → mobile-firebase/references/flutter/firestore.md} +2 -8
- package/skills/{firebase-cloud-functions/SKILL.md → mobile-firebase/references/flutter/functions.md} +2 -8
- package/skills/{firebase-in-app-messaging/SKILL.md → mobile-firebase/references/flutter/in-app-messaging.md} +2 -8
- package/skills/{firebase-messaging/SKILL.md → mobile-firebase/references/flutter/messaging.md} +2 -8
- package/skills/{firebase-database/SKILL.md → mobile-firebase/references/flutter/realtime-database.md} +2 -8
- package/skills/{firebase-remote-config/SKILL.md → mobile-firebase/references/flutter/remote-config.md} +2 -8
- package/skills/{flutterfire-configure/SKILL.md → mobile-firebase/references/flutter/setup-flutterfire.md} +1 -8
- package/skills/{firebase-data-connect/SKILL.md → mobile-firebase/references/flutter/sql-connect.md} +2 -8
- package/skills/{firebase-storage/SKILL.md → mobile-firebase/references/flutter/storage.md} +2 -8
- package/skills/mobile-firebase/references/flutter.md +36 -0
- package/skills/mobile-firebase/references/ios/ai-logic.md +30 -0
- package/skills/mobile-firebase/references/ios/analytics.md +33 -0
- package/skills/mobile-firebase/references/ios/app-check.md +36 -0
- package/skills/mobile-firebase/references/ios/auth.md +48 -0
- package/skills/mobile-firebase/references/ios/crashlytics.md +34 -0
- package/skills/mobile-firebase/references/ios/firestore.md +53 -0
- package/skills/mobile-firebase/references/ios/functions.md +35 -0
- package/skills/mobile-firebase/references/ios/in-app-messaging.md +26 -0
- package/skills/mobile-firebase/references/ios/messaging.md +45 -0
- package/skills/mobile-firebase/references/ios/realtime-database.md +49 -0
- package/skills/mobile-firebase/references/ios/remote-config.md +44 -0
- package/skills/mobile-firebase/references/ios/sql-connect.md +33 -0
- package/skills/mobile-firebase/references/ios/storage.md +36 -0
- package/skills/mobile-firebase/references/ios.md +28 -0
- package/skills/mobile-firebase/references/react-native/ai-logic.md +32 -0
- package/skills/mobile-firebase/references/react-native/analytics.md +43 -0
- package/skills/mobile-firebase/references/react-native/app-check.md +35 -0
- package/skills/mobile-firebase/references/react-native/auth.md +50 -0
- package/skills/mobile-firebase/references/react-native/crashlytics.md +36 -0
- package/skills/mobile-firebase/references/react-native/firestore.md +45 -0
- package/skills/mobile-firebase/references/react-native/functions.md +28 -0
- package/skills/mobile-firebase/references/react-native/in-app-messaging.md +27 -0
- package/skills/mobile-firebase/references/react-native/messaging.md +46 -0
- package/skills/mobile-firebase/references/react-native/realtime-database.md +46 -0
- package/skills/mobile-firebase/references/react-native/remote-config.md +32 -0
- package/skills/mobile-firebase/references/react-native/sql-connect.md +11 -0
- package/skills/mobile-firebase/references/react-native/storage.md +30 -0
- package/skills/mobile-firebase/references/react-native.md +29 -0
- package/skills/mobile-test/SKILL.md +63 -0
- package/skills/mobile-test/references/android/setup/GUIDE.md +183 -0
- package/skills/mobile-test/references/android/setup/references/android/develop/ui/compose/testing/common-patterns.md +181 -0
- package/skills/mobile-test/references/android/setup/references/android/studio/preview/compose-screenshot-testing.md +255 -0
- package/skills/mobile-test/references/android/setup/references/android/training/dependency-injection/hilt-testing.md +447 -0
- package/skills/mobile-test/references/android.md +83 -0
- package/skills/{dart-collect-coverage/SKILL.md → mobile-test/references/flutter/coverage.md} +2 -11
- package/skills/{dart-generate-test-mocks/SKILL.md → mobile-test/references/flutter/generate-mocks.md} +2 -11
- package/skills/{flutter-add-integration-test/SKILL.md → mobile-test/references/flutter/integration-tests.md} +2 -11
- package/skills/{dart-migrate-to-checks-package/SKILL.md → mobile-test/references/flutter/migrate-to-checks.md} +2 -13
- package/skills/{mockito/SKILL.md → mobile-test/references/flutter/mockito.md} +1 -8
- package/skills/{mocktail/SKILL.md → mobile-test/references/flutter/mocktail.md} +1 -8
- package/skills/{patrol-e2e-testing/SKILL.md → mobile-test/references/flutter/patrol.md} +1 -8
- package/skills/{testing/SKILL.md → mobile-test/references/flutter/testing-guidelines.md} +1 -8
- package/skills/{dart-add-unit-test/SKILL.md → mobile-test/references/flutter/unit-tests.md} +2 -11
- package/skills/{flutter-add-widget-test/SKILL.md → mobile-test/references/flutter/widget-tests.md} +2 -11
- package/skills/mobile-test/references/flutter.md +44 -0
- package/skills/mobile-test/references/ios.md +79 -0
- package/skills/mobile-test/references/react-native.md +62 -0
- /package/skills/{dart-use-pattern-matching → flutter-dart-use-pattern-matching}/examples/json_patterns.dart +0 -0
package/skills/android-display-glasses-with-jetpack-compose-glimmer/references/surface-source.md
ADDED
|
@@ -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 />
|