@ethora/chat-component-rn 26.10.4 → 26.10.6

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 (451) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +15 -0
  3. package/app.plugin.js +2 -0
  4. package/lib/commonjs/assets/icons.js +2 -2
  5. package/lib/commonjs/assets/icons.js.map +1 -1
  6. package/lib/commonjs/components/MainComponents/AlbumGrid.js +152 -0
  7. package/lib/commonjs/components/MainComponents/AlbumGrid.js.map +1 -0
  8. package/lib/commonjs/components/MainComponents/ChatRoom.js +2 -2
  9. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  10. package/lib/commonjs/components/MainComponents/MediaMessage.js +199 -123
  11. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  12. package/lib/commonjs/components/MainComponents/MediaUploadOverlay.js +109 -0
  13. package/lib/commonjs/components/MainComponents/MediaUploadOverlay.js.map +1 -0
  14. package/lib/commonjs/components/MessageBubble/MediaCaption.js +51 -0
  15. package/lib/commonjs/components/MessageBubble/MediaCaption.js.map +1 -0
  16. package/lib/commonjs/components/MessageBubble/Message.js +40 -1
  17. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  18. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +2 -1
  19. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  20. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +75 -0
  21. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  22. package/lib/commonjs/components/RoomComponents/LastMessageItem.js +20 -4
  23. package/lib/commonjs/components/RoomComponents/LastMessageItem.js.map +1 -1
  24. package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js +3 -2
  25. package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
  26. package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js +3 -2
  27. package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
  28. package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js +3 -2
  29. package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
  30. package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js +3 -2
  31. package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
  32. package/lib/commonjs/components/Thread/ThreadWrapper.js +3 -2
  33. package/lib/commonjs/components/Thread/ThreadWrapper.js.map +1 -1
  34. package/lib/commonjs/components/VideoCalls/ActiveCallBar.js +61 -16
  35. package/lib/commonjs/components/VideoCalls/ActiveCallBar.js.map +1 -1
  36. package/lib/commonjs/components/VideoCalls/CallIcons.js +41 -16
  37. package/lib/commonjs/components/VideoCalls/CallIcons.js.map +1 -1
  38. package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js +183 -9
  39. package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js.map +1 -1
  40. package/lib/commonjs/components/VideoCalls/VideoCallSession.js +40 -15
  41. package/lib/commonjs/components/VideoCalls/VideoCallSession.js.map +1 -1
  42. package/lib/commonjs/components/VideoCalls/callSessionController.js +49 -26
  43. package/lib/commonjs/components/VideoCalls/callSessionController.js.map +1 -1
  44. package/lib/commonjs/components/VideoCalls/callUiStore.js +33 -4
  45. package/lib/commonjs/components/VideoCalls/callUiStore.js.map +1 -1
  46. package/lib/commonjs/components/VideoCalls/livekitRuntime.js +9 -1
  47. package/lib/commonjs/components/VideoCalls/livekitRuntime.js.map +1 -1
  48. package/lib/commonjs/components/VideoCalls/useCallKeep.js +197 -28
  49. package/lib/commonjs/components/VideoCalls/useCallKeep.js.map +1 -1
  50. package/lib/commonjs/components/VideoCalls/useOngoingCallNotification.js +88 -0
  51. package/lib/commonjs/components/VideoCalls/useOngoingCallNotification.js.map +1 -0
  52. package/lib/commonjs/components/VideoCalls/usePipTile.js +232 -0
  53. package/lib/commonjs/components/VideoCalls/usePipTile.js.map +1 -0
  54. package/lib/commonjs/components/styled/MediaFilePreview.js +60 -47
  55. package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
  56. package/lib/commonjs/components/styled/MessageImage.js +16 -6
  57. package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
  58. package/lib/commonjs/components/styled/SendInput.js +99 -68
  59. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  60. package/lib/commonjs/components/styled/VideoMessage.js +24 -10
  61. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  62. package/lib/commonjs/context/xmppProvider.js +46 -0
  63. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  64. package/lib/commonjs/e2ee/fileEnvelope.js +6 -0
  65. package/lib/commonjs/e2ee/fileEnvelope.js.map +1 -1
  66. package/lib/commonjs/e2ee/sealedBody.js +32 -2
  67. package/lib/commonjs/e2ee/sealedBody.js.map +1 -1
  68. package/lib/commonjs/helpers/albumLayout.js +150 -0
  69. package/lib/commonjs/helpers/albumLayout.js.map +1 -0
  70. package/lib/commonjs/helpers/attachments.js +255 -0
  71. package/lib/commonjs/helpers/attachments.js.map +1 -0
  72. package/lib/commonjs/helpers/caption.js +214 -0
  73. package/lib/commonjs/helpers/caption.js.map +1 -0
  74. package/lib/commonjs/helpers/createMainMessageForThread.js +5 -1
  75. package/lib/commonjs/helpers/createMainMessageForThread.js.map +1 -1
  76. package/lib/commonjs/helpers/createMessageFromXml.js +19 -5
  77. package/lib/commonjs/helpers/createMessageFromXml.js.map +1 -1
  78. package/lib/commonjs/helpers/fileKind.js +40 -0
  79. package/lib/commonjs/helpers/fileKind.js.map +1 -0
  80. package/lib/commonjs/helpers/getDataFromXml.js +6 -1
  81. package/lib/commonjs/helpers/getDataFromXml.js.map +1 -1
  82. package/lib/commonjs/helpers/mapWithConcurrency.js +41 -0
  83. package/lib/commonjs/helpers/mapWithConcurrency.js.map +1 -0
  84. package/lib/commonjs/helpers/mediaLimits.js +33 -0
  85. package/lib/commonjs/helpers/mediaLimits.js.map +1 -0
  86. package/lib/commonjs/helpers/mediaSendRetry.js +64 -0
  87. package/lib/commonjs/helpers/mediaSendRetry.js.map +1 -0
  88. package/lib/commonjs/helpers/mediaWireData.js +62 -0
  89. package/lib/commonjs/helpers/mediaWireData.js.map +1 -0
  90. package/lib/commonjs/helpers/mentionSpans.js +19 -0
  91. package/lib/commonjs/helpers/mentionSpans.js.map +1 -0
  92. package/lib/commonjs/helpers/mimeToExtension.js +45 -0
  93. package/lib/commonjs/helpers/mimeToExtension.js.map +1 -1
  94. package/lib/commonjs/helpers/readReceipts.js +173 -0
  95. package/lib/commonjs/helpers/readReceipts.js.map +1 -0
  96. package/lib/commonjs/helpers/repairFilenameEncoding.js +79 -0
  97. package/lib/commonjs/helpers/repairFilenameEncoding.js.map +1 -0
  98. package/lib/commonjs/helpers/safeJson.js +18 -0
  99. package/lib/commonjs/helpers/safeJson.js.map +1 -0
  100. package/lib/commonjs/helpers/updateMessagesTillLast.js +3 -1
  101. package/lib/commonjs/helpers/updateMessagesTillLast.js.map +1 -1
  102. package/lib/commonjs/hooks/useSendMessage.js +384 -193
  103. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  104. package/lib/commonjs/i18n/strings.js +144 -0
  105. package/lib/commonjs/i18n/strings.js.map +1 -1
  106. package/lib/commonjs/main.js.map +1 -1
  107. package/lib/commonjs/networking/api-requests/auth.api.js +62 -3
  108. package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
  109. package/lib/commonjs/networking/stanzaHandlers.js +100 -3
  110. package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
  111. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +4 -2
  112. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  113. package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js +28 -0
  114. package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
  115. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
  116. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  117. package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js +28 -0
  118. package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
  119. package/lib/commonjs/networking/xmppClient.js +64 -0
  120. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  121. package/lib/commonjs/roomStore/persistence.js +78 -5
  122. package/lib/commonjs/roomStore/persistence.js.map +1 -1
  123. package/lib/commonjs/roomStore/roomHeapSlice.js +4 -0
  124. package/lib/commonjs/roomStore/roomHeapSlice.js.map +1 -1
  125. package/lib/commonjs/roomStore/roomsSlice.js +114 -1
  126. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  127. package/lib/commonjs/types/models/attachment.model.js +2 -0
  128. package/lib/commonjs/types/models/attachment.model.js.map +1 -0
  129. package/lib/commonjs/version.js +1 -1
  130. package/lib/module/assets/icons.js +1 -1
  131. package/lib/module/assets/icons.js.map +1 -1
  132. package/lib/module/components/MainComponents/AlbumGrid.js +146 -0
  133. package/lib/module/components/MainComponents/AlbumGrid.js.map +1 -0
  134. package/lib/module/components/MainComponents/ChatRoom.js +2 -2
  135. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  136. package/lib/module/components/MainComponents/MediaMessage.js +200 -124
  137. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  138. package/lib/module/components/MainComponents/MediaUploadOverlay.js +104 -0
  139. package/lib/module/components/MainComponents/MediaUploadOverlay.js.map +1 -0
  140. package/lib/module/components/MessageBubble/MediaCaption.js +46 -0
  141. package/lib/module/components/MessageBubble/MediaCaption.js.map +1 -0
  142. package/lib/module/components/MessageBubble/Message.js +44 -5
  143. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  144. package/lib/module/components/MessageBubble/MessageInteractions.js +2 -1
  145. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  146. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +75 -0
  147. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  148. package/lib/module/components/RoomComponents/LastMessageItem.js +20 -4
  149. package/lib/module/components/RoomComponents/LastMessageItem.js.map +1 -1
  150. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js +3 -2
  151. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
  152. package/lib/module/components/RoomComponents/styled/LastMessageFile.js +3 -2
  153. package/lib/module/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
  154. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js +3 -2
  155. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
  156. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js +3 -2
  157. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
  158. package/lib/module/components/Thread/ThreadWrapper.js +3 -2
  159. package/lib/module/components/Thread/ThreadWrapper.js.map +1 -1
  160. package/lib/module/components/VideoCalls/ActiveCallBar.js +63 -18
  161. package/lib/module/components/VideoCalls/ActiveCallBar.js.map +1 -1
  162. package/lib/module/components/VideoCalls/CallIcons.js +38 -15
  163. package/lib/module/components/VideoCalls/CallIcons.js.map +1 -1
  164. package/lib/module/components/VideoCalls/VideoCallOverlay.js +185 -11
  165. package/lib/module/components/VideoCalls/VideoCallOverlay.js.map +1 -1
  166. package/lib/module/components/VideoCalls/VideoCallSession.js +41 -16
  167. package/lib/module/components/VideoCalls/VideoCallSession.js.map +1 -1
  168. package/lib/module/components/VideoCalls/callSessionController.js +46 -24
  169. package/lib/module/components/VideoCalls/callSessionController.js.map +1 -1
  170. package/lib/module/components/VideoCalls/callUiStore.js +30 -3
  171. package/lib/module/components/VideoCalls/callUiStore.js.map +1 -1
  172. package/lib/module/components/VideoCalls/livekitRuntime.js +9 -1
  173. package/lib/module/components/VideoCalls/livekitRuntime.js.map +1 -1
  174. package/lib/module/components/VideoCalls/useCallKeep.js +198 -29
  175. package/lib/module/components/VideoCalls/useCallKeep.js.map +1 -1
  176. package/lib/module/components/VideoCalls/useOngoingCallNotification.js +84 -0
  177. package/lib/module/components/VideoCalls/useOngoingCallNotification.js.map +1 -0
  178. package/lib/module/components/VideoCalls/usePipTile.js +228 -0
  179. package/lib/module/components/VideoCalls/usePipTile.js.map +1 -0
  180. package/lib/module/components/styled/MediaFilePreview.js +61 -48
  181. package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
  182. package/lib/module/components/styled/MessageImage.js +16 -6
  183. package/lib/module/components/styled/MessageImage.js.map +1 -1
  184. package/lib/module/components/styled/SendInput.js +99 -68
  185. package/lib/module/components/styled/SendInput.js.map +1 -1
  186. package/lib/module/components/styled/VideoMessage.js +24 -10
  187. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  188. package/lib/module/context/xmppProvider.js +46 -0
  189. package/lib/module/context/xmppProvider.js.map +1 -1
  190. package/lib/module/e2ee/fileEnvelope.js +1 -1
  191. package/lib/module/e2ee/fileEnvelope.js.map +1 -1
  192. package/lib/module/e2ee/sealedBody.js +32 -2
  193. package/lib/module/e2ee/sealedBody.js.map +1 -1
  194. package/lib/module/helpers/albumLayout.js +143 -0
  195. package/lib/module/helpers/albumLayout.js.map +1 -0
  196. package/lib/module/helpers/attachments.js +245 -0
  197. package/lib/module/helpers/attachments.js.map +1 -0
  198. package/lib/module/helpers/caption.js +207 -0
  199. package/lib/module/helpers/caption.js.map +1 -0
  200. package/lib/module/helpers/createMainMessageForThread.js +5 -1
  201. package/lib/module/helpers/createMainMessageForThread.js.map +1 -1
  202. package/lib/module/helpers/createMessageFromXml.js +20 -6
  203. package/lib/module/helpers/createMessageFromXml.js.map +1 -1
  204. package/lib/module/helpers/fileKind.js +34 -0
  205. package/lib/module/helpers/fileKind.js.map +1 -0
  206. package/lib/module/helpers/getDataFromXml.js +6 -1
  207. package/lib/module/helpers/getDataFromXml.js.map +1 -1
  208. package/lib/module/helpers/mapWithConcurrency.js +37 -0
  209. package/lib/module/helpers/mapWithConcurrency.js.map +1 -0
  210. package/lib/module/helpers/mediaLimits.js +27 -0
  211. package/lib/module/helpers/mediaLimits.js.map +1 -0
  212. package/lib/module/helpers/mediaSendRetry.js +55 -0
  213. package/lib/module/helpers/mediaSendRetry.js.map +1 -0
  214. package/lib/module/helpers/mediaWireData.js +58 -0
  215. package/lib/module/helpers/mediaWireData.js.map +1 -0
  216. package/lib/module/helpers/mentionSpans.js +14 -0
  217. package/lib/module/helpers/mentionSpans.js.map +1 -0
  218. package/lib/module/helpers/mimeToExtension.js +43 -0
  219. package/lib/module/helpers/mimeToExtension.js.map +1 -1
  220. package/lib/module/helpers/readReceipts.js +160 -0
  221. package/lib/module/helpers/readReceipts.js.map +1 -0
  222. package/lib/module/helpers/repairFilenameEncoding.js +74 -0
  223. package/lib/module/helpers/repairFilenameEncoding.js.map +1 -0
  224. package/lib/module/helpers/safeJson.js +13 -0
  225. package/lib/module/helpers/safeJson.js.map +1 -0
  226. package/lib/module/helpers/updateMessagesTillLast.js +3 -1
  227. package/lib/module/helpers/updateMessagesTillLast.js.map +1 -1
  228. package/lib/module/hooks/useSendMessage.js +387 -195
  229. package/lib/module/hooks/useSendMessage.js.map +1 -1
  230. package/lib/module/i18n/strings.js +144 -0
  231. package/lib/module/i18n/strings.js.map +1 -1
  232. package/lib/module/main.js.map +1 -1
  233. package/lib/module/networking/api-requests/auth.api.js +62 -3
  234. package/lib/module/networking/api-requests/auth.api.js.map +1 -1
  235. package/lib/module/networking/stanzaHandlers.js +97 -4
  236. package/lib/module/networking/stanzaHandlers.js.map +1 -1
  237. package/lib/module/networking/xmpp/handleStanzas.xmpp.js +5 -3
  238. package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  239. package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js +25 -0
  240. package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
  241. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
  242. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  243. package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js +25 -0
  244. package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
  245. package/lib/module/networking/xmppClient.js +64 -0
  246. package/lib/module/networking/xmppClient.js.map +1 -1
  247. package/lib/module/roomStore/persistence.js +78 -5
  248. package/lib/module/roomStore/persistence.js.map +1 -1
  249. package/lib/module/roomStore/roomHeapSlice.js +4 -0
  250. package/lib/module/roomStore/roomHeapSlice.js.map +1 -1
  251. package/lib/module/roomStore/roomsSlice.js +109 -0
  252. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  253. package/lib/module/types/models/attachment.model.js +2 -0
  254. package/lib/module/types/models/attachment.model.js.map +1 -0
  255. package/lib/module/version.js +1 -1
  256. package/lib/typescript/assets/icons.d.ts +6 -1
  257. package/lib/typescript/assets/icons.d.ts.map +1 -1
  258. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts +21 -0
  259. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts.map +1 -0
  260. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  261. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  262. package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts +17 -0
  263. package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts.map +1 -0
  264. package/lib/typescript/components/MessageBubble/MediaCaption.d.ts +12 -0
  265. package/lib/typescript/components/MessageBubble/MediaCaption.d.ts.map +1 -0
  266. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  267. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  268. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  269. package/lib/typescript/components/RoomComponents/LastMessageItem.d.ts.map +1 -1
  270. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts +2 -0
  271. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts.map +1 -1
  272. package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts +2 -0
  273. package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts.map +1 -1
  274. package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts +2 -0
  275. package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts.map +1 -1
  276. package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts +2 -0
  277. package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts.map +1 -1
  278. package/lib/typescript/components/Thread/ThreadWrapper.d.ts.map +1 -1
  279. package/lib/typescript/components/VideoCalls/ActiveCallBar.d.ts +9 -3
  280. package/lib/typescript/components/VideoCalls/ActiveCallBar.d.ts.map +1 -1
  281. package/lib/typescript/components/VideoCalls/CallIcons.d.ts +9 -0
  282. package/lib/typescript/components/VideoCalls/CallIcons.d.ts.map +1 -1
  283. package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts.map +1 -1
  284. package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts.map +1 -1
  285. package/lib/typescript/components/VideoCalls/callSessionController.d.ts +7 -0
  286. package/lib/typescript/components/VideoCalls/callSessionController.d.ts.map +1 -1
  287. package/lib/typescript/components/VideoCalls/callUiStore.d.ts +13 -0
  288. package/lib/typescript/components/VideoCalls/callUiStore.d.ts.map +1 -1
  289. package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts +5 -0
  290. package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts.map +1 -1
  291. package/lib/typescript/components/VideoCalls/useCallKeep.d.ts +12 -0
  292. package/lib/typescript/components/VideoCalls/useCallKeep.d.ts.map +1 -1
  293. package/lib/typescript/components/VideoCalls/useOngoingCallNotification.d.ts +10 -0
  294. package/lib/typescript/components/VideoCalls/useOngoingCallNotification.d.ts.map +1 -0
  295. package/lib/typescript/components/VideoCalls/usePipTile.d.ts +46 -0
  296. package/lib/typescript/components/VideoCalls/usePipTile.d.ts.map +1 -0
  297. package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
  298. package/lib/typescript/components/styled/MessageImage.d.ts +10 -0
  299. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  300. package/lib/typescript/components/styled/SendInput.d.ts +7 -1
  301. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  302. package/lib/typescript/components/styled/VideoMessage.d.ts +7 -0
  303. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  304. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  305. package/lib/typescript/e2ee/fileEnvelope.d.ts +1 -1
  306. package/lib/typescript/e2ee/fileEnvelope.d.ts.map +1 -1
  307. package/lib/typescript/e2ee/sealedBody.d.ts +16 -0
  308. package/lib/typescript/e2ee/sealedBody.d.ts.map +1 -1
  309. package/lib/typescript/helpers/albumLayout.d.ts +66 -0
  310. package/lib/typescript/helpers/albumLayout.d.ts.map +1 -0
  311. package/lib/typescript/helpers/attachments.d.ts +55 -0
  312. package/lib/typescript/helpers/attachments.d.ts.map +1 -0
  313. package/lib/typescript/helpers/caption.d.ts +64 -0
  314. package/lib/typescript/helpers/caption.d.ts.map +1 -0
  315. package/lib/typescript/helpers/createMainMessageForThread.d.ts.map +1 -1
  316. package/lib/typescript/helpers/createMessageFromXml.d.ts.map +1 -1
  317. package/lib/typescript/helpers/fileKind.d.ts +20 -0
  318. package/lib/typescript/helpers/fileKind.d.ts.map +1 -0
  319. package/lib/typescript/helpers/getDataFromXml.d.ts.map +1 -1
  320. package/lib/typescript/helpers/mapWithConcurrency.d.ts +9 -0
  321. package/lib/typescript/helpers/mapWithConcurrency.d.ts.map +1 -0
  322. package/lib/typescript/helpers/mediaLimits.d.ts +13 -0
  323. package/lib/typescript/helpers/mediaLimits.d.ts.map +1 -0
  324. package/lib/typescript/helpers/mediaSendRetry.d.ts +26 -0
  325. package/lib/typescript/helpers/mediaSendRetry.d.ts.map +1 -0
  326. package/lib/typescript/helpers/mediaWireData.d.ts +16 -0
  327. package/lib/typescript/helpers/mediaWireData.d.ts.map +1 -0
  328. package/lib/typescript/helpers/mentionSpans.d.ts +8 -0
  329. package/lib/typescript/helpers/mentionSpans.d.ts.map +1 -0
  330. package/lib/typescript/helpers/mimeToExtension.d.ts +7 -0
  331. package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -1
  332. package/lib/typescript/helpers/readReceipts.d.ts +82 -0
  333. package/lib/typescript/helpers/readReceipts.d.ts.map +1 -0
  334. package/lib/typescript/helpers/repairFilenameEncoding.d.ts +12 -0
  335. package/lib/typescript/helpers/repairFilenameEncoding.d.ts.map +1 -0
  336. package/lib/typescript/helpers/safeJson.d.ts +2 -0
  337. package/lib/typescript/helpers/safeJson.d.ts.map +1 -0
  338. package/lib/typescript/helpers/updateMessagesTillLast.d.ts.map +1 -1
  339. package/lib/typescript/hooks/useSendMessage.d.ts +2 -1
  340. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  341. package/lib/typescript/i18n/strings.d.ts.map +1 -1
  342. package/lib/typescript/main.d.ts +1 -1
  343. package/lib/typescript/main.d.ts.map +1 -1
  344. package/lib/typescript/networking/api-requests/auth.api.d.ts +2 -0
  345. package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
  346. package/lib/typescript/networking/stanzaHandlers.d.ts +7 -3
  347. package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
  348. package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts.map +1 -1
  349. package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts +3 -0
  350. package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts.map +1 -0
  351. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts +12 -0
  352. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts.map +1 -1
  353. package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts +3 -0
  354. package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts.map +1 -0
  355. package/lib/typescript/networking/xmppClient.d.ts +15 -0
  356. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  357. package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
  358. package/lib/typescript/roomStore/roomHeapSlice.d.ts +22 -1
  359. package/lib/typescript/roomStore/roomHeapSlice.d.ts.map +1 -1
  360. package/lib/typescript/roomStore/roomsSlice.d.ts +67 -3
  361. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  362. package/lib/typescript/types/models/attachment.model.d.ts +52 -0
  363. package/lib/typescript/types/models/attachment.model.d.ts.map +1 -0
  364. package/lib/typescript/types/models/message.model.d.ts +26 -0
  365. package/lib/typescript/types/models/message.model.d.ts.map +1 -1
  366. package/lib/typescript/types/models/modal.model.d.ts +7 -0
  367. package/lib/typescript/types/models/modal.model.d.ts.map +1 -1
  368. package/lib/typescript/types/models/room.model.d.ts +2 -0
  369. package/lib/typescript/types/models/room.model.d.ts.map +1 -1
  370. package/lib/typescript/types/types.d.ts +69 -1
  371. package/lib/typescript/types/types.d.ts.map +1 -1
  372. package/lib/typescript/version.d.ts +1 -1
  373. package/package.json +4 -1
  374. package/plugins/notification-service/Info.plist +31 -0
  375. package/plugins/notification-service/NotificationService.swift +82 -0
  376. package/plugins/withNotificationServiceExtension.js +137 -0
  377. package/src/assets/icons.tsx +20 -2
  378. package/src/components/MainComponents/AlbumGrid.tsx +183 -0
  379. package/src/components/MainComponents/ChatRoom.tsx +4 -2
  380. package/src/components/MainComponents/MediaMessage.tsx +246 -130
  381. package/src/components/MainComponents/MediaUploadOverlay.tsx +102 -0
  382. package/src/components/MessageBubble/MediaCaption.tsx +45 -0
  383. package/src/components/MessageBubble/Message.tsx +71 -3
  384. package/src/components/MessageBubble/MessageInteractions.tsx +2 -1
  385. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +64 -0
  386. package/src/components/RoomComponents/LastMessageItem.tsx +15 -4
  387. package/src/components/RoomComponents/styled/LastAudioMessage.tsx +6 -3
  388. package/src/components/RoomComponents/styled/LastMessageFile.tsx +6 -2
  389. package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +6 -2
  390. package/src/components/RoomComponents/styled/LastMessageVideo.tsx +6 -2
  391. package/src/components/Thread/ThreadWrapper.tsx +5 -9
  392. package/src/components/VideoCalls/ActiveCallBar.tsx +56 -10
  393. package/src/components/VideoCalls/CallIcons.tsx +36 -12
  394. package/src/components/VideoCalls/VideoCallOverlay.tsx +205 -8
  395. package/src/components/VideoCalls/VideoCallSession.tsx +45 -13
  396. package/src/components/VideoCalls/callSessionController.ts +51 -20
  397. package/src/components/VideoCalls/callUiStore.ts +33 -3
  398. package/src/components/VideoCalls/livekitRuntime.ts +13 -0
  399. package/src/components/VideoCalls/useCallKeep.ts +204 -33
  400. package/src/components/VideoCalls/useOngoingCallNotification.ts +113 -0
  401. package/src/components/VideoCalls/usePipTile.ts +214 -0
  402. package/src/components/styled/MediaFilePreview.tsx +49 -34
  403. package/src/components/styled/MessageImage.tsx +21 -5
  404. package/src/components/styled/SendInput.tsx +101 -87
  405. package/src/components/styled/VideoMessage.tsx +34 -9
  406. package/src/context/xmppProvider.tsx +32 -0
  407. package/src/e2ee/fileEnvelope.ts +1 -1
  408. package/src/e2ee/sealedBody.ts +44 -5
  409. package/src/helpers/albumLayout.ts +207 -0
  410. package/src/helpers/attachments.ts +316 -0
  411. package/src/helpers/caption.ts +283 -0
  412. package/src/helpers/createMainMessageForThread.ts +6 -1
  413. package/src/helpers/createMessageFromXml.ts +21 -5
  414. package/src/helpers/fileKind.ts +38 -0
  415. package/src/helpers/getDataFromXml.ts +5 -1
  416. package/src/helpers/mapWithConcurrency.ts +32 -0
  417. package/src/helpers/mediaLimits.ts +27 -0
  418. package/src/helpers/mediaSendRetry.ts +59 -0
  419. package/src/helpers/mediaWireData.ts +57 -0
  420. package/src/helpers/mentionSpans.ts +17 -0
  421. package/src/helpers/mimeToExtension.ts +51 -0
  422. package/src/helpers/readReceipts.ts +219 -0
  423. package/src/helpers/repairFilenameEncoding.ts +82 -0
  424. package/src/helpers/safeJson.ts +11 -0
  425. package/src/helpers/updateMessagesTillLast.tsx +2 -1
  426. package/src/hooks/useSendMessage.tsx +448 -249
  427. package/src/i18n/strings.ts +144 -0
  428. package/src/main.ts +3 -0
  429. package/src/networking/api-requests/auth.api.ts +60 -3
  430. package/src/networking/stanzaHandlers.ts +91 -2
  431. package/src/networking/xmpp/handleStanzas.xmpp.ts +6 -2
  432. package/src/networking/xmpp/sendDisplayedMarker.xmpp.ts +31 -0
  433. package/src/networking/xmpp/sendMediaMessage.xmpp.ts +60 -3
  434. package/src/networking/xmpp/sendReceivedReceipt.xmpp.ts +31 -0
  435. package/src/networking/xmppClient.ts +48 -0
  436. package/src/roomStore/persistence.ts +94 -3
  437. package/src/roomStore/roomHeapSlice.ts +23 -1
  438. package/src/roomStore/roomsSlice.ts +112 -1
  439. package/src/types/models/attachment.model.ts +51 -0
  440. package/src/types/models/message.model.ts +29 -0
  441. package/src/types/models/modal.model.ts +7 -0
  442. package/src/types/models/room.model.ts +2 -0
  443. package/src/types/types.ts +83 -1
  444. package/src/version.ts +1 -1
  445. package/lib/commonjs/components/VideoCalls/useDraggable.js +0 -59
  446. package/lib/commonjs/components/VideoCalls/useDraggable.js.map +0 -1
  447. package/lib/module/components/VideoCalls/useDraggable.js +0 -55
  448. package/lib/module/components/VideoCalls/useDraggable.js.map +0 -1
  449. package/lib/typescript/components/VideoCalls/useDraggable.d.ts +0 -19
  450. package/lib/typescript/components/VideoCalls/useDraggable.d.ts.map +0 -1
  451. package/src/components/VideoCalls/useDraggable.ts +0 -46
