peachsim 0.1.940 → 0.1.1399
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/README.md +28 -22
- package/cli/app-catalog.ts +29 -14
- package/cli/app-fonts.ts +5 -3
- package/cli/bridge-flow-runner.ts +40 -29
- package/cli/cloud-client.ts +57 -21
- package/cli/cloud-test-tunnel.ts +12 -18
- package/cli/commands/agent.ts +1 -1
- package/cli/commands/app-fonts.ts +3 -3
- package/cli/commands/assert.ts +8 -8
- package/cli/commands/auth.ts +8 -3
- package/cli/commands/box/bundle-plane.ts +13 -1
- package/cli/commands/box.ts +16 -5
- package/cli/commands/compat.ts +1 -1
- package/cli/commands/config.ts +2 -2
- package/cli/commands/control.ts +125 -71
- package/cli/commands/cpu-profile.ts +45 -1
- package/cli/commands/daemon.ts +131 -33
- package/cli/commands/debug.ts +24 -17
- package/cli/commands/desktop.ts +7 -1
- package/cli/commands/detect.ts +101 -0
- package/cli/commands/detox.ts +6 -6
- package/cli/commands/device.ts +12 -12
- package/cli/commands/electron.ts +32 -14
- package/cli/commands/flow.ts +49 -41
- package/cli/commands/guest-host.ts +39 -1
- package/cli/commands/inspect/core.ts +14 -8
- package/cli/commands/inspect/list.ts +1 -1
- package/cli/commands/inspect/settle.ts +5 -3
- package/cli/commands/inspect/settling.ts +229 -44
- package/cli/commands/inspect/wait-idle.ts +3 -2
- package/cli/commands/inspect.ts +105 -93
- package/cli/commands/install-cli.ts +36 -15
- package/cli/commands/install-desktop.ts +68 -195
- package/cli/commands/kotlin.ts +6 -5
- package/cli/commands/login.ts +5 -5
- package/cli/commands/maestro-compare.ts +554 -0
- package/cli/commands/maestro-generate.ts +6 -3
- package/cli/commands/maestro.ts +22 -0
- package/cli/commands/mode.ts +2 -2
- package/cli/commands/no-bridge-hint.ts +6 -2
- package/cli/commands/platform.ts +35 -6
- package/cli/commands/preview.ts +6 -0
- package/cli/commands/project-report.ts +10 -12
- package/cli/commands/record.ts +37 -37
- package/cli/commands/runtime.ts +9 -9
- package/cli/commands/screenshot-layers.ts +4 -4
- package/cli/commands/screenshot-mode.ts +2 -2
- package/cli/commands/screenshot.ts +8 -8
- package/cli/commands/screenshots-capture.ts +2 -2
- package/cli/commands/serve.ts +9 -12
- package/cli/commands/setup.ts +29 -14
- package/cli/commands/shell-boolean-mode.ts +2 -2
- package/cli/commands/show-touches-mode.ts +2 -2
- package/cli/commands/skills.ts +11 -11
- package/cli/commands/swift.ts +29 -11
- package/cli/commands/telemetry.ts +2 -2
- package/cli/commands/test.ts +5 -0
- package/cli/commands/three-mode.ts +3 -3
- package/cli/commands/timeline.ts +4 -4
- package/cli/commands/upgrade.ts +2 -2
- package/cli/commands/upload.ts +15 -15
- package/cli/commands/what-happened.ts +2 -2
- package/cli/current-sim.ts +6 -3
- package/cli/desktop-companion.ts +55 -149
- package/cli/dev-build-freshness.ts +2 -2
- package/cli/drivers/desktop.ts +17 -9
- package/cli/drivers/electron.ts +1 -2
- package/cli/drivers/playwright-sim-host.ts +11 -2
- package/cli/drivers/playwright.ts +35 -2
- package/cli/drivers/types.ts +8 -0
- package/cli/maestro-compare.ts +670 -0
- package/cli/outbound-endpoints.ts +46 -8
- package/cli/recording-access.ts +6 -6
- package/cli/run-registry.ts +1 -1
- package/cli/runtime-compat.ts +5 -1
- package/cli/send-to-box.ts +3 -3
- package/cli/shell-init.ts +2 -1
- package/cli/ws-bridge.ts +9 -5
- package/detox/element-types.ts +1 -0
- package/detox/gestures.ts +3 -3
- package/detox/index.ts +67 -38
- package/detox/navigation-lifecycle.ts +9 -6
- package/detox/proof-frame.cjs +6 -4
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +42 -17
- package/dist-lib/bridge-contract-input.mjs +41 -16
- package/dist-lib/bridge-contract.cjs +1 -4
- package/dist-lib/bridge-contract.mjs +1 -3
- package/dist-lib/bundle-channel.cjs +2918 -550
- package/dist-lib/capture-contract.cjs +65 -24
- package/dist-lib/capture-contract.mjs +63 -24
- package/dist-lib/cloud-contract.cjs +2 -2
- package/dist-lib/cloud-contract.mjs +2 -2
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +3 -3
- package/dist-lib/cloud.mjs +3 -3
- package/dist-lib/config.cjs +15 -5
- package/dist-lib/config.mjs +154 -0
- package/dist-lib/detox/index.cjs +495 -139
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/expo-swift-bundle.cjs +1168 -238
- package/dist-lib/expo-swift-metadata.cjs +34 -23
- package/dist-lib/expo-swift-metadata.mjs +32 -23
- package/dist-lib/home-paths.cjs +2 -2
- package/dist-lib/host/bridge-host.cjs +2938 -654
- package/dist-lib/host/fetch-proxy-handler.cjs +14 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +7 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +5 -1
- package/dist-lib/host/replacement-module-handler.cjs +2 -2
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +981 -353
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/kotlin-host.cjs +332 -19
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +7 -7
- package/dist-lib/metro-production-bundle.mjs +5 -5
- package/dist-lib/metro.cjs +670 -250
- package/dist-lib/native-app-identity-project.cjs +2647 -0
- package/dist-lib/native-app-identity-project.mjs +1257 -0
- package/dist-lib/native-app-identity.cjs +304 -25
- package/dist-lib/native-app-identity.mjs +302 -25
- package/dist-lib/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.mjs +1 -1
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +5 -1
- package/dist-lib/react-native-host-modules.mjs +5 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +57 -32
- package/dist-lib/sdk.cjs +231 -51
- package/dist-lib/sdk.mjs +231 -51
- package/dist-lib/skills.cjs +4107 -1374
- package/dist-lib/swift-compile.cjs +290 -30
- package/dist-lib/swift-compile.mjs +290 -30
- package/dist-lib/swift-package.cjs +143 -12
- package/dist-lib/swift-package.mjs +137 -11
- package/dist-lib/swift-submit.cjs +47 -5
- package/dist-lib/swift-submit.mjs +42 -5
- package/dist-lib/swift-toolchain.cjs +7 -2
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +1392 -458
- package/dist-lib/vite.cjs +13 -17
- package/dist-lib/worklet-executable.cjs +248 -33
- package/dist-lib/worklet-executable.mjs +248 -33
- package/install.sh +10 -1
- package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Resources.kt +9 -9
- package/kotlin/AndroidViewSources/android/graphics/Canvas.kt +20 -0
- package/kotlin/AndroidViewSources/android/view/View.kt +211 -0
- package/kotlin/AndroidViewSources/android/widget/FrameLayout.kt +61 -0
- package/kotlin/AndroidViewSources/android/widget/LinearLayout.kt +49 -0
- package/kotlin/AppSources/android/view/Window.kt +36 -0
- package/kotlin/AppSources/android/view/WindowInsets.kt +9 -0
- package/kotlin/AppSources/android/view/WindowInsetsController.kt +5 -0
- package/kotlin/AppSources/android/window/OnBackInvokedDispatcher.kt +48 -0
- package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcher.kt +20 -5
- package/kotlin/AppSources/androidx/compose/foundation/layout/WindowInsets.kt +15 -0
- package/kotlin/AppSources/androidx/compose/ui/platform/ComposeView.kt +2 -1
- package/kotlin/AppSources/dev/peach/app/PeachApp.kt +42 -50
- package/kotlin/ComposeSources/PeachComposeHost.kt +200 -84
- package/kotlin/ExpoComposeSources/PeachAndroidView.kt +87 -0
- package/kotlin/ExpoComposeSources/PeachExpoViewMount.kt +24 -0
- package/kotlin/ExpoComposeSources/dev/peach/expo/PeachExpoCompose.kt +28 -0
- package/kotlin/ExpoComposeSources/expo/modules/kotlin/views/ExpoComposeView.kt +70 -0
- package/kotlin/ExpoSources/dev/peach/expo/PeachExpoRuntime.kt +513 -0
- package/kotlin/ExpoSources/dev/peach/expo/PeachExpoTypes.kt +173 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/AppContext.kt +20 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/Promise.kt +19 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/classcomponent/ClassComponentBuilder.kt +198 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/exception/CodedException.kt +126 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/functions/Functions.kt +132 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/jni/JavaScriptTypedArray.kt +81 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/modules/Module.kt +98 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +236 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/records/Record.kt +12 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/runtime/Runtime.kt +9 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObject.kt +50 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObjectRegistry.kt +57 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedRef.kt +19 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/ConcreteTypedArrays.kt +158 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/GenericTypedArray.kt +8 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArray.kt +29 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArrayIterator.kt +9 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/types/AnyType.kt +155 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/types/Either.kt +91 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/types/Enumerable.kt +6 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/viewevent/ViewEvent.kt +54 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/views/ExpoView.kt +23 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewDefinitionBuilder.kt +71 -0
- package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewProps.kt +35 -0
- package/kotlin/README.md +13 -3
- package/kotlin/Sources/android/os/Build.kt +17 -0
- package/kotlin/Sources/android/os/Handler.kt +35 -0
- package/kotlin/Sources/android/os/Looper.kt +17 -0
- package/kotlin/Sources/android/os/MessageQueue.kt +56 -0
- package/kotlin/Sources/android/os/SystemClock.kt +14 -0
- package/kotlin/Sources/android/util/Log.kt +64 -0
- package/kotlin/Sources/java/security/MessageDigest.kt +114 -0
- package/kotlin/Sources/kotlin/text/StringFormat.kt +9 -0
- package/kotlin/Sources/org/json/JSON.kt +44 -0
- package/kotlin/fixtures/counter/Counter.d.kt.ts +1 -0
- package/kotlin/fixtures/counter/Counter.kt +10 -3
- package/kotlin/fixtures/counter/CounterApp.native.tsx +1 -1
- package/kotlin/fixtures/counter/vite.config.ts +2 -2
- package/kotlin/fixtures/headless-counter/Headless.kt +7 -6
- package/kotlin/fixtures/import-animation/Animation.d.kt.ts +7 -0
- package/kotlin/fixtures/import-animation/Animation.kt +80 -0
- package/kotlin/fixtures/import-animation/ImportAnimation.native.tsx +13 -0
- package/kotlin/fixtures/import-animation/README.md +24 -0
- package/kotlin/fixtures/import-animation/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-animation/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-animation/app/routes.d.ts +18 -0
- package/kotlin/fixtures/import-animation/package.json +23 -0
- package/kotlin/fixtures/import-animation/tsconfig.json +20 -0
- package/kotlin/fixtures/import-animation/vite.config.ts +20 -0
- package/kotlin/fixtures/import-editor/Editor.d.kt.ts +7 -0
- package/kotlin/fixtures/import-editor/Editor.kt +59 -0
- package/kotlin/fixtures/import-editor/ImportEditor.native.tsx +23 -0
- package/kotlin/fixtures/import-editor/README.md +27 -0
- package/kotlin/fixtures/import-editor/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-editor/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-editor/package.json +23 -0
- package/kotlin/fixtures/import-editor/tsconfig.json +20 -0
- package/kotlin/fixtures/import-editor/vite.config.ts +20 -0
- package/kotlin/fixtures/import-effects/Effects.d.kt.ts +7 -0
- package/kotlin/fixtures/import-effects/Effects.kt +87 -0
- package/kotlin/fixtures/import-effects/ImportEffects.native.tsx +13 -0
- package/kotlin/fixtures/import-effects/README.md +19 -0
- package/kotlin/fixtures/import-effects/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-effects/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-effects/package.json +23 -0
- package/kotlin/fixtures/import-effects/tsconfig.json +20 -0
- package/kotlin/fixtures/import-effects/vite.config.ts +20 -0
- package/kotlin/fixtures/import-layout/ImportLayout.native.tsx +13 -0
- package/kotlin/fixtures/import-layout/Layout.d.kt.ts +7 -0
- package/kotlin/fixtures/import-layout/Layout.kt +72 -0
- package/kotlin/fixtures/import-layout/README.md +24 -0
- package/kotlin/fixtures/import-layout/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-layout/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-layout/package.json +23 -0
- package/kotlin/fixtures/import-layout/tsconfig.json +20 -0
- package/kotlin/fixtures/import-layout/vite.config.ts +20 -0
- package/kotlin/fixtures/import-navigation/ImportNavigation.native.tsx +18 -0
- package/kotlin/fixtures/import-navigation/Navigation.d.kt.ts +7 -0
- package/kotlin/fixtures/import-navigation/Navigation.kt +104 -0
- package/kotlin/fixtures/import-navigation/README.md +31 -0
- package/kotlin/fixtures/import-navigation/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-navigation/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-navigation/package.json +23 -0
- package/kotlin/fixtures/import-navigation/tsconfig.json +20 -0
- package/kotlin/fixtures/import-navigation/vite.config.ts +20 -0
- package/kotlin/fixtures/import-overlays/ImportOverlays.native.tsx +29 -0
- package/kotlin/fixtures/import-overlays/Overlays.d.kt.ts +7 -0
- package/kotlin/fixtures/import-overlays/Overlays.kt +93 -0
- package/kotlin/fixtures/import-overlays/README.md +34 -0
- package/kotlin/fixtures/import-overlays/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-overlays/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-overlays/package.json +23 -0
- package/kotlin/fixtures/import-overlays/tsconfig.json +20 -0
- package/kotlin/fixtures/import-overlays/vite.config.ts +20 -0
- package/kotlin/fixtures/import-scroll/ImportScroll.native.tsx +13 -0
- package/kotlin/fixtures/import-scroll/README.md +23 -0
- package/kotlin/fixtures/import-scroll/Scroll.d.kt.ts +7 -0
- package/kotlin/fixtures/import-scroll/Scroll.kt +91 -0
- package/kotlin/fixtures/import-scroll/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-scroll/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-scroll/package.json +23 -0
- package/kotlin/fixtures/import-scroll/tsconfig.json +20 -0
- package/kotlin/fixtures/import-scroll/vite.config.ts +20 -0
- package/kotlin/fixtures/import-selection/ImportSelection.native.tsx +44 -0
- package/kotlin/fixtures/import-selection/README.md +32 -0
- package/kotlin/fixtures/import-selection/Selection.d.kt.ts +8 -0
- package/kotlin/fixtures/import-selection/Selection.kt +78 -0
- package/kotlin/fixtures/import-selection/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-selection/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-selection/package.json +23 -0
- package/kotlin/fixtures/import-selection/tsconfig.json +20 -0
- package/kotlin/fixtures/import-selection/vite.config.ts +20 -0
- package/kotlin/fixtures/import-surfaces/ImportSurfaces.native.tsx +13 -0
- package/kotlin/fixtures/import-surfaces/README.md +22 -0
- package/kotlin/fixtures/import-surfaces/Surfaces.d.kt.ts +7 -0
- package/kotlin/fixtures/import-surfaces/Surfaces.kt +64 -0
- package/kotlin/fixtures/import-surfaces/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-surfaces/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-surfaces/package.json +23 -0
- package/kotlin/fixtures/import-surfaces/tsconfig.json +20 -0
- package/kotlin/fixtures/import-surfaces/vite.config.ts +20 -0
- package/kotlin/fixtures/import-values/ImportValues.native.tsx +35 -0
- package/kotlin/fixtures/import-values/README.md +23 -0
- package/kotlin/fixtures/import-values/Values.d.kt.ts +11 -0
- package/kotlin/fixtures/import-values/Values.kt +66 -0
- package/kotlin/fixtures/import-values/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-values/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-values/package.json +23 -0
- package/kotlin/fixtures/import-values/tsconfig.json +20 -0
- package/kotlin/fixtures/import-values/vite.config.ts +20 -0
- package/kotlin/fixtures/kotlin-app-variants/BarBehavior.kt +38 -0
- package/kotlin/fixtures/kotlin-app-variants/BarVisibility.kt +36 -0
- package/kotlin/fixtures/kotlin-app-variants/Ime.kt +3 -1
- package/kotlin/fixtures/kotlin-app-variants/TransientInsets.kt +50 -0
- package/kotlin/fork/NOTICE +6 -1
- package/kotlin/fork/README.md +15 -7
- package/kotlin/fork/build.sh +2 -0
- package/kotlin/fork/compose-worker-globals.patch +186 -4
- package/kotlin/fork/fork.json +2 -2
- package/kotlin/fork/skiko-worker-clock.patch +12 -0
- package/kotlin/toolchain.json +12 -12
- package/kotlin/tools/expo-types.main.kts +115 -0
- package/package.json +17 -23
- package/scripts/dev-server-scanner.ts +92 -66
- package/skills/contrast/SKILL.md +2 -2
- package/skills/peach-debug/SKILL.md +1 -1
- package/skills/peach-perf/SKILL.md +1 -1
- package/skills/peach-setup/SKILL.md +2 -1
- package/skills/peach-test/SKILL.md +8 -0
- package/src/assetCatalogContract.ts +43 -1
- package/src/bridge-contract-input.ts +46 -19
- package/src/bridge-contract.ts +0 -1
- package/src/bundle-channel.mjs +197 -84
- package/src/bundleGlobalFooter.ts +38 -0
- package/src/capture-contract.ts +107 -33
- package/src/cloud-contract.ts +5 -1
- package/src/cloud.ts +5 -2
- package/src/config.ts +22 -3
- package/src/configured-bundle-producer.ts +1 -1
- package/src/desktop-host-delivery.ts +95 -1
- package/src/expo-kotlin-bundle.ts +29 -0
- package/src/expo-kotlin-host.ts +201 -0
- package/src/expo-swift-host.ts +96 -76
- package/src/expoKotlinContract.ts +70 -0
- package/src/expoSwiftContract.ts +4 -25
- package/src/home-paths.ts +7 -4
- package/src/host/bridge-host.ts +23 -3
- package/src/host/fetch-proxy-handler.ts +13 -0
- package/src/host/fetch-proxy-overrides.ts +11 -0
- package/src/host/replacement-module-handler.ts +5 -2
- package/src/index.ts +3 -1
- package/src/kotlin-host.ts +463 -19
- package/src/metro-plugin.ts +56 -28
- package/src/metro-production-bundle.ts +2 -2
- package/src/native-app-identity-project.ts +190 -0
- package/src/native-app-identity.ts +367 -34
- package/src/native-firebase-config.ts +69 -15
- package/src/native-seam-manifest.ts +6 -0
- package/src/one-widget-discovery.ts +2 -1
- package/src/openstep-plist.ts +107 -0
- package/src/react-native-host-modules.ts +4 -0
- package/src/runtime-assets.ts +2 -2
- package/src/sim-client.ts +2 -2
- package/src/swift-binary-modules.ts +3 -1
- package/src/swift-compile.ts +286 -38
- package/src/swift-distribution-contract.ts +4 -0
- package/src/swift-distribution.lock.json +123 -12
- package/src/swift-distribution.ts +7 -3
- package/src/swift-host.ts +298 -83
- package/src/swift-package-manifest.ts +29 -2
- package/src/swift-package.ts +180 -10
- package/src/swift-packages.ts +4 -0
- package/src/swift-standin-emit.ts +4 -1
- package/src/swift-standins.ts +274 -15
- package/src/swift-submit.ts +62 -7
- package/src/swift-toolchain.ts +6 -1
- package/src/swiftContract.ts +126 -3
- package/src/vite-plugin-one.ts +13 -16
- package/src/vite-plugin-swift.ts +4 -5
- package/src/vite-plugin.ts +360 -144
- package/src/vite.d.ts +1 -1
- package/src/worker-system-chunks.ts +221 -0
- package/src/worklet-executable.ts +348 -38
- package/src/worklets-babel.ts +1 -1
- package/src/xcode-project.ts +243 -127
- package/dist-lib/agent-identity.cjs +0 -39
- package/dist-lib/agent-sessions.cjs +0 -760
- package/dist-lib/cli-constants.cjs +0 -32
- package/dist-lib/render-mode.cjs +0 -44
- package/kotlin/AppSources/android/view/View.kt +0 -74
- package/kotlin/AppSources/android/widget/FrameLayout.kt +0 -6
- package/src/render-mode.ts +0 -15
- /package/kotlin/{AppSources → AndroidViewSources}/android/content/Context.kt +0 -0
- /package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Configuration.kt +0 -0
- /package/kotlin/{AppSources → AndroidViewSources}/android/graphics/Color.kt +0 -0
- /package/kotlin/{AppSources → AndroidViewSources}/android/util/DisplayMetrics.kt +0 -0
- /package/kotlin/{AppSources → AndroidViewSources}/dev/peach/app/JavaFormat.kt +0 -0
- /package/kotlin/{AppSources → Sources}/java/lang/Runnable.kt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@file:OptIn(androidx.compose.ui.ExperimentalComposeUiApi::class, androidx.compose.ui.InternalComposeUiApi::class, androidx.compose.ui.text.ExperimentalTextApi::class, kotlin.js.ExperimentalJsExport::class, kotlin.js.ExperimentalWasmJsInterop::class)
|
|
1
|
+
@file:OptIn(androidx.compose.ui.ExperimentalComposeUiApi::class, androidx.compose.ui.InternalComposeUiApi::class, androidx.compose.ui.text.ExperimentalTextApi::class, kotlin.js.ExperimentalJsExport::class, kotlin.js.ExperimentalWasmJsInterop::class, org.jetbrains.skiko.InternalSkikoApi::class)
|
|
2
2
|
|
|
3
3
|
import androidx.compose.runtime.Composable
|
|
4
4
|
import androidx.compose.runtime.CompositionLocalProvider
|
|
@@ -18,9 +18,11 @@ import androidx.compose.ui.input.key.KeyEvent
|
|
|
18
18
|
import androidx.compose.ui.input.key.KeyEventType
|
|
19
19
|
import androidx.compose.ui.input.pointer.PointerEventType
|
|
20
20
|
import androidx.compose.ui.input.pointer.PointerType
|
|
21
|
+
import androidx.compose.ui.layout.Layout
|
|
21
22
|
import androidx.compose.ui.platform.FrameRecomposer
|
|
22
23
|
import androidx.compose.ui.platform.LocalFontFamilyResolver
|
|
23
24
|
import androidx.compose.ui.platform.PlatformArchitectureComponentsOwner
|
|
25
|
+
import androidx.compose.ui.platform.PlatformInsets
|
|
24
26
|
import androidx.compose.ui.platform.PlatformWindowInsets
|
|
25
27
|
import androidx.compose.ui.platform.PlatformContext
|
|
26
28
|
import androidx.compose.ui.platform.PlatformTextInputMethodRequest
|
|
@@ -33,6 +35,7 @@ import androidx.compose.ui.semantics.SemanticsOwner
|
|
|
33
35
|
import androidx.compose.ui.text.font.FontFamily
|
|
34
36
|
import androidx.compose.ui.text.font.FontStyle
|
|
35
37
|
import androidx.compose.ui.text.font.FontSynthesis
|
|
38
|
+
import androidx.compose.ui.text.font.FontVariation
|
|
36
39
|
import androidx.compose.ui.text.font.FontWeight
|
|
37
40
|
import androidx.compose.ui.text.font.PlatformTypefacesLoader
|
|
38
41
|
import androidx.compose.ui.text.font.createPlatformFontFamilyResolver
|
|
@@ -58,6 +61,12 @@ import androidx.compose.ui.unit.Density
|
|
|
58
61
|
import androidx.compose.ui.unit.DpSize
|
|
59
62
|
import androidx.compose.ui.unit.IntSize
|
|
60
63
|
import androidx.compose.ui.unit.LayoutDirection
|
|
64
|
+
import androidx.compose.ui.unit.constrainHeight
|
|
65
|
+
import androidx.compose.ui.unit.constrainWidth
|
|
66
|
+
import androidx.navigationevent.NavigationEventDispatcher
|
|
67
|
+
import androidx.navigationevent.NavigationEventInput
|
|
68
|
+
import androidx.navigationevent.NavigationEventDispatcherOwner
|
|
69
|
+
import androidx.navigationevent.compose.LocalNavigationEventDispatcherOwner
|
|
61
70
|
import dev.onejs.one.OneNativeSourceViewDispatch
|
|
62
71
|
import kotlin.io.encoding.Base64
|
|
63
72
|
import kotlinx.coroutines.Dispatchers
|
|
@@ -66,26 +75,45 @@ import kotlinx.coroutines.awaitCancellation
|
|
|
66
75
|
import kotlinx.coroutines.currentCoroutineContext
|
|
67
76
|
import kotlinx.coroutines.job
|
|
68
77
|
import org.jetbrains.skiko.SystemTheme
|
|
69
|
-
import org.jetbrains.skia.BackendRenderTarget
|
|
70
|
-
import org.jetbrains.skia.Bitmap
|
|
71
78
|
import org.jetbrains.skia.Color
|
|
72
|
-
import org.jetbrains.skia.
|
|
73
|
-
import org.jetbrains.skia.
|
|
74
|
-
import org.jetbrains.skia.
|
|
75
|
-
import org.jetbrains.skia.
|
|
76
|
-
import org.jetbrains.skia.ImageInfo
|
|
77
|
-
import org.jetbrains.skia.Surface
|
|
78
|
-
import org.jetbrains.skia.SurfaceColorFormat
|
|
79
|
-
import org.jetbrains.skia.SurfaceOrigin
|
|
80
|
-
import org.jetbrains.skia.SurfaceProps
|
|
79
|
+
import org.jetbrains.skia.Picture
|
|
80
|
+
import org.jetbrains.skia.PictureRecorder
|
|
81
|
+
import org.jetbrains.skia.Rect as SkRect
|
|
82
|
+
import org.jetbrains.skia.impl.getPtr
|
|
81
83
|
import org.jetbrains.skiko.OS
|
|
82
84
|
import org.jetbrains.skiko.setHostOsProvider
|
|
83
85
|
import org.json.JSONArray
|
|
84
86
|
import org.json.JSONObject
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
internal fun readPeachWindowInsets(insets: String) = insets.split(',').map { it.toInt() }.also {
|
|
89
|
+
require(it.size == 24 && it.all { pixel -> pixel >= 0 })
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// the window delivers inset types to its compose views; a fitting decor consumes them.
|
|
93
|
+
internal class PeachWindowInsets(
|
|
94
|
+
private val pixels: () -> List<Int>,
|
|
95
|
+
imeBottom: () -> Int,
|
|
96
|
+
private val consumesInsets: () -> Boolean,
|
|
97
|
+
) : PlatformWindowInsets {
|
|
98
|
+
private fun type(index: Int): PlatformInsets {
|
|
99
|
+
fun side(offset: Int) = { if (consumesInsets()) 0 else pixels()[index * 4 + offset] }
|
|
100
|
+
return PlatformInsets(side(0), side(1), side(2), side(3))
|
|
101
|
+
}
|
|
102
|
+
override val statusBars = type(0)
|
|
103
|
+
override val navigationBars = type(1)
|
|
104
|
+
override val displayCutout = type(2)
|
|
105
|
+
override val systemGestures = type(3)
|
|
106
|
+
override val mandatorySystemGestures = type(4)
|
|
107
|
+
override val tappableElement = type(5)
|
|
108
|
+
override val ime = PlatformInsets({ 0 }, { 0 }, { 0 }, { if (consumesInsets()) 0 else imeBottom() })
|
|
109
|
+
override val systemBars = PlatformInsets(
|
|
110
|
+
{ maxOf(statusBars.left, navigationBars.left) },
|
|
111
|
+
{ maxOf(statusBars.top, navigationBars.top) },
|
|
112
|
+
{ maxOf(statusBars.right, navigationBars.right) },
|
|
113
|
+
{ maxOf(statusBars.bottom, navigationBars.bottom) },
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
89
117
|
// the values android.view.ViewConfiguration gives a window: config.xml's dp
|
|
90
118
|
// dimensions resolved as getDimensionPixelSize does, rounding half up
|
|
91
119
|
internal class AndroidViewConfiguration(private val density: Float) : ViewConfiguration {
|
|
@@ -141,6 +169,23 @@ internal class PeachComposePlatform(
|
|
|
141
169
|
private var session: PeachInputSession? = null
|
|
142
170
|
val input get() = session?.takeIf { it.job.isActive }
|
|
143
171
|
var onInputChange: () -> Unit = {}
|
|
172
|
+
var onDialogChange: () -> Unit = {}
|
|
173
|
+
private val dialogs = mutableListOf<PeachDialogWindow>()
|
|
174
|
+
private var nextDialog = 0
|
|
175
|
+
|
|
176
|
+
override fun attachDialogWindow(dispatcher: NavigationEventDispatcher): AutoCloseable {
|
|
177
|
+
val window = PeachDialogWindow(++nextDialog, dispatcher, onDialogChange)
|
|
178
|
+
dialogs.add(window)
|
|
179
|
+
onDialogChange()
|
|
180
|
+
return AutoCloseable { dialogs.remove(window); onDialogChange() }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fun dialogState(): JSONArray = JSONArray().also { state -> dialogs.forEach { state.put(it.describe()) } }
|
|
184
|
+
fun dialogBack(): Boolean {
|
|
185
|
+
val window = dialogs.lastOrNull() ?: return false
|
|
186
|
+
window.back()
|
|
187
|
+
return true
|
|
188
|
+
}
|
|
144
189
|
private var nextInput = 1
|
|
145
190
|
|
|
146
191
|
override suspend fun startInputMethod(request: PlatformTextInputMethodRequest): Nothing {
|
|
@@ -168,6 +213,43 @@ internal class PeachComposePlatform(
|
|
|
168
213
|
}
|
|
169
214
|
}
|
|
170
215
|
|
|
216
|
+
// each dialog has its own window dispatch. scoped AndroidX inputs observe
|
|
217
|
+
// default and overlay handlers; registration ids change only on re-registration.
|
|
218
|
+
private class PeachDialogWindow(
|
|
219
|
+
private val id: Int,
|
|
220
|
+
dispatcher: NavigationEventDispatcher,
|
|
221
|
+
private val changed: () -> Unit,
|
|
222
|
+
) {
|
|
223
|
+
private var nextRegistration = 0
|
|
224
|
+
private inner class BackInput(private val priority: Int) : NavigationEventInput() {
|
|
225
|
+
var registration = 0
|
|
226
|
+
private set
|
|
227
|
+
override fun onHasEnabledHandlersChanged(hasEnabledHandlers: Boolean) {
|
|
228
|
+
registration = if (hasEnabledHandlers) ++nextRegistration else 0
|
|
229
|
+
changed()
|
|
230
|
+
}
|
|
231
|
+
override fun onRemoved() { registration = 0; changed() }
|
|
232
|
+
fun back() = dispatchOnBackCompleted()
|
|
233
|
+
fun describe(): JSONObject = JSONObject().put("priority", priority).put("registration", registration)
|
|
234
|
+
}
|
|
235
|
+
private val default = BackInput(0)
|
|
236
|
+
private val overlay = BackInput(1000000)
|
|
237
|
+
init {
|
|
238
|
+
dispatcher.addInput(default, NavigationEventDispatcher.PRIORITY_DEFAULT)
|
|
239
|
+
dispatcher.addInput(overlay, NavigationEventDispatcher.PRIORITY_OVERLAY)
|
|
240
|
+
}
|
|
241
|
+
fun back(): Boolean {
|
|
242
|
+
if (overlay.registration != 0) overlay.back()
|
|
243
|
+
else if (default.registration != 0) default.back()
|
|
244
|
+
else return false
|
|
245
|
+
return true
|
|
246
|
+
}
|
|
247
|
+
fun describe(): JSONObject = JSONObject().put("id", id).put("callbacks", JSONArray().also { callbacks ->
|
|
248
|
+
listOf(default, overlay).filter { it.registration != 0 }.sortedBy { it.registration }
|
|
249
|
+
.forEach { callbacks.put(it.describe()) }
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
|
|
171
253
|
internal class PeachInputSession(val token: Int, val request: PlatformTextInputMethodRequest, val job: Job) {
|
|
172
254
|
var keyboard = true
|
|
173
255
|
private set
|
|
@@ -284,8 +366,6 @@ internal class PeachComposeMount(
|
|
|
284
366
|
density: Float,
|
|
285
367
|
fontScale: Float,
|
|
286
368
|
locale: String,
|
|
287
|
-
private val gpu: Boolean,
|
|
288
|
-
private val stencilBits: Int,
|
|
289
369
|
private val fonts: FontFamily,
|
|
290
370
|
val invalidate: (Boolean) -> Unit,
|
|
291
371
|
night: Boolean,
|
|
@@ -305,29 +385,62 @@ internal class PeachComposeMount(
|
|
|
305
385
|
// state reported once a frame, read when the frame draws: like the decor,
|
|
306
386
|
// the device sees the state a frame ends with, not each step to it
|
|
307
387
|
private val frameState = linkedMapOf<String, () -> Any?>()
|
|
308
|
-
private
|
|
388
|
+
private var importedInsets by mutableStateOf(List(24) { 0 })
|
|
389
|
+
private var importedIme by mutableStateOf(0)
|
|
390
|
+
private val platform = PeachComposePlatform(Density(density, fontScale), LocaleList(locale), IntSize.Zero, owners,
|
|
391
|
+
insets ?: PeachWindowInsets({ importedInsets }, { importedIme }, { false }))
|
|
392
|
+
// imported views own a dispatcher instead of sharing the empty platform owner's.
|
|
393
|
+
private val importedNavigation = if (owners == null) object : NavigationEventDispatcherOwner {
|
|
394
|
+
override val navigationEventDispatcher = NavigationEventDispatcher()
|
|
395
|
+
} else null
|
|
396
|
+
private val importedBack = importedNavigation?.let {
|
|
397
|
+
PeachDialogWindow(0, it.navigationEventDispatcher) {}
|
|
398
|
+
}
|
|
309
399
|
private val recomposer = FrameRecomposer(Dispatchers.Main) { invalidate(false) }
|
|
310
400
|
private var needsMeasure = true
|
|
311
401
|
private var measuredSize = IntSize.Zero
|
|
402
|
+
private var rootSize = IntSize.Zero
|
|
312
403
|
private val scene = CanvasLayersComposeScene(
|
|
313
404
|
recomposer, platform.density, LayoutDirection.Ltr, null, platform,
|
|
314
405
|
invalidateLayout = { needsMeasure = true; invalidate(true) }, invalidateDraw = { invalidate(false) },
|
|
315
406
|
)
|
|
316
407
|
// the session's state as last reported; a change reports it again
|
|
317
408
|
private var reportedInput: String? = null
|
|
318
|
-
private var
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
409
|
+
private var picture: Picture? = null
|
|
410
|
+
|
|
411
|
+
init {
|
|
412
|
+
platform.onInputChange = { invalidate(false) }
|
|
413
|
+
platform.onDialogChange = { emitAtFrame("dialogs") { platform.dialogState().toString() } }
|
|
414
|
+
}
|
|
323
415
|
|
|
324
|
-
|
|
416
|
+
fun dialogState(): JSONArray = platform.dialogState()
|
|
417
|
+
fun dialogBack(): Boolean = platform.dialogBack()
|
|
418
|
+
fun back(): Boolean {
|
|
419
|
+
// app activities dispatch their own back; imported views have no activity owner.
|
|
420
|
+
val receivingWindow = importedBack ?: return false
|
|
421
|
+
return platform.dialogBack() || receivingWindow.back()
|
|
422
|
+
}
|
|
325
423
|
|
|
326
424
|
fun setContent(content: @Composable () -> Unit) {
|
|
327
425
|
try {
|
|
328
426
|
scene.setContent {
|
|
329
|
-
|
|
330
|
-
|
|
427
|
+
// the parent measures the imported root; popup windows keep their own minimums.
|
|
428
|
+
Layout(content = {
|
|
429
|
+
CompositionLocalProvider(
|
|
430
|
+
LocalSystemTheme provides if (night) SystemTheme.DARK else SystemTheme.LIGHT,
|
|
431
|
+
LocalNavigationEventDispatcherOwner provides
|
|
432
|
+
checkNotNull(importedNavigation ?: LocalNavigationEventDispatcherOwner.current),
|
|
433
|
+
) {
|
|
434
|
+
PeachAndroidFonts(fonts, content)
|
|
435
|
+
}
|
|
436
|
+
}) { measurables, _ ->
|
|
437
|
+
val rootConstraints = checkNotNull(constraints)
|
|
438
|
+
val placeables = measurables.map { it.measure(rootConstraints) }
|
|
439
|
+
rootSize = IntSize(
|
|
440
|
+
rootConstraints.constrainWidth(placeables.maxOfOrNull { it.width } ?: 0),
|
|
441
|
+
rootConstraints.constrainHeight(placeables.maxOfOrNull { it.height } ?: 0),
|
|
442
|
+
)
|
|
443
|
+
layout(rootSize.width, rootSize.height) { placeables.forEach { it.place(0, 0) } }
|
|
331
444
|
}
|
|
332
445
|
}
|
|
333
446
|
} catch (failure: Throwable) {
|
|
@@ -341,13 +454,25 @@ internal class PeachComposeMount(
|
|
|
341
454
|
invalidate(false)
|
|
342
455
|
}
|
|
343
456
|
|
|
457
|
+
fun windowInsets(insets: String, imeBottom: Int) {
|
|
458
|
+
require(imeBottom >= 0)
|
|
459
|
+
val next = readPeachWindowInsets(insets)
|
|
460
|
+
if (next == importedInsets && imeBottom == importedIme) return
|
|
461
|
+
importedInsets = next
|
|
462
|
+
importedIme = imeBottom
|
|
463
|
+
needsMeasure = true
|
|
464
|
+
invalidate(true)
|
|
465
|
+
}
|
|
466
|
+
|
|
344
467
|
fun emitAtFrame(name: String, args: () -> Any?) {
|
|
345
468
|
frameState[name] = args
|
|
346
469
|
invalidate(false)
|
|
347
470
|
}
|
|
348
471
|
|
|
349
472
|
fun measure(): IntSize {
|
|
350
|
-
|
|
473
|
+
// intrinsic root constraints can be unbounded; popup windows measure within the laid-out view.
|
|
474
|
+
scene.measureContent(Constraints(maxWidth = measuredSize.width, maxHeight = measuredSize.height))
|
|
475
|
+
val measured = rootSize
|
|
351
476
|
scene.size = measured
|
|
352
477
|
platform.size = measured
|
|
353
478
|
measuredSize = measured
|
|
@@ -356,52 +481,40 @@ internal class PeachComposeMount(
|
|
|
356
481
|
}
|
|
357
482
|
|
|
358
483
|
fun frame(nanos: Long): String {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
val backing = IntSize(measured.width.coerceAtLeast(1), measured.height.coerceAtLeast(1))
|
|
365
|
-
if (backing != size) {
|
|
366
|
-
releaseSurface()
|
|
367
|
-
size = backing
|
|
484
|
+
releaseFrame()
|
|
485
|
+
try {
|
|
486
|
+
Snapshot.sendApplyNotifications()
|
|
487
|
+
if (clockRunning()) recomposer.performFrame(nanos)
|
|
488
|
+
val measured = if (needsMeasure && constraints != null) measure() else measuredSize
|
|
368
489
|
platform.size = measured
|
|
369
490
|
scene.size = measured
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
491
|
+
scene.measureAndLayout()
|
|
492
|
+
val recorder = PictureRecorder()
|
|
493
|
+
try {
|
|
494
|
+
val canvas = recorder.beginRecording(SkRect.makeWH(
|
|
495
|
+
measured.width.coerceAtLeast(1).toFloat(), measured.height.coerceAtLeast(1).toFloat(),
|
|
496
|
+
))
|
|
497
|
+
canvas.clear(Color.TRANSPARENT)
|
|
498
|
+
scene.draw(canvas.asComposeCanvas())
|
|
499
|
+
picture = recorder.finishRecordingAsPicture()
|
|
500
|
+
} finally { recorder.close() }
|
|
501
|
+
val reply = JSONObject().put("width", measured.width).put("height", measured.height)
|
|
502
|
+
.put("pending", recomposer.hasPendingWork() || scene.hasInvalidations())
|
|
503
|
+
for ((name, args) in frameState) events.add(JSONObject().put("name", name).put("args", args()))
|
|
504
|
+
frameState.clear()
|
|
505
|
+
reply.put("events", JSONArray(events))
|
|
506
|
+
val input = platform.input?.state()?.toString()
|
|
507
|
+
if (input != reportedInput) {
|
|
508
|
+
reportedInput = input
|
|
509
|
+
reply.put("input", input?.let { JSONObject(it) } ?: JSONObject.NULL)
|
|
380
510
|
}
|
|
511
|
+
events.clear()
|
|
512
|
+
reply.put("picture", getPtr(checkNotNull(picture)))
|
|
513
|
+
return reply.toString()
|
|
514
|
+
} catch (failure: Throwable) {
|
|
515
|
+
releaseFrame()
|
|
516
|
+
throw failure
|
|
381
517
|
}
|
|
382
|
-
scene.measureAndLayout()
|
|
383
|
-
val canvas = checkNotNull(surface).canvas
|
|
384
|
-
canvas.clear(Color.TRANSPARENT)
|
|
385
|
-
scene.draw(canvas.asComposeCanvas())
|
|
386
|
-
val reply = JSONObject().put("width", measured.width).put("height", measured.height)
|
|
387
|
-
.put("pending", recomposer.hasPendingWork() || scene.hasInvalidations())
|
|
388
|
-
for ((name, args) in frameState) events.add(JSONObject().put("name", name).put("args", args()))
|
|
389
|
-
frameState.clear()
|
|
390
|
-
reply.put("events", JSONArray(events))
|
|
391
|
-
val input = platform.input?.state()?.toString()
|
|
392
|
-
if (input != reportedInput) {
|
|
393
|
-
reportedInput = input
|
|
394
|
-
reply.put("input", input?.let { JSONObject(it) } ?: JSONObject.NULL)
|
|
395
|
-
}
|
|
396
|
-
events.clear()
|
|
397
|
-
if (gpu) checkNotNull(surface).flushAndSubmit()
|
|
398
|
-
else {
|
|
399
|
-
check(checkNotNull(surface).readPixels(checkNotNull(pixels), 0, 0))
|
|
400
|
-
val map = checkNotNull(checkNotNull(pixels).peekPixels())
|
|
401
|
-
try { reply.put("pointer", map.addr).put("length", size.width * size.height * 4) }
|
|
402
|
-
finally { map.close() }
|
|
403
|
-
}
|
|
404
|
-
return reply.toString()
|
|
405
518
|
}
|
|
406
519
|
|
|
407
520
|
fun pointer(phase: String, x: Float, y: Float, millis: Long) {
|
|
@@ -455,18 +568,17 @@ internal class PeachComposeMount(
|
|
|
455
568
|
return nodes.toString()
|
|
456
569
|
}
|
|
457
570
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
target?.close(); target = null
|
|
462
|
-
context?.close(); context = null
|
|
571
|
+
fun releaseFrame() {
|
|
572
|
+
picture?.close()
|
|
573
|
+
picture = null
|
|
463
574
|
}
|
|
464
575
|
|
|
465
576
|
override fun close() {
|
|
466
577
|
try { scene.close() }
|
|
467
578
|
finally {
|
|
579
|
+
importedNavigation?.navigationEventDispatcher?.dispose()
|
|
468
580
|
try { recomposer.close() }
|
|
469
|
-
finally {
|
|
581
|
+
finally { releaseFrame() }
|
|
470
582
|
}
|
|
471
583
|
}
|
|
472
584
|
}
|
|
@@ -503,23 +615,24 @@ internal fun preparePeachDevice(locale: String) {
|
|
|
503
615
|
require(deviceLocale == locale) { "device locale changes require a new Kotlin process" }
|
|
504
616
|
}
|
|
505
617
|
|
|
506
|
-
internal fun robotoFamily(
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
)
|
|
618
|
+
internal fun robotoFamily(font: String): FontFamily {
|
|
619
|
+
val data = Base64.decode(font)
|
|
620
|
+
return FontFamily(listOf(FontWeight.Normal, FontWeight.Medium, FontWeight.Bold).map { weight ->
|
|
621
|
+
Font("Roboto-${weight.weight}", getData = { data }, weight = weight,
|
|
622
|
+
variationSettings = FontVariation.Settings(weight, FontStyle.Normal))
|
|
623
|
+
})
|
|
624
|
+
}
|
|
511
625
|
|
|
512
626
|
@JsExport
|
|
513
627
|
fun peachViewMount(source: String, view: String, hash: String, props: String,
|
|
514
|
-
density: Float, fontScale: Float, locale: String, night: Boolean,
|
|
515
|
-
|
|
628
|
+
density: Float, fontScale: Float, locale: String, night: Boolean,
|
|
629
|
+
font: String, invalidate: (Boolean) -> Unit): String {
|
|
516
630
|
try {
|
|
517
631
|
val dispatch = peachViewDispatcher(source)
|
|
518
632
|
if (dispatch.contractHash != hash) return JSONObject().put("ok", false).put("code", "E_NATIVE_SOURCE_REBUILD")
|
|
519
633
|
.put("error", "composable source $source changed; rebuild the app").toString()
|
|
520
|
-
require(backend == "graphite" || backend == "raster")
|
|
521
634
|
preparePeachDevice(locale)
|
|
522
|
-
val mount = PeachComposeMount(density, fontScale, locale,
|
|
635
|
+
val mount = PeachComposeMount(density, fontScale, locale, robotoFamily(font), invalidate, night)
|
|
523
636
|
mount.props = props
|
|
524
637
|
mount.setContent { dispatch.Content(view, mount.props, mount::emit) }
|
|
525
638
|
return JSONObject().put("ok", true).put("id", addMount(mount)).toString()
|
|
@@ -532,6 +645,7 @@ fun peachViewMount(source: String, view: String, hash: String, props: String,
|
|
|
532
645
|
// a host activity that handles uiMode itself, as React Native's does, keeps
|
|
533
646
|
// its views and hands them the new configuration
|
|
534
647
|
@JsExport fun peachViewNight(id: Int, night: Boolean) { checkNotNull(mounts[id]).night = night }
|
|
648
|
+
@JsExport fun peachViewInsets(id: Int, insets: String, imeBottom: Int) { checkNotNull(mounts[id]).windowInsets(insets, imeBottom) }
|
|
535
649
|
@JsExport fun peachViewConstraints(id: Int, minWidth: Int, maxWidth: Int, minHeight: Int, maxHeight: Int) {
|
|
536
650
|
checkNotNull(mounts[id]).also {
|
|
537
651
|
it.constraints = Constraints(minWidth, maxWidth, minHeight, maxHeight)
|
|
@@ -539,6 +653,8 @@ fun peachViewMount(source: String, view: String, hash: String, props: String,
|
|
|
539
653
|
}
|
|
540
654
|
}
|
|
541
655
|
@JsExport fun peachViewFrame(id: Int, millis: Double): String = checkNotNull(mounts[id]).frame((millis * 1_000_000).toLong())
|
|
656
|
+
@JsExport fun peachViewReleaseFrame(id: Int) { checkNotNull(mounts[id]).releaseFrame() }
|
|
657
|
+
@JsExport fun peachViewBack(id: Int): Boolean = checkNotNull(mounts[id]).back()
|
|
542
658
|
@JsExport fun peachViewPointer(id: Int, phase: String, x: Float, y: Float, millis: Double) = checkNotNull(mounts[id]).pointer(phase, x, y, millis.toLong())
|
|
543
659
|
@JsExport fun peachViewInputEdit(id: Int, token: Int, command: String): Boolean = checkNotNull(mounts[id]).inputEdit(token, command)
|
|
544
660
|
@JsExport fun peachViewInputKey(id: Int, token: Int, key: String, ctrl: Boolean, shift: Boolean, alt: Boolean): Boolean =
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import android.graphics.Canvas
|
|
2
|
+
import android.graphics.ViewCanvas
|
|
3
|
+
import android.view.View
|
|
4
|
+
import androidx.compose.runtime.Composable
|
|
5
|
+
import androidx.compose.runtime.DisposableEffect
|
|
6
|
+
import androidx.compose.runtime.getValue
|
|
7
|
+
import androidx.compose.runtime.mutableIntStateOf
|
|
8
|
+
import androidx.compose.runtime.remember
|
|
9
|
+
import androidx.compose.runtime.setValue
|
|
10
|
+
import androidx.compose.ui.Modifier
|
|
11
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
12
|
+
import androidx.compose.ui.draw.drawWithContent
|
|
13
|
+
import androidx.compose.ui.graphics.drawscope.clipRect
|
|
14
|
+
import androidx.compose.ui.graphics.nativeCanvas
|
|
15
|
+
import androidx.compose.ui.layout.Layout
|
|
16
|
+
import androidx.compose.ui.unit.constrainWidth
|
|
17
|
+
import androidx.compose.ui.unit.constrainHeight
|
|
18
|
+
import org.jetbrains.skia.Paint
|
|
19
|
+
import org.jetbrains.skia.Rect
|
|
20
|
+
|
|
21
|
+
// the Android view tree draws into the same Skia picture as a compose view.
|
|
22
|
+
// invalidation schedules drawing; requestLayout also schedules measurement.
|
|
23
|
+
@Composable
|
|
24
|
+
internal fun PeachAndroidView(view: View) {
|
|
25
|
+
var drawRevision by remember(view) { mutableIntStateOf(0) }
|
|
26
|
+
var layoutRevision by remember(view) { mutableIntStateOf(0) }
|
|
27
|
+
DisposableEffect(view) {
|
|
28
|
+
view.invalidateHost = { layout ->
|
|
29
|
+
drawRevision++
|
|
30
|
+
if (layout) layoutRevision++
|
|
31
|
+
}
|
|
32
|
+
view.dispatchAttachedToWindow(Any())
|
|
33
|
+
onDispose {
|
|
34
|
+
view.dispatchDetachedFromWindow()
|
|
35
|
+
view.invalidateHost = null
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
Layout(content = {}, modifier = Modifier.fillMaxSize().drawWithContent {
|
|
39
|
+
drawRevision
|
|
40
|
+
clipRect {
|
|
41
|
+
val native = drawContext.canvas.nativeCanvas
|
|
42
|
+
view.draw(Canvas(object : ViewCanvas {
|
|
43
|
+
private var offsetX = 0f
|
|
44
|
+
private var offsetY = 0f
|
|
45
|
+
private var clip = Rect.makeWH(view.width.toFloat(), view.height.toFloat())
|
|
46
|
+
private val saves = mutableMapOf<Int, Triple<Float, Float, Rect>>()
|
|
47
|
+
override fun drawColor(color: Int) {
|
|
48
|
+
val paint = Paint()
|
|
49
|
+
try { paint.color = color; native.drawPaint(paint) } finally { paint.close() }
|
|
50
|
+
}
|
|
51
|
+
override fun save(): Int {
|
|
52
|
+
val count = native.save()
|
|
53
|
+
saves[count] = Triple(offsetX, offsetY, clip)
|
|
54
|
+
return count
|
|
55
|
+
}
|
|
56
|
+
override fun restoreToCount(count: Int) {
|
|
57
|
+
native.restoreToCount(count)
|
|
58
|
+
val state = checkNotNull(saves[count])
|
|
59
|
+
offsetX = state.first; offsetY = state.second; clip = state.third
|
|
60
|
+
saves.keys.filter { it >= count }.forEach { saves.remove(it) }
|
|
61
|
+
}
|
|
62
|
+
override fun translate(dx: Float, dy: Float) {
|
|
63
|
+
offsetX += dx; offsetY += dy
|
|
64
|
+
native.translate(dx, dy)
|
|
65
|
+
}
|
|
66
|
+
override fun clipRect(left: Float, top: Float, right: Float, bottom: Float): Boolean {
|
|
67
|
+
native.clipRect(Rect.makeLTRB(left, top, right, bottom))
|
|
68
|
+
clip = Rect.makeLTRB(maxOf(clip.left, left + offsetX), maxOf(clip.top, top + offsetY),
|
|
69
|
+
minOf(clip.right, right + offsetX), minOf(clip.bottom, bottom + offsetY))
|
|
70
|
+
return clip.left < clip.right && clip.top < clip.bottom
|
|
71
|
+
}
|
|
72
|
+
}))
|
|
73
|
+
}
|
|
74
|
+
}) { _, constraints ->
|
|
75
|
+
layoutRevision
|
|
76
|
+
fun spec(min: Int, max: Int, bounded: Boolean): Int = when {
|
|
77
|
+
min == max -> View.MeasureSpec.makeMeasureSpec(max, View.MeasureSpec.EXACTLY)
|
|
78
|
+
bounded -> View.MeasureSpec.makeMeasureSpec(max, View.MeasureSpec.AT_MOST)
|
|
79
|
+
else -> View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
|
80
|
+
}
|
|
81
|
+
view.measure(spec(constraints.minWidth, constraints.maxWidth, constraints.hasBoundedWidth),
|
|
82
|
+
spec(constraints.minHeight, constraints.maxHeight, constraints.hasBoundedHeight))
|
|
83
|
+
val width = constraints.constrainWidth(view.measuredWidth)
|
|
84
|
+
val height = constraints.constrainHeight(view.measuredHeight)
|
|
85
|
+
layout(width, height) { view.layout(0, 0, width, height) }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@file:OptIn(kotlin.js.ExperimentalJsExport::class, androidx.compose.ui.ExperimentalComposeUiApi::class, androidx.compose.ui.InternalComposeUiApi::class)
|
|
2
|
+
|
|
3
|
+
import dev.peach.expo.PeachExpoRuntime
|
|
4
|
+
import expo.modules.kotlin.views.ExpoComposeView
|
|
5
|
+
import org.json.JSONObject
|
|
6
|
+
|
|
7
|
+
// one mounted Expo view as the native scene the tenant presents. the
|
|
8
|
+
// view already exists: its first props created it
|
|
9
|
+
@JsExport
|
|
10
|
+
fun peachExpoViewMount(id: String, density: Float, fontScale: Float, locale: String, night: Boolean,
|
|
11
|
+
font: String, invalidate: (Boolean) -> Unit): String {
|
|
12
|
+
try {
|
|
13
|
+
val view = PeachExpoRuntime.mountedView(id)
|
|
14
|
+
if (view is ExpoComposeView<*>) view.validateHostingAncestor()
|
|
15
|
+
preparePeachDevice(locale)
|
|
16
|
+
val mount = PeachComposeMount(density, fontScale, locale, robotoFamily(font), invalidate, night)
|
|
17
|
+
mount.setContent {
|
|
18
|
+
if (view is ExpoComposeView<*>) view.HostedContent() else PeachAndroidView(view)
|
|
19
|
+
}
|
|
20
|
+
return JSONObject().put("ok", true).put("id", addMount(mount)).toString()
|
|
21
|
+
} catch (error: Throwable) {
|
|
22
|
+
return JSONObject().put("ok", false).put("code", "E_EXPO_VIEW").put("error", "Expo view $id: ${error.message}").toString()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
@file:Suppress("UNCHECKED_CAST")
|
|
2
|
+
|
|
3
|
+
package dev.peach.expo
|
|
4
|
+
|
|
5
|
+
import androidx.compose.runtime.MutableState
|
|
6
|
+
import expo.modules.kotlin.exception.PropSetException
|
|
7
|
+
import expo.modules.kotlin.exception.exceptionDecorator
|
|
8
|
+
import expo.modules.kotlin.types.AnyType
|
|
9
|
+
import expo.modules.kotlin.types.toAnyType
|
|
10
|
+
import expo.modules.kotlin.views.AnyViewProp
|
|
11
|
+
import expo.modules.kotlin.views.ExpoComposeView
|
|
12
|
+
import expo.modules.kotlin.views.ExpoView
|
|
13
|
+
|
|
14
|
+
// a compose view's MutableState prop: a new value replaces the state's, as
|
|
15
|
+
// ComposeViewProp sets it, so the content recomposes
|
|
16
|
+
class ComposeStateProp(name: String, type: AnyType, private val write: (Any, Any?) -> Unit) : AnyViewProp(name, type) {
|
|
17
|
+
override fun set(prop: Any?, onView: ExpoView) {
|
|
18
|
+
exceptionDecorator({ PropSetException(name, onView::class, it) }) {
|
|
19
|
+
val props = (onView as ExpoComposeView<*>).props ?: return
|
|
20
|
+
write(props, type.convert(prop))
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
object PeachExpoCompose {
|
|
26
|
+
inline fun <P : Any, reified T> prop(name: String, noinline state: (P) -> MutableState<T>): AnyViewProp =
|
|
27
|
+
ComposeStateProp(name, toAnyType<T>()) { props, value -> state(props as P).value = value as T }
|
|
28
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
package expo.modules.kotlin.views
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import androidx.compose.runtime.Composable
|
|
6
|
+
import expo.modules.kotlin.AppContext
|
|
7
|
+
import expo.modules.kotlin.exception.CodedException
|
|
8
|
+
import expo.modules.kotlin.viewevent.ViewEvent
|
|
9
|
+
|
|
10
|
+
// a scope passed through the compose view hierarchy
|
|
11
|
+
interface ComposableScope
|
|
12
|
+
|
|
13
|
+
private object EmptyComposableScope : ComposableScope
|
|
14
|
+
|
|
15
|
+
fun ComposableScope(): ComposableScope = EmptyComposableScope
|
|
16
|
+
|
|
17
|
+
inline fun <T : ComposableScope> T.withIf(condition: Boolean, block: T.() -> T): T =
|
|
18
|
+
if (condition) block() else this
|
|
19
|
+
|
|
20
|
+
// a marker for the classes that carry a compose view's props
|
|
21
|
+
interface ComposeProps
|
|
22
|
+
|
|
23
|
+
@Target(AnnotationTarget.CLASS)
|
|
24
|
+
@Retention(AnnotationRetention.BINARY)
|
|
25
|
+
annotation class OptimizedComposeProps
|
|
26
|
+
|
|
27
|
+
// the name for the global event dispatcher
|
|
28
|
+
internal const val GLOBAL_EVENT_NAME = "onGlobalEvent"
|
|
29
|
+
|
|
30
|
+
// a compose view. one with a hosting view composes its own content in a
|
|
31
|
+
// ComposeView, which Peach presents as one compose scene; one without renders
|
|
32
|
+
// only inside a compose parent, which Peach has no React Native children for
|
|
33
|
+
// yet, so it stays hidden and logs the missing host, as upstream does when its
|
|
34
|
+
// parent is not a compose view
|
|
35
|
+
abstract class ExpoComposeView<T : ComposeProps>(
|
|
36
|
+
context: Context,
|
|
37
|
+
appContext: AppContext,
|
|
38
|
+
private val withHostingView: Boolean = false,
|
|
39
|
+
) : ExpoView(context, appContext) {
|
|
40
|
+
open val props: T? = null
|
|
41
|
+
|
|
42
|
+
private val globalEvent by lazy { ViewEvent<Pair<String, Map<String, Any?>>>(GLOBAL_EVENT_NAME, this, null) }
|
|
43
|
+
|
|
44
|
+
val globalEventDispatcher: (String, Map<String, Any?>) -> Unit = { name, params ->
|
|
45
|
+
globalEvent.invoke(Pair(name, params))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Composable
|
|
49
|
+
abstract fun ComposableScope.Content()
|
|
50
|
+
|
|
51
|
+
override val shouldUseAndroidLayout = withHostingView
|
|
52
|
+
|
|
53
|
+
@Composable
|
|
54
|
+
internal fun HostedContent() {
|
|
55
|
+
if (withHostingView) with(ComposableScope()) { Content() }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
internal fun validateHostingAncestor() {
|
|
59
|
+
if (withHostingView) return
|
|
60
|
+
Log.e("ExpoComposeView", "", MissingHostException(this::class.simpleName ?: "ExpoComposeView"))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
internal class MissingHostException(componentName: String) :
|
|
65
|
+
CodedException(
|
|
66
|
+
"A Jetpack Compose view \"$componentName\" must be rendered as a direct child of a <Host> component. " +
|
|
67
|
+
"Wrap your component with `<Host>` from '@expo/ui/jetpack-compose'. " +
|
|
68
|
+
"Note that inserting another `<View>` (or any non-Compose ViewGroup) between `<Host>` and this " +
|
|
69
|
+
"component breaks the Compose composition boundary and will still trigger this error."
|
|
70
|
+
)
|