@q9labsai/chalk-react-native 4.1.17 → 5.0.1

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 (544) hide show
  1. package/README.md +134 -29
  2. package/android/build.gradle +35 -0
  3. package/android/compatibility/ModuleApiCompatibility.java +215 -0
  4. package/android/src/main/AndroidManifest.xml +2 -1
  5. package/android/src/main/java/ai/q9labs/chalk/reactnative/ChalkSpaceForegroundService.java +105 -31
  6. package/android/src/main/kotlin/ai/q9labs/chalk/reactnative/ChalkAndroidConnectionServicePackage.kt +2 -0
  7. package/android/src/main/kotlin/ai/q9labs/chalk/reactnative/ChalkAudioRouteModule.kt +137 -0
  8. package/android/src/main/kotlin/ai/q9labs/chalk/reactnative/ChalkCallServiceModule.kt +32 -0
  9. package/android/src/main/kotlin/ai/q9labs/chalk/reactnative/CommunicationDeviceListener.kt +23 -0
  10. package/android/src/test/kotlin/ai/q9labs/chalk/compatibility/UnsafeSignatures.kt +29 -0
  11. package/dist/android/call-service.d.ts +4 -0
  12. package/dist/android/call-service.d.ts.map +1 -0
  13. package/dist/avatar/FaceAvatar.d.ts +19 -0
  14. package/dist/avatar/FaceAvatar.d.ts.map +1 -0
  15. package/dist/avatar/tile-avatar-size.d.ts +3 -0
  16. package/dist/avatar/tile-avatar-size.d.ts.map +1 -0
  17. package/dist/chunk-GYVZSOGQ.js +30 -0
  18. package/dist/chunk-IHE6EA3T.js +25 -0
  19. package/dist/chunk-OIBT2FNM.js +1479 -0
  20. package/dist/client.d.ts +1 -5
  21. package/dist/client.d.ts.map +1 -1
  22. package/dist/client.js +185 -2
  23. package/dist/components/EmbeddedWhiteboard.d.ts.map +1 -1
  24. package/dist/components/native-media-visibility.d.ts +3 -2
  25. package/dist/components/native-media-visibility.d.ts.map +1 -1
  26. package/dist/components/useAndroidCallService.d.ts +8 -0
  27. package/dist/components/useAndroidCallService.d.ts.map +1 -0
  28. package/dist/dev.d.ts +10 -0
  29. package/dist/dev.d.ts.map +1 -0
  30. package/dist/dev.js +10 -0
  31. package/dist/dock/Dock.d.ts +23 -0
  32. package/dist/dock/Dock.d.ts.map +1 -0
  33. package/dist/dock/LeaveMenu.d.ts +12 -0
  34. package/dist/dock/LeaveMenu.d.ts.map +1 -0
  35. package/dist/dock/MoreSheet.d.ts +17 -0
  36. package/dist/dock/MoreSheet.d.ts.map +1 -0
  37. package/dist/dock/ReactionBurst.d.ts +8 -0
  38. package/dist/dock/ReactionBurst.d.ts.map +1 -0
  39. package/dist/dock/RoutePicker.d.ts +16 -0
  40. package/dist/dock/RoutePicker.d.ts.map +1 -0
  41. package/dist/dock/audio-route.d.ts +25 -0
  42. package/dist/dock/audio-route.d.ts.map +1 -0
  43. package/dist/error-YOPTEH5N.wav +0 -0
  44. package/dist/frame/CallFrame.d.ts +17 -0
  45. package/dist/frame/CallFrame.d.ts.map +1 -0
  46. package/dist/frame/Chalk.d.ts +40 -0
  47. package/dist/frame/Chalk.d.ts.map +1 -0
  48. package/dist/frame/back-handler.d.ts +6 -0
  49. package/dist/frame/back-handler.d.ts.map +1 -0
  50. package/dist/frame/dev-scenario.d.ts +28 -0
  51. package/dist/frame/dev-scenario.d.ts.map +1 -0
  52. package/dist/frame/device-layout.d.ts +16 -0
  53. package/dist/frame/device-layout.d.ts.map +1 -0
  54. package/dist/frame/measurement.d.ts +28 -0
  55. package/dist/frame/measurement.d.ts.map +1 -0
  56. package/dist/frame/portal.d.ts +24 -0
  57. package/dist/frame/portal.d.ts.map +1 -0
  58. package/dist/frame/preferences.d.ts +21 -0
  59. package/dist/frame/preferences.d.ts.map +1 -0
  60. package/dist/frame/regions.d.ts +25 -0
  61. package/dist/frame/regions.d.ts.map +1 -0
  62. package/dist/hand-DFWVXPH2.wav +0 -0
  63. package/dist/haptics/haptics.d.ts +12 -0
  64. package/dist/haptics/haptics.d.ts.map +1 -0
  65. package/dist/header/CallDetailsSheet.d.ts +18 -0
  66. package/dist/header/CallDetailsSheet.d.ts.map +1 -0
  67. package/dist/header/Header.d.ts +17 -0
  68. package/dist/header/Header.d.ts.map +1 -0
  69. package/dist/header/QrCode.d.ts +6 -0
  70. package/dist/header/QrCode.d.ts.map +1 -0
  71. package/dist/icons/Icon.d.ts +12 -0
  72. package/dist/icons/Icon.d.ts.map +1 -0
  73. package/dist/icons/icon-element.d.ts +24 -0
  74. package/dist/icons/icon-element.d.ts.map +1 -0
  75. package/dist/icons/icon-paths.d.ts +530 -0
  76. package/dist/icons/icon-paths.d.ts.map +1 -0
  77. package/dist/index.d.ts +6 -9
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +2956 -38
  80. package/dist/join-Z6RL5AHE.wav +0 -0
  81. package/dist/knock-4VBQSHK4.wav +0 -0
  82. package/dist/leave-MQLZOWQD.wav +0 -0
  83. package/dist/limbo/Limbo.d.ts +25 -0
  84. package/dist/limbo/Limbo.d.ts.map +1 -0
  85. package/dist/limbo/Veil.d.ts +10 -0
  86. package/dist/limbo/Veil.d.ts.map +1 -0
  87. package/dist/media/native-capture-error.d.ts +7 -0
  88. package/dist/media/native-capture-error.d.ts.map +1 -0
  89. package/dist/message-6Y6WJGTB.wav +0 -0
  90. package/dist/motion/MovingFrame.d.ts +36 -0
  91. package/dist/motion/MovingFrame.d.ts.map +1 -0
  92. package/dist/motion/motion.d.ts +82 -0
  93. package/dist/motion/motion.d.ts.map +1 -0
  94. package/dist/motion/presence.d.ts +6 -0
  95. package/dist/motion/presence.d.ts.map +1 -0
  96. package/dist/motion/press.d.ts +15 -0
  97. package/dist/motion/press.d.ts.map +1 -0
  98. package/dist/mute-H5SJFXVI.wav +0 -0
  99. package/dist/primitives/BottomSheet.d.ts +36 -0
  100. package/dist/primitives/BottomSheet.d.ts.map +1 -0
  101. package/dist/primitives/ChalkText.d.ts +13 -0
  102. package/dist/primitives/ChalkText.d.ts.map +1 -0
  103. package/dist/primitives/CopyButton.d.ts +11 -0
  104. package/dist/primitives/CopyButton.d.ts.map +1 -0
  105. package/dist/primitives/IconButton.d.ts +26 -0
  106. package/dist/primitives/IconButton.d.ts.map +1 -0
  107. package/dist/primitives/Menu.d.ts +39 -0
  108. package/dist/primitives/Menu.d.ts.map +1 -0
  109. package/dist/primitives/NameTag.d.ts +19 -0
  110. package/dist/primitives/NameTag.d.ts.map +1 -0
  111. package/dist/primitives/PillButton.d.ts +19 -0
  112. package/dist/primitives/PillButton.d.ts.map +1 -0
  113. package/dist/primitives/SegmentedControl.d.ts +17 -0
  114. package/dist/primitives/SegmentedControl.d.ts.map +1 -0
  115. package/dist/primitives/SheetGroup.d.ts +24 -0
  116. package/dist/primitives/SheetGroup.d.ts.map +1 -0
  117. package/dist/primitives/Switch.d.ts +9 -0
  118. package/dist/primitives/Switch.d.ts.map +1 -0
  119. package/dist/primitives/TileShell.d.ts +18 -0
  120. package/dist/primitives/TileShell.d.ts.map +1 -0
  121. package/dist/primitives/announce.d.ts +3 -0
  122. package/dist/primitives/announce.d.ts.map +1 -0
  123. package/dist/primitives/menu-placement.d.ts +32 -0
  124. package/dist/primitives/menu-placement.d.ts.map +1 -0
  125. package/dist/primitives/toast/Toast.d.ts +8 -0
  126. package/dist/primitives/toast/Toast.d.ts.map +1 -0
  127. package/dist/primitives/toast/ToastStack.d.ts +18 -0
  128. package/dist/primitives/toast/ToastStack.d.ts.map +1 -0
  129. package/dist/primitives/toast/toast-store.d.ts +39 -0
  130. package/dist/primitives/toast/toast-store.d.ts.map +1 -0
  131. package/dist/recording-Q4GB77Y4.wav +0 -0
  132. package/dist/sheets/people-chat/ChatNotices.d.ts +14 -0
  133. package/dist/sheets/people-chat/ChatNotices.d.ts.map +1 -0
  134. package/dist/sheets/people-chat/ChatTab.d.ts +6 -0
  135. package/dist/sheets/people-chat/ChatTab.d.ts.map +1 -0
  136. package/dist/sheets/people-chat/Composer.d.ts +7 -0
  137. package/dist/sheets/people-chat/Composer.d.ts.map +1 -0
  138. package/dist/sheets/people-chat/PeopleChatSheet.d.ts +17 -0
  139. package/dist/sheets/people-chat/PeopleChatSheet.d.ts.map +1 -0
  140. package/dist/sheets/people-chat/PeopleTab.d.ts +3 -0
  141. package/dist/sheets/people-chat/PeopleTab.d.ts.map +1 -0
  142. package/dist/sheets/people-chat/PhotoViewer.d.ts +19 -0
  143. package/dist/sheets/people-chat/PhotoViewer.d.ts.map +1 -0
  144. package/dist/sheets/people-chat/TextField.d.ts +4 -0
  145. package/dist/sheets/people-chat/TextField.d.ts.map +1 -0
  146. package/dist/sheets/people-chat/attachments.d.ts +33 -0
  147. package/dist/sheets/people-chat/attachments.d.ts.map +1 -0
  148. package/dist/sheets/people-chat/chat-model.d.ts +64 -0
  149. package/dist/sheets/people-chat/chat-model.d.ts.map +1 -0
  150. package/dist/sheets/people-chat/people-model.d.ts +43 -0
  151. package/dist/sheets/people-chat/people-model.d.ts.map +1 -0
  152. package/dist/sounds/sounds.d.ts +33 -0
  153. package/dist/sounds/sounds.d.ts.map +1 -0
  154. package/dist/space-client/create-native-space-client.d.ts +0 -4
  155. package/dist/space-client/create-native-space-client.d.ts.map +1 -1
  156. package/dist/space-client/random-uuid.d.ts +7 -0
  157. package/dist/space-client/random-uuid.d.ts.map +1 -0
  158. package/dist/stage/ContentTile.d.ts +18 -0
  159. package/dist/stage/ContentTile.d.ts.map +1 -0
  160. package/dist/stage/ParticipantTile.d.ts +18 -0
  161. package/dist/stage/ParticipantTile.d.ts.map +1 -0
  162. package/dist/stage/Stage.d.ts +14 -0
  163. package/dist/stage/Stage.d.ts.map +1 -0
  164. package/dist/stage/TileSheet.d.ts +12 -0
  165. package/dist/stage/TileSheet.d.ts.map +1 -0
  166. package/dist/stage/camera.d.ts +7 -0
  167. package/dist/stage/camera.d.ts.map +1 -0
  168. package/dist/stage/layout.d.ts +57 -0
  169. package/dist/stage/layout.d.ts.map +1 -0
  170. package/dist/stage/space-selector.d.ts +10 -0
  171. package/dist/stage/space-selector.d.ts.map +1 -0
  172. package/dist/stage/stage-view.d.ts +23 -0
  173. package/dist/stage/stage-view.d.ts.map +1 -0
  174. package/dist/status/CallOverlays.d.ts +17 -0
  175. package/dist/status/CallOverlays.d.ts.map +1 -0
  176. package/dist/status/ChalkArrival.d.ts +24 -0
  177. package/dist/status/ChalkArrival.d.ts.map +1 -0
  178. package/dist/status/StatusPage.d.ts +14 -0
  179. package/dist/status/StatusPage.d.ts.map +1 -0
  180. package/dist/status/call-screen.d.ts +20 -0
  181. package/dist/status/call-screen.d.ts.map +1 -0
  182. package/dist/test-VD2KAM4D.wav +0 -0
  183. package/dist/theme/theme.d.ts +15 -0
  184. package/dist/theme/theme.d.ts.map +1 -0
  185. package/dist/theme/tokens.d.ts +248 -0
  186. package/dist/theme/tokens.d.ts.map +1 -0
  187. package/dist/toasts/CallEventToasts.d.ts +21 -0
  188. package/dist/toasts/CallEventToasts.d.ts.map +1 -0
  189. package/dist/toasts/notices.d.ts +34 -0
  190. package/dist/toasts/notices.d.ts.map +1 -0
  191. package/dist/ui/ChalkUI.d.ts +12 -0
  192. package/dist/ui/ChalkUI.d.ts.map +1 -0
  193. package/dist/ui.d.ts +25 -0
  194. package/dist/ui.d.ts.map +1 -0
  195. package/dist/ui.js +77 -0
  196. package/dist/unmute-2XR2LICU.wav +0 -0
  197. package/embedded/chalk-whiteboard/asset-manifest.json +2 -2
  198. package/embedded/chalk-whiteboard/renderer.js +510 -510
  199. package/ios/ChalkAudioRouteModule.swift +93 -0
  200. package/ios/ChalkAudioRouteModuleBridge.m +9 -0
  201. package/package.json +46 -76
  202. package/src/android/call-service.ts +19 -0
  203. package/src/avatar/FaceAvatar.tsx +145 -0
  204. package/src/avatar/tile-avatar-size.ts +4 -0
  205. package/src/client.ts +1 -5
  206. package/src/components/EmbeddedWhiteboard.tsx +14 -71
  207. package/src/components/native-media-visibility.ts +1 -2
  208. package/src/components/useAndroidCallService.ts +41 -0
  209. package/src/dev.ts +9 -0
  210. package/src/dock/Dock.tsx +95 -0
  211. package/src/dock/LeaveMenu.tsx +21 -0
  212. package/src/dock/MoreSheet.tsx +233 -0
  213. package/src/dock/ReactionBurst.tsx +55 -0
  214. package/src/dock/RoutePicker.tsx +42 -0
  215. package/src/dock/audio-route.ts +69 -0
  216. package/src/frame/CallFrame.tsx +209 -0
  217. package/src/frame/Chalk.tsx +83 -0
  218. package/src/frame/back-handler.ts +19 -0
  219. package/src/frame/dev-scenario.ts +48 -0
  220. package/src/frame/device-layout.tsx +31 -0
  221. package/src/frame/measurement.ts +44 -0
  222. package/src/frame/portal.tsx +107 -0
  223. package/src/frame/preferences.tsx +48 -0
  224. package/src/frame/regions.test.ts +38 -0
  225. package/src/frame/regions.ts +60 -0
  226. package/src/haptics/haptics.ts +19 -0
  227. package/src/header/CallDetailsSheet.tsx +139 -0
  228. package/src/header/Header.tsx +107 -0
  229. package/src/header/QrCode.tsx +34 -0
  230. package/src/icons/Icon.tsx +35 -0
  231. package/src/icons/icon-element.ts +11 -0
  232. package/src/icons/icon-paths.ts +199 -0
  233. package/src/index.ts +6 -9
  234. package/src/limbo/Limbo.tsx +159 -0
  235. package/src/limbo/Veil.tsx +36 -0
  236. package/src/media/native-capture-error.test.ts +19 -0
  237. package/src/media/native-capture-error.ts +13 -0
  238. package/src/motion/MovingFrame.tsx +101 -0
  239. package/src/motion/motion.ts +33 -0
  240. package/src/motion/presence.ts +20 -0
  241. package/src/motion/press.ts +35 -0
  242. package/src/primitives/BottomSheet.tsx +211 -0
  243. package/src/primitives/ChalkText.tsx +23 -0
  244. package/src/primitives/CopyButton.tsx +78 -0
  245. package/src/primitives/IconButton.tsx +90 -0
  246. package/src/primitives/Menu.tsx +212 -0
  247. package/src/primitives/NameTag.tsx +68 -0
  248. package/src/primitives/PillButton.tsx +56 -0
  249. package/src/primitives/SegmentedControl.tsx +55 -0
  250. package/src/primitives/SheetGroup.tsx +72 -0
  251. package/src/primitives/Switch.tsx +39 -0
  252. package/src/primitives/TileShell.tsx +53 -0
  253. package/src/primitives/announce.ts +7 -0
  254. package/src/primitives/menu-placement.test.ts +50 -0
  255. package/src/primitives/menu-placement.ts +43 -0
  256. package/src/primitives/toast/Toast.tsx +50 -0
  257. package/src/primitives/toast/ToastStack.tsx +89 -0
  258. package/src/primitives/toast/toast-store.ts +86 -0
  259. package/src/sheets/people-chat/ChatNotices.tsx +69 -0
  260. package/src/sheets/people-chat/ChatTab.tsx +186 -0
  261. package/src/sheets/people-chat/Composer.tsx +182 -0
  262. package/src/sheets/people-chat/PeopleChatSheet.tsx +100 -0
  263. package/src/sheets/people-chat/PeopleTab.tsx +326 -0
  264. package/src/sheets/people-chat/PhotoViewer.tsx +132 -0
  265. package/src/sheets/people-chat/TextField.tsx +14 -0
  266. package/src/sheets/people-chat/attachments.tsx +183 -0
  267. package/src/sheets/people-chat/chat-model.ts +157 -0
  268. package/src/sheets/people-chat/people-chat-model.test.ts +98 -0
  269. package/src/sheets/people-chat/people-model.ts +68 -0
  270. package/src/sounds/assets/error.wav +0 -0
  271. package/src/sounds/assets/hand.wav +0 -0
  272. package/src/sounds/assets/join.wav +0 -0
  273. package/src/sounds/assets/knock.wav +0 -0
  274. package/src/sounds/assets/leave.wav +0 -0
  275. package/src/sounds/assets/message.wav +0 -0
  276. package/src/sounds/assets/mute.wav +0 -0
  277. package/src/sounds/assets/recording.wav +0 -0
  278. package/src/sounds/assets/test.wav +0 -0
  279. package/src/sounds/assets/unmute.wav +0 -0
  280. package/src/sounds/assets.d.ts +5 -0
  281. package/src/sounds/sounds.ts +50 -0
  282. package/src/space-client/create-native-space-client.ts +20 -181
  283. package/src/space-client/random-uuid.ts +17 -0
  284. package/src/space-client/whiteboard-watchdog.test.ts +23 -0
  285. package/src/stage/ContentTile.tsx +100 -0
  286. package/src/stage/ParticipantTile.tsx +140 -0
  287. package/src/stage/Stage.tsx +152 -0
  288. package/src/stage/TileSheet.tsx +116 -0
  289. package/src/stage/camera.ts +20 -0
  290. package/src/stage/layout.test.ts +77 -0
  291. package/src/stage/layout.ts +226 -0
  292. package/src/stage/space-selector.ts +31 -0
  293. package/src/stage/stage-view.tsx +68 -0
  294. package/src/status/CallOverlays.tsx +47 -0
  295. package/src/status/ChalkArrival.tsx +53 -0
  296. package/src/status/StatusPage.tsx +123 -0
  297. package/src/status/call-screen.test.ts +42 -0
  298. package/src/status/call-screen.ts +29 -0
  299. package/src/theme/theme.tsx +28 -0
  300. package/src/theme/tokens.ts +247 -0
  301. package/src/toasts/CallEventToasts.tsx +192 -0
  302. package/src/toasts/notices.test.ts +92 -0
  303. package/src/toasts/notices.ts +62 -0
  304. package/src/ui/ChalkUI.tsx +49 -0
  305. package/src/ui.ts +24 -0
  306. package/dist/android.d.ts +0 -2
  307. package/dist/android.d.ts.map +0 -1
  308. package/dist/android.js +0 -62
  309. package/dist/chunk-2OS7SSXE.js +0 -552
  310. package/dist/chunk-5DQ4KBRH.js +0 -690
  311. package/dist/chunk-ITLUHTVS.js +0 -5271
  312. package/dist/chunk-JB4LNWVP.js +0 -247
  313. package/dist/components/Chalk.d.ts +0 -79
  314. package/dist/components/Chalk.d.ts.map +0 -1
  315. package/dist/components/ChatMessageList.d.ts +0 -11
  316. package/dist/components/ChatMessageList.d.ts.map +0 -1
  317. package/dist/components/Entrance.d.ts +0 -22
  318. package/dist/components/Entrance.d.ts.map +0 -1
  319. package/dist/components/EntranceView.android.d.ts +0 -4
  320. package/dist/components/EntranceView.android.d.ts.map +0 -1
  321. package/dist/components/EntranceView.d.ts +0 -18
  322. package/dist/components/EntranceView.d.ts.map +0 -1
  323. package/dist/components/EntranceView.ios-pad.d.ts +0 -3
  324. package/dist/components/EntranceView.ios-pad.d.ts.map +0 -1
  325. package/dist/components/EntranceView.ios-phone.d.ts +0 -3
  326. package/dist/components/EntranceView.ios-phone.d.ts.map +0 -1
  327. package/dist/components/EntranceView.macos.d.ts +0 -4
  328. package/dist/components/EntranceView.macos.d.ts.map +0 -1
  329. package/dist/components/FaceAvatar.d.ts +0 -16
  330. package/dist/components/FaceAvatar.d.ts.map +0 -1
  331. package/dist/components/GradientSurface.d.ts +0 -14
  332. package/dist/components/GradientSurface.d.ts.map +0 -1
  333. package/dist/components/MediaView.d.ts +0 -14
  334. package/dist/components/MediaView.d.ts.map +0 -1
  335. package/dist/components/ReactionPicker.d.ts +0 -10
  336. package/dist/components/ReactionPicker.d.ts.map +0 -1
  337. package/dist/components/SpaceView.android.d.ts +0 -2
  338. package/dist/components/SpaceView.android.d.ts.map +0 -1
  339. package/dist/components/SpaceView.d.ts +0 -33
  340. package/dist/components/SpaceView.d.ts.map +0 -1
  341. package/dist/components/SpaceView.ios-pad.d.ts +0 -2
  342. package/dist/components/SpaceView.ios-pad.d.ts.map +0 -1
  343. package/dist/components/SpaceView.ios-phone.d.ts +0 -2
  344. package/dist/components/SpaceView.ios-phone.d.ts.map +0 -1
  345. package/dist/components/SpaceView.macos.d.ts +0 -2
  346. package/dist/components/SpaceView.macos.d.ts.map +0 -1
  347. package/dist/components/SpaceView.shared.d.ts +0 -3
  348. package/dist/components/SpaceView.shared.d.ts.map +0 -1
  349. package/dist/components/episode-diagnostic-render-observer.d.ts +0 -2
  350. package/dist/components/episode-diagnostic-render-observer.d.ts.map +0 -1
  351. package/dist/components/logo/Logo.d.ts +0 -11
  352. package/dist/components/logo/Logo.d.ts.map +0 -1
  353. package/dist/components/native-animation-controller.d.ts +0 -7
  354. package/dist/components/native-animation-controller.d.ts.map +0 -1
  355. package/dist/components/native-chat.d.ts +0 -8
  356. package/dist/components/native-chat.d.ts.map +0 -1
  357. package/dist/components/native-entrance/entrance-controller-store.d.ts +0 -34
  358. package/dist/components/native-entrance/entrance-controller-store.d.ts.map +0 -1
  359. package/dist/components/native-entrance/useEntranceController.d.ts +0 -19
  360. package/dist/components/native-entrance/useEntranceController.d.ts.map +0 -1
  361. package/dist/components/native-joining-loading-animation.d.ts +0 -9
  362. package/dist/components/native-joining-loading-animation.d.ts.map +0 -1
  363. package/dist/components/native-space-view/AppearanceSettings.d.ts +0 -2
  364. package/dist/components/native-space-view/AppearanceSettings.d.ts.map +0 -1
  365. package/dist/components/native-space-view/FeedbackSheet.d.ts +0 -12
  366. package/dist/components/native-space-view/FeedbackSheet.d.ts.map +0 -1
  367. package/dist/components/native-space-view/ParticipantActionSheet.d.ts +0 -9
  368. package/dist/components/native-space-view/ParticipantActionSheet.d.ts.map +0 -1
  369. package/dist/components/native-space-view/SettingsSheet.d.ts +0 -7
  370. package/dist/components/native-space-view/SettingsSheet.d.ts.map +0 -1
  371. package/dist/components/native-space-view/SpaceActionMenu.d.ts +0 -10
  372. package/dist/components/native-space-view/SpaceActionMenu.d.ts.map +0 -1
  373. package/dist/components/native-space-view/SpaceBottomDock.android.d.ts +0 -4
  374. package/dist/components/native-space-view/SpaceBottomDock.android.d.ts.map +0 -1
  375. package/dist/components/native-space-view/SpaceChatAttachment.d.ts +0 -8
  376. package/dist/components/native-space-view/SpaceChatAttachment.d.ts.map +0 -1
  377. package/dist/components/native-space-view/SpaceChatSheet.d.ts +0 -5
  378. package/dist/components/native-space-view/SpaceChatSheet.d.ts.map +0 -1
  379. package/dist/components/native-space-view/SpaceGrid.android.d.ts +0 -8
  380. package/dist/components/native-space-view/SpaceGrid.android.d.ts.map +0 -1
  381. package/dist/components/native-space-view/SpaceMoreSheet.d.ts +0 -7
  382. package/dist/components/native-space-view/SpaceMoreSheet.d.ts.map +0 -1
  383. package/dist/components/native-space-view/SpacePanelSheet.d.ts +0 -5
  384. package/dist/components/native-space-view/SpacePanelSheet.d.ts.map +0 -1
  385. package/dist/components/native-space-view/SpacePeopleSheet.d.ts +0 -5
  386. package/dist/components/native-space-view/SpacePeopleSheet.d.ts.map +0 -1
  387. package/dist/components/native-space-view/SpaceStage.android.d.ts +0 -29
  388. package/dist/components/native-space-view/SpaceStage.android.d.ts.map +0 -1
  389. package/dist/components/native-space-view/SpaceSurfacePrimitives.d.ts +0 -30
  390. package/dist/components/native-space-view/SpaceSurfacePrimitives.d.ts.map +0 -1
  391. package/dist/components/native-space-view/SpaceTopBar.android.d.ts +0 -11
  392. package/dist/components/native-space-view/SpaceTopBar.android.d.ts.map +0 -1
  393. package/dist/components/native-space-view/SpaceWhiteboardSurface.d.ts +0 -15
  394. package/dist/components/native-space-view/SpaceWhiteboardSurface.d.ts.map +0 -1
  395. package/dist/components/native-space-view/diagnostics.d.ts +0 -48
  396. package/dist/components/native-space-view/diagnostics.d.ts.map +0 -1
  397. package/dist/components/native-space-view/format-space-duration.d.ts +0 -2
  398. package/dist/components/native-space-view/format-space-duration.d.ts.map +0 -1
  399. package/dist/components/native-space-view/native-space-grid-keys.d.ts +0 -3
  400. package/dist/components/native-space-view/native-space-grid-keys.d.ts.map +0 -1
  401. package/dist/components/native-space-view/native-space-page-store.d.ts +0 -8
  402. package/dist/components/native-space-view/native-space-page-store.d.ts.map +0 -1
  403. package/dist/components/native-space-view/space-chat-attachments.d.ts +0 -15
  404. package/dist/components/native-space-view/space-chat-attachments.d.ts.map +0 -1
  405. package/dist/components/native-space-view/space-panel-layout.d.ts +0 -10
  406. package/dist/components/native-space-view/space-panel-layout.d.ts.map +0 -1
  407. package/dist/components/native-space-view/space-progressive-surface-helpers.d.ts +0 -16
  408. package/dist/components/native-space-view/space-progressive-surface-helpers.d.ts.map +0 -1
  409. package/dist/components/native-space-view/space-progressive-surface-types.d.ts +0 -3
  410. package/dist/components/native-space-view/space-progressive-surface-types.d.ts.map +0 -1
  411. package/dist/components/native-space-view/types.d.ts +0 -24
  412. package/dist/components/native-space-view/types.d.ts.map +0 -1
  413. package/dist/components/native-space-view/useSpaceViewController.contract.d.ts +0 -41
  414. package/dist/components/native-space-view/useSpaceViewController.contract.d.ts.map +0 -1
  415. package/dist/components/native-space-view/useSpaceViewController.d.ts +0 -156
  416. package/dist/components/native-space-view/useSpaceViewController.d.ts.map +0 -1
  417. package/dist/components/native-space-view/useSpaceViewDerived.d.ts +0 -12
  418. package/dist/components/native-space-view/useSpaceViewDerived.d.ts.map +0 -1
  419. package/dist/components/native-space-view/useSpaceViewPanels.d.ts +0 -70
  420. package/dist/components/native-space-view/useSpaceViewPanels.d.ts.map +0 -1
  421. package/dist/feedback/native-capture.d.ts +0 -9
  422. package/dist/feedback/native-capture.d.ts.map +0 -1
  423. package/dist/feedback/native-evidence.d.ts +0 -3
  424. package/dist/feedback/native-evidence.d.ts.map +0 -1
  425. package/dist/hooks/entrance-preview-store.d.ts +0 -16
  426. package/dist/hooks/entrance-preview-store.d.ts.map +0 -1
  427. package/dist/hooks/useEntrancePreview.d.ts +0 -7
  428. package/dist/hooks/useEntrancePreview.d.ts.map +0 -1
  429. package/dist/hooks/useLayout.d.ts +0 -16
  430. package/dist/hooks/useLayout.d.ts.map +0 -1
  431. package/dist/hooks/usePanels.d.ts +0 -12
  432. package/dist/hooks/usePanels.d.ts.map +0 -1
  433. package/dist/ios-pad.d.ts +0 -2
  434. package/dist/ios-pad.d.ts.map +0 -1
  435. package/dist/ios-pad.js +0 -62
  436. package/dist/ios-phone.d.ts +0 -2
  437. package/dist/ios-phone.d.ts.map +0 -1
  438. package/dist/ios-phone.js +0 -62
  439. package/dist/ios.d.ts +0 -2
  440. package/dist/ios.d.ts.map +0 -1
  441. package/dist/ios.js +0 -62
  442. package/dist/macos.d.ts +0 -2
  443. package/dist/macos.d.ts.map +0 -1
  444. package/dist/macos.js +0 -62
  445. package/dist/space-client/cloudflare-rtk-native.d.ts +0 -3
  446. package/dist/space-client/cloudflare-rtk-native.d.ts.map +0 -1
  447. package/dist/telemetry.d.ts +0 -63
  448. package/dist/telemetry.d.ts.map +0 -1
  449. package/dist/ui/appearance.d.ts +0 -246
  450. package/dist/ui/appearance.d.ts.map +0 -1
  451. package/dist/ui/appearance.js +0 -14
  452. package/dist/ui/native-appearance-context.d.ts +0 -15
  453. package/dist/ui/native-appearance-context.d.ts.map +0 -1
  454. package/dist/ui/native-theme.d.ts +0 -17
  455. package/dist/ui/native-theme.d.ts.map +0 -1
  456. package/dist/ui/native-types.d.ts +0 -38
  457. package/dist/ui/native-types.d.ts.map +0 -1
  458. package/dist/ui/participant-avatar.d.ts +0 -21
  459. package/dist/ui/participant-avatar.d.ts.map +0 -1
  460. package/dist/ui/theme-tokens.d.ts +0 -25
  461. package/dist/ui/theme-tokens.d.ts.map +0 -1
  462. package/dist/ui/theme.d.ts +0 -404
  463. package/dist/ui/theme.d.ts.map +0 -1
  464. package/dist/ui/theme.js +0 -8
  465. package/dist/utils/native-space-layout.d.ts +0 -42
  466. package/dist/utils/native-space-layout.d.ts.map +0 -1
  467. package/src/android.ts +0 -1
  468. package/src/components/Chalk.tsx +0 -363
  469. package/src/components/ChatMessageList.tsx +0 -209
  470. package/src/components/Entrance.tsx +0 -42
  471. package/src/components/EntranceView.android.tsx +0 -274
  472. package/src/components/EntranceView.ios-pad.tsx +0 -326
  473. package/src/components/EntranceView.ios-phone.tsx +0 -271
  474. package/src/components/EntranceView.macos.tsx +0 -278
  475. package/src/components/EntranceView.tsx +0 -38
  476. package/src/components/FaceAvatar.tsx +0 -120
  477. package/src/components/GradientSurface.tsx +0 -23
  478. package/src/components/MediaView.tsx +0 -76
  479. package/src/components/ReactionPicker.tsx +0 -98
  480. package/src/components/SpaceView.android.tsx +0 -1
  481. package/src/components/SpaceView.ios-pad.tsx +0 -1
  482. package/src/components/SpaceView.ios-phone.tsx +0 -1
  483. package/src/components/SpaceView.macos.tsx +0 -1
  484. package/src/components/SpaceView.shared.tsx +0 -98
  485. package/src/components/SpaceView.tsx +0 -54
  486. package/src/components/episode-diagnostic-render-observer.ts +0 -12
  487. package/src/components/logo/Logo.tsx +0 -309
  488. package/src/components/native-animation-controller.ts +0 -36
  489. package/src/components/native-chat.ts +0 -23
  490. package/src/components/native-entrance/entrance-controller-store.ts +0 -112
  491. package/src/components/native-entrance/useEntranceController.ts +0 -60
  492. package/src/components/native-joining-loading-animation.ts +0 -26
  493. package/src/components/native-space-view/AppearanceSettings.tsx +0 -114
  494. package/src/components/native-space-view/FeedbackSheet.tsx +0 -296
  495. package/src/components/native-space-view/ParticipantActionSheet.tsx +0 -99
  496. package/src/components/native-space-view/SettingsSheet.tsx +0 -164
  497. package/src/components/native-space-view/SpaceActionMenu.tsx +0 -109
  498. package/src/components/native-space-view/SpaceBottomDock.android.tsx +0 -115
  499. package/src/components/native-space-view/SpaceChatAttachment.tsx +0 -105
  500. package/src/components/native-space-view/SpaceChatSheet.tsx +0 -184
  501. package/src/components/native-space-view/SpaceGrid.android.tsx +0 -323
  502. package/src/components/native-space-view/SpaceMoreSheet.tsx +0 -190
  503. package/src/components/native-space-view/SpacePanelSheet.tsx +0 -45
  504. package/src/components/native-space-view/SpacePeopleSheet.tsx +0 -116
  505. package/src/components/native-space-view/SpaceStage.android.tsx +0 -372
  506. package/src/components/native-space-view/SpaceSurfacePrimitives.tsx +0 -75
  507. package/src/components/native-space-view/SpaceTopBar.android.tsx +0 -113
  508. package/src/components/native-space-view/SpaceWhiteboardSurface.tsx +0 -69
  509. package/src/components/native-space-view/diagnostics.ts +0 -84
  510. package/src/components/native-space-view/format-space-duration.ts +0 -3
  511. package/src/components/native-space-view/native-space-grid-keys.ts +0 -6
  512. package/src/components/native-space-view/native-space-page-store.ts +0 -41
  513. package/src/components/native-space-view/space-chat-attachments.test.ts +0 -26
  514. package/src/components/native-space-view/space-chat-attachments.ts +0 -37
  515. package/src/components/native-space-view/space-panel-layout.ts +0 -10
  516. package/src/components/native-space-view/space-progressive-surface-helpers.ts +0 -33
  517. package/src/components/native-space-view/space-progressive-surface-types.ts +0 -3
  518. package/src/components/native-space-view/types.ts +0 -25
  519. package/src/components/native-space-view/useSpaceViewController.contract.ts +0 -34
  520. package/src/components/native-space-view/useSpaceViewController.ts +0 -416
  521. package/src/components/native-space-view/useSpaceViewDerived.ts +0 -29
  522. package/src/components/native-space-view/useSpaceViewPanels.ts +0 -170
  523. package/src/feedback/native-capture.ts +0 -62
  524. package/src/feedback/native-evidence.ts +0 -37
  525. package/src/hooks/entrance-preview-store.ts +0 -99
  526. package/src/hooks/useEntrancePreview.ts +0 -26
  527. package/src/hooks/useLayout.ts +0 -37
  528. package/src/hooks/usePanels.ts +0 -30
  529. package/src/ios-pad.ts +0 -1
  530. package/src/ios-phone.ts +0 -1
  531. package/src/ios.ts +0 -1
  532. package/src/macos.ts +0 -1
  533. package/src/space-client/cloudflare-rtk-native.ts +0 -104
  534. package/src/telemetry.ts +0 -234
  535. package/src/types/hugeicons-direct.d.ts +0 -4
  536. package/src/types/react-native-safe-area-context.d.ts +0 -8
  537. package/src/ui/appearance.ts +0 -808
  538. package/src/ui/native-appearance-context.tsx +0 -74
  539. package/src/ui/native-theme.tsx +0 -54
  540. package/src/ui/native-types.ts +0 -40
  541. package/src/ui/participant-avatar.ts +0 -47
  542. package/src/ui/theme-tokens.ts +0 -41
  543. package/src/ui/theme.ts +0 -214
  544. package/src/utils/native-space-layout.ts +0 -135
