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.
Files changed (396) hide show
  1. package/README.md +28 -22
  2. package/cli/app-catalog.ts +29 -14
  3. package/cli/app-fonts.ts +5 -3
  4. package/cli/bridge-flow-runner.ts +40 -29
  5. package/cli/cloud-client.ts +57 -21
  6. package/cli/cloud-test-tunnel.ts +12 -18
  7. package/cli/commands/agent.ts +1 -1
  8. package/cli/commands/app-fonts.ts +3 -3
  9. package/cli/commands/assert.ts +8 -8
  10. package/cli/commands/auth.ts +8 -3
  11. package/cli/commands/box/bundle-plane.ts +13 -1
  12. package/cli/commands/box.ts +16 -5
  13. package/cli/commands/compat.ts +1 -1
  14. package/cli/commands/config.ts +2 -2
  15. package/cli/commands/control.ts +125 -71
  16. package/cli/commands/cpu-profile.ts +45 -1
  17. package/cli/commands/daemon.ts +131 -33
  18. package/cli/commands/debug.ts +24 -17
  19. package/cli/commands/desktop.ts +7 -1
  20. package/cli/commands/detect.ts +101 -0
  21. package/cli/commands/detox.ts +6 -6
  22. package/cli/commands/device.ts +12 -12
  23. package/cli/commands/electron.ts +32 -14
  24. package/cli/commands/flow.ts +49 -41
  25. package/cli/commands/guest-host.ts +39 -1
  26. package/cli/commands/inspect/core.ts +14 -8
  27. package/cli/commands/inspect/list.ts +1 -1
  28. package/cli/commands/inspect/settle.ts +5 -3
  29. package/cli/commands/inspect/settling.ts +229 -44
  30. package/cli/commands/inspect/wait-idle.ts +3 -2
  31. package/cli/commands/inspect.ts +105 -93
  32. package/cli/commands/install-cli.ts +36 -15
  33. package/cli/commands/install-desktop.ts +68 -195
  34. package/cli/commands/kotlin.ts +6 -5
  35. package/cli/commands/login.ts +5 -5
  36. package/cli/commands/maestro-compare.ts +554 -0
  37. package/cli/commands/maestro-generate.ts +6 -3
  38. package/cli/commands/maestro.ts +22 -0
  39. package/cli/commands/mode.ts +2 -2
  40. package/cli/commands/no-bridge-hint.ts +6 -2
  41. package/cli/commands/platform.ts +35 -6
  42. package/cli/commands/preview.ts +6 -0
  43. package/cli/commands/project-report.ts +10 -12
  44. package/cli/commands/record.ts +37 -37
  45. package/cli/commands/runtime.ts +9 -9
  46. package/cli/commands/screenshot-layers.ts +4 -4
  47. package/cli/commands/screenshot-mode.ts +2 -2
  48. package/cli/commands/screenshot.ts +8 -8
  49. package/cli/commands/screenshots-capture.ts +2 -2
  50. package/cli/commands/serve.ts +9 -12
  51. package/cli/commands/setup.ts +29 -14
  52. package/cli/commands/shell-boolean-mode.ts +2 -2
  53. package/cli/commands/show-touches-mode.ts +2 -2
  54. package/cli/commands/skills.ts +11 -11
  55. package/cli/commands/swift.ts +29 -11
  56. package/cli/commands/telemetry.ts +2 -2
  57. package/cli/commands/test.ts +5 -0
  58. package/cli/commands/three-mode.ts +3 -3
  59. package/cli/commands/timeline.ts +4 -4
  60. package/cli/commands/upgrade.ts +2 -2
  61. package/cli/commands/upload.ts +15 -15
  62. package/cli/commands/what-happened.ts +2 -2
  63. package/cli/current-sim.ts +6 -3
  64. package/cli/desktop-companion.ts +55 -149
  65. package/cli/dev-build-freshness.ts +2 -2
  66. package/cli/drivers/desktop.ts +17 -9
  67. package/cli/drivers/electron.ts +1 -2
  68. package/cli/drivers/playwright-sim-host.ts +11 -2
  69. package/cli/drivers/playwright.ts +35 -2
  70. package/cli/drivers/types.ts +8 -0
  71. package/cli/maestro-compare.ts +670 -0
  72. package/cli/outbound-endpoints.ts +46 -8
  73. package/cli/recording-access.ts +6 -6
  74. package/cli/run-registry.ts +1 -1
  75. package/cli/runtime-compat.ts +5 -1
  76. package/cli/send-to-box.ts +3 -3
  77. package/cli/shell-init.ts +2 -1
  78. package/cli/ws-bridge.ts +9 -5
  79. package/detox/element-types.ts +1 -0
  80. package/detox/gestures.ts +3 -3
  81. package/detox/index.ts +67 -38
  82. package/detox/navigation-lifecycle.ts +9 -6
  83. package/detox/proof-frame.cjs +6 -4
  84. package/dist-lib/agent-daemon-client.cjs +1 -1
  85. package/dist-lib/agent-events.cjs +1 -1
  86. package/dist-lib/attached-projects.cjs +1 -1
  87. package/dist-lib/auth/shared-session.cjs +1 -1
  88. package/dist-lib/backend-origin.cjs +1 -1
  89. package/dist-lib/beta.cjs +1 -1
  90. package/dist-lib/beta.mjs +1 -1
  91. package/dist-lib/bridge-constants.cjs +1 -1
  92. package/dist-lib/bridge-contract-input.cjs +42 -17
  93. package/dist-lib/bridge-contract-input.mjs +41 -16
  94. package/dist-lib/bridge-contract.cjs +1 -4
  95. package/dist-lib/bridge-contract.mjs +1 -3
  96. package/dist-lib/bundle-channel.cjs +2918 -550
  97. package/dist-lib/capture-contract.cjs +65 -24
  98. package/dist-lib/capture-contract.mjs +63 -24
  99. package/dist-lib/cloud-contract.cjs +2 -2
  100. package/dist-lib/cloud-contract.mjs +2 -2
  101. package/dist-lib/cloud-test-args.cjs +1 -1
  102. package/dist-lib/cloud-test-args.mjs +1 -1
  103. package/dist-lib/cloud.cjs +3 -3
  104. package/dist-lib/cloud.mjs +3 -3
  105. package/dist-lib/config.cjs +15 -5
  106. package/dist-lib/config.mjs +154 -0
  107. package/dist-lib/detox/index.cjs +495 -139
  108. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  109. package/dist-lib/expo-swift-bundle.cjs +1168 -238
  110. package/dist-lib/expo-swift-metadata.cjs +34 -23
  111. package/dist-lib/expo-swift-metadata.mjs +32 -23
  112. package/dist-lib/home-paths.cjs +2 -2
  113. package/dist-lib/host/bridge-host.cjs +2938 -654
  114. package/dist-lib/host/fetch-proxy-handler.cjs +14 -1
  115. package/dist-lib/host/fetch-proxy-overrides.cjs +7 -1
  116. package/dist-lib/host/fetch-proxy-overrides.mjs +5 -1
  117. package/dist-lib/host/replacement-module-handler.cjs +2 -2
  118. package/dist-lib/host/websocket-proxy.cjs +1 -1
  119. package/dist-lib/index.cjs +981 -353
  120. package/dist-lib/jump-to-source-babel.cjs +1 -1
  121. package/dist-lib/jump-to-source-native.cjs +1 -1
  122. package/dist-lib/kotlin-host.cjs +332 -19
  123. package/dist-lib/menu.cjs +1 -1
  124. package/dist-lib/menu.mjs +1 -1
  125. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  126. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  127. package/dist-lib/metro-production-bundle.cjs +7 -7
  128. package/dist-lib/metro-production-bundle.mjs +5 -5
  129. package/dist-lib/metro.cjs +670 -250
  130. package/dist-lib/native-app-identity-project.cjs +2647 -0
  131. package/dist-lib/native-app-identity-project.mjs +1257 -0
  132. package/dist-lib/native-app-identity.cjs +304 -25
  133. package/dist-lib/native-app-identity.mjs +302 -25
  134. package/dist-lib/native-firebase-metadata.cjs +1 -1
  135. package/dist-lib/native-firebase-metadata.mjs +1 -1
  136. package/dist-lib/plist.cjs +1 -1
  137. package/dist-lib/plist.mjs +1 -1
  138. package/dist-lib/profiles.cjs +1 -1
  139. package/dist-lib/public-brand.cjs +1 -1
  140. package/dist-lib/react-native-host-modules.cjs +5 -1
  141. package/dist-lib/react-native-host-modules.mjs +5 -1
  142. package/dist-lib/scripts/dev-server-scanner.cjs +57 -32
  143. package/dist-lib/sdk.cjs +231 -51
  144. package/dist-lib/sdk.mjs +231 -51
  145. package/dist-lib/skills.cjs +4107 -1374
  146. package/dist-lib/swift-compile.cjs +290 -30
  147. package/dist-lib/swift-compile.mjs +290 -30
  148. package/dist-lib/swift-package.cjs +143 -12
  149. package/dist-lib/swift-package.mjs +137 -11
  150. package/dist-lib/swift-submit.cjs +47 -5
  151. package/dist-lib/swift-submit.mjs +42 -5
  152. package/dist-lib/swift-toolchain.cjs +7 -2
  153. package/dist-lib/swift.cjs +1 -1
  154. package/dist-lib/vite-swift.cjs +1392 -458
  155. package/dist-lib/vite.cjs +13 -17
  156. package/dist-lib/worklet-executable.cjs +248 -33
  157. package/dist-lib/worklet-executable.mjs +248 -33
  158. package/install.sh +10 -1
  159. package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Resources.kt +9 -9
  160. package/kotlin/AndroidViewSources/android/graphics/Canvas.kt +20 -0
  161. package/kotlin/AndroidViewSources/android/view/View.kt +211 -0
  162. package/kotlin/AndroidViewSources/android/widget/FrameLayout.kt +61 -0
  163. package/kotlin/AndroidViewSources/android/widget/LinearLayout.kt +49 -0
  164. package/kotlin/AppSources/android/view/Window.kt +36 -0
  165. package/kotlin/AppSources/android/view/WindowInsets.kt +9 -0
  166. package/kotlin/AppSources/android/view/WindowInsetsController.kt +5 -0
  167. package/kotlin/AppSources/android/window/OnBackInvokedDispatcher.kt +48 -0
  168. package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcher.kt +20 -5
  169. package/kotlin/AppSources/androidx/compose/foundation/layout/WindowInsets.kt +15 -0
  170. package/kotlin/AppSources/androidx/compose/ui/platform/ComposeView.kt +2 -1
  171. package/kotlin/AppSources/dev/peach/app/PeachApp.kt +42 -50
  172. package/kotlin/ComposeSources/PeachComposeHost.kt +200 -84
  173. package/kotlin/ExpoComposeSources/PeachAndroidView.kt +87 -0
  174. package/kotlin/ExpoComposeSources/PeachExpoViewMount.kt +24 -0
  175. package/kotlin/ExpoComposeSources/dev/peach/expo/PeachExpoCompose.kt +28 -0
  176. package/kotlin/ExpoComposeSources/expo/modules/kotlin/views/ExpoComposeView.kt +70 -0
  177. package/kotlin/ExpoSources/dev/peach/expo/PeachExpoRuntime.kt +513 -0
  178. package/kotlin/ExpoSources/dev/peach/expo/PeachExpoTypes.kt +173 -0
  179. package/kotlin/ExpoSources/expo/modules/kotlin/AppContext.kt +20 -0
  180. package/kotlin/ExpoSources/expo/modules/kotlin/Promise.kt +19 -0
  181. package/kotlin/ExpoSources/expo/modules/kotlin/classcomponent/ClassComponentBuilder.kt +198 -0
  182. package/kotlin/ExpoSources/expo/modules/kotlin/exception/CodedException.kt +126 -0
  183. package/kotlin/ExpoSources/expo/modules/kotlin/functions/Functions.kt +132 -0
  184. package/kotlin/ExpoSources/expo/modules/kotlin/jni/JavaScriptTypedArray.kt +81 -0
  185. package/kotlin/ExpoSources/expo/modules/kotlin/modules/Module.kt +98 -0
  186. package/kotlin/ExpoSources/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +236 -0
  187. package/kotlin/ExpoSources/expo/modules/kotlin/records/Record.kt +12 -0
  188. package/kotlin/ExpoSources/expo/modules/kotlin/runtime/Runtime.kt +9 -0
  189. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObject.kt +50 -0
  190. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObjectRegistry.kt +57 -0
  191. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedRef.kt +19 -0
  192. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/ConcreteTypedArrays.kt +158 -0
  193. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/GenericTypedArray.kt +8 -0
  194. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArray.kt +29 -0
  195. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArrayIterator.kt +9 -0
  196. package/kotlin/ExpoSources/expo/modules/kotlin/types/AnyType.kt +155 -0
  197. package/kotlin/ExpoSources/expo/modules/kotlin/types/Either.kt +91 -0
  198. package/kotlin/ExpoSources/expo/modules/kotlin/types/Enumerable.kt +6 -0
  199. package/kotlin/ExpoSources/expo/modules/kotlin/viewevent/ViewEvent.kt +54 -0
  200. package/kotlin/ExpoSources/expo/modules/kotlin/views/ExpoView.kt +23 -0
  201. package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewDefinitionBuilder.kt +71 -0
  202. package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewProps.kt +35 -0
  203. package/kotlin/README.md +13 -3
  204. package/kotlin/Sources/android/os/Build.kt +17 -0
  205. package/kotlin/Sources/android/os/Handler.kt +35 -0
  206. package/kotlin/Sources/android/os/Looper.kt +17 -0
  207. package/kotlin/Sources/android/os/MessageQueue.kt +56 -0
  208. package/kotlin/Sources/android/os/SystemClock.kt +14 -0
  209. package/kotlin/Sources/android/util/Log.kt +64 -0
  210. package/kotlin/Sources/java/security/MessageDigest.kt +114 -0
  211. package/kotlin/Sources/kotlin/text/StringFormat.kt +9 -0
  212. package/kotlin/Sources/org/json/JSON.kt +44 -0
  213. package/kotlin/fixtures/counter/Counter.d.kt.ts +1 -0
  214. package/kotlin/fixtures/counter/Counter.kt +10 -3
  215. package/kotlin/fixtures/counter/CounterApp.native.tsx +1 -1
  216. package/kotlin/fixtures/counter/vite.config.ts +2 -2
  217. package/kotlin/fixtures/headless-counter/Headless.kt +7 -6
  218. package/kotlin/fixtures/import-animation/Animation.d.kt.ts +7 -0
  219. package/kotlin/fixtures/import-animation/Animation.kt +80 -0
  220. package/kotlin/fixtures/import-animation/ImportAnimation.native.tsx +13 -0
  221. package/kotlin/fixtures/import-animation/README.md +24 -0
  222. package/kotlin/fixtures/import-animation/app/_layout.tsx +4 -0
  223. package/kotlin/fixtures/import-animation/app/index+spa.tsx +1 -0
  224. package/kotlin/fixtures/import-animation/app/routes.d.ts +18 -0
  225. package/kotlin/fixtures/import-animation/package.json +23 -0
  226. package/kotlin/fixtures/import-animation/tsconfig.json +20 -0
  227. package/kotlin/fixtures/import-animation/vite.config.ts +20 -0
  228. package/kotlin/fixtures/import-editor/Editor.d.kt.ts +7 -0
  229. package/kotlin/fixtures/import-editor/Editor.kt +59 -0
  230. package/kotlin/fixtures/import-editor/ImportEditor.native.tsx +23 -0
  231. package/kotlin/fixtures/import-editor/README.md +27 -0
  232. package/kotlin/fixtures/import-editor/app/_layout.tsx +4 -0
  233. package/kotlin/fixtures/import-editor/app/index+spa.tsx +1 -0
  234. package/kotlin/fixtures/import-editor/package.json +23 -0
  235. package/kotlin/fixtures/import-editor/tsconfig.json +20 -0
  236. package/kotlin/fixtures/import-editor/vite.config.ts +20 -0
  237. package/kotlin/fixtures/import-effects/Effects.d.kt.ts +7 -0
  238. package/kotlin/fixtures/import-effects/Effects.kt +87 -0
  239. package/kotlin/fixtures/import-effects/ImportEffects.native.tsx +13 -0
  240. package/kotlin/fixtures/import-effects/README.md +19 -0
  241. package/kotlin/fixtures/import-effects/app/_layout.tsx +4 -0
  242. package/kotlin/fixtures/import-effects/app/index+spa.tsx +1 -0
  243. package/kotlin/fixtures/import-effects/package.json +23 -0
  244. package/kotlin/fixtures/import-effects/tsconfig.json +20 -0
  245. package/kotlin/fixtures/import-effects/vite.config.ts +20 -0
  246. package/kotlin/fixtures/import-layout/ImportLayout.native.tsx +13 -0
  247. package/kotlin/fixtures/import-layout/Layout.d.kt.ts +7 -0
  248. package/kotlin/fixtures/import-layout/Layout.kt +72 -0
  249. package/kotlin/fixtures/import-layout/README.md +24 -0
  250. package/kotlin/fixtures/import-layout/app/_layout.tsx +4 -0
  251. package/kotlin/fixtures/import-layout/app/index+spa.tsx +1 -0
  252. package/kotlin/fixtures/import-layout/package.json +23 -0
  253. package/kotlin/fixtures/import-layout/tsconfig.json +20 -0
  254. package/kotlin/fixtures/import-layout/vite.config.ts +20 -0
  255. package/kotlin/fixtures/import-navigation/ImportNavigation.native.tsx +18 -0
  256. package/kotlin/fixtures/import-navigation/Navigation.d.kt.ts +7 -0
  257. package/kotlin/fixtures/import-navigation/Navigation.kt +104 -0
  258. package/kotlin/fixtures/import-navigation/README.md +31 -0
  259. package/kotlin/fixtures/import-navigation/app/_layout.tsx +4 -0
  260. package/kotlin/fixtures/import-navigation/app/index+spa.tsx +1 -0
  261. package/kotlin/fixtures/import-navigation/package.json +23 -0
  262. package/kotlin/fixtures/import-navigation/tsconfig.json +20 -0
  263. package/kotlin/fixtures/import-navigation/vite.config.ts +20 -0
  264. package/kotlin/fixtures/import-overlays/ImportOverlays.native.tsx +29 -0
  265. package/kotlin/fixtures/import-overlays/Overlays.d.kt.ts +7 -0
  266. package/kotlin/fixtures/import-overlays/Overlays.kt +93 -0
  267. package/kotlin/fixtures/import-overlays/README.md +34 -0
  268. package/kotlin/fixtures/import-overlays/app/_layout.tsx +4 -0
  269. package/kotlin/fixtures/import-overlays/app/index+spa.tsx +1 -0
  270. package/kotlin/fixtures/import-overlays/package.json +23 -0
  271. package/kotlin/fixtures/import-overlays/tsconfig.json +20 -0
  272. package/kotlin/fixtures/import-overlays/vite.config.ts +20 -0
  273. package/kotlin/fixtures/import-scroll/ImportScroll.native.tsx +13 -0
  274. package/kotlin/fixtures/import-scroll/README.md +23 -0
  275. package/kotlin/fixtures/import-scroll/Scroll.d.kt.ts +7 -0
  276. package/kotlin/fixtures/import-scroll/Scroll.kt +91 -0
  277. package/kotlin/fixtures/import-scroll/app/_layout.tsx +4 -0
  278. package/kotlin/fixtures/import-scroll/app/index+spa.tsx +1 -0
  279. package/kotlin/fixtures/import-scroll/package.json +23 -0
  280. package/kotlin/fixtures/import-scroll/tsconfig.json +20 -0
  281. package/kotlin/fixtures/import-scroll/vite.config.ts +20 -0
  282. package/kotlin/fixtures/import-selection/ImportSelection.native.tsx +44 -0
  283. package/kotlin/fixtures/import-selection/README.md +32 -0
  284. package/kotlin/fixtures/import-selection/Selection.d.kt.ts +8 -0
  285. package/kotlin/fixtures/import-selection/Selection.kt +78 -0
  286. package/kotlin/fixtures/import-selection/app/_layout.tsx +4 -0
  287. package/kotlin/fixtures/import-selection/app/index+spa.tsx +1 -0
  288. package/kotlin/fixtures/import-selection/package.json +23 -0
  289. package/kotlin/fixtures/import-selection/tsconfig.json +20 -0
  290. package/kotlin/fixtures/import-selection/vite.config.ts +20 -0
  291. package/kotlin/fixtures/import-surfaces/ImportSurfaces.native.tsx +13 -0
  292. package/kotlin/fixtures/import-surfaces/README.md +22 -0
  293. package/kotlin/fixtures/import-surfaces/Surfaces.d.kt.ts +7 -0
  294. package/kotlin/fixtures/import-surfaces/Surfaces.kt +64 -0
  295. package/kotlin/fixtures/import-surfaces/app/_layout.tsx +4 -0
  296. package/kotlin/fixtures/import-surfaces/app/index+spa.tsx +1 -0
  297. package/kotlin/fixtures/import-surfaces/package.json +23 -0
  298. package/kotlin/fixtures/import-surfaces/tsconfig.json +20 -0
  299. package/kotlin/fixtures/import-surfaces/vite.config.ts +20 -0
  300. package/kotlin/fixtures/import-values/ImportValues.native.tsx +35 -0
  301. package/kotlin/fixtures/import-values/README.md +23 -0
  302. package/kotlin/fixtures/import-values/Values.d.kt.ts +11 -0
  303. package/kotlin/fixtures/import-values/Values.kt +66 -0
  304. package/kotlin/fixtures/import-values/app/_layout.tsx +4 -0
  305. package/kotlin/fixtures/import-values/app/index+spa.tsx +1 -0
  306. package/kotlin/fixtures/import-values/package.json +23 -0
  307. package/kotlin/fixtures/import-values/tsconfig.json +20 -0
  308. package/kotlin/fixtures/import-values/vite.config.ts +20 -0
  309. package/kotlin/fixtures/kotlin-app-variants/BarBehavior.kt +38 -0
  310. package/kotlin/fixtures/kotlin-app-variants/BarVisibility.kt +36 -0
  311. package/kotlin/fixtures/kotlin-app-variants/Ime.kt +3 -1
  312. package/kotlin/fixtures/kotlin-app-variants/TransientInsets.kt +50 -0
  313. package/kotlin/fork/NOTICE +6 -1
  314. package/kotlin/fork/README.md +15 -7
  315. package/kotlin/fork/build.sh +2 -0
  316. package/kotlin/fork/compose-worker-globals.patch +186 -4
  317. package/kotlin/fork/fork.json +2 -2
  318. package/kotlin/fork/skiko-worker-clock.patch +12 -0
  319. package/kotlin/toolchain.json +12 -12
  320. package/kotlin/tools/expo-types.main.kts +115 -0
  321. package/package.json +17 -23
  322. package/scripts/dev-server-scanner.ts +92 -66
  323. package/skills/contrast/SKILL.md +2 -2
  324. package/skills/peach-debug/SKILL.md +1 -1
  325. package/skills/peach-perf/SKILL.md +1 -1
  326. package/skills/peach-setup/SKILL.md +2 -1
  327. package/skills/peach-test/SKILL.md +8 -0
  328. package/src/assetCatalogContract.ts +43 -1
  329. package/src/bridge-contract-input.ts +46 -19
  330. package/src/bridge-contract.ts +0 -1
  331. package/src/bundle-channel.mjs +197 -84
  332. package/src/bundleGlobalFooter.ts +38 -0
  333. package/src/capture-contract.ts +107 -33
  334. package/src/cloud-contract.ts +5 -1
  335. package/src/cloud.ts +5 -2
  336. package/src/config.ts +22 -3
  337. package/src/configured-bundle-producer.ts +1 -1
  338. package/src/desktop-host-delivery.ts +95 -1
  339. package/src/expo-kotlin-bundle.ts +29 -0
  340. package/src/expo-kotlin-host.ts +201 -0
  341. package/src/expo-swift-host.ts +96 -76
  342. package/src/expoKotlinContract.ts +70 -0
  343. package/src/expoSwiftContract.ts +4 -25
  344. package/src/home-paths.ts +7 -4
  345. package/src/host/bridge-host.ts +23 -3
  346. package/src/host/fetch-proxy-handler.ts +13 -0
  347. package/src/host/fetch-proxy-overrides.ts +11 -0
  348. package/src/host/replacement-module-handler.ts +5 -2
  349. package/src/index.ts +3 -1
  350. package/src/kotlin-host.ts +463 -19
  351. package/src/metro-plugin.ts +56 -28
  352. package/src/metro-production-bundle.ts +2 -2
  353. package/src/native-app-identity-project.ts +190 -0
  354. package/src/native-app-identity.ts +367 -34
  355. package/src/native-firebase-config.ts +69 -15
  356. package/src/native-seam-manifest.ts +6 -0
  357. package/src/one-widget-discovery.ts +2 -1
  358. package/src/openstep-plist.ts +107 -0
  359. package/src/react-native-host-modules.ts +4 -0
  360. package/src/runtime-assets.ts +2 -2
  361. package/src/sim-client.ts +2 -2
  362. package/src/swift-binary-modules.ts +3 -1
  363. package/src/swift-compile.ts +286 -38
  364. package/src/swift-distribution-contract.ts +4 -0
  365. package/src/swift-distribution.lock.json +123 -12
  366. package/src/swift-distribution.ts +7 -3
  367. package/src/swift-host.ts +298 -83
  368. package/src/swift-package-manifest.ts +29 -2
  369. package/src/swift-package.ts +180 -10
  370. package/src/swift-packages.ts +4 -0
  371. package/src/swift-standin-emit.ts +4 -1
  372. package/src/swift-standins.ts +274 -15
  373. package/src/swift-submit.ts +62 -7
  374. package/src/swift-toolchain.ts +6 -1
  375. package/src/swiftContract.ts +126 -3
  376. package/src/vite-plugin-one.ts +13 -16
  377. package/src/vite-plugin-swift.ts +4 -5
  378. package/src/vite-plugin.ts +360 -144
  379. package/src/vite.d.ts +1 -1
  380. package/src/worker-system-chunks.ts +221 -0
  381. package/src/worklet-executable.ts +348 -38
  382. package/src/worklets-babel.ts +1 -1
  383. package/src/xcode-project.ts +243 -127
  384. package/dist-lib/agent-identity.cjs +0 -39
  385. package/dist-lib/agent-sessions.cjs +0 -760
  386. package/dist-lib/cli-constants.cjs +0 -32
  387. package/dist-lib/render-mode.cjs +0 -44
  388. package/kotlin/AppSources/android/view/View.kt +0 -74
  389. package/kotlin/AppSources/android/widget/FrameLayout.kt +0 -6
  390. package/src/render-mode.ts +0 -15
  391. /package/kotlin/{AppSources → AndroidViewSources}/android/content/Context.kt +0 -0
  392. /package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Configuration.kt +0 -0
  393. /package/kotlin/{AppSources → AndroidViewSources}/android/graphics/Color.kt +0 -0
  394. /package/kotlin/{AppSources → AndroidViewSources}/android/util/DisplayMetrics.kt +0 -0
  395. /package/kotlin/{AppSources → AndroidViewSources}/dev/peach/app/JavaFormat.kt +0 -0
  396. /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.ColorAlphaType