@@ -0,0 +1,214 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { Animated, LayoutChangeEvent, PanResponder } from 'react-native';
3
+
4
+ /**
5
+ * The small video tile of a call, Telegram-style: drag it anywhere and it
6
+ * snaps to the nearest corner when released (a flick carries it towards
7
+ * the flick), pinch it to switch between a small and a large size.
8
+ *
9
+ * Positions are kept per corner rather than per pixel, so the tile stays
10
+ * glued to its corner when the stage changes size (rotation, iPad split
11
+ * view) or the tile itself changes size.
12
+ */
13
+
14
+ export type PipTileSize = 'small' | 'large';
15
+
16
+ export const PIP_TILE_SIZES: Record<PipTileSize, { width: number; height: number }> = {
17
+ small: { width: 104, height: 148 },
18
+ large: { width: 156, height: 222 },
19
+ };
20
+
21
+ const MARGIN = 12;
22
+ // How far a flick carries the tile before picking a corner, in ms of velocity.
23
+ const FLICK_PROJECTION_MS = 160;
24
+ // Pinch past these ratios to change size; in between keeps the current one.
25
+ const GROW_RATIO = 1.12;
26
+ const SHRINK_RATIO = 0.9;
27
+
28
+ interface Corner {
29
+ right: boolean;
30
+ bottom: boolean;
31
+ }
32
+
33
+ interface Stage {
34
+ width: number;
35
+ height: number;
36
+ }
37
+
38
+ const SPRING = { useNativeDriver: false, damping: 20, stiffness: 220, mass: 0.8 };
39
+
40
+ const touchDistance = (touches: ReadonlyArray<{ pageX: number; pageY: number }>) => {
41
+ const [a, b] = touches;
42
+ return Math.hypot(a.pageX - b.pageX, a.pageY - b.pageY);
43
+ };
44
+
45
+ export const usePipTile = ({ topInset = 0 }: { topInset?: number } = {}) => {
46
+ const [stage, setStage] = useState<Stage | null>(null);
47
+ const [size, setSize] = useState<PipTileSize>('small');
48
+ const stageRef = useRef<Stage | null>(null);
49
+ const sizeRef = useRef<PipTileSize>('small');
50
+ const cornerRef = useRef<Corner>({ right: true, bottom: false });
51
+ const topInsetRef = useRef(topInset);
52
+ topInsetRef.current = topInset;
53
+
54
+ const position = useRef(new Animated.ValueXY({ x: 0, y: 0 })).current;
55
+ const width = useRef(new Animated.Value(PIP_TILE_SIZES.small.width)).current;
56
+ const height = useRef(new Animated.Value(PIP_TILE_SIZES.small.height)).current;
57
+ const pinchScale = useRef(new Animated.Value(1)).current;
58
+ // Where the tile rests, i.e. where a drag starts from.
59
+ const restRef = useRef({ x: 0, y: 0 });
60
+ const gestureRef = useRef({ pinching: false, startDistance: 0, scale: 1 });
61
+
62
+ const cornerPoint = useCallback((corner: Corner, tileSize: PipTileSize) => {
63
+ const area = stageRef.current;
64
+ if (!area) {return { x: 0, y: 0 };}
65
+ const tile = PIP_TILE_SIZES[tileSize];
66
+ const top = MARGIN + topInsetRef.current;
67
+ return {
68
+ x: corner.right ? Math.max(MARGIN, area.width - tile.width - MARGIN) : MARGIN,
69
+ y: corner.bottom ? Math.max(top, area.height - tile.height - MARGIN) : top,
70
+ };
71
+ }, []);
72
+
73
+ const settle = useCallback(
74
+ (animated: boolean) => {
75
+ const tileSize = sizeRef.current;
76
+ const target = cornerPoint(cornerRef.current, tileSize);
77
+ const tile = PIP_TILE_SIZES[tileSize];
78
+ restRef.current = target;
79
+ if (!animated) {
80
+ position.setValue(target);
81
+ width.setValue(tile.width);
82
+ height.setValue(tile.height);
83
+ pinchScale.setValue(1);
84
+ return;
85
+ }
86
+ Animated.parallel([
87
+ Animated.spring(position, { ...SPRING, toValue: target }),
88
+ Animated.spring(width, { ...SPRING, toValue: tile.width }),
89
+ Animated.spring(height, { ...SPRING, toValue: tile.height }),
90
+ Animated.spring(pinchScale, { ...SPRING, toValue: 1 }),
91
+ ]).start();
92
+ },
93
+ [cornerPoint, position, width, height, pinchScale]
94
+ );
95
+
96
+ const onStageLayout = useCallback(
97
+ (event: LayoutChangeEvent) => {
98
+ const { width: w, height: h } = event.nativeEvent.layout;
99
+ const prev = stageRef.current;
100
+ if (prev && prev.width === w && prev.height === h) {return;}
101
+ stageRef.current = { width: w, height: h };
102
+ setStage(stageRef.current);
103
+ settle(false);
104
+ },
105
+ [settle]
106
+ );
107
+
108
+ // The safe-area inset arrives after the first layout on some devices.
109
+ useEffect(() => {
110
+ if (stageRef.current) {settle(false);}
111
+ }, [topInset, settle]);
112
+
113
+ const panHandlers = useMemo(
114
+ () =>
115
+ PanResponder.create({
116
+ // Two fingers are always ours (pinch); one finger only once it
117
+ // moves, so a tap still reaches the tile's onPress (swap videos).
118
+ onStartShouldSetPanResponderCapture: (event) =>
119
+ event.nativeEvent.touches.length >= 2,
120
+ // Own the touch from the start when the tile has no tap target
121
+ // (nothing to swap): an ancestor outside the call Modal would take
122
+ // it otherwise, and the move-capture below would never be asked.
123
+ onStartShouldSetPanResponder: () => true,
124
+ onMoveShouldSetPanResponderCapture: (event, gesture) =>
125
+ event.nativeEvent.touches.length >= 2 ||
126
+ Math.abs(gesture.dx) > 4 ||
127
+ Math.abs(gesture.dy) > 4,
128
+ // Never hand the tile over to the swipe-to-minimize of the screen.
129
+ onPanResponderTerminationRequest: () => false,
130
+ onPanResponderGrant: () => {
131
+ position.stopAnimation();
132
+ width.stopAnimation();
133
+ height.stopAnimation();
134
+ pinchScale.stopAnimation();
135
+ gestureRef.current = { pinching: false, startDistance: 0, scale: 1 };
136
+ },
137
+ onPanResponderMove: (event, gesture) => {
138
+ const touches = event.nativeEvent.touches;
139
+ const state = gestureRef.current;
140
+ if (touches.length >= 2) {
141
+ const distance = touchDistance(touches);
142
+ if (!state.pinching || state.startDistance <= 0) {
143
+ state.pinching = true;
144
+ state.startDistance = distance;
145
+ } else {
146
+ state.scale = Math.min(1.8, Math.max(0.6, distance / state.startDistance));
147
+ pinchScale.setValue(state.scale);
148
+ }
149
+ }
150
+ position.setValue({
151
+ x: restRef.current.x + gesture.dx,
152
+ y: restRef.current.y + gesture.dy,
153
+ });
154
+ },
155
+ onPanResponderRelease: (_event, gesture) => {
156
+ const area = stageRef.current;
157
+ const state = gestureRef.current;
158
+ if (state.pinching) {
159
+ const next: PipTileSize =
160
+ state.scale >= GROW_RATIO
161
+ ? 'large'
162
+ : state.scale <= SHRINK_RATIO
163
+ ? 'small'
164
+ : sizeRef.current;
165
+ if (next !== sizeRef.current) {
166
+ // Keep the size the finger left it at, then let the spring
167
+ // carry it to the new one, so the tile doesn't jump.
168
+ const from = PIP_TILE_SIZES[sizeRef.current];
169
+ width.setValue(from.width * state.scale);
170
+ height.setValue(from.height * state.scale);
171
+ pinchScale.setValue(1);
172
+ sizeRef.current = next;
173
+ setSize(next);
174
+ }
175
+ }
176
+ if (area) {
177
+ const tile = PIP_TILE_SIZES[sizeRef.current];
178
+ const centerX =
179
+ restRef.current.x + gesture.dx + gesture.vx * FLICK_PROJECTION_MS + tile.width / 2;
180
+ const centerY =
181
+ restRef.current.y + gesture.dy + gesture.vy * FLICK_PROJECTION_MS + tile.height / 2;
182
+ cornerRef.current = {
183
+ right: centerX > area.width / 2,
184
+ bottom: centerY > area.height / 2,
185
+ };
186
+ }
187
+ settle(true);
188
+ },
189
+ onPanResponderTerminate: () => settle(true),
190
+ }).panHandlers,
191
+ [position, width, height, pinchScale, settle]
192
+ );
193
+
194
+ return {
195
+ /** Put on the area the tile moves within. */
196
+ onStageLayout,
197
+ panHandlers,
198
+ size,
199
+ /** Hidden until the stage is measured, so it never flashes at 0,0. */
200
+ style: {
201
+ position: 'absolute' as const,
202
+ left: 0,
203
+ top: 0,
204
+ width,
205
+ height,
206
+ opacity: stage ? 1 : 0,
207
+ transform: [
208
+ { translateX: position.x },
209
+ { translateY: position.y },
210
+ { scale: pinchScale },
211
+ ],
212
+ },
213
+ };
214
+ };
@@ -1,4 +1,4 @@
1
- import { View, Text, StyleSheet, Modal, TouchableOpacity, SafeAreaView } from 'react-native';
1
+ import { View, Text, StyleSheet, Modal, TouchableOpacity, SafeAreaView, ScrollView } from 'react-native';
2
2
  import {
3
3
  MediaContainer,
4
4
  MediaImage,
@@ -8,6 +8,7 @@ import { RemoveButton, RemoveButtonText } from './StyledComponents';
8
8
  import { FC, useState } from 'react';
9
9
  import { MediaFile } from '../../types/types';
10
10
  import { useVideoPlayer, VideoView } from 'expo-video';
11
+ import { useT } from '../../i18n/useT';
11
12
 
12
13
  interface MediaFilePreviewProps {
13
14
  filePreviews: MediaFile[];
@@ -62,42 +63,56 @@ export const MediaFilePreview: FC<MediaFilePreviewProps> = ({
62
63
  handleRemoveImage,
63
64
  }) => {
64
65
  const [previewUri, setPreviewUri] = useState<string | null>(null);
66
+ const t = useT();
65
67
 
68
+ // The tray holds up to ten files, so it scrolls sideways rather than wrap.
66
69
  return (
67
- <MediaContainer>
68
- {filePreviews.map((file, index) => {
69
- const isImage = file.type?.startsWith('image');
70
- const isVideo = file.type?.startsWith('video');
71
- return (
72
- <View key={`${file.name}_${index}`}>
73
- {isImage ? (
74
- <MediaImage source={{ uri: file.uri }} />
75
- ) : isVideo ? (
76
- <VideoThumb uri={file.uri} onPress={() => setPreviewUri(file.uri)} />
77
- ) : (
78
- <View style={{ width: 70, height: 70, borderRadius: 8 }}>
79
- <FileIcon width={70} height={70} />
80
- </View>
81
- )}
82
- <RemoveButton onPress={() => handleRemoveImage(index)}>
83
- <RemoveButtonText>&times;</RemoveButtonText>
84
- </RemoveButton>
85
- </View>
86
- );
87
- })}
70
+ <ScrollView
71
+ horizontal
72
+ showsHorizontalScrollIndicator={false}
73
+ keyboardShouldPersistTaps="handled"
74
+ testID="media-tray"
75
+ >
76
+ <MediaContainer>
77
+ {filePreviews.map((file, index) => {
78
+ const isImage = file.type?.startsWith('image');
79
+ const isVideo = file.type?.startsWith('video');
80
+ return (
81
+ <View key={`${file.name}_${index}`}>
82
+ {isImage ? (
83
+ <MediaImage source={{ uri: file.uri }} />
84
+ ) : isVideo ? (
85
+ <VideoThumb uri={file.uri} onPress={() => setPreviewUri(file.uri)} />
86
+ ) : (
87
+ <View style={{ width: 70, height: 70, borderRadius: 8 }}>
88
+ <FileIcon width={70} height={70} />
89
+ </View>
90
+ )}
91
+ <RemoveButton
92
+ testID={`media-tray-remove-${index}`}
93
+ accessibilityRole="button"
94
+ accessibilityLabel={t('attachment.remove')}
95
+ onPress={() => handleRemoveImage(index)}
96
+ >
97
+ <RemoveButtonText>&times;</RemoveButtonText>
98
+ </RemoveButton>
99
+ </View>
100
+ );
101
+ })}
88
102
 
89
- <Modal visible={!!previewUri} animationType="fade" transparent>
90
- <SafeAreaView style={styles.modalContainer}>
91
- <TouchableOpacity
92
- style={styles.closeButton}
93
- onPress={() => setPreviewUri(null)}
94
- >
95
- <Text style={styles.closeText}>✕</Text>
96
- </TouchableOpacity>
97
- {previewUri && <FullscreenPreviewVideo uri={previewUri} />}
98
- </SafeAreaView>
99
- </Modal>
100
- </MediaContainer>
103
+ <Modal visible={!!previewUri} animationType="fade" transparent>
104
+ <SafeAreaView style={styles.modalContainer}>
105
+ <TouchableOpacity
106
+ style={styles.closeButton}
107
+ onPress={() => setPreviewUri(null)}
108
+ >
109
+ <Text style={styles.closeText}>✕</Text>
110
+ </TouchableOpacity>
111
+ {previewUri && <FullscreenPreviewVideo uri={previewUri} />}
112
+ </SafeAreaView>
113
+ </Modal>
114
+ </MediaContainer>
115
+ </ScrollView>
101
116
  );
102
117
  };
103
118
 
@@ -36,6 +36,13 @@ interface CustomMessageImageProps {
36
36
  * route; rendering it inline is what left the bubbles blank.
37
37
  */
38
38
  locationPreview?: string;
39
+ /**
40
+ * Album mode: fill a tile of exactly this size (cover, square corners; the
41
+ * tile clips) instead of sizing itself to the picture.
42
+ */
43
+ tile?: { width: number; height: number };
44
+ /** Replaces the default "open this one file" tap, e.g. to open an album at an index. */
45
+ onOpen?: () => void;
39
46
  }
40
47
 
41
48
  /** Keep the personal file token out of the logs. */
@@ -51,6 +58,8 @@ const CustomMessageImage: React.FC<CustomMessageImageProps> = ({
51
58
  originalName,
52
59
  mimetype,
53
60
  locationPreview,
61
+ tile,
62
+ onOpen,
54
63
  }) => {
55
64
  // Preview first, original only as a fallback — mirrors the web client,
56
65
  // where the <img> src is the preview and `fileURL` is reserved for the
@@ -59,12 +68,13 @@ const CustomMessageImage: React.FC<CustomMessageImageProps> = ({
59
68
  const [loading, setLoading] = useState(true);
60
69
  // Start with a neutral box; swap to the real aspect ratio once the
61
70
  // image's natural dimensions are known so it's shown scaled, not cropped.
62
- const [dims, setDims] = useState<MediaDims>(defaultMediaDims());
71
+ const [measured, setDims] = useState<MediaDims>(defaultMediaDims());
72
+ const dims: MediaDims = tile ?? measured;
63
73
 
64
74
  const dispatch = useDispatch();
65
75
 
66
76
  useEffect(() => {
67
- if (!displayURL) {
77
+ if (!displayURL || tile) {
68
78
  return;
69
79
  }
70
80
  let active = true;
@@ -88,15 +98,21 @@ const CustomMessageImage: React.FC<CustomMessageImageProps> = ({
88
98
  return () => {
89
99
  active = false;
90
100
  };
91
- }, [displayURL]);
101
+ }, [displayURL, tile]);
92
102
 
93
103
  const handleOpen = () => {
104
+ if (onOpen) {
105
+ onOpen();
106
+ return;
107
+ }
94
108
  dispatch(setActiveFile({ fileName, fileURL, mimetype, originalName }));
95
109
  dispatch(setActiveModal(MODAL_TYPES.FILE_PREVIEW));
96
110
  };
97
111
 
98
112
  return (
99
- <Container style={{ width: dims.width }}>
113
+ <Container
114
+ style={tile ? { width: dims.width, borderRadius: 0 } : { width: dims.width }}
115
+ >
100
116
  <TouchableOpacity onPress={handleOpen} activeOpacity={0.9}>
101
117
  {loading && (
102
118
  <View
@@ -117,7 +133,7 @@ const CustomMessageImage: React.FC<CustomMessageImageProps> = ({
117
133
  style={{
118
134
  width: dims.width,
119
135
  height: dims.height,
120
- borderRadius: 10,
136
+ borderRadius: tile ? 0 : 10,
121
137
  backgroundColor: 'rgba(0, 0, 0, 0)',
122
138
  }}
123
139
  onLoadEnd={() => setLoading(false)}
@@ -17,6 +17,7 @@ import { getElementFont } from '../../helpers/getElementFont';
17
17
  import { useTheme } from '../../hooks/useTheme';
18
18
  import { useT } from '../../i18n/useT';
19
19
  import { getMediaLibrary } from '../../helpers/mediaLibraryRuntime';
20
+ import { mediaFileCap, mediaSizeLimit } from '../../helpers/mediaLimits';
20
21
  import * as ImagePicker from 'expo-image-picker';
21
22
  import * as DocumentPicker from 'expo-document-picker';
22
23
  import * as ImageManipulator from 'expo-image-manipulator';
@@ -44,14 +45,6 @@ import {
44
45
  // receivers draw it without downloading and decoding the clip first.
45
46
  const VOICE_PRESET = { ...RecordingPresets.HIGH_QUALITY, isMeteringEnabled: true };
46
47
 
47
- // iOS photos default to HEIC; many web backends (incl. ours) 500 on
48
- // HEIC uploads because they can't decode it. Convert to JPEG before
49
- // upload — JPEG is universally supported. JPEGs pass through.
50
- // Server rejects uploads over its body-size cap with HTTP 413 (Request
51
- // Entity Too Large). Guard client-side so the user gets a clear message
52
- // instead of a silent failed bubble. 50 MB matches the backend limit.
53
- const MAX_MEDIA_BYTES = 50 * 1024 * 1024;
54
-
55
48
  const formatBytes = (bytes: number): string => {
56
49
  if (bytes >= 1024 * 1024) {
57
50
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
@@ -89,7 +82,11 @@ interface SendInputProps {
89
82
  sendMessage: (message: string) => void;
90
83
  isLoading: boolean;
91
84
  editMessage?: string;
92
- sendMedia: (data: any, type: string) => void;
85
+ /**
86
+ * Sends one message carrying `data` (a file or the picked list). The typed
87
+ * text, when there is any, goes with it as the caption.
88
+ */
89
+ sendMedia: (data: any, type: string, caption?: { text: string }) => unknown;
93
90
  config?: IConfig;
94
91
  onFocus?: () => void;
95
92
  onBlur?: () => void;
@@ -164,25 +161,64 @@ const SendInput: React.FC<SendInputProps> = ({
164
161
  // "Document" row or a dev-mode double-run of the sheet's close callback.
165
162
  const pickingDocRef = useRef(false);
166
163
 
167
- const handleFileSelect = (files: MediaFile[]) => {
168
- // Enforce the upload size cap before anything reaches the network.
169
- // The backend rejects oversized bodies with HTTP 413; catching it
170
- // here gives a clear message instead of a silent failed bubble.
171
- const limit = maxMediaBytes ?? MAX_MEDIA_BYTES;
172
- const oversize = files.find(
173
- (f) => typeof f.size === 'number' && f.size > limit
174
- );
175
- if (oversize) {
176
- Alert.alert(
177
- 'File too large',
178
- `"${oversize.name}" is ${formatBytes(oversize.size!)}. The maximum allowed size is ${Math.round(
179
- limit / (1024 * 1024)
180
- )} MB.`
181
- );
182
- return;
164
+ /**
165
+ * Adds picked files to the tray. The tray is a list, not a slot: a second
166
+ * pick or a camera shot appends. Enforces the per-message file cap and the
167
+ * per-file size cap before anything reaches the network; the size cap is
168
+ * the upload limit (50 MB, or the sealed-file limit in an e2ee room),
169
+ * lowered by `config.attachments.maxSizeMB`. The backend rejects oversized
170
+ * bodies with HTTP 413, so catching them here gives a clear message.
171
+ */
172
+ const handleFileSelect = (incoming: MediaFile[]) => {
173
+ if (incoming.length === 0) {return;}
174
+ const limit = mediaSizeLimit(config, maxMediaBytes);
175
+ const fitting = incoming.filter((file) => {
176
+ if (typeof file.size === 'number' && file.size > limit) {
177
+ Alert.alert(
178
+ t('attachment.tooLarge', {
179
+ name: file.name,
180
+ max: formatBytes(limit),
181
+ })
182
+ );
183
+ return false;
184
+ }
185
+ return true;
186
+ });
187
+ const cap = mediaFileCap(config);
188
+ const room = cap - filePreviewsRef.current.length;
189
+ if (fitting.length === 0) {return;}
190
+ if (room <= 0 || fitting.length > room) {
191
+ Alert.alert(t('attachment.limit', { count: cap }));
183
192
  }
184
- filePreviewsRef.current = files;
185
- setFilePreviews([...files]);
193
+ const accepted = fitting.slice(0, Math.max(0, room));
194
+ if (accepted.length === 0) {return;}
195
+ const next = [...filePreviewsRef.current, ...accepted];
196
+ filePreviewsRef.current = next;
197
+ setFilePreviews(next);
198
+ };
199
+
200
+ /** One picked image or video as the file the tray and the upload carry. */
201
+ const assetToFile = async (
202
+ asset: ImagePicker.ImagePickerAsset,
203
+ fallbackPrefix: string
204
+ ): Promise<MediaFile> => {
205
+ if (asset.type === 'video') {
206
+ return {
207
+ uri: asset.uri,
208
+ type: asset.mimeType || 'video/mp4',
209
+ name: asset.fileName || asset.uri.split('/').pop() || `${fallbackPrefix}_${Date.now()}.mp4`,
210
+ size: (asset as any).fileSize,
211
+ };
212
+ }
213
+ // Videos pass through unchanged; only images get the HEIC→JPEG
214
+ // normalization (manipulator is image-only).
215
+ const normalized = await normalizeImageAsset(asset);
216
+ return {
217
+ uri: normalized.uri,
218
+ type: normalized.mime,
219
+ name: normalized.name,
220
+ size: (asset as any).fileSize,
221
+ };
186
222
  };
187
223
 
188
224
  // Both pickers use expo-image-picker / expo-document-picker. They
@@ -205,33 +241,14 @@ const SendInput: React.FC<SendInputProps> = ({
205
241
  }
206
242
 
207
243
  try {
244
+ // Camera is a single shot per tap; the shot joins the tray.
208
245
  const result = await ImagePicker.launchCameraAsync({
209
246
  mediaTypes: ['images', 'videos'],
210
247
  quality: 0.9,
211
248
  videoQuality: ImagePicker.UIImagePickerControllerQualityType.Medium,
212
249
  });
213
250
  if (result.canceled || !result.assets?.[0]) {return;}
214
- const asset = result.assets[0];
215
- if (asset.type === 'video') {
216
- handleFileSelect([
217
- {
218
- uri: asset.uri,
219
- type: asset.mimeType || 'video/mp4',
220
- name: asset.fileName || asset.uri.split('/').pop() || `camera_${Date.now()}.mp4`,
221
- size: (asset as any).fileSize,
222
- },
223
- ]);
224
- return;
225
- }
226
- const normalized = await normalizeImageAsset(asset);
227
- handleFileSelect([
228
- {
229
- uri: normalized.uri,
230
- type: normalized.mime,
231
- name: normalized.name,
232
- size: (asset as any).fileSize,
233
- },
234
- ]);
251
+ handleFileSelect([await assetToFile(result.assets[0], 'camera')]);
235
252
  } catch (error) {
236
253
  console.error('Camera error:', error);
237
254
  }
@@ -244,38 +261,29 @@ const SendInput: React.FC<SendInputProps> = ({
244
261
  return;
245
262
  }
246
263
 
264
+ // The library takes as many as the tray has room for (at least one).
265
+ const remaining = mediaFileCap(config) - filePreviewsRef.current.length;
266
+ if (remaining <= 0) {
267
+ Alert.alert(t('attachment.limit', { count: mediaFileCap(config) }));
268
+ return;
269
+ }
270
+
247
271
  try {
248
272
  const result = await ImagePicker.launchImageLibraryAsync({
249
273
  mediaTypes: ['images', 'videos'],
250
274
  allowsEditing: false,
251
275
  quality: 0.9,
276
+ allowsMultipleSelection: true,
277
+ selectionLimit: remaining,
252
278
  preferredAssetRepresentationMode:
253
279
  ImagePicker.UIImagePickerPreferredAssetRepresentationMode.Compatible,
254
280
  });
255
- if (result.canceled || !result.assets?.[0]) {return;}
256
- const asset = result.assets[0];
257
- // Videos pass through unchanged; only images get the HEIC→JPEG
258
- // normalization (manipulator is image-only).
259
- if (asset.type === 'video') {
260
- handleFileSelect([
261
- {
262
- uri: asset.uri,
263
- type: asset.mimeType || 'video/mp4',
264
- name: asset.fileName || asset.uri.split('/').pop() || `gallery_${Date.now()}.mp4`,
265
- size: (asset as any).fileSize,
266
- },
267
- ]);
268
- return;
281
+ if (result.canceled || !result.assets?.length) {return;}
282
+ const picked: MediaFile[] = [];
283
+ for (const asset of result.assets) {
284
+ picked.push(await assetToFile(asset, 'gallery'));
269
285
  }
270
- const normalized = await normalizeImageAsset(asset);
271
- handleFileSelect([
272
- {
273
- uri: normalized.uri,
274
- type: normalized.mime,
275
- name: normalized.name,
276
- size: (asset as any).fileSize,
277
- },
278
- ]);
286
+ handleFileSelect(picked);
279
287
  } catch (error) {
280
288
  console.error('Gallery error:', error);
281
289
  }
@@ -336,19 +344,18 @@ const SendInput: React.FC<SendInputProps> = ({
336
344
  try {
337
345
  const result = await DocumentPicker.getDocumentAsync({
338
346
  type: '*/*',
339
- multiple: false,
347
+ multiple: true,
340
348
  copyToCacheDirectory: true,
341
349
  });
342
- if (result.canceled || !result.assets?.[0]) {return;}
343
- const asset = result.assets[0];
344
- handleFileSelect([
345
- {
350
+ if (result.canceled || !result.assets?.length) {return;}
351
+ handleFileSelect(
352
+ result.assets.map((asset) => ({
346
353
  uri: asset.uri,
347
354
  type: asset.mimeType || 'application/octet-stream',
348
355
  name: asset.name || `file_${Date.now()}`,
349
356
  size: asset.size ?? undefined,
350
- },
351
- ]);
357
+ }))
358
+ );
352
359
  } catch (err) {
353
360
  console.error('DocumentPicker error:', err);
354
361
  } finally {
@@ -383,6 +390,11 @@ const SendInput: React.FC<SendInputProps> = ({
383
390
  const libraryPermissionCheckedRef = useRef(false);
384
391
 
385
392
  const handleAttachPress = () => {
393
+ const cap = mediaFileCap(config);
394
+ if (filePreviewsRef.current.length >= cap) {
395
+ Alert.alert(t('attachment.limit', { count: cap }));
396
+ return;
397
+ }
386
398
  if (libraryPermissionCheckedRef.current) {
387
399
  setShowMediaMenu(true);
388
400
  return;
@@ -594,17 +606,19 @@ const SendInput: React.FC<SendInputProps> = ({
594
606
  setFilePreviews([]);
595
607
 
596
608
  try {
597
- for (const file of filesToSend) {
598
- try {
599
- await sendMedia(file, file.type);
600
- } catch (err) {
601
- console.error(err);
602
- return;
603
- }
604
- }
605
- if (messageToSend) {
609
+ if (filesToSend.length > 0) {
610
+ // The files go as ONE message. The text typed with them is its
611
+ // caption, never a separate text message.
612
+ await sendMedia(
613
+ filesToSend,
614
+ filesToSend[0]!.type,
615
+ messageToSend ? { text: messageToSend } : undefined
616
+ );
617
+ } else if (messageToSend) {
606
618
  sendMessage(messageToSend);
607
619
  }
620
+ } catch (err) {
621
+ console.error(err);
608
622
  } finally {
609
623
  sendingRef.current = false;
610
624
  }