@@ -0,0 +1,209 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { Pressable, StatusBar, StyleSheet, View } from "react-native";
3
+ import Animated, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
4
+ import { useAndroidCallService } from "../components/useAndroidCallService";
5
+ import { useSpaceClient } from "../context/space-client-context";
6
+ import { Dock } from "../dock/Dock";
7
+ import { LeaveMenu } from "../dock/LeaveMenu";
8
+ import { MoreSheet } from "../dock/MoreSheet";
9
+ import { haptics } from "../haptics/haptics";
10
+ import { CallDetailsSheet } from "../header/CallDetailsSheet";
11
+ import { Header } from "../header/Header";
12
+ import { useCan, useConnection, useMedia } from "../hooks/space-hooks";
13
+ import { timings } from "../motion/motion";
14
+ import { ToastStack, useToasts } from "../primitives/toast/ToastStack";
15
+ import { ChatNotices } from "../sheets/people-chat/ChatNotices";
16
+ import { PeopleChatSheet } from "../sheets/people-chat/PeopleChatSheet";
17
+ import { playSound } from "../sounds/sounds";
18
+ import { Stage } from "../stage/Stage";
19
+ import { CallOverlays } from "../status/CallOverlays";
20
+ import { ArrivalScreen } from "../status/ChalkArrival";
21
+ import { callScreen } from "../status/call-screen";
22
+ import { useTheme } from "../theme/theme";
23
+ import { durations } from "../theme/tokens";
24
+ import { useCallEventToasts, type Permission } from "../toasts/CallEventToasts";
25
+ import { useDevScenario } from "./dev-scenario";
26
+ import { useDeviceLayout } from "./device-layout";
27
+ import { PortalHost, usePortalCount } from "./portal";
28
+ import { useMeasuredRef } from "./measurement";
29
+ import { usePreferences } from "./preferences";
30
+ import { frameRegions } from "./regions";
31
+
32
+ export type CallFrameProps = {
33
+ readonly displayName?: string;
34
+ readonly callName?: string;
35
+ readonly inviteLink?: string;
36
+ readonly onLeave?: () => void;
37
+ readonly onOpenSettings?: (permission: Permission) => void;
38
+ readonly resumedAfterSettings?: Permission;
39
+ readonly onRejoin: () => void;
40
+ };
41
+
42
+ type OpenSheet = "more" | "people" | "chat" | "details" | null;
43
+
44
+ /**
45
+ * The call's frame: header, stage and dock regions, then the sheets layer, the limbo and status
46
+ * overlays, the menus layer and the toasts layer, each inside the safe area for the current
47
+ * arrangement. Each region lives in its own folder so the part lanes can work in parallel.
48
+ */
49
+ export function CallFrame({ displayName, callName = "Call", inviteLink, onLeave, onOpenSettings, onRejoin, resumedAfterSettings }: CallFrameProps): React.JSX.Element {
50
+ const client = useSpaceClient();
51
+ const connection = useConnection();
52
+ const media = useMedia();
53
+ const { colors, scheme } = useTheme();
54
+ const layout = useDeviceLayout();
55
+ const { soundsEnabled } = usePreferences();
56
+ const toasts = useToasts();
57
+ const callEvents = useCallEventToasts({ onOpenSettings, resumedAfterSettings });
58
+ const [leftByChoice, setLeftByChoice] = useState(false);
59
+ const [sheet, setSheet] = useState<OpenSheet>(null);
60
+ const moreRef = useRef<View>(null);
61
+ const leaveRef = useRef<View>(null);
62
+ const nameRef = useRef<View>(null);
63
+ const canEnd = useCan("endEpisode");
64
+ const [leaveMenuOpen, setLeaveMenuOpen] = useState(false);
65
+ const sheetCount = usePortalCount("sheets");
66
+ const menuCount = usePortalCount("menus");
67
+ const layered = sheetCount > 0 || menuCount > 0;
68
+ const regions = useMemo(() => frameRegions(layout.arrangement, layout.width, layout.height, layout.insets), [layout]);
69
+ const screenStyle = [styles.root, { backgroundColor: colors.canvas }];
70
+ const screenRef = useMeasuredRef<View>("frame.screen", screenStyle);
71
+
72
+ const join = useCallback(() => {
73
+ // A failed join shows up as `connection.status === "failed"` with `lastError`, which the status page reads.
74
+ setLeftByChoice(false);
75
+ client.join({ displayName, microphone: false, camera: false }).catch(() => undefined);
76
+ }, [client, displayName]);
77
+ useEffect(() => {
78
+ if (client.getSnapshot().connection.status === "idle") join();
79
+ }, [client, join]);
80
+ useAndroidCallService();
81
+
82
+ const microphoneOn = media.local.microphone.state === "enabled";
83
+ const cameraOn = media.local.camera.state === "enabled";
84
+ const { noteOwnMicrophoneChange, reportMediaFailure } = callEvents;
85
+ const toggleMicrophone = useCallback(() => {
86
+ haptics.mute();
87
+ playSound(microphoneOn ? "mute" : "unmute", soundsEnabled);
88
+ if (microphoneOn) noteOwnMicrophoneChange();
89
+ const turnOn = () => client.media.setMicrophoneEnabled(true).catch((cause: unknown) => reportMediaFailure("microphone", cause, turnOn));
90
+ if (microphoneOn) client.media.setMicrophoneEnabled(false).catch(() => toasts.show({ kind: "error", dismissible: true, message: "Couldn’t mute your microphone." }));
91
+ else void turnOn();
92
+ }, [client, microphoneOn, noteOwnMicrophoneChange, reportMediaFailure, soundsEnabled, toasts]);
93
+ const toggleCamera = useCallback(() => {
94
+ const turnOn = () => client.media.setCameraEnabled(true).catch((cause: unknown) => reportMediaFailure("camera", cause, turnOn));
95
+ if (cameraOn) client.media.setCameraEnabled(false).catch(() => toasts.show({ kind: "error", dismissible: true, message: "Couldn’t turn off your camera." }));
96
+ else void turnOn();
97
+ }, [cameraOn, client, reportMediaFailure, toasts]);
98
+ const exit = useCallback(
99
+ (how: "leave" | "end") => {
100
+ setSheet(null);
101
+ setLeaveMenuOpen(false);
102
+ setLeftByChoice(true);
103
+ // The status page (A3, A4) follows; its Home button calls `onLeave`. A failed leave is still the person's choice.
104
+ (how === "end" ? client.endEpisode() : client.leave()).catch(() => {
105
+ toasts.show({ kind: "error", dismissible: true, message: how === "end" ? "Couldn’t end the call." : "Couldn’t leave the call." });
106
+ });
107
+ },
108
+ [client, toasts],
109
+ );
110
+ const pressLeave = useCallback(() => {
111
+ haptics.leave();
112
+ if (canEnd) setLeaveMenuOpen((open) => !open);
113
+ else exit("leave");
114
+ }, [canEnd, exit]);
115
+
116
+ const devScenario = useDevScenario();
117
+ useEffect(() => {
118
+ if (devScenario.sheet && devScenario.sheet !== "route") setSheet(devScenario.sheet);
119
+ if (devScenario.menu === "leave") setLeaveMenuOpen(true);
120
+ }, [devScenario]);
121
+
122
+ const chrome = useLandscapeChrome(layout.arrangement === "landscape" && sheet === null && !layered);
123
+ const forced = devScenario.callState;
124
+ const screen = forced === "hold" ? { kind: "call" as const } : forced === "left" || forced === "ended" || forced === "failed" || forced === "removed" || forced === "exited" ? { kind: "status" as const, outcome: forced } : callScreen(connection, leftByChoice);
125
+
126
+ return (
127
+ <View ref={screenRef} style={screenStyle} testID="frame.screen">
128
+ <StatusBar barStyle={scheme === "dark" ? "light-content" : "dark-content"} />
129
+ <View accessibilityElementsHidden={layered} importantForAccessibility={layered ? "no-hide-descendants" : "auto"} style={StyleSheet.absoluteFill}>
130
+ <Stage frame={regions.stage} onOpenPeople={() => setSheet("people")} />
131
+ {chrome.hidden ? <Pressable accessibilityLabel="Show call controls" onPress={chrome.show} style={StyleSheet.absoluteFill} testID="frame.show-controls" /> : null}
132
+ <Animated.View pointerEvents={chrome.hidden ? "none" : "box-none"} style={[styles.region, frameStyle(regions.header), chrome.style]}>
133
+ <Header arrangement={layout.arrangement} callName={callName} detailsOpen={sheet === "details"} onOpenDetails={() => setSheet("details")} ref={nameRef} />
134
+ </Animated.View>
135
+ <Animated.View pointerEvents={chrome.hidden ? "none" : "box-none"} style={[styles.region, frameStyle(regions.dock), chrome.style]}>
136
+ <Dock
137
+ arrangement={layout.arrangement}
138
+ cameraOn={cameraOn}
139
+ microphoneOn={microphoneOn}
140
+ leaveOpen={canEnd ? leaveMenuOpen : undefined}
141
+ leaveRef={leaveRef}
142
+ moreOpen={sheet === "more"}
143
+ moreRef={moreRef}
144
+ onLeave={pressLeave}
145
+ onOpenMore={() => setSheet("more")}
146
+ onToggleCamera={toggleCamera}
147
+ onToggleMicrophone={toggleMicrophone}
148
+ />
149
+ </Animated.View>
150
+ </View>
151
+ <View pointerEvents="box-none" style={StyleSheet.absoluteFill} testID="frame.sheets">
152
+ {/* Focus goes back to More only once no sheet is open, so switching to People keeps it there. */}
153
+ <MoreSheet inviteLink={inviteLink} onClose={() => setSheet(null)} onOpenChat={() => setSheet("chat")} onOpenPeople={() => setSheet("people")} open={sheet === "more"} returnFocusRef={sheet === null ? moreRef : undefined} />
154
+ <CallDetailsSheet callName={callName} inviteLink={inviteLink} onClose={() => setSheet(null)} open={sheet === "details"} returnFocusRef={nameRef} />
155
+ <PeopleChatSheet onClose={() => setSheet(null)} onTabChange={setSheet} open={sheet === "people" || sheet === "chat"} returnFocusRef={sheet === null ? moreRef : undefined} tab={sheet === "chat" ? "chat" : "people"} />
156
+ <PortalHost layer="sheets" />
157
+ </View>
158
+ <View pointerEvents="box-none" style={StyleSheet.absoluteFill} testID="frame.overlays">
159
+ {forced === "knocking" || forced === "refused" || forced === "full" ? (
160
+ <ArrivalScreen callName={callName} onCancel={() => onLeave?.()} onRetry={join} state={forced} />
161
+ ) : (
162
+ <CallOverlays callName={callName} onHold={forced === "hold"} onHome={() => onLeave?.()} onRejoin={onRejoin} rejoining={resumedAfterSettings !== undefined} screen={screen} />
163
+ )}
164
+ </View>
165
+ <View pointerEvents="box-none" style={StyleSheet.absoluteFill} testID="frame.menus">
166
+ <LeaveMenu anchorRef={leaveRef} onClose={() => setLeaveMenuOpen(false)} onEnd={() => exit("end")} onLeave={() => exit("leave")} open={leaveMenuOpen} />
167
+ <PortalHost layer="menus" />
168
+ </View>
169
+ <ToastStack />
170
+ <ChatNotices chatVisible={sheet === "chat"} onOpenChat={() => setSheet("chat")} sheetOpen={sheet === "people" || sheet === "chat"} />
171
+ </View>
172
+ );
173
+ }
174
+
175
+ /** Phone landscape (L1): the controls fade after 4 s and come back on a tap. */
176
+ function useLandscapeChrome(fades: boolean): { readonly hidden: boolean; readonly show: () => void; readonly style: ReturnType<typeof useAnimatedStyle> } {
177
+ const [hidden, setHidden] = useState(false);
178
+ const [shownAt, setShownAt] = useState(0);
179
+ const opacity = useSharedValue(1);
180
+ useEffect(() => {
181
+ opacity.value = withTiming(hidden ? 0 : 1, timings.fade);
182
+ }, [hidden, opacity]);
183
+ useEffect(() => {
184
+ if (!fades) {
185
+ setHidden(false);
186
+ return;
187
+ }
188
+ const timer = setTimeout(() => setHidden(true), durations.landscapeHide);
189
+ return () => clearTimeout(timer);
190
+ }, [fades, shownAt]);
191
+ const show = useCallback(() => {
192
+ setHidden(false);
193
+ setShownAt(Date.now());
194
+ }, []);
195
+ const style = useAnimatedStyle(() => {
196
+ "worklet";
197
+ return { opacity: opacity.value };
198
+ });
199
+ return { hidden, show, style };
200
+ }
201
+
202
+ function frameStyle(frame: { readonly x: number; readonly y: number; readonly width: number; readonly height: number }) {
203
+ return { left: frame.x, top: frame.y, width: frame.width, height: frame.height };
204
+ }
205
+
206
+ const styles = StyleSheet.create({
207
+ root: { flex: 1 },
208
+ region: { position: "absolute" },
209
+ });
@@ -0,0 +1,83 @@
1
+ import type { SpaceClient } from "@q9labsai/chalk-client";
2
+ import { useEffect } from "react";
3
+ import { StyleSheet } from "react-native";
4
+ import { GestureHandlerRootView } from "react-native-gesture-handler";
5
+ import { SafeAreaProvider } from "react-native-safe-area-context";
6
+ import { ChalkProvider } from "../context/space-client-context";
7
+ import { ToastProvider } from "../primitives/toast/ToastStack";
8
+ import { releaseSounds } from "../sounds/sounds";
9
+ import { ThemeProvider } from "../theme/theme";
10
+ import type { ColorScheme } from "../theme/tokens";
11
+ import type { Permission } from "../toasts/CallEventToasts";
12
+ import { StageViewProvider } from "../stage/stage-view";
13
+ import { CallFrame } from "./CallFrame";
14
+ import { DeviceLayoutProvider } from "./device-layout";
15
+ import { PortalProvider } from "./portal";
16
+ import { PreferencesProvider } from "./preferences";
17
+
18
+ export type ChalkProps = {
19
+ /** The call's client. The app creates it with `createNativeSpaceClient` and disposes it; `Chalk` joins it. */
20
+ readonly client: SpaceClient;
21
+ /** The name others see. Without it the server's stored name is used. */
22
+ readonly displayName?: string;
23
+ /** The call's name in the header, such as "Q9 team". */
24
+ readonly callName?: string;
25
+ /** The call's link, which More and the call details offer to copy, share and show as a QR code. */
26
+ readonly inviteLink?: string;
27
+ /** Dark by default. Set it with `onColorSchemeChange` to keep the person's choice. */
28
+ readonly colorScheme?: ColorScheme;
29
+ readonly onColorSchemeChange?: (colorScheme: ColorScheme) => void;
30
+ /** Call sounds, on by default. Set it with `onSoundsEnabledChange` to keep the person's choice. */
31
+ readonly soundsEnabled?: boolean;
32
+ readonly onSoundsEnabledChange?: (enabled: boolean) => void;
33
+ /** Called when the person is done with the call: Home on the page that shows after they leave, the call ends, or it fails. */
34
+ readonly onLeave?: () => void;
35
+ /**
36
+ * Called just before Open Settings takes the person to the system settings to allow the microphone
37
+ * or camera. iOS restarts the app when a permission changes, so store the call here and, on the
38
+ * next launch, open it again with `resumedAfterSettings`.
39
+ */
40
+ readonly onOpenSettings?: (permission: Permission) => void;
41
+ /** Set when the app reopens the call after Open Settings; the call says "Rejoining" and then that it can use the device now. */
42
+ readonly resumedAfterSettings?: Permission;
43
+ /**
44
+ * Called when the person chooses Rejoin or Try again on the page that shows after they leave, the call
45
+ * ends, or it fails. That client can't join again: give the arrival back, then render `Chalk` with a new client.
46
+ */
47
+ readonly onRejoin: () => void;
48
+ };
49
+
50
+ /**
51
+ * The drop-in call view. It fills the screen it's given, joins the call muted and with the camera
52
+ * off, and draws everything inside the safe area in portrait, landscape and on iPad.
53
+ */
54
+ export function Chalk({ client, colorScheme, onColorSchemeChange, soundsEnabled, onSoundsEnabledChange, ...frame }: ChalkProps): React.JSX.Element {
55
+ useEffect(() => releaseSounds, []);
56
+ return (
57
+ <GestureHandlerRootView style={styles.root}>
58
+ <SafeAreaProvider>
59
+ <PreferencesProvider colorScheme={colorScheme} onColorSchemeChange={onColorSchemeChange} onSoundsEnabledChange={onSoundsEnabledChange} soundsEnabled={soundsEnabled}>
60
+ {(preferences) => (
61
+ <ThemeProvider colorScheme={preferences.colorScheme}>
62
+ <DeviceLayoutProvider>
63
+ <PortalProvider>
64
+ <ToastProvider>
65
+ <ChalkProvider client={client}>
66
+ <StageViewProvider>
67
+ <CallFrame {...frame} />
68
+ </StageViewProvider>
69
+ </ChalkProvider>
70
+ </ToastProvider>
71
+ </PortalProvider>
72
+ </DeviceLayoutProvider>
73
+ </ThemeProvider>
74
+ )}
75
+ </PreferencesProvider>
76
+ </SafeAreaProvider>
77
+ </GestureHandlerRootView>
78
+ );
79
+ }
80
+
81
+ const styles = StyleSheet.create({
82
+ root: { flex: 1 },
83
+ });
@@ -0,0 +1,19 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { BackHandler } from "react-native";
3
+
4
+ /**
5
+ * While `active`, Android's back gesture calls `onBack` and stops there. Handlers run newest
6
+ * first, so the sheet or menu opened last closes first.
7
+ */
8
+ export function useBackHandler(active: boolean, onBack: () => void): void {
9
+ const latest = useRef(onBack);
10
+ latest.current = onBack;
11
+ useEffect(() => {
12
+ if (!active) return;
13
+ const subscription = BackHandler.addEventListener("hardwareBackPress", () => {
14
+ latest.current();
15
+ return true;
16
+ });
17
+ return () => subscription.remove();
18
+ }, [active]);
19
+ }
@@ -0,0 +1,48 @@
1
+ import { useSyncExternalStore, type ReactNode } from "react";
2
+ import type { ArrivalState } from "../status/ChalkArrival";
3
+ import type { CallOutcome } from "../status/StatusPage";
4
+ import type { AudioRoutes } from "../dock/audio-route";
5
+
6
+ /** UI-only state a development harness forces on the call. Nothing sets it in a real call. */
7
+ export type DevScenario = {
8
+ /** Opens this sheet as if the user had opened it; the user can still close it. */
9
+ readonly sheet?: "more" | "people" | "chat" | "details" | "route";
10
+ /** Opens this menu as if the user had opened it. */
11
+ readonly menu?: "leave" | "route";
12
+ /** Content drawn in a tile in place of the camera, keyed by participant id. */
13
+ readonly videoSlots?: Readonly<Record<string, ReactNode>>;
14
+ /** Shows a call state the fixture client can't reach: a screen before joining, a status page, or a phone call holding the call. */
15
+ readonly callState?: ArrivalState | CallOutcome | "hold";
16
+ /** Content drawn in place of a person's shared screen, keyed by the sharer's participant id. */
17
+ readonly screenSlots?: Readonly<Record<string, ReactNode>>;
18
+ /** The shared screen's width over height (S1 to S4). A real share is drawn as 16:9. */
19
+ readonly shareAspect?: number;
20
+ /** Spotlight (G1) or Grid (G2), as if the user had chosen it. */
21
+ readonly layout?: "spotlight" | "grid";
22
+ /** Pins this participant, as if the user had pinned them. */
23
+ readonly pinned?: string;
24
+ /** Audio outputs in place of the device's, such as a Bluetooth headset on a simulator (K3). */
25
+ readonly audioRoutes?: AudioRoutes;
26
+ };
27
+
28
+ const empty: DevScenario = {};
29
+ let scenario = empty;
30
+ const listeners = new Set<() => void>();
31
+
32
+ export function setDevScenario(next: DevScenario | null): void {
33
+ scenario = next ?? empty;
34
+ for (const listener of listeners) listener();
35
+ }
36
+
37
+ function subscribe(listener: () => void): () => void {
38
+ listeners.add(listener);
39
+ return () => listeners.delete(listener);
40
+ }
41
+
42
+ export function useDevScenario(): DevScenario {
43
+ return useSyncExternalStore(
44
+ subscribe,
45
+ () => scenario,
46
+ () => scenario,
47
+ );
48
+ }
@@ -0,0 +1,31 @@
1
+ import { createContext, useContext, useMemo, type ReactNode } from "react";
2
+ import { useWindowDimensions } from "react-native";
3
+ import { useSafeAreaInsets, type EdgeInsets } from "react-native-safe-area-context";
4
+ import { arrangementFor, type Arrangement } from "./regions";
5
+
6
+ export type DeviceLayout = {
7
+ readonly arrangement: Arrangement;
8
+ /** True for `portrait` and `landscape`: the phone look (bottom sheets, 52 pt dock, 48 pt menu rows). */
9
+ readonly phone: boolean;
10
+ readonly width: number;
11
+ readonly height: number;
12
+ readonly insets: EdgeInsets;
13
+ };
14
+
15
+ const DeviceLayoutContext = createContext<DeviceLayout | null>(null);
16
+
17
+ export function DeviceLayoutProvider({ children }: { readonly children: ReactNode }): React.JSX.Element {
18
+ const { width, height } = useWindowDimensions();
19
+ const insets = useSafeAreaInsets();
20
+ const value = useMemo<DeviceLayout>(() => {
21
+ const arrangement = arrangementFor(width, height);
22
+ return { arrangement, phone: arrangement !== "pad", width, height, insets };
23
+ }, [height, insets, width]);
24
+ return <DeviceLayoutContext.Provider value={value}>{children}</DeviceLayoutContext.Provider>;
25
+ }
26
+
27
+ export function useDeviceLayout(): DeviceLayout {
28
+ const layout = useContext(DeviceLayoutContext);
29
+ if (!layout) throw new Error("Chalk primitives must be rendered inside <Chalk>.");
30
+ return layout;
31
+ }
@@ -0,0 +1,44 @@
1
+ import { useEffect, useRef, type RefObject } from "react";
2
+ import { StyleSheet, type StyleProp, type TextStyle, type ViewStyle } from "react-native";
3
+
4
+ export type MeasuredFrame = { readonly x: number; readonly y: number; readonly width: number; readonly height: number };
5
+
6
+ /** A host view's frame in window points and the style it declares at rest (animated values excluded). */
7
+ export type Measurement = { readonly frame: MeasuredFrame; readonly style: TextStyle };
8
+
9
+ /** Receives each measured testID while it's mounted; returns the function that forgets it. */
10
+ export type MeasurementRegistry = (testID: string, measure: () => Promise<Measurement>) => () => void;
11
+
12
+ type MeasurableNode = { measureInWindow: (callback: (x: number, y: number, width: number, height: number) => void) => void };
13
+
14
+ let registry: MeasurementRegistry | null = null;
15
+
16
+ /**
17
+ * Development tooling only: the app's parity harness installs a registry before it renders
18
+ * `Chalk`, and the call's regions and primitives report their frames and declared styles to it.
19
+ * Nothing is measured while no registry is installed.
20
+ */
21
+ export function setMeasurementRegistry(next: MeasurementRegistry | null): void {
22
+ registry = next;
23
+ }
24
+
25
+ /** A ref for the host view that carries `testID`; attach it to that view. */
26
+ export function useMeasuredRef<TNode extends MeasurableNode>(testID: string | undefined, style: StyleProp<ViewStyle | TextStyle>): RefObject<TNode | null> {
27
+ const ref = useRef<TNode>(null);
28
+ useEffect(() => {
29
+ if (!registry || !testID) return;
30
+ const declared = StyleSheet.flatten(style) ?? {};
31
+ return registry(
32
+ testID,
33
+ () =>
34
+ new Promise((resolve, reject) => {
35
+ if (!ref.current) {
36
+ reject(new Error(`Unmounted testID: ${testID}`));
37
+ return;
38
+ }
39
+ ref.current.measureInWindow((x, y, width, height) => resolve({ frame: { x, y, width, height }, style: declared }));
40
+ }),
41
+ );
42
+ }, [style, testID]);
43
+ return ref;
44
+ }
@@ -0,0 +1,107 @@
1
+ import { createContext, Fragment, useContext, useLayoutEffect, useRef, useState, useSyncExternalStore, type ReactNode } from "react";
2
+
3
+ /** Layers above the call, bottom to top. Sheets sit under the limbo and status overlays; menus and toasts sit above everything. */
4
+ export type PortalLayer = "sheets" | "menus" | "toasts";
5
+
6
+ type Entry = { readonly key: number; readonly node: ReactNode };
7
+
8
+ type PortalStore = {
9
+ readonly set: (layer: PortalLayer, key: number, node: ReactNode) => void;
10
+ readonly remove: (layer: PortalLayer, key: number) => void;
11
+ readonly subscribe: (listener: () => void) => () => void;
12
+ readonly entries: (layer: PortalLayer) => readonly Entry[];
13
+ };
14
+
15
+ function createPortalStore(): PortalStore {
16
+ let layers: Readonly<Record<PortalLayer, readonly Entry[]>> = { sheets: [], menus: [], toasts: [] };
17
+ const listeners = new Set<() => void>();
18
+ const publish = (layer: PortalLayer, next: readonly Entry[]) => {
19
+ layers = { ...layers, [layer]: next };
20
+ for (const listener of listeners) listener();
21
+ };
22
+ return {
23
+ set(layer, key, node) {
24
+ const current = layers[layer];
25
+ const index = current.findIndex((entry) => entry.key === key);
26
+ publish(layer, index < 0 ? [...current, { key, node }] : current.map((entry) => (entry.key === key ? { key, node } : entry)));
27
+ },
28
+ remove(layer, key) {
29
+ publish(
30
+ layer,
31
+ layers[layer].filter((entry) => entry.key !== key),
32
+ );
33
+ },
34
+ subscribe(listener) {
35
+ listeners.add(listener);
36
+ return () => listeners.delete(listener);
37
+ },
38
+ entries: (layer) => layers[layer],
39
+ };
40
+ }
41
+
42
+ const PortalContext = createContext<PortalStore | null>(null);
43
+
44
+ export function PortalProvider({ children }: { readonly children: ReactNode }): React.JSX.Element {
45
+ const [store] = useState(createPortalStore);
46
+ return <PortalContext.Provider value={store}>{children}</PortalContext.Provider>;
47
+ }
48
+
49
+ function usePortalStore(): PortalStore {
50
+ const store = useContext(PortalContext);
51
+ if (!store) throw new Error("Chalk portals must be rendered inside <Chalk>.");
52
+ return store;
53
+ }
54
+
55
+ let nextKey = 0;
56
+
57
+ /**
58
+ * Renders its children in one of the frame's layers, so a sheet or menu opened deep in the tree
59
+ * draws above the whole call. Context providers between `<Chalk>` and the portal don't reach the
60
+ * children; pass what they need as props.
61
+ */
62
+ export function Portal({ layer, children }: { readonly layer: PortalLayer; readonly children: ReactNode }): null {
63
+ const store = usePortalStore();
64
+ const key = useRef(-1);
65
+ if (key.current < 0) key.current = nextKey++;
66
+ useLayoutEffect(() => {
67
+ store.set(layer, key.current, children);
68
+ });
69
+ useLayoutEffect(() => {
70
+ const own = key.current;
71
+ return () => store.remove(layer, own);
72
+ }, [layer, store]);
73
+ return null;
74
+ }
75
+
76
+ export function PortalHost({ layer }: { readonly layer: PortalLayer }): React.JSX.Element {
77
+ const entries = usePortalEntries(layer);
78
+ return (
79
+ <>
80
+ {entries.map((entry) => (
81
+ <Fragment key={entry.key}>{entry.node}</Fragment>
82
+ ))}
83
+ </>
84
+ );
85
+ }
86
+
87
+ function usePortalEntries(layer: PortalLayer): readonly Entry[] {
88
+ const store = usePortalStore();
89
+ return useSyncExternalStore(
90
+ store.subscribe,
91
+ () => store.entries(layer),
92
+ () => store.entries(layer),
93
+ );
94
+ }
95
+
96
+ /**
97
+ * How many portals a layer holds. Portals publish on every render, so an ancestor of a portal must
98
+ * read this count, not the entries, or each publish re-renders the portal and loops.
99
+ */
100
+ export function usePortalCount(layer: PortalLayer): number {
101
+ const store = usePortalStore();
102
+ return useSyncExternalStore(
103
+ store.subscribe,
104
+ () => store.entries(layer).length,
105
+ () => store.entries(layer).length,
106
+ );
107
+ }
@@ -0,0 +1,48 @@
1
+ import { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from "react";
2
+ import type { ColorScheme } from "../theme/tokens";
3
+
4
+ /** Choices the person makes inside the call. The app stores them; `Chalk` reports every change. */
5
+ export type CallPreferences = {
6
+ readonly colorScheme: ColorScheme;
7
+ readonly setColorScheme: (scheme: ColorScheme) => void;
8
+ readonly soundsEnabled: boolean;
9
+ readonly setSoundsEnabled: (enabled: boolean) => void;
10
+ };
11
+
12
+ const PreferencesContext = createContext<CallPreferences | null>(null);
13
+
14
+ type ProviderProps = {
15
+ readonly colorScheme?: ColorScheme;
16
+ readonly onColorSchemeChange?: (scheme: ColorScheme) => void;
17
+ readonly soundsEnabled?: boolean;
18
+ readonly onSoundsEnabledChange?: (enabled: boolean) => void;
19
+ readonly children: (preferences: CallPreferences) => ReactNode;
20
+ };
21
+
22
+ /** Each preference is controlled when its prop is set, and kept inside the call otherwise. */
23
+ export function PreferencesProvider({ colorScheme, onColorSchemeChange, soundsEnabled, onSoundsEnabledChange, children }: ProviderProps): React.JSX.Element {
24
+ const [ownScheme, setOwnScheme] = useState<ColorScheme>("dark");
25
+ const [ownSounds, setOwnSounds] = useState(true);
26
+ const setColorScheme = useCallback(
27
+ (scheme: ColorScheme) => {
28
+ setOwnScheme(scheme);
29
+ onColorSchemeChange?.(scheme);
30
+ },
31
+ [onColorSchemeChange],
32
+ );
33
+ const setSoundsEnabled = useCallback(
34
+ (enabled: boolean) => {
35
+ setOwnSounds(enabled);
36
+ onSoundsEnabledChange?.(enabled);
37
+ },
38
+ [onSoundsEnabledChange],
39
+ );
40
+ const value = useMemo<CallPreferences>(() => ({ colorScheme: colorScheme ?? ownScheme, setColorScheme, soundsEnabled: soundsEnabled ?? ownSounds, setSoundsEnabled }), [colorScheme, ownScheme, ownSounds, setColorScheme, setSoundsEnabled, soundsEnabled]);
41
+ return <PreferencesContext.Provider value={value}>{children(value)}</PreferencesContext.Provider>;
42
+ }
43
+
44
+ export function usePreferences(): CallPreferences {
45
+ const preferences = useContext(PreferencesContext);
46
+ if (!preferences) throw new Error("usePreferences must be used inside <Chalk>.");
47
+ return preferences;
48
+ }
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { arrangementFor, frameRegions } from "./regions";
3
+
4
+ describe("frameRegions", () => {
5
+ it("matches the mockup's D1 iPhone portrait stage", () => {
6
+ const { header, stage, dock } = frameRegions("portrait", 393, 852, { top: 59, bottom: 34, left: 0, right: 0 });
7
+ expect(header).toEqual({ x: 0, y: 59, width: 393, height: 52 });
8
+ expect(stage).toEqual({ x: 8, y: 111, width: 377, height: 852 - 111 - (34 + 8 + 52 + 14) - 8 });
9
+ expect(dock.y + dock.height).toBe(852 - 34 - 14);
10
+ });
11
+
12
+ it("gives the phone stage the whole screen in landscape, inside the side insets", () => {
13
+ const { stage } = frameRegions("landscape", 852, 393, { top: 0, bottom: 21, left: 59, right: 59 });
14
+ expect(stage).toEqual({ x: 67, y: 8, width: 852 - 118 - 16, height: 393 - 8 - 21 - 4 });
15
+ });
16
+
17
+ it("starts the landscape header and stage below the top inset, as portrait does", () => {
18
+ const { header, stage } = frameRegions("landscape", 852, 393, { top: 24, bottom: 0, left: 0, right: 0 });
19
+ expect(header.y).toBe(32);
20
+ expect(stage).toEqual({ x: 8, y: 32, width: 852 - 16, height: 393 - 32 - 0 - 4 });
21
+ });
22
+
23
+ it("narrows the iPad stage when the side card is open", () => {
24
+ const closed = frameRegions("pad", 820, 1180, { top: 24, bottom: 20, left: 0, right: 0 });
25
+ const open = frameRegions("pad", 820, 1180, { top: 24, bottom: 20, left: 0, right: 0 }, true);
26
+ expect(closed.stage.width - open.stage.width).toBe(352);
27
+ expect(closed.stage.y).toBe(84);
28
+ });
29
+ });
30
+
31
+ describe("arrangementFor", () => {
32
+ it("gives the iPad the phone layout today, like a phone", () => {
33
+ expect(arrangementFor(820, 1180)).toBe("portrait");
34
+ expect(arrangementFor(1180, 820)).toBe("landscape");
35
+ expect(arrangementFor(375, 820)).toBe("portrait");
36
+ expect(arrangementFor(852, 393)).toBe("landscape");
37
+ });
38
+ });