73
- import org.jetbrains.skia.ColorSpace
74
- import org.jetbrains.skia.ColorType
75
- import org.jetbrains.skia.DirectContext
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
- // owners and window insets come from the hosting window: an activity's
87
- // ComposeView passes the activity and the insets its window delivers; an
88
- // imported composable keeps the empty defaults.
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 val platform = PeachComposePlatform(Density(density, fontScale), LocaleList(locale), IntSize.Zero, owners, insets)
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 surface: Surface? = null
319
- private var context: DirectContext? = null
320
- private var target: BackendRenderTarget? = null
321
- private var pixels: Bitmap? = null
322
- private var size = IntSize.Zero
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
- init { platform.onInputChange = { invalidate(false) } }
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
- CompositionLocalProvider(LocalSystemTheme provides if (night) SystemTheme.DARK else SystemTheme.LIGHT) {
330
- PeachAndroidFonts(fonts, content)
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
- val measured = scene.measureContent(checkNotNull(constraints))
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
- Snapshot.sendApplyNotifications()
360
- if (clockRunning()) recomposer.performFrame(nanos)
361
- val measured = if (needsMeasure && constraints != null) measure() else measuredSize
362
- // a zero-size composition still runs its effects and reports its size;
363
- // its backing surface uses one pixel until layout gives it real bounds.
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
- if (gpu) {
371
- context = DirectContext.makeGL()
372
- target = BackendRenderTarget.makeGL(size.width, size.height, 1, stencilBits, 0, 0x8058)
373
- surface = checkNotNull(Surface.makeFromBackendRenderTarget(
374
- checkNotNull(context), checkNotNull(target), SurfaceOrigin.BOTTOM_LEFT,
375
- SurfaceColorFormat.RGBA_8888, ColorSpace.sRGB, SurfaceProps(),
376
- )) { "Skiko GPU surface creation failed" }
377
- } else {
378
- surface = Surface.makeRasterN32Premul(size.width, size.height)
379
- pixels = Bitmap().also { check(it.allocPixels(ImageInfo(size.width, size.height, ColorType.RGBA_8888, ColorAlphaType.PREMUL, ColorSpace.sRGB))) }
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
- private fun releaseSurface() {
459
- pixels?.close(); pixels = null
460
- surface?.close(); surface = null
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 { releaseSurface() }
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(regular: String, medium: String, bold: String) = FontFamily(
507
- Font("Roboto-400", Base64.decode(regular), FontWeight.Normal),
508
- Font("Roboto-500", Base64.decode(medium), FontWeight.Medium),
509
- Font("Roboto-700", Base64.decode(bold), FontWeight.Bold),
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, backend: String, stencilBits: Int,
515
- regular: String, medium: String, bold: String, invalidate: (Boolean) -> Unit): String {
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, backend == "graphite", stencilBits, robotoFamily(regular, medium, bold), invalidate, night)
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
+ )