@ethora/chat-component-rn 26.10.5 → 26.10.7

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 (426) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +18 -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/styled/MediaFilePreview.js +60 -47
  35. package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
  36. package/lib/commonjs/components/styled/MessageImage.js +16 -6
  37. package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
  38. package/lib/commonjs/components/styled/SendInput.js +99 -68
  39. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  40. package/lib/commonjs/components/styled/VideoMessage.js +24 -10
  41. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  42. package/lib/commonjs/context/xmppProvider.js +46 -0
  43. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  44. package/lib/commonjs/e2ee/fileEnvelope.js +6 -0
  45. package/lib/commonjs/e2ee/fileEnvelope.js.map +1 -1
  46. package/lib/commonjs/e2ee/sealedBody.js +32 -2
  47. package/lib/commonjs/e2ee/sealedBody.js.map +1 -1
  48. package/lib/commonjs/helpers/albumLayout.js +150 -0
  49. package/lib/commonjs/helpers/albumLayout.js.map +1 -0
  50. package/lib/commonjs/helpers/attachments.js +255 -0
  51. package/lib/commonjs/helpers/attachments.js.map +1 -0
  52. package/lib/commonjs/helpers/caption.js +214 -0
  53. package/lib/commonjs/helpers/caption.js.map +1 -0
  54. package/lib/commonjs/helpers/createMainMessageForThread.js +5 -1
  55. package/lib/commonjs/helpers/createMainMessageForThread.js.map +1 -1
  56. package/lib/commonjs/helpers/createMessageFromXml.js +19 -5
  57. package/lib/commonjs/helpers/createMessageFromXml.js.map +1 -1
  58. package/lib/commonjs/helpers/fileKind.js +40 -0
  59. package/lib/commonjs/helpers/fileKind.js.map +1 -0
  60. package/lib/commonjs/helpers/getDataFromXml.js +6 -1
  61. package/lib/commonjs/helpers/getDataFromXml.js.map +1 -1
  62. package/lib/commonjs/helpers/mapWithConcurrency.js +41 -0
  63. package/lib/commonjs/helpers/mapWithConcurrency.js.map +1 -0
  64. package/lib/commonjs/helpers/mediaLimits.js +33 -0
  65. package/lib/commonjs/helpers/mediaLimits.js.map +1 -0
  66. package/lib/commonjs/helpers/mediaSendRetry.js +64 -0
  67. package/lib/commonjs/helpers/mediaSendRetry.js.map +1 -0
  68. package/lib/commonjs/helpers/mediaWireData.js +62 -0
  69. package/lib/commonjs/helpers/mediaWireData.js.map +1 -0
  70. package/lib/commonjs/helpers/mentionSpans.js +19 -0
  71. package/lib/commonjs/helpers/mentionSpans.js.map +1 -0
  72. package/lib/commonjs/helpers/mimeToExtension.js +45 -0
  73. package/lib/commonjs/helpers/mimeToExtension.js.map +1 -1
  74. package/lib/commonjs/helpers/readReceipts.js +173 -0
  75. package/lib/commonjs/helpers/readReceipts.js.map +1 -0
  76. package/lib/commonjs/helpers/repairFilenameEncoding.js +79 -0
  77. package/lib/commonjs/helpers/repairFilenameEncoding.js.map +1 -0
  78. package/lib/commonjs/helpers/safeJson.js +18 -0
  79. package/lib/commonjs/helpers/safeJson.js.map +1 -0
  80. package/lib/commonjs/helpers/secureStoreRuntime.js +10 -1
  81. package/lib/commonjs/helpers/secureStoreRuntime.js.map +1 -1
  82. package/lib/commonjs/hooks/useSendMessage.js +384 -193
  83. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  84. package/lib/commonjs/i18n/strings.js +96 -0
  85. package/lib/commonjs/i18n/strings.js.map +1 -1
  86. package/lib/commonjs/main.js +81 -0
  87. package/lib/commonjs/main.js.map +1 -1
  88. package/lib/commonjs/networking/api-requests/auth.api.js +62 -3
  89. package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
  90. package/lib/commonjs/networking/api-requests/push.api.js +22 -7
  91. package/lib/commonjs/networking/api-requests/push.api.js.map +1 -1
  92. package/lib/commonjs/networking/stanzaHandlers.js +100 -3
  93. package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
  94. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +4 -2
  95. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  96. package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js +28 -0
  97. package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
  98. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
  99. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  100. package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js +28 -0
  101. package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
  102. package/lib/commonjs/networking/xmppClient.js +64 -0
  103. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  104. package/lib/commonjs/push/pushKeyStore.js +169 -0
  105. package/lib/commonjs/push/pushKeyStore.js.map +1 -0
  106. package/lib/commonjs/push/sealedBox.js +106 -0
  107. package/lib/commonjs/push/sealedBox.js.map +1 -0
  108. package/lib/commonjs/push/sealedPush.js +124 -0
  109. package/lib/commonjs/push/sealedPush.js.map +1 -0
  110. package/lib/commonjs/push/sealedPushTask.js +195 -0
  111. package/lib/commonjs/push/sealedPushTask.js.map +1 -0
  112. package/lib/commonjs/roomStore/persistence.js +78 -5
  113. package/lib/commonjs/roomStore/persistence.js.map +1 -1
  114. package/lib/commonjs/roomStore/roomHeapSlice.js +4 -0
  115. package/lib/commonjs/roomStore/roomHeapSlice.js.map +1 -1
  116. package/lib/commonjs/roomStore/roomsSlice.js +114 -1
  117. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  118. package/lib/commonjs/services/pushRegistration.js +12 -7
  119. package/lib/commonjs/services/pushRegistration.js.map +1 -1
  120. package/lib/commonjs/types/models/attachment.model.js +2 -0
  121. package/lib/commonjs/types/models/attachment.model.js.map +1 -0
  122. package/lib/commonjs/version.js +1 -1
  123. package/lib/module/assets/icons.js +1 -1
  124. package/lib/module/assets/icons.js.map +1 -1
  125. package/lib/module/components/MainComponents/AlbumGrid.js +146 -0
  126. package/lib/module/components/MainComponents/AlbumGrid.js.map +1 -0
  127. package/lib/module/components/MainComponents/ChatRoom.js +2 -2
  128. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  129. package/lib/module/components/MainComponents/MediaMessage.js +200 -124
  130. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  131. package/lib/module/components/MainComponents/MediaUploadOverlay.js +104 -0
  132. package/lib/module/components/MainComponents/MediaUploadOverlay.js.map +1 -0
  133. package/lib/module/components/MessageBubble/MediaCaption.js +46 -0
  134. package/lib/module/components/MessageBubble/MediaCaption.js.map +1 -0
  135. package/lib/module/components/MessageBubble/Message.js +44 -5
  136. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  137. package/lib/module/components/MessageBubble/MessageInteractions.js +2 -1
  138. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  139. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +75 -0
  140. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  141. package/lib/module/components/RoomComponents/LastMessageItem.js +20 -4
  142. package/lib/module/components/RoomComponents/LastMessageItem.js.map +1 -1
  143. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js +3 -2
  144. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
  145. package/lib/module/components/RoomComponents/styled/LastMessageFile.js +3 -2
  146. package/lib/module/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
  147. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js +3 -2
  148. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
  149. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js +3 -2
  150. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
  151. package/lib/module/components/Thread/ThreadWrapper.js +3 -2
  152. package/lib/module/components/Thread/ThreadWrapper.js.map +1 -1
  153. package/lib/module/components/styled/MediaFilePreview.js +61 -48
  154. package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
  155. package/lib/module/components/styled/MessageImage.js +16 -6
  156. package/lib/module/components/styled/MessageImage.js.map +1 -1
  157. package/lib/module/components/styled/SendInput.js +99 -68
  158. package/lib/module/components/styled/SendInput.js.map +1 -1
  159. package/lib/module/components/styled/VideoMessage.js +24 -10
  160. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  161. package/lib/module/context/xmppProvider.js +46 -0
  162. package/lib/module/context/xmppProvider.js.map +1 -1
  163. package/lib/module/e2ee/fileEnvelope.js +1 -1
  164. package/lib/module/e2ee/fileEnvelope.js.map +1 -1
  165. package/lib/module/e2ee/sealedBody.js +32 -2
  166. package/lib/module/e2ee/sealedBody.js.map +1 -1
  167. package/lib/module/helpers/albumLayout.js +143 -0
  168. package/lib/module/helpers/albumLayout.js.map +1 -0
  169. package/lib/module/helpers/attachments.js +245 -0
  170. package/lib/module/helpers/attachments.js.map +1 -0
  171. package/lib/module/helpers/caption.js +207 -0
  172. package/lib/module/helpers/caption.js.map +1 -0
  173. package/lib/module/helpers/createMainMessageForThread.js +5 -1
  174. package/lib/module/helpers/createMainMessageForThread.js.map +1 -1
  175. package/lib/module/helpers/createMessageFromXml.js +20 -6
  176. package/lib/module/helpers/createMessageFromXml.js.map +1 -1
  177. package/lib/module/helpers/fileKind.js +34 -0
  178. package/lib/module/helpers/fileKind.js.map +1 -0
  179. package/lib/module/helpers/getDataFromXml.js +6 -1
  180. package/lib/module/helpers/getDataFromXml.js.map +1 -1
  181. package/lib/module/helpers/mapWithConcurrency.js +37 -0
  182. package/lib/module/helpers/mapWithConcurrency.js.map +1 -0
  183. package/lib/module/helpers/mediaLimits.js +27 -0
  184. package/lib/module/helpers/mediaLimits.js.map +1 -0
  185. package/lib/module/helpers/mediaSendRetry.js +55 -0
  186. package/lib/module/helpers/mediaSendRetry.js.map +1 -0
  187. package/lib/module/helpers/mediaWireData.js +58 -0
  188. package/lib/module/helpers/mediaWireData.js.map +1 -0
  189. package/lib/module/helpers/mentionSpans.js +14 -0
  190. package/lib/module/helpers/mentionSpans.js.map +1 -0
  191. package/lib/module/helpers/mimeToExtension.js +43 -0
  192. package/lib/module/helpers/mimeToExtension.js.map +1 -1
  193. package/lib/module/helpers/readReceipts.js +160 -0
  194. package/lib/module/helpers/readReceipts.js.map +1 -0
  195. package/lib/module/helpers/repairFilenameEncoding.js +74 -0
  196. package/lib/module/helpers/repairFilenameEncoding.js.map +1 -0
  197. package/lib/module/helpers/safeJson.js +13 -0
  198. package/lib/module/helpers/safeJson.js.map +1 -0
  199. package/lib/module/helpers/secureStoreRuntime.js +10 -1
  200. package/lib/module/helpers/secureStoreRuntime.js.map +1 -1
  201. package/lib/module/hooks/useSendMessage.js +387 -195
  202. package/lib/module/hooks/useSendMessage.js.map +1 -1
  203. package/lib/module/i18n/strings.js +96 -0
  204. package/lib/module/i18n/strings.js.map +1 -1
  205. package/lib/module/main.js +6 -0
  206. package/lib/module/main.js.map +1 -1
  207. package/lib/module/networking/api-requests/auth.api.js +62 -3
  208. package/lib/module/networking/api-requests/auth.api.js.map +1 -1
  209. package/lib/module/networking/api-requests/push.api.js +21 -6
  210. package/lib/module/networking/api-requests/push.api.js.map +1 -1
  211. package/lib/module/networking/stanzaHandlers.js +97 -4
  212. package/lib/module/networking/stanzaHandlers.js.map +1 -1
  213. package/lib/module/networking/xmpp/handleStanzas.xmpp.js +5 -3
  214. package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  215. package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js +25 -0
  216. package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
  217. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
  218. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  219. package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js +25 -0
  220. package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
  221. package/lib/module/networking/xmppClient.js +64 -0
  222. package/lib/module/networking/xmppClient.js.map +1 -1
  223. package/lib/module/push/pushKeyStore.js +159 -0
  224. package/lib/module/push/pushKeyStore.js.map +1 -0
  225. package/lib/module/push/sealedBox.js +97 -0
  226. package/lib/module/push/sealedBox.js.map +1 -0
  227. package/lib/module/push/sealedPush.js +117 -0
  228. package/lib/module/push/sealedPush.js.map +1 -0
  229. package/lib/module/push/sealedPushTask.js +187 -0
  230. package/lib/module/push/sealedPushTask.js.map +1 -0
  231. package/lib/module/roomStore/persistence.js +78 -5
  232. package/lib/module/roomStore/persistence.js.map +1 -1
  233. package/lib/module/roomStore/roomHeapSlice.js +4 -0
  234. package/lib/module/roomStore/roomHeapSlice.js.map +1 -1
  235. package/lib/module/roomStore/roomsSlice.js +109 -0
  236. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  237. package/lib/module/services/pushRegistration.js +12 -7
  238. package/lib/module/services/pushRegistration.js.map +1 -1
  239. package/lib/module/types/models/attachment.model.js +2 -0
  240. package/lib/module/types/models/attachment.model.js.map +1 -0
  241. package/lib/module/version.js +1 -1
  242. package/lib/typescript/assets/icons.d.ts +6 -1
  243. package/lib/typescript/assets/icons.d.ts.map +1 -1
  244. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts +21 -0
  245. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts.map +1 -0
  246. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  247. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  248. package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts +17 -0
  249. package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts.map +1 -0
  250. package/lib/typescript/components/MessageBubble/MediaCaption.d.ts +12 -0
  251. package/lib/typescript/components/MessageBubble/MediaCaption.d.ts.map +1 -0
  252. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  253. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  254. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  255. package/lib/typescript/components/RoomComponents/LastMessageItem.d.ts.map +1 -1
  256. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts +2 -0
  257. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts.map +1 -1
  258. package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts +2 -0
  259. package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts.map +1 -1
  260. package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts +2 -0
  261. package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts.map +1 -1
  262. package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts +2 -0
  263. package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts.map +1 -1
  264. package/lib/typescript/components/Thread/ThreadWrapper.d.ts.map +1 -1
  265. package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
  266. package/lib/typescript/components/styled/MessageImage.d.ts +10 -0
  267. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  268. package/lib/typescript/components/styled/SendInput.d.ts +7 -1
  269. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  270. package/lib/typescript/components/styled/VideoMessage.d.ts +7 -0
  271. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  272. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  273. package/lib/typescript/e2ee/fileEnvelope.d.ts +1 -1
  274. package/lib/typescript/e2ee/fileEnvelope.d.ts.map +1 -1
  275. package/lib/typescript/e2ee/sealedBody.d.ts +16 -0
  276. package/lib/typescript/e2ee/sealedBody.d.ts.map +1 -1
  277. package/lib/typescript/helpers/albumLayout.d.ts +66 -0
  278. package/lib/typescript/helpers/albumLayout.d.ts.map +1 -0
  279. package/lib/typescript/helpers/attachments.d.ts +55 -0
  280. package/lib/typescript/helpers/attachments.d.ts.map +1 -0
  281. package/lib/typescript/helpers/caption.d.ts +64 -0
  282. package/lib/typescript/helpers/caption.d.ts.map +1 -0
  283. package/lib/typescript/helpers/createMainMessageForThread.d.ts.map +1 -1
  284. package/lib/typescript/helpers/createMessageFromXml.d.ts.map +1 -1
  285. package/lib/typescript/helpers/fileKind.d.ts +20 -0
  286. package/lib/typescript/helpers/fileKind.d.ts.map +1 -0
  287. package/lib/typescript/helpers/getDataFromXml.d.ts.map +1 -1
  288. package/lib/typescript/helpers/mapWithConcurrency.d.ts +9 -0
  289. package/lib/typescript/helpers/mapWithConcurrency.d.ts.map +1 -0
  290. package/lib/typescript/helpers/mediaLimits.d.ts +13 -0
  291. package/lib/typescript/helpers/mediaLimits.d.ts.map +1 -0
  292. package/lib/typescript/helpers/mediaSendRetry.d.ts +26 -0
  293. package/lib/typescript/helpers/mediaSendRetry.d.ts.map +1 -0
  294. package/lib/typescript/helpers/mediaWireData.d.ts +16 -0
  295. package/lib/typescript/helpers/mediaWireData.d.ts.map +1 -0
  296. package/lib/typescript/helpers/mentionSpans.d.ts +8 -0
  297. package/lib/typescript/helpers/mentionSpans.d.ts.map +1 -0
  298. package/lib/typescript/helpers/mimeToExtension.d.ts +7 -0
  299. package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -1
  300. package/lib/typescript/helpers/readReceipts.d.ts +82 -0
  301. package/lib/typescript/helpers/readReceipts.d.ts.map +1 -0
  302. package/lib/typescript/helpers/repairFilenameEncoding.d.ts +12 -0
  303. package/lib/typescript/helpers/repairFilenameEncoding.d.ts.map +1 -0
  304. package/lib/typescript/helpers/safeJson.d.ts +2 -0
  305. package/lib/typescript/helpers/safeJson.d.ts.map +1 -0
  306. package/lib/typescript/helpers/secureStoreRuntime.d.ts +17 -3
  307. package/lib/typescript/helpers/secureStoreRuntime.d.ts.map +1 -1
  308. package/lib/typescript/hooks/useSendMessage.d.ts +2 -1
  309. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  310. package/lib/typescript/i18n/strings.d.ts.map +1 -1
  311. package/lib/typescript/main.d.ts +6 -0
  312. package/lib/typescript/main.d.ts.map +1 -1
  313. package/lib/typescript/networking/api-requests/auth.api.d.ts +2 -0
  314. package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
  315. package/lib/typescript/networking/api-requests/push.api.d.ts +14 -2
  316. package/lib/typescript/networking/api-requests/push.api.d.ts.map +1 -1
  317. package/lib/typescript/networking/stanzaHandlers.d.ts +7 -3
  318. package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
  319. package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts.map +1 -1
  320. package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts +3 -0
  321. package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts.map +1 -0
  322. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts +12 -0
  323. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts.map +1 -1
  324. package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts +3 -0
  325. package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts.map +1 -0
  326. package/lib/typescript/networking/xmppClient.d.ts +15 -0
  327. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  328. package/lib/typescript/push/pushKeyStore.d.ts +35 -0
  329. package/lib/typescript/push/pushKeyStore.d.ts.map +1 -0
  330. package/lib/typescript/push/sealedBox.d.ts +24 -0
  331. package/lib/typescript/push/sealedBox.d.ts.map +1 -0
  332. package/lib/typescript/push/sealedPush.d.ts +61 -0
  333. package/lib/typescript/push/sealedPush.d.ts.map +1 -0
  334. package/lib/typescript/push/sealedPushTask.d.ts +33 -0
  335. package/lib/typescript/push/sealedPushTask.d.ts.map +1 -0
  336. package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
  337. package/lib/typescript/roomStore/roomHeapSlice.d.ts +22 -1
  338. package/lib/typescript/roomStore/roomHeapSlice.d.ts.map +1 -1
  339. package/lib/typescript/roomStore/roomsSlice.d.ts +67 -3
  340. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  341. package/lib/typescript/services/pushRegistration.d.ts +8 -0
  342. package/lib/typescript/services/pushRegistration.d.ts.map +1 -1
  343. package/lib/typescript/types/models/attachment.model.d.ts +52 -0
  344. package/lib/typescript/types/models/attachment.model.d.ts.map +1 -0
  345. package/lib/typescript/types/models/message.model.d.ts +26 -0
  346. package/lib/typescript/types/models/message.model.d.ts.map +1 -1
  347. package/lib/typescript/types/models/modal.model.d.ts +7 -0
  348. package/lib/typescript/types/models/modal.model.d.ts.map +1 -1
  349. package/lib/typescript/types/models/room.model.d.ts +2 -0
  350. package/lib/typescript/types/models/room.model.d.ts.map +1 -1
  351. package/lib/typescript/types/types.d.ts +35 -1
  352. package/lib/typescript/types/types.d.ts.map +1 -1
  353. package/lib/typescript/version.d.ts +1 -1
  354. package/package.json +14 -1
  355. package/plugins/notification-service/Info.plist +31 -0
  356. package/plugins/notification-service/NotificationService.swift +195 -0
  357. package/plugins/notification-service/SealedBox.swift +355 -0
  358. package/plugins/notification-service/check/gen-vectors.mjs +24 -0
  359. package/plugins/notification-service/check/main.swift +37 -0
  360. package/plugins/notification-service/check/run.sh +12 -0
  361. package/plugins/notification-service/check/vectors.json +1 -0
  362. package/plugins/withNotificationServiceExtension.js +189 -0
  363. package/src/assets/icons.tsx +20 -2
  364. package/src/components/MainComponents/AlbumGrid.tsx +183 -0
  365. package/src/components/MainComponents/ChatRoom.tsx +4 -2
  366. package/src/components/MainComponents/MediaMessage.tsx +246 -130
  367. package/src/components/MainComponents/MediaUploadOverlay.tsx +102 -0
  368. package/src/components/MessageBubble/MediaCaption.tsx +45 -0
  369. package/src/components/MessageBubble/Message.tsx +71 -3
  370. package/src/components/MessageBubble/MessageInteractions.tsx +2 -1
  371. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +64 -0
  372. package/src/components/RoomComponents/LastMessageItem.tsx +15 -4
  373. package/src/components/RoomComponents/styled/LastAudioMessage.tsx +6 -3
  374. package/src/components/RoomComponents/styled/LastMessageFile.tsx +6 -2
  375. package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +6 -2
  376. package/src/components/RoomComponents/styled/LastMessageVideo.tsx +6 -2
  377. package/src/components/Thread/ThreadWrapper.tsx +5 -9
  378. package/src/components/styled/MediaFilePreview.tsx +49 -34
  379. package/src/components/styled/MessageImage.tsx +21 -5
  380. package/src/components/styled/SendInput.tsx +101 -87
  381. package/src/components/styled/VideoMessage.tsx +34 -9
  382. package/src/context/xmppProvider.tsx +32 -0
  383. package/src/e2ee/fileEnvelope.ts +1 -1
  384. package/src/e2ee/sealedBody.ts +44 -5
  385. package/src/helpers/albumLayout.ts +207 -0
  386. package/src/helpers/attachments.ts +316 -0
  387. package/src/helpers/caption.ts +283 -0
  388. package/src/helpers/createMainMessageForThread.ts +6 -1
  389. package/src/helpers/createMessageFromXml.ts +21 -5
  390. package/src/helpers/fileKind.ts +38 -0
  391. package/src/helpers/getDataFromXml.ts +5 -1
  392. package/src/helpers/mapWithConcurrency.ts +32 -0
  393. package/src/helpers/mediaLimits.ts +27 -0
  394. package/src/helpers/mediaSendRetry.ts +59 -0
  395. package/src/helpers/mediaWireData.ts +57 -0
  396. package/src/helpers/mentionSpans.ts +17 -0
  397. package/src/helpers/mimeToExtension.ts +51 -0
  398. package/src/helpers/readReceipts.ts +219 -0
  399. package/src/helpers/repairFilenameEncoding.ts +82 -0
  400. package/src/helpers/safeJson.ts +11 -0
  401. package/src/helpers/secureStoreRuntime.ts +21 -3
  402. package/src/hooks/useSendMessage.tsx +448 -249
  403. package/src/i18n/strings.ts +96 -0
  404. package/src/main.ts +30 -0
  405. package/src/networking/api-requests/auth.api.ts +60 -3
  406. package/src/networking/api-requests/push.api.ts +29 -4
  407. package/src/networking/stanzaHandlers.ts +91 -2
  408. package/src/networking/xmpp/handleStanzas.xmpp.ts +6 -2
  409. package/src/networking/xmpp/sendDisplayedMarker.xmpp.ts +31 -0
  410. package/src/networking/xmpp/sendMediaMessage.xmpp.ts +60 -3
  411. package/src/networking/xmpp/sendReceivedReceipt.xmpp.ts +31 -0
  412. package/src/networking/xmppClient.ts +48 -0
  413. package/src/push/pushKeyStore.ts +163 -0
  414. package/src/push/sealedBox.ts +104 -0
  415. package/src/push/sealedPush.ts +127 -0
  416. package/src/push/sealedPushTask.ts +175 -0
  417. package/src/roomStore/persistence.ts +94 -3
  418. package/src/roomStore/roomHeapSlice.ts +23 -1
  419. package/src/roomStore/roomsSlice.ts +112 -1
  420. package/src/services/pushRegistration.ts +25 -5
  421. package/src/types/models/attachment.model.ts +51 -0
  422. package/src/types/models/message.model.ts +29 -0
  423. package/src/types/models/modal.model.ts +7 -0
  424. package/src/types/models/room.model.ts +2 -0
  425. package/src/types/types.ts +41 -1
  426. package/src/version.ts +1 -1
