@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,140 @@
1
+ import type { SpaceSnapshot } from "@q9labsai/chalk-client";
2
+ import { memo, useCallback, useEffect, useMemo, type ReactNode } from "react";
3
+ import { Pressable, StyleSheet, View } from "react-native";
4
+ import { useSharedValue, withTiming } from "react-native-reanimated";
5
+ import { FaceAvatar } from "../avatar/FaceAvatar";
6
+ import { tileAvatarSize } from "../avatar/tile-avatar-size";
7
+ import { RtcVideoView } from "../components/RtcVideoView";
8
+ import { useSpaceClient } from "../context/space-client-context";
9
+ import { useDevScenario } from "../frame/dev-scenario";
10
+ import { useMeasuredRef } from "../frame/measurement";
11
+ import { Icon } from "../icons/Icon";
12
+ import { createNativeMediaStream } from "../media/native-webrtc";
13
+ import { timings } from "../motion/motion";
14
+ import { NameTag, nameTagZone } from "../primitives/NameTag";
15
+ import { TileShell } from "../primitives/TileShell";
16
+ import { useToasts } from "../primitives/toast/ToastStack";
17
+ import { fixed, pressScale } from "../theme/tokens";
18
+ import { cameraFacing, otherCamera } from "./camera";
19
+ import { shallowEqual, useSpaceSelector } from "./space-selector";
20
+
21
+ export type ParticipantTileProps = {
22
+ readonly id: string;
23
+ readonly width: number;
24
+ readonly height: number;
25
+ /** The small tile floating over the other person when two are in the call. */
26
+ readonly float: boolean;
27
+ readonly pinned: boolean;
28
+ /** Your own front-camera video is mirrored while this is on. */
29
+ readonly mirror: boolean;
30
+ readonly onOpen: (participantId: string) => void;
31
+ };
32
+
33
+ type TileModel = {
34
+ readonly name: string;
35
+ readonly isSelf: boolean;
36
+ readonly muted: boolean;
37
+ readonly handRaised: boolean;
38
+ readonly speaking: boolean;
39
+ readonly track: MediaStreamTrack | null;
40
+ readonly back: boolean;
41
+ };
42
+
43
+ /** Below this width your own tile reads "You", because "<name> (You)" leaves no room for the name. */
44
+ const SELF_NAME_MIN_WIDTH = 120;
45
+
46
+ function selectTile(id: string) {
47
+ return (snapshot: SpaceSnapshot): TileModel => {
48
+ const participant = snapshot.participants.roster.find((person) => person.participantId === id);
49
+ const isSelf = snapshot.self.participantId === id;
50
+ const { local, remote } = snapshot.media;
51
+ const track = isSelf ? (local.camera.state === "enabled" ? local.camera.track : null) : participant?.media.camera === "active" ? (remote.find((media) => media.participantId === id && media.source === "camera")?.track ?? null) : null;
52
+ return {
53
+ name: participant?.displayName ?? snapshot.self.displayName ?? "",
54
+ isSelf,
55
+ muted: isSelf ? local.microphone.state !== "enabled" : participant?.media.microphone !== "active",
56
+ handRaised: isSelf ? snapshot.self.handRaised : (participant?.handRaised ?? false),
57
+ speaking: participant?.presence.speaking ?? false,
58
+ track,
59
+ back: isSelf && cameraFacing(snapshot.media) === "back",
60
+ };
61
+ };
62
+ }
63
+
64
+ /**
65
+ * One person's tile: their camera, or their bold facehash when the camera is off, the name tag,
66
+ * and the speaking ring. It subscribes to that person alone, so nobody else's change re-renders
67
+ * it, and the stage keeps it mounted across every move so the video view keeps its surface.
68
+ */
69
+ export const ParticipantTile = memo(function ParticipantTile({ id, width, height, float, pinned, mirror, onOpen }: ParticipantTileProps): React.JSX.Element {
70
+ const select = useMemo(() => selectTile(id), [id]);
71
+ const tile = useSpaceSelector(select, shallowEqual);
72
+ const slot = useDevScenario().videoSlots?.[id];
73
+ const level = useSharedValue(0);
74
+ useEffect(() => {
75
+ level.value = withTiming(tile.speaking ? 0.6 : 0, timings.level);
76
+ }, [level, tile.speaking]);
77
+
78
+ const streamURL = useMemo(() => (tile.track ? createNativeMediaStream(tile.track).toURL() : null), [tile.track]);
79
+ const video = tileVideo(slot, streamURL, tile.isSelf && !tile.back && mirror);
80
+ const youOnly = tile.isSelf && (float || width < SELF_NAME_MIN_WIDTH);
81
+ const name = youOnly ? "You" : tile.name;
82
+ const you = tile.isSelf && !youOnly;
83
+ const state = `${tile.muted ? ", microphone off" : ""}${tile.handRaised ? ", hand raised" : ""}`;
84
+ const hitRef = useMeasuredRef<View>(`tile.${id}.open`, StyleSheet.absoluteFill);
85
+ const label = `Options for ${tile.name}${tile.isSelf ? " (you)" : ""}${pinned ? ", pinned" : ""}${state}`;
86
+
87
+ return (
88
+ <TileShell cameraOn={video !== null} level={level} speaking={tile.speaking} testID={`tile.${id}`}>
89
+ {video ?? (
90
+ <View style={styles.center}>
91
+ <FaceAvatar level={level} lookAround name={tile.name} size={tileAvatarSize(width, height - nameTagZone)} style="bold" />
92
+ </View>
93
+ )}
94
+ <Pressable accessibilityHint="Opens a sheet" accessibilityLabel={label} accessibilityRole="button" onPress={() => onOpen(id)} ref={hitRef} style={StyleSheet.absoluteFill} testID={`tile.${id}.open`} />
95
+ <NameTag handRaised={tile.handRaised} muted={tile.muted} name={name} pinned={pinned} testID={`tile.${id}.name`} you={you} />
96
+ {tile.isSelf && video !== null ? <FlipCameraButton back={tile.back} float={float} testID={`tile.${id}.flip`} /> : null}
97
+ </TileShell>
98
+ );
99
+ });
100
+
101
+ /** A harness's fake video slot wins over the camera, so fixtures can show video without media. */
102
+ function tileVideo(slot: ReactNode, streamURL: string | null, mirror: boolean): React.JSX.Element | null {
103
+ if (slot !== undefined) return <View style={[StyleSheet.absoluteFill, mirror ? styles.mirrored : null]}>{slot}</View>;
104
+ if (streamURL) return <RtcVideoView mirror={mirror} objectFit="cover" streamURL={streamURL} style={StyleSheet.absoluteFill} />;
105
+ return null;
106
+ }
107
+
108
+ const selectOtherCamera = (snapshot: SpaceSnapshot) => otherCamera(snapshot.media)?.deviceId ?? null;
109
+
110
+ /** `.flip`: switches between the front and back camera, top right of your own video. */
111
+ function FlipCameraButton({ back, float, testID }: { readonly back: boolean; readonly float: boolean; readonly testID: string }): React.JSX.Element | null {
112
+ const client = useSpaceClient();
113
+ const toasts = useToasts();
114
+ const other = useSpaceSelector(selectOtherCamera);
115
+ const flip = useCallback(() => {
116
+ if (other === null) return;
117
+ client.media.selectCamera(other).catch(() => toasts.show({ kind: "error", dismissible: true, message: "Couldn't switch the camera." }));
118
+ }, [client, other, toasts]);
119
+ if (other === null) return null;
120
+
121
+ return (
122
+ <Pressable accessibilityLabel={`Switch to the ${back ? "front" : "back"} camera`} accessibilityRole="button" onPress={flip} style={[styles.flip, float ? styles.flipFloat : null]} testID={testID}>
123
+ {({ pressed }) => (
124
+ <View style={[styles.flipKnob, { backgroundColor: pressed ? fixed.overlayPressed : fixed.overlay }, pressed ? styles.flipPressed : null]}>
125
+ <Icon color={fixed.overlayText} name="flip" size={17} />
126
+ </View>
127
+ )}
128
+ </Pressable>
129
+ );
130
+ }
131
+
132
+ const styles = StyleSheet.create({
133
+ // Centred above the name tag, so a short strip tile never hides the face behind the tag.
134
+ center: { ...StyleSheet.absoluteFillObject, bottom: nameTagZone, alignItems: "center", justifyContent: "center" },
135
+ mirrored: { transform: [{ scaleX: -1 }] },
136
+ flip: { position: "absolute", top: 2, right: 2, width: 44, height: 44, alignItems: "center", justifyContent: "center", zIndex: 4 },
137
+ flipFloat: { top: 0, right: 0 },
138
+ flipKnob: { width: 32, height: 32, borderRadius: 16, alignItems: "center", justifyContent: "center" },
139
+ flipPressed: { transform: [{ scale: pressScale.small }] },
140
+ });
@@ -0,0 +1,152 @@
1
+ import type { SpaceSnapshot } from "@q9labsai/chalk-client";
2
+ import { memo, useCallback, useMemo, useState } from "react";
3
+ import { Pressable, StyleSheet, View } from "react-native";
4
+ import Animated from "react-native-reanimated";
5
+ import { useDevScenario } from "../frame/dev-scenario";
6
+ import { useDeviceLayout } from "../frame/device-layout";
7
+ import { useMeasuredRef } from "../frame/measurement";
8
+ import { MovingFrame, type Frame } from "../motion/MovingFrame";
9
+ import { tileEntering, tileLeaving } from "../motion/presence";
10
+ import { ChalkText } from "../primitives/ChalkText";
11
+ import { useTheme } from "../theme/theme";
12
+ import { elevations, fontFamilies, radii } from "../theme/tokens";
13
+ import { ContentTile, type SharedContent } from "./ContentTile";
14
+ import { nextStageOrder, stageLayout, type StageItem, type StageOrder } from "./layout";
15
+ import { ParticipantTile } from "./ParticipantTile";
16
+ import { shallowEqual, useSpaceSelector } from "./space-selector";
17
+ import { useStageView } from "./stage-view";
18
+ import { TileSheet } from "./TileSheet";
19
+
20
+ export type StageProps = {
21
+ /** The stage box in screen points, from `frameRegions`. */
22
+ readonly frame: Frame;
23
+ /** The "+N" tile opens People. */
24
+ readonly onOpenPeople: () => void;
25
+ };
26
+
27
+ type Structure = {
28
+ readonly selfId: string | null;
29
+ /** Everyone present, you first, joined by newlines so the selector compares one string. */
30
+ readonly present: string;
31
+ readonly speaker: string | null;
32
+ readonly sharer: string | null;
33
+ readonly board: boolean;
34
+ };
35
+
36
+ /** Only what decides where tiles go, so a mute or a hand raise never re-renders the stage. */
37
+ function selectStructure(snapshot: SpaceSnapshot): Structure {
38
+ const selfId = snapshot.self.participantId;
39
+ const { roster } = snapshot.participants;
40
+ const others = roster.filter((person) => person.participantId !== selfId).map((person) => person.participantId);
41
+ const hasSelf = roster.some((person) => person.participantId === selfId);
42
+ return {
43
+ selfId,
44
+ present: (hasSelf && selfId !== null ? [selfId, ...others] : others).join("\n"),
45
+ speaker: roster.find((person) => person.presence.activeSpeaker)?.participantId ?? null,
46
+ sharer: roster.find((person) => person.participantId !== selfId && person.media.screenShare === "active")?.participantId ?? null,
47
+ board: snapshot.whiteboard.open,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * The stage: Spotlight or Grid, smart placement of everyone present, and the shared screen or
53
+ * board. Tiles are keyed by participant and moved with `MovingFrame`, so a video view is never
54
+ * remounted by a move, a layout switch or a sheet; only tiles on the stage subscribe to video.
55
+ */
56
+ export const Stage = memo(function Stage({ frame, onOpenPeople }: StageProps): React.JSX.Element {
57
+ const structure = useSpaceSelector(selectStructure, shallowEqual);
58
+ const view = useStageView();
59
+ const { arrangement } = useDeviceLayout();
60
+ const { shareAspect } = useDevScenario();
61
+ const [sheet, setSheet] = useState<{ readonly id: string | null; readonly open: boolean }>({ id: null, open: false });
62
+
63
+ const present = useMemo(() => (structure.present ? structure.present.split("\n") : []), [structure.present]);
64
+ const [order, setOrder] = useState<StageOrder>({ order: [], spot: null });
65
+ const next = nextStageOrder(order, present, structure.selfId, structure.speaker);
66
+ if (next !== order) setOrder(next);
67
+
68
+ const content = useMemo<SharedContent | null>(() => (structure.board ? { kind: "board" } : structure.sharer !== null ? { kind: "screen", sharerId: structure.sharer } : null), [structure.board, structure.sharer]);
69
+ const pinned = view.pinned !== null && present.includes(view.pinned) ? view.pinned : null;
70
+ const items = useMemo(
71
+ () =>
72
+ stageLayout({
73
+ stage: frame,
74
+ // The iPad uses the phone layout today.
75
+ portrait: arrangement !== "landscape",
76
+ layout: view.layout,
77
+ order: next.order,
78
+ spot: next.spot,
79
+ pinned,
80
+ content: content ? { kind: content.kind, aspect: content.kind === "board" ? null : (shareAspect ?? 16 / 9) } : null,
81
+ }),
82
+ [arrangement, content, frame, next, pinned, shareAspect, view.layout],
83
+ );
84
+
85
+ const openTile = useCallback((id: string) => setSheet({ id, open: true }), []);
86
+ const closeTile = useCallback(() => setSheet((current) => ({ ...current, open: false })), []);
87
+ // Like the mockup's `.stage`, the stage is a full-screen layer: tiles are placed in screen points
88
+ // inside `frame`, so they can move across regions.
89
+ const stageRef = useMeasuredRef<View>("frame.stage", styles.layer);
90
+
91
+ return (
92
+ <View pointerEvents="box-none" ref={stageRef} style={styles.layer} testID="frame.stage">
93
+ {items.map((item) => (
94
+ <StageTile content={content} item={item} key={itemKey(item)} mirror={view.mirror} onOpenPeople={onOpenPeople} onOpenTile={openTile} onShowContent={view.showContent} peopleCount={present.length} pinned={pinned} />
95
+ ))}
96
+ <TileSheet onClose={closeTile} open={sheet.open} participantId={sheet.id} />
97
+ </View>
98
+ );
99
+ });
100
+
101
+ function itemKey(item: StageItem): string {
102
+ return item.kind === "person" ? `person:${item.id}` : item.kind;
103
+ }
104
+
105
+ type StageTileProps = {
106
+ readonly item: StageItem;
107
+ readonly content: SharedContent | null;
108
+ readonly pinned: string | null;
109
+ readonly mirror: boolean;
110
+ readonly peopleCount: number;
111
+ readonly onOpenTile: (participantId: string) => void;
112
+ readonly onOpenPeople: () => void;
113
+ readonly onShowContent: () => void;
114
+ };
115
+
116
+ function StageTile({ item, content, pinned, mirror, peopleCount, onOpenTile, onOpenPeople, onShowContent }: StageTileProps): React.JSX.Element | null {
117
+ const { scheme } = useTheme();
118
+ const float = item.kind === "person" && item.float;
119
+ return (
120
+ <MovingFrame frame={item.frame} style={float ? [styles.float, { boxShadow: elevations[scheme].floatTile }] : null}>
121
+ <Animated.View entering={tileEntering} exiting={tileLeaving} style={styles.fill}>
122
+ {item.kind === "person" ? (
123
+ <ParticipantTile float={float} height={item.frame.height} id={item.id} mirror={mirror} onOpen={onOpenTile} pinned={pinned === item.id} width={item.frame.width} />
124
+ ) : item.kind === "content" && content ? (
125
+ <ContentTile content={content} inner={item.inner} mini={item.mini} onShowInSpotlight={onShowContent} />
126
+ ) : item.kind === "more" ? (
127
+ <MoreTile count={item.count} onPress={onOpenPeople} peopleCount={peopleCount} />
128
+ ) : null}
129
+ </Animated.View>
130
+ </MovingFrame>
131
+ );
132
+ }
133
+
134
+ /** `.more`: the last tile when the rest don't fit, showing how many more people there are. */
135
+ const MoreTile = memo(function MoreTile({ count, peopleCount, onPress }: { readonly count: number; readonly peopleCount: number; readonly onPress: () => void }): React.JSX.Element {
136
+ const { colors } = useTheme();
137
+ const restStyle = [styles.more, { backgroundColor: colors.raised }];
138
+ const moreRef = useMeasuredRef<View>("tile.more", restStyle);
139
+ return (
140
+ <Pressable accessibilityLabel={`Show all ${peopleCount} people`} accessibilityRole="button" onPress={onPress} ref={moreRef} style={({ pressed }) => (pressed ? [styles.more, { backgroundColor: colors.hover }] : restStyle)} testID="tile.more">
141
+ <ChalkText style={styles.moreText}>{`+${count}`}</ChalkText>
142
+ </Pressable>
143
+ );
144
+ });
145
+
146
+ const styles = StyleSheet.create({
147
+ fill: { flex: 1 },
148
+ layer: StyleSheet.absoluteFillObject,
149
+ float: { zIndex: 12, borderRadius: radii.tilePhone },
150
+ more: { flex: 1, borderRadius: radii.tilePhone, alignItems: "center", justifyContent: "center" },
151
+ moreText: { fontFamily: fontFamilies[600], fontSize: 22, lineHeight: 22 * 1.4 },
152
+ });
@@ -0,0 +1,116 @@
1
+ import type { SpaceSnapshot } from "@q9labsai/chalk-client";
2
+ import { useCallback, useMemo } from "react";
3
+ import { StyleSheet, View } from "react-native";
4
+ import { FaceAvatar } from "../avatar/FaceAvatar";
5
+ import { useSpaceClient } from "../context/space-client-context";
6
+ import { announce } from "../primitives/announce";
7
+ import { BottomSheet } from "../primitives/BottomSheet";
8
+ import { ChalkText } from "../primitives/ChalkText";
9
+ import { SheetGroup, SheetRow } from "../primitives/SheetGroup";
10
+ import { Switch } from "../primitives/Switch";
11
+ import { useToasts } from "../primitives/toast/ToastStack";
12
+ import { cameraFacing, otherCamera } from "./camera";
13
+ import { shallowEqual, useSpaceSelector } from "./space-selector";
14
+ import { useStageView } from "./stage-view";
15
+
16
+ export type TileSheetProps = {
17
+ /** The person whose tile was tapped last; it stays set while the sheet closes, so the rows don't empty mid-slide. */
18
+ readonly participantId: string | null;
19
+ readonly open: boolean;
20
+ readonly onClose: () => void;
21
+ };
22
+
23
+ type SheetModel = {
24
+ readonly name: string;
25
+ readonly isSelf: boolean;
26
+ readonly cameraOn: boolean;
27
+ readonly back: boolean;
28
+ readonly otherCamera: string | null;
29
+ readonly muted: boolean;
30
+ readonly canMute: boolean;
31
+ readonly canRemove: boolean;
32
+ };
33
+
34
+ const closed: SheetModel = { name: "", isSelf: false, cameraOn: false, back: false, otherCamera: null, muted: false, canMute: false, canRemove: false };
35
+
36
+ /**
37
+ * The phone's tile sheet (`#tsheet` with `tileSheetHTML`): pin, your camera and mirror, and the
38
+ * host's mute and remove for someone else. Board following waits for board editing.
39
+ */
40
+ export function TileSheet({ participantId, open, onClose }: TileSheetProps): React.JSX.Element {
41
+ const client = useSpaceClient();
42
+ const toasts = useToasts();
43
+ const view = useStageView();
44
+ const select = useMemo(
45
+ () =>
46
+ (snapshot: SpaceSnapshot): SheetModel => {
47
+ if (participantId === null) return closed;
48
+ const person = snapshot.participants.roster.find((participant) => participant.participantId === participantId);
49
+ const isSelf = snapshot.self.participantId === participantId;
50
+ return {
51
+ name: person?.displayName ?? snapshot.self.displayName ?? "",
52
+ isSelf,
53
+ cameraOn: isSelf && snapshot.media.local.camera.state === "enabled",
54
+ back: cameraFacing(snapshot.media) === "back",
55
+ otherCamera: otherCamera(snapshot.media)?.deviceId ?? null,
56
+ muted: person?.media.microphone !== "active",
57
+ canMute: !isSelf && snapshot.self.can("muteOthers"),
58
+ canRemove: !isSelf && snapshot.self.can("removeParticipant"),
59
+ };
60
+ },
61
+ [participantId],
62
+ );
63
+ const person = useSpaceSelector(select, shallowEqual);
64
+ const id = participantId ?? "";
65
+ const failed = useCallback((message: string) => () => toasts.show({ kind: "error", dismissible: true, message }), [toasts]);
66
+ const run = (action: () => void) => () => {
67
+ onClose();
68
+ action();
69
+ };
70
+
71
+ return (
72
+ <BottomSheet accessibilityLabel={`${person.name}${person.isSelf ? " (You)" : ""}`} onClose={onClose} open={open && participantId !== null} testID="sheet.tile">
73
+ <View style={styles.header}>
74
+ <FaceAvatar name={person.name} size={32} />
75
+ <ChalkText numberOfLines={1} style={styles.name} variant="sheetTitle">
76
+ {`${person.name}${person.isSelf ? " (You)" : ""}`}
77
+ </ChalkText>
78
+ </View>
79
+ <SheetGroup>
80
+ <SheetRow icon="pin" label={view.pinned === participantId ? "Unpin" : "Pin"} onPress={run(() => view.togglePin(id))} testID="tile-sheet.pin" />
81
+ {person.cameraOn && person.otherCamera !== null ? (
82
+ <SheetRow
83
+ icon="flip"
84
+ label={`Switch to the ${person.back ? "front" : "back"} camera`}
85
+ onPress={run(() => {
86
+ if (person.otherCamera) client.media.selectCamera(person.otherCamera).then(() => announce(`Switched to the ${person.back ? "front" : "back"} camera`), failed("Couldn't switch the camera."));
87
+ })}
88
+ testID="tile-sheet.flip"
89
+ />
90
+ ) : null}
91
+ {person.cameraOn && !person.back ? <SheetRow end={<Switch accessibilityLabel="Mirror my video" onValueChange={view.setMirror} testID="tile-sheet.mirror" value={view.mirror} />} icon="swap" label="Mirror my video" /> : null}
92
+ {person.canMute && !person.muted ? <SheetRow icon="micOff" label="Mute" onPress={run(() => client.participants.mute(id).catch(failed(`Couldn't mute ${person.name}.`)))} testID="tile-sheet.mute" /> : null}
93
+ {person.canRemove ? (
94
+ <SheetRow
95
+ danger
96
+ icon="x"
97
+ label="Remove from call"
98
+ onPress={run(() =>
99
+ client.participants
100
+ .remove(id)
101
+ .then(() => announce(`${person.name} was removed`))
102
+ .catch(failed(`Couldn't remove ${person.name}.`)),
103
+ )}
104
+ testID="tile-sheet.remove"
105
+ />
106
+ ) : null}
107
+ </SheetGroup>
108
+ </BottomSheet>
109
+ );
110
+ }
111
+
112
+ const styles = StyleSheet.create({
113
+ // `.shd`: the person's face and name over the rows.
114
+ header: { flexDirection: "row", alignItems: "center", gap: 12, paddingTop: 4, paddingHorizontal: 8, paddingBottom: 14 },
115
+ name: { flexShrink: 1 },
116
+ });
@@ -0,0 +1,20 @@
1
+ import type { MediaDevice, MediaSlice } from "@q9labsai/chalk-client";
2
+
3
+ export type CameraFacing = "front" | "back";
4
+
5
+ const BACK = /back|rear|environment/i;
6
+
7
+ function facingOf(device: MediaDevice | undefined): CameraFacing {
8
+ return device && BACK.test(device.label) ? "back" : "front";
9
+ }
10
+
11
+ /** Which way your selected camera faces. Without a selection the phone uses the front camera. */
12
+ export function cameraFacing(media: MediaSlice): CameraFacing {
13
+ return facingOf(media.devices.cameras.find((camera) => camera.deviceId === media.selection.camera));
14
+ }
15
+
16
+ /** The camera facing the other way, or null when the phone has only one. */
17
+ export function otherCamera(media: MediaSlice): MediaDevice | null {
18
+ const current = cameraFacing(media);
19
+ return media.devices.cameras.find((camera) => facingOf(camera) !== current) ?? null;
20
+ }
@@ -0,0 +1,77 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { nextStageOrder, stageLayout, type StageInput, type StageItem } from "./layout";
3
+
4
+ // The D1 portrait stage from `frameRegions`. Expected frames come from the mockup's own `layout()`.
5
+ const stage = { x: 8, y: 111, width: 377, height: 625 };
6
+ const ids = (count: number) => Array.from({ length: count }, (_, index) => String(index));
7
+ const input = (overrides: Partial<StageInput>): StageInput => ({ stage, portrait: true, layout: "spotlight", order: ids(6), spot: "1", pinned: null, content: null, ...overrides });
8
+ const rounded = (item: StageItem) => [item.frame.x, item.frame.y, item.frame.width, item.frame.height].map((value) => Math.round(value * 10) / 10);
9
+
10
+ describe("stageLayout", () => {
11
+ it("floats you over the other person when two are in the call (P2)", () => {
12
+ const [main, you] = stageLayout(input({ order: ids(2) }));
13
+ expect(main).toMatchObject({ kind: "person", id: "1", frame: stage });
14
+ expect(you).toMatchObject({ kind: "person", id: "0", float: true });
15
+ if (you) expect(rounded(you)).toEqual([277, 123, 96, 128]);
16
+ });
17
+
18
+ it("puts the speaker big and everyone else in justified rows below (P3, G1)", () => {
19
+ const items = stageLayout(input({}));
20
+ expect(items.map((item) => (item.kind === "person" ? item.id : item.kind))).toEqual(["1", "0", "2", "3", "4", "5"]);
21
+ expect(items[0]).toMatchObject({ big: true });
22
+ expect(items.map(rounded)).toEqual([
23
+ [8, 111, 377, 429],
24
+ [8, 548, 120.3, 90],
25
+ [136.3, 548, 120.3, 90],
26
+ [264.7, 548, 120.3, 90],
27
+ [8, 646, 184.5, 90],
28
+ [200.5, 646, 184.5, 90],
29
+ ]);
30
+ });
31
+
32
+ it("keeps a pinned person big over the speaker", () => {
33
+ expect(stageLayout(input({ pinned: "4" }))[0]).toMatchObject({ kind: "person", id: "4", big: true });
34
+ });
35
+
36
+ it("fills the stage with 24 tiles in Grid (P4, G2)", () => {
37
+ const items = stageLayout(input({ layout: "grid", order: ids(24) }));
38
+ expect(items).toHaveLength(24);
39
+ expect(items.every((item) => item.kind === "person" && Math.round(item.frame.width * 10) / 10 === 88.3)).toBe(true);
40
+ });
41
+
42
+ it("stands a phone share at full height with people beside it (C2, S4)", () => {
43
+ const [share, ...rest] = stageLayout(input({ content: { kind: "screen", aspect: 9 / 16 } }));
44
+ expect(share).toMatchObject({ kind: "content", mini: false });
45
+ if (share) expect(rounded(share)).toEqual([8, 111, 249, 625]);
46
+ expect(rest.every((item) => item.frame.x === 265 && item.frame.width === 120)).toBe(true);
47
+ });
48
+
49
+ it("gives a shared board the stage with one row of people and a +N tile (C3)", () => {
50
+ const items = stageLayout(input({ content: { kind: "board", aspect: null } }));
51
+ expect(items[0] && rounded(items[0])).toEqual([8, 111, 377, 511]);
52
+ expect(items.at(-1)).toMatchObject({ kind: "more", count: 3 });
53
+ });
54
+
55
+ it("shows the share as a double tile in Grid", () => {
56
+ const [share] = stageLayout(input({ layout: "grid", content: { kind: "screen", aspect: 16 / 9 } }));
57
+ expect(share).toMatchObject({ kind: "content", mini: true });
58
+ });
59
+ });
60
+
61
+ describe("nextStageOrder", () => {
62
+ it("swaps a new speaker with the person in the spotlight, so only those two tiles move", () => {
63
+ const start = nextStageOrder({ order: [], spot: null }, ["you", "a", "b", "c"], "you", null);
64
+ expect(start).toEqual({ order: ["you", "a", "b", "c"], spot: "a" });
65
+ expect(nextStageOrder(start, ["you", "a", "b", "c"], "you", "c")).toEqual({ order: ["you", "c", "b", "a"], spot: "c" });
66
+ });
67
+
68
+ it("ignores your own speech and keeps the same object when nothing changes", () => {
69
+ const start = nextStageOrder({ order: [], spot: null }, ["you", "a", "b"], "you", null);
70
+ expect(nextStageOrder(start, ["you", "a", "b"], "you", "you")).toBe(start);
71
+ });
72
+
73
+ it("drops leavers, appends newcomers and moves the spotlight off someone who left", () => {
74
+ const start = { order: ["you", "a", "b"], spot: "a" };
75
+ expect(nextStageOrder(start, ["you", "b", "d"], "you", null)).toEqual({ order: ["you", "b", "d"], spot: "b" });
76
+ });
77
+ });