@@ -0,0 +1,102 @@
1
+ import React from 'react';
2
+ import { ActivityIndicator, Pressable, StyleSheet, Text, View } from 'react-native';
3
+ import { useT } from '../../i18n/useT';
4
+
5
+ interface MediaUploadOverlayProps {
6
+ /** `uploading` shows progress; `failed` shows the reason and a Retry. */
7
+ status: 'uploading' | 'failed';
8
+ /** 0 to 100, while uploading. */
9
+ progress?: number;
10
+ /** Re-runs this file's upload. Only offered for a failed file. */
11
+ onRetry?: () => void;
12
+ }
13
+
14
+ /**
15
+ * Covers one tile of an outgoing media message while its file uploads, and
16
+ * offers Retry when it did not. The scrim sits on the picture itself, so its
17
+ * colours are fixed rather than taken from the theme.
18
+ */
19
+ const MediaUploadOverlay: React.FC<MediaUploadOverlayProps> = ({
20
+ status,
21
+ progress = 0,
22
+ onRetry,
23
+ }) => {
24
+ const t = useT();
25
+ const percent = Math.min(100, Math.max(0, Math.round(progress)));
26
+
27
+ if (status === 'failed') {
28
+ return (
29
+ <View testID="media-upload-overlay" style={styles.overlay}>
30
+ <Text style={styles.failedText}>{t('attachment.uploadFailed')}</Text>
31
+ {onRetry && (
32
+ <Pressable
33
+ testID="media-upload-retry"
34
+ accessibilityRole="button"
35
+ accessibilityLabel={t('attachment.retryUpload')}
36
+ onPress={onRetry}
37
+ style={styles.retry}
38
+ >
39
+ <Text style={styles.retryText}>{t('attachment.retryUpload')}</Text>
40
+ </Pressable>
41
+ )}
42
+ </View>
43
+ );
44
+ }
45
+
46
+ return (
47
+ <View testID="media-upload-overlay" style={styles.overlay}>
48
+ <ActivityIndicator size="small" color="#FFFFFF" />
49
+ <Text style={styles.progressText}>{`${percent}%`}</Text>
50
+ <View style={styles.track} accessibilityRole="progressbar">
51
+ <View style={[styles.fill, { width: `${percent}%` }]} />
52
+ </View>
53
+ </View>
54
+ );
55
+ };
56
+
57
+ export default MediaUploadOverlay;
58
+
59
+ const styles = StyleSheet.create({
60
+ overlay: {
61
+ ...StyleSheet.absoluteFill,
62
+ alignItems: 'center',
63
+ justifyContent: 'center',
64
+ gap: 6,
65
+ padding: 8,
66
+ backgroundColor: 'rgba(8, 12, 24, 0.5)',
67
+ },
68
+ progressText: {
69
+ color: '#FFFFFF',
70
+ fontSize: 13,
71
+ fontWeight: '600',
72
+ },
73
+ track: {
74
+ position: 'absolute',
75
+ left: 0,
76
+ right: 0,
77
+ bottom: 0,
78
+ height: 3,
79
+ backgroundColor: 'rgba(255, 255, 255, 0.3)',
80
+ },
81
+ fill: {
82
+ height: 3,
83
+ backgroundColor: '#FFFFFF',
84
+ },
85
+ failedText: {
86
+ color: '#FFFFFF',
87
+ fontSize: 13,
88
+ fontWeight: '600',
89
+ textAlign: 'center',
90
+ },
91
+ retry: {
92
+ paddingHorizontal: 12,
93
+ paddingVertical: 5,
94
+ borderRadius: 14,
95
+ backgroundColor: '#FFFFFF',
96
+ },
97
+ retryText: {
98
+ color: '#111827',
99
+ fontSize: 13,
100
+ fontWeight: '700',
101
+ },
102
+ });
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { StyleSheet, Text } from 'react-native';
3
+ import { useChatSettingState } from '../../hooks/useChatSettingState';
4
+ import { useTheme } from '../../hooks/useTheme';
5
+
6
+ interface MediaCaptionProps {
7
+ text?: string;
8
+ isUser: boolean;
9
+ }
10
+
11
+ /**
12
+ * The text sent with a media message, under the media inside the bubble.
13
+ * Plain text: mention spans are stored on the message but not rendered here.
14
+ */
15
+ const MediaCaption: React.FC<MediaCaptionProps> = ({ text, isUser }) => {
16
+ const theme = useTheme();
17
+ const { config } = useChatSettingState();
18
+ if (!text) {return null;}
19
+ const typography = config?.typography?.messageText;
20
+ return (
21
+ <Text
22
+ testID="media-caption"
23
+ style={[
24
+ styles.caption,
25
+ { color: isUser ? theme.messageTextUser : theme.messageText },
26
+ typography?.fontSize ? { fontSize: typography.fontSize } : null,
27
+ typography?.fontWeight ? { fontWeight: typography.fontWeight as any } : null,
28
+ ]}
29
+ >
30
+ {text}
31
+ </Text>
32
+ );
33
+ };
34
+
35
+ export default MediaCaption;
36
+
37
+ const styles = StyleSheet.create({
38
+ caption: {
39
+ fontSize: 16,
40
+ paddingHorizontal: 12,
41
+ paddingTop: 6,
42
+ paddingBottom: 2,
43
+ alignSelf: 'stretch',
44
+ },
45
+ });
@@ -1,4 +1,5 @@
1
1
  import React, {
2
+ useCallback,
2
3
  useEffect,
3
4
  useMemo,
4
5
  useRef,
@@ -15,7 +16,7 @@ import {
15
16
  Text,
16
17
  Keyboard,
17
18
  } from 'react-native';
18
- import { useDispatch, useSelector } from 'react-redux';
19
+ import { shallowEqual, useDispatch, useSelector, useStore } from 'react-redux';
19
20
  import { RootState } from '../../roomStore';
20
21
  import { Avatar } from './Avatar';
21
22
  import { BubbleHighlight } from './BubbleHighlight';
@@ -43,7 +44,7 @@ import { useChatSettingState } from '../../hooks/useChatSettingState';
43
44
  import { parseMessageBody } from '../../helpers/parseMessageBody';
44
45
  import { chatTextStyle } from '../../helpers/typography';
45
46
  import { useMessageHeapState } from '../../hooks/useMessageHeapState';
46
- import { DoubleTick, LockIcon, LockOffIcon } from '../../assets/icons';
47
+ import { DoubleTick, LockIcon, LockOffIcon, SingleTick } from '../../assets/icons';
47
48
  import { useT } from '../../i18n/useT';
48
49
  import { useFileToken } from '../../hooks/useFileToken';
49
50
  import {
@@ -66,6 +67,17 @@ import {
66
67
  requestUsers,
67
68
  subscribeUserResolver,
68
69
  } from '../../helpers/userResolver';
70
+ import {
71
+ deliveredToOthers,
72
+ describeReceipt,
73
+ getReceiptStatus,
74
+ receiptNameFor,
75
+ selectSeenBy,
76
+ statusOf,
77
+ } from '../../helpers/readReceipts';
78
+
79
+ // Shared empty result for the seen-by selector (keeps shallowEqual cheap).
80
+ const NO_RECEIPT_VIEWERS: string[] = [];
69
81
 
70
82
  // Stable-width stand-in for the sender name while its lookup is pending.
71
83
  const SENDER_NAME_PLACEHOLDER = '\u2026';
@@ -507,6 +519,42 @@ const Message: React.FC<MessageProps> = ({ message, isUser, isReply }) => {
507
519
  const isPending =
508
520
  !isFailed && (idSet.has(message.id) || message?.pending || false);
509
521
 
522
+ // Delivery and read state of our own messages (XEP-0184 / XEP-0333). The
523
+ // seen list is a small array compared shallowly, so a bubble only renders
524
+ // again when its own readers change, not on every message in the room.
525
+ // Same identity the list uses to decide ownership (MessageList passes
526
+ // xmppUsername || walletAddress), so the tick and the watermark agree.
527
+ const selfXmppUsername = user?.xmppUsername || user?.walletAddress || '';
528
+ const seenBy = useSelector(
529
+ (state: RootState) =>
530
+ isUser
531
+ ? selectSeenBy(state, message.roomJid, String(message.id), selfXmppUsername)
532
+ : NO_RECEIPT_VIEWERS,
533
+ shallowEqual
534
+ );
535
+ const readStatus = statusOf(
536
+ seenBy,
537
+ isUser ? deliveredToOthers(message, selfXmppUsername) : []
538
+ );
539
+ const reduxStore = useStore<RootState>();
540
+ // Touch (no hover on mobile): the details sheet names who read or got it.
541
+ const onReceiptPress = useCallback(() => {
542
+ const state = reduxStore.getState();
543
+ const room = state.rooms.rooms[message.roomJid];
544
+ const details = getReceiptStatus(message, room, selfXmppUsername);
545
+ Alert.alert(
546
+ describeReceipt({
547
+ status: details.status,
548
+ isGroup: room?.type !== 'private',
549
+ seenBy: details.seenBy,
550
+ deliveredTo: details.deliveredTo,
551
+ nameOf: (xmppUsername) =>
552
+ receiptNameFor(state.rooms.usersSet, xmppUsername),
553
+ t,
554
+ })
555
+ );
556
+ }, [reduxStore, message, selfXmppUsername, t]);
557
+
510
558
  const onRetryPress = () => {
511
559
  if (isFailed) {retryMessage(message.id);}
512
560
  };
@@ -667,7 +715,27 @@ const Message: React.FC<MessageProps> = ({ message, isUser, isReply }) => {
667
715
  {timeLabel}
668
716
  </Text>
669
717
  {!config?.disableSentLogic && isUser && !isPending && !isFailed && (
670
- <DoubleTick />
718
+ <Pressable
719
+ onPress={onReceiptPress}
720
+ hitSlop={10}
721
+ accessibilityRole="button"
722
+ accessibilityLabel={
723
+ readStatus === 'seen'
724
+ ? t('message.receipt.read')
725
+ : readStatus === 'delivered'
726
+ ? t('message.receipt.delivered')
727
+ : t('message.receipt.sent')
728
+ }
729
+ testID="message-receipt-tick"
730
+ >
731
+ {readStatus === 'sent' ? (
732
+ <SingleTick stroke={theme.textMuted} />
733
+ ) : (
734
+ <DoubleTick
735
+ stroke={readStatus === 'seen' ? theme.primary : theme.textMuted}
736
+ />
737
+ )}
738
+ </Pressable>
671
739
  )}
672
740
  </CustomTimestampRow>
673
741
 
@@ -26,6 +26,7 @@ import { useInteractionsOverlay } from './InteractionsOverlay';
26
26
  import { useTheme } from '../../hooks/useTheme';
27
27
  import { useT } from '../../i18n/useT';
28
28
  import { getEmojiNativeById } from '../../helpers/emoji';
29
+ import { getMessageCaption } from '../../helpers/caption';
29
30
  import {
30
31
  quickReactionIds,
31
32
  reactionPickerEnabled,
@@ -376,7 +377,7 @@ const MessageInteractions: React.FC<MessageInteractionsProps> = ({
376
377
  <Delimeter />
377
378
  </>
378
379
  )}
379
- <MenuItem onPress={() => handleCopyMessage(message.body!)}>
380
+ <MenuItem onPress={() => handleCopyMessage(getMessageCaption(message) ?? message.body!)}>
380
381
  <Text style={[styles.menuText, themedStyles.text]}>
381
382
  {MESSAGE_INTERACTIONS.COPY}
382
383
  </Text>
@@ -430,6 +430,17 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
430
430
  }
431
431
  };
432
432
 
433
+ // An album opens with its siblings; the viewer pages through them.
434
+ const albumSize = activeFile.siblings?.length ?? 0;
435
+ const albumIndex = activeFile.index ?? 0;
436
+ const goToSibling = (next: number) => {
437
+ const target = activeFile.siblings?.[next];
438
+ if (!target) {return;}
439
+ dispatch(
440
+ setActiveFile({ ...target, siblings: activeFile.siblings, index: next })
441
+ );
442
+ };
443
+
433
444
  const closeModal = () => {
434
445
  dispatch(
435
446
  setActiveFile({
@@ -606,6 +617,39 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
606
617
  {getMediaComponent}
607
618
  </CenterContainer>
608
619
 
620
+ {albumSize > 1 && (
621
+ <View style={styles.albumNav} testID="file-preview-album-nav">
622
+ <TouchableOpacity
623
+ testID="file-preview-prev"
624
+ accessibilityRole="button"
625
+ accessibilityLabel={t('attachment.previous')}
626
+ disabled={albumIndex <= 0}
627
+ onPress={() => goToSibling(albumIndex - 1)}
628
+ hitSlop={12}
629
+ style={[styles.albumArrow, albumIndex <= 0 && styles.albumArrowOff]}
630
+ >
631
+ <Text style={[styles.albumArrowText, { color: theme.text }]}>{'\u2039'}</Text>
632
+ </TouchableOpacity>
633
+ <Text style={{ color: theme.textSecondary }}>
634
+ {t('attachment.position', { index: albumIndex + 1, total: albumSize })}
635
+ </Text>
636
+ <TouchableOpacity
637
+ testID="file-preview-next"
638
+ accessibilityRole="button"
639
+ accessibilityLabel={t('attachment.next')}
640
+ disabled={albumIndex >= albumSize - 1}
641
+ onPress={() => goToSibling(albumIndex + 1)}
642
+ hitSlop={12}
643
+ style={[
644
+ styles.albumArrow,
645
+ albumIndex >= albumSize - 1 && styles.albumArrowOff,
646
+ ]}
647
+ >
648
+ <Text style={[styles.albumArrowText, { color: theme.text }]}>{'\u203A'}</Text>
649
+ </TouchableOpacity>
650
+ </View>
651
+ )}
652
+
609
653
  </ModalContainerFullScreen>
610
654
  );
611
655
  };
@@ -629,6 +673,26 @@ const styles = StyleSheet.create({
629
673
  fontSize: 13,
630
674
  fontWeight: '600',
631
675
  },
676
+ albumNav: {
677
+ flexDirection: 'row',
678
+ alignItems: 'center',
679
+ justifyContent: 'center',
680
+ gap: 24,
681
+ paddingBottom: 24,
682
+ },
683
+ albumArrow: {
684
+ width: 44,
685
+ height: 44,
686
+ alignItems: 'center',
687
+ justifyContent: 'center',
688
+ },
689
+ albumArrowOff: {
690
+ opacity: 0.3,
691
+ },
692
+ albumArrowText: {
693
+ fontSize: 32,
694
+ lineHeight: 36,
695
+ },
632
696
  videoMessage: {
633
697
  alignItems: 'center',
634
698
  gap: 8,
@@ -7,12 +7,16 @@ import LastMessageEmoji from './styled/LastMessageEmoji';
7
7
  import LastMessageFile from './styled/LastMessageFile';
8
8
  import LastAudioMessage from './styled/LastAudioMessage';
9
9
  import LastMessageSealed from './styled/LastMessageSealed';
10
+ import { getMessageAttachments } from '../../helpers/attachments';
11
+ import { getMessageCaption } from '../../helpers/caption';
12
+ import { useT } from '../../i18n/useT';
10
13
 
11
14
  interface LastMessageItemProps {
12
15
  lastMessage: LastMessage;
13
16
  }
14
17
 
15
18
  const LastMessageItem: FC<LastMessageItemProps> = ({ lastMessage }) => {
19
+ const t = useT();
16
20
  const { body, emoji, mimetype } = lastMessage;
17
21
 
18
22
  if (lastMessage?.clientEncrypted === 'true') {
@@ -20,22 +24,29 @@ const LastMessageItem: FC<LastMessageItemProps> = ({ lastMessage }) => {
20
24
  }
21
25
 
22
26
  if (mimetype) {
27
+ // What the row says about the file: its caption, else how many files
28
+ // there are; undefined keeps the single file's own name.
29
+ const count = getMessageAttachments(lastMessage).length;
30
+ const label =
31
+ getMessageCaption(lastMessage, { isMedia: true }) ??
32
+ (count > 1 ? t('attachment.count', { count }) : undefined);
33
+
23
34
  if (mimetype.startsWith('image/')) {
24
- return <LastMessagePhoto {...lastMessage} />;
35
+ return <LastMessagePhoto {...lastMessage} label={label} />;
25
36
  }
26
37
 
27
38
  if (mimetype.startsWith('video/')) {
28
- return <LastMessageVideo {...lastMessage} />;
39
+ return <LastMessageVideo {...lastMessage} label={label} />;
29
40
  }
30
41
 
31
42
  if (
32
43
  mimetype.startsWith('audio/') ||
33
44
  mimetype.includes('application/octet-stream')
34
45
  ) {
35
- return <LastAudioMessage {...lastMessage} />;
46
+ return <LastAudioMessage {...lastMessage} label={label} />;
36
47
  }
37
48
 
38
- return <LastMessageFile {...lastMessage} />;
49
+ return <LastMessageFile {...lastMessage} label={label} />;
39
50
  }
40
51
 
41
52
  if (emoji) {
@@ -10,7 +10,10 @@ import { View } from 'react-native';
10
10
  import { PlayIcon } from '../../../assets/icons';
11
11
  import { useTheme } from '../../../hooks/useTheme';
12
12
 
13
- interface LastMessageEmojiProps extends Pick<LastMessage, 'user' | 'body'> {}
13
+ interface LastMessageEmojiProps extends Pick<LastMessage, 'user' | 'body'> {
14
+ /** Replaces the body: the caption, or the count of files. */
15
+ label?: string;
16
+ }
14
17
 
15
18
  const PlayButton = styled.View`
16
19
  width: 20px;
@@ -29,7 +32,7 @@ const AudioRow = styled.View`
29
32
  gap: 6px;
30
33
  `;
31
34
 
32
- const LastAudioMessage: FC<LastMessageEmojiProps> = ({ user, body }) => {
35
+ const LastAudioMessage: FC<LastMessageEmojiProps> = ({ user, body, label }) => {
33
36
  const theme = useTheme();
34
37
  return (
35
38
  <LastRoomMessageContainer>
@@ -41,7 +44,7 @@ const LastAudioMessage: FC<LastMessageEmojiProps> = ({ user, body }) => {
41
44
  <PlayIcon width={10} height={10} color={theme.dark ? theme.text : '#1F2937'} />
42
45
  </PlayButton>
43
46
  <LastRoomMessageText numberOfLines={1}>
44
- {body?.trim() ? body : 'audio'}
47
+ {label ?? (body?.trim() ? body : 'audio')}
45
48
  </LastRoomMessageText>
46
49
  </AudioRow>
47
50
  </LastRoomMessageContainer>
@@ -20,7 +20,10 @@ interface LastMessageFileProps
20
20
  extends Pick<
21
21
  LastMessage,
22
22
  'user' | 'originalName' | 'fileName' | 'mimetype' | 'locationPreview'
23
- > {}
23
+ > {
24
+ /** Replaces the file name: the caption, or the count of files. */
25
+ label?: string;
26
+ }
24
27
 
25
28
  const fallbackImage =
26
29
  'https://as2.ftcdn.net/v2/jpg/02/51/95/53/1000_F_251955356_FAQH0U1y1TZw3ZcdPGybwUkH90a3VAhb.jpg';
@@ -31,6 +34,7 @@ const LastMessageFile: FC<LastMessageFileProps> = ({
31
34
  fileName,
32
35
  mimetype,
33
36
  locationPreview,
37
+ label,
34
38
  }) => {
35
39
  const [imgSrc, setImgSrc] = useState(locationPreview);
36
40
  const fileToken = useFileToken();
@@ -69,7 +73,7 @@ const LastMessageFile: FC<LastMessageFileProps> = ({
69
73
  ) : (
70
74
  <FileIcon width={20} height={20} />
71
75
  )}
72
- <LastRoomMessageText>{displayName}</LastRoomMessageText>
76
+ <LastRoomMessageText>{label ?? displayName}</LastRoomMessageText>
73
77
  </View>
74
78
  </LastRoomMessageContainer>
75
79
  );
@@ -29,7 +29,10 @@ interface LastMessagePhotoProps
29
29
  extends Pick<
30
30
  LastMessage,
31
31
  'user' | 'originalName' | 'fileName' | 'mimetype' | 'locationPreview'
32
- > {}
32
+ > {
33
+ /** Replaces the file name: the caption, or the count of files. */
34
+ label?: string;
35
+ }
33
36
 
34
37
  const LastMessagePhoto: FC<LastMessagePhotoProps> = ({
35
38
  user,
@@ -37,6 +40,7 @@ const LastMessagePhoto: FC<LastMessagePhotoProps> = ({
37
40
  originalName,
38
41
  fileName,
39
42
  mimetype,
43
+ label,
40
44
  }) => {
41
45
  const fileToken = useFileToken();
42
46
  // Bug #40: prefer the sender's original name over the stored hash
@@ -60,7 +64,7 @@ const LastMessagePhoto: FC<LastMessagePhotoProps> = ({
60
64
  <LastMessageImg src={appendFileToken(locationPreview, fileToken)} />
61
65
  </ShadeWrapper>
62
66
  </PhotoContainer>
63
- <LastRoomMessageText>{displayName}</LastRoomMessageText>
67
+ <LastRoomMessageText>{label ?? displayName}</LastRoomMessageText>
64
68
  </View>
65
69
  </LastRoomMessageContainer>
66
70
  );
@@ -44,7 +44,10 @@ interface LastMessageVideoProps
44
44
  extends Pick<
45
45
  LastMessage,
46
46
  'user' | 'location' | 'originalName' | 'fileName' | 'mimetype'
47
- > {}
47
+ > {
48
+ /** Replaces the file name: the caption, or the count of files. */
49
+ label?: string;
50
+ }
48
51
 
49
52
  const LastMessageVideo: FC<LastMessageVideoProps> = ({
50
53
  user,
@@ -52,6 +55,7 @@ const LastMessageVideo: FC<LastMessageVideoProps> = ({
52
55
  originalName,
53
56
  fileName,
54
57
  mimetype,
58
+ label,
55
59
  }) => {
56
60
  const fileToken = useFileToken();
57
61
  // Bug #40: prefer the sender's original name over the stored hash
@@ -76,7 +80,7 @@ const LastMessageVideo: FC<LastMessageVideoProps> = ({
76
80
  </ShadeWrapper>
77
81
  <PlayButton><Text>▶</Text></PlayButton>
78
82
  </VideoContainer>
79
- <LastRoomMessageText>{displayName}</LastRoomMessageText>
83
+ <LastRoomMessageText>{label ?? displayName}</LastRoomMessageText>
80
84
  </View>
81
85
  </LastRoomMessageContainer>
82
86
  );
@@ -237,15 +237,11 @@ const ThreadWrapper: FC<ThreadWrapperProps> = ({
237
237
  );
238
238
 
239
239
  const sendMedia = useCallback(
240
- (data: any, type: string) => {
241
- return sendMessageMedia(
242
- data,
243
- type,
244
- roomJid,
245
- true,
246
- isChecked,
247
- createMainMessageForThread(activeMessage)
248
- );
240
+ (data: any, type: string, caption?: { text: string }) => {
241
+ const mainMessage = createMainMessageForThread(activeMessage);
242
+ return caption
243
+ ? sendMessageMedia(data, type, roomJid, true, isChecked, mainMessage, undefined, caption)
244
+ : sendMessageMedia(data, type, roomJid, true, isChecked, mainMessage);
249
245
  },
250
246
  [activeMessage, isChecked, roomJid, sendMessageMedia]
251
247
  );
@@ -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