@ethora/chat-component-rn 26.10.5 → 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 (365) 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/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/hooks/useSendMessage.js +384 -193
  81. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  82. package/lib/commonjs/i18n/strings.js +96 -0
  83. package/lib/commonjs/i18n/strings.js.map +1 -1
  84. package/lib/commonjs/networking/api-requests/auth.api.js +62 -3
  85. package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
  86. package/lib/commonjs/networking/stanzaHandlers.js +100 -3
  87. package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
  88. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +4 -2
  89. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  90. package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js +28 -0
  91. package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
  92. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
  93. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  94. package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js +28 -0
  95. package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
  96. package/lib/commonjs/networking/xmppClient.js +64 -0
  97. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  98. package/lib/commonjs/roomStore/persistence.js +78 -5
  99. package/lib/commonjs/roomStore/persistence.js.map +1 -1
  100. package/lib/commonjs/roomStore/roomHeapSlice.js +4 -0
  101. package/lib/commonjs/roomStore/roomHeapSlice.js.map +1 -1
  102. package/lib/commonjs/roomStore/roomsSlice.js +114 -1
  103. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  104. package/lib/commonjs/types/models/attachment.model.js +2 -0
  105. package/lib/commonjs/types/models/attachment.model.js.map +1 -0
  106. package/lib/commonjs/version.js +1 -1
  107. package/lib/module/assets/icons.js +1 -1
  108. package/lib/module/assets/icons.js.map +1 -1
  109. package/lib/module/components/MainComponents/AlbumGrid.js +146 -0
  110. package/lib/module/components/MainComponents/AlbumGrid.js.map +1 -0
  111. package/lib/module/components/MainComponents/ChatRoom.js +2 -2
  112. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  113. package/lib/module/components/MainComponents/MediaMessage.js +200 -124
  114. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  115. package/lib/module/components/MainComponents/MediaUploadOverlay.js +104 -0
  116. package/lib/module/components/MainComponents/MediaUploadOverlay.js.map +1 -0
  117. package/lib/module/components/MessageBubble/MediaCaption.js +46 -0
  118. package/lib/module/components/MessageBubble/MediaCaption.js.map +1 -0
  119. package/lib/module/components/MessageBubble/Message.js +44 -5
  120. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  121. package/lib/module/components/MessageBubble/MessageInteractions.js +2 -1
  122. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  123. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +75 -0
  124. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  125. package/lib/module/components/RoomComponents/LastMessageItem.js +20 -4
  126. package/lib/module/components/RoomComponents/LastMessageItem.js.map +1 -1
  127. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js +3 -2
  128. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
  129. package/lib/module/components/RoomComponents/styled/LastMessageFile.js +3 -2
  130. package/lib/module/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
  131. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js +3 -2
  132. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
  133. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js +3 -2
  134. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
  135. package/lib/module/components/Thread/ThreadWrapper.js +3 -2
  136. package/lib/module/components/Thread/ThreadWrapper.js.map +1 -1
  137. package/lib/module/components/styled/MediaFilePreview.js +61 -48
  138. package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
  139. package/lib/module/components/styled/MessageImage.js +16 -6
  140. package/lib/module/components/styled/MessageImage.js.map +1 -1
  141. package/lib/module/components/styled/SendInput.js +99 -68
  142. package/lib/module/components/styled/SendInput.js.map +1 -1
  143. package/lib/module/components/styled/VideoMessage.js +24 -10
  144. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  145. package/lib/module/context/xmppProvider.js +46 -0
  146. package/lib/module/context/xmppProvider.js.map +1 -1
  147. package/lib/module/e2ee/fileEnvelope.js +1 -1
  148. package/lib/module/e2ee/fileEnvelope.js.map +1 -1
  149. package/lib/module/e2ee/sealedBody.js +32 -2
  150. package/lib/module/e2ee/sealedBody.js.map +1 -1
  151. package/lib/module/helpers/albumLayout.js +143 -0
  152. package/lib/module/helpers/albumLayout.js.map +1 -0
  153. package/lib/module/helpers/attachments.js +245 -0
  154. package/lib/module/helpers/attachments.js.map +1 -0
  155. package/lib/module/helpers/caption.js +207 -0
  156. package/lib/module/helpers/caption.js.map +1 -0
  157. package/lib/module/helpers/createMainMessageForThread.js +5 -1
  158. package/lib/module/helpers/createMainMessageForThread.js.map +1 -1
  159. package/lib/module/helpers/createMessageFromXml.js +20 -6
  160. package/lib/module/helpers/createMessageFromXml.js.map +1 -1
  161. package/lib/module/helpers/fileKind.js +34 -0
  162. package/lib/module/helpers/fileKind.js.map +1 -0
  163. package/lib/module/helpers/getDataFromXml.js +6 -1
  164. package/lib/module/helpers/getDataFromXml.js.map +1 -1
  165. package/lib/module/helpers/mapWithConcurrency.js +37 -0
  166. package/lib/module/helpers/mapWithConcurrency.js.map +1 -0
  167. package/lib/module/helpers/mediaLimits.js +27 -0
  168. package/lib/module/helpers/mediaLimits.js.map +1 -0
  169. package/lib/module/helpers/mediaSendRetry.js +55 -0
  170. package/lib/module/helpers/mediaSendRetry.js.map +1 -0
  171. package/lib/module/helpers/mediaWireData.js +58 -0
  172. package/lib/module/helpers/mediaWireData.js.map +1 -0
  173. package/lib/module/helpers/mentionSpans.js +14 -0
  174. package/lib/module/helpers/mentionSpans.js.map +1 -0
  175. package/lib/module/helpers/mimeToExtension.js +43 -0
  176. package/lib/module/helpers/mimeToExtension.js.map +1 -1
  177. package/lib/module/helpers/readReceipts.js +160 -0
  178. package/lib/module/helpers/readReceipts.js.map +1 -0
  179. package/lib/module/helpers/repairFilenameEncoding.js +74 -0
  180. package/lib/module/helpers/repairFilenameEncoding.js.map +1 -0
  181. package/lib/module/helpers/safeJson.js +13 -0
  182. package/lib/module/helpers/safeJson.js.map +1 -0
  183. package/lib/module/hooks/useSendMessage.js +387 -195
  184. package/lib/module/hooks/useSendMessage.js.map +1 -1
  185. package/lib/module/i18n/strings.js +96 -0
  186. package/lib/module/i18n/strings.js.map +1 -1
  187. package/lib/module/networking/api-requests/auth.api.js +62 -3
  188. package/lib/module/networking/api-requests/auth.api.js.map +1 -1
  189. package/lib/module/networking/stanzaHandlers.js +97 -4
  190. package/lib/module/networking/stanzaHandlers.js.map +1 -1
  191. package/lib/module/networking/xmpp/handleStanzas.xmpp.js +5 -3
  192. package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  193. package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js +25 -0
  194. package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
  195. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
  196. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  197. package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js +25 -0
  198. package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
  199. package/lib/module/networking/xmppClient.js +64 -0
  200. package/lib/module/networking/xmppClient.js.map +1 -1
  201. package/lib/module/roomStore/persistence.js +78 -5
  202. package/lib/module/roomStore/persistence.js.map +1 -1
  203. package/lib/module/roomStore/roomHeapSlice.js +4 -0
  204. package/lib/module/roomStore/roomHeapSlice.js.map +1 -1
  205. package/lib/module/roomStore/roomsSlice.js +109 -0
  206. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  207. package/lib/module/types/models/attachment.model.js +2 -0
  208. package/lib/module/types/models/attachment.model.js.map +1 -0
  209. package/lib/module/version.js +1 -1
  210. package/lib/typescript/assets/icons.d.ts +6 -1
  211. package/lib/typescript/assets/icons.d.ts.map +1 -1
  212. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts +21 -0
  213. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts.map +1 -0
  214. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  215. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  216. package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts +17 -0
  217. package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts.map +1 -0
  218. package/lib/typescript/components/MessageBubble/MediaCaption.d.ts +12 -0
  219. package/lib/typescript/components/MessageBubble/MediaCaption.d.ts.map +1 -0
  220. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  221. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  222. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  223. package/lib/typescript/components/RoomComponents/LastMessageItem.d.ts.map +1 -1
  224. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts +2 -0
  225. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts.map +1 -1
  226. package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts +2 -0
  227. package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts.map +1 -1
  228. package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts +2 -0
  229. package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts.map +1 -1
  230. package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts +2 -0
  231. package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts.map +1 -1
  232. package/lib/typescript/components/Thread/ThreadWrapper.d.ts.map +1 -1
  233. package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
  234. package/lib/typescript/components/styled/MessageImage.d.ts +10 -0
  235. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  236. package/lib/typescript/components/styled/SendInput.d.ts +7 -1
  237. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  238. package/lib/typescript/components/styled/VideoMessage.d.ts +7 -0
  239. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  240. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  241. package/lib/typescript/e2ee/fileEnvelope.d.ts +1 -1
  242. package/lib/typescript/e2ee/fileEnvelope.d.ts.map +1 -1
  243. package/lib/typescript/e2ee/sealedBody.d.ts +16 -0
  244. package/lib/typescript/e2ee/sealedBody.d.ts.map +1 -1
  245. package/lib/typescript/helpers/albumLayout.d.ts +66 -0
  246. package/lib/typescript/helpers/albumLayout.d.ts.map +1 -0
  247. package/lib/typescript/helpers/attachments.d.ts +55 -0
  248. package/lib/typescript/helpers/attachments.d.ts.map +1 -0
  249. package/lib/typescript/helpers/caption.d.ts +64 -0
  250. package/lib/typescript/helpers/caption.d.ts.map +1 -0
  251. package/lib/typescript/helpers/createMainMessageForThread.d.ts.map +1 -1
  252. package/lib/typescript/helpers/createMessageFromXml.d.ts.map +1 -1
  253. package/lib/typescript/helpers/fileKind.d.ts +20 -0
  254. package/lib/typescript/helpers/fileKind.d.ts.map +1 -0
  255. package/lib/typescript/helpers/getDataFromXml.d.ts.map +1 -1
  256. package/lib/typescript/helpers/mapWithConcurrency.d.ts +9 -0
  257. package/lib/typescript/helpers/mapWithConcurrency.d.ts.map +1 -0
  258. package/lib/typescript/helpers/mediaLimits.d.ts +13 -0
  259. package/lib/typescript/helpers/mediaLimits.d.ts.map +1 -0
  260. package/lib/typescript/helpers/mediaSendRetry.d.ts +26 -0
  261. package/lib/typescript/helpers/mediaSendRetry.d.ts.map +1 -0
  262. package/lib/typescript/helpers/mediaWireData.d.ts +16 -0
  263. package/lib/typescript/helpers/mediaWireData.d.ts.map +1 -0
  264. package/lib/typescript/helpers/mentionSpans.d.ts +8 -0
  265. package/lib/typescript/helpers/mentionSpans.d.ts.map +1 -0
  266. package/lib/typescript/helpers/mimeToExtension.d.ts +7 -0
  267. package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -1
  268. package/lib/typescript/helpers/readReceipts.d.ts +82 -0
  269. package/lib/typescript/helpers/readReceipts.d.ts.map +1 -0
  270. package/lib/typescript/helpers/repairFilenameEncoding.d.ts +12 -0
  271. package/lib/typescript/helpers/repairFilenameEncoding.d.ts.map +1 -0
  272. package/lib/typescript/helpers/safeJson.d.ts +2 -0
  273. package/lib/typescript/helpers/safeJson.d.ts.map +1 -0
  274. package/lib/typescript/hooks/useSendMessage.d.ts +2 -1
  275. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  276. package/lib/typescript/i18n/strings.d.ts.map +1 -1
  277. package/lib/typescript/networking/api-requests/auth.api.d.ts +2 -0
  278. package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
  279. package/lib/typescript/networking/stanzaHandlers.d.ts +7 -3
  280. package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
  281. package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts.map +1 -1
  282. package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts +3 -0
  283. package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts.map +1 -0
  284. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts +12 -0
  285. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts.map +1 -1
  286. package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts +3 -0
  287. package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts.map +1 -0
  288. package/lib/typescript/networking/xmppClient.d.ts +15 -0
  289. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  290. package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
  291. package/lib/typescript/roomStore/roomHeapSlice.d.ts +22 -1
  292. package/lib/typescript/roomStore/roomHeapSlice.d.ts.map +1 -1
  293. package/lib/typescript/roomStore/roomsSlice.d.ts +67 -3
  294. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  295. package/lib/typescript/types/models/attachment.model.d.ts +52 -0
  296. package/lib/typescript/types/models/attachment.model.d.ts.map +1 -0
  297. package/lib/typescript/types/models/message.model.d.ts +26 -0
  298. package/lib/typescript/types/models/message.model.d.ts.map +1 -1
  299. package/lib/typescript/types/models/modal.model.d.ts +7 -0
  300. package/lib/typescript/types/models/modal.model.d.ts.map +1 -1
  301. package/lib/typescript/types/models/room.model.d.ts +2 -0
  302. package/lib/typescript/types/models/room.model.d.ts.map +1 -1
  303. package/lib/typescript/types/types.d.ts +29 -1
  304. package/lib/typescript/types/types.d.ts.map +1 -1
  305. package/lib/typescript/version.d.ts +1 -1
  306. package/package.json +4 -1
  307. package/plugins/notification-service/Info.plist +31 -0
  308. package/plugins/notification-service/NotificationService.swift +82 -0
  309. package/plugins/withNotificationServiceExtension.js +137 -0
  310. package/src/assets/icons.tsx +20 -2
  311. package/src/components/MainComponents/AlbumGrid.tsx +183 -0
  312. package/src/components/MainComponents/ChatRoom.tsx +4 -2
  313. package/src/components/MainComponents/MediaMessage.tsx +246 -130
  314. package/src/components/MainComponents/MediaUploadOverlay.tsx +102 -0
  315. package/src/components/MessageBubble/MediaCaption.tsx +45 -0
  316. package/src/components/MessageBubble/Message.tsx +71 -3
  317. package/src/components/MessageBubble/MessageInteractions.tsx +2 -1
  318. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +64 -0
  319. package/src/components/RoomComponents/LastMessageItem.tsx +15 -4
  320. package/src/components/RoomComponents/styled/LastAudioMessage.tsx +6 -3
  321. package/src/components/RoomComponents/styled/LastMessageFile.tsx +6 -2
  322. package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +6 -2
  323. package/src/components/RoomComponents/styled/LastMessageVideo.tsx +6 -2
  324. package/src/components/Thread/ThreadWrapper.tsx +5 -9
  325. package/src/components/styled/MediaFilePreview.tsx +49 -34
  326. package/src/components/styled/MessageImage.tsx +21 -5
  327. package/src/components/styled/SendInput.tsx +101 -87
  328. package/src/components/styled/VideoMessage.tsx +34 -9
  329. package/src/context/xmppProvider.tsx +32 -0
  330. package/src/e2ee/fileEnvelope.ts +1 -1
  331. package/src/e2ee/sealedBody.ts +44 -5
  332. package/src/helpers/albumLayout.ts +207 -0
  333. package/src/helpers/attachments.ts +316 -0
  334. package/src/helpers/caption.ts +283 -0
  335. package/src/helpers/createMainMessageForThread.ts +6 -1
  336. package/src/helpers/createMessageFromXml.ts +21 -5
  337. package/src/helpers/fileKind.ts +38 -0
  338. package/src/helpers/getDataFromXml.ts +5 -1
  339. package/src/helpers/mapWithConcurrency.ts +32 -0
  340. package/src/helpers/mediaLimits.ts +27 -0
  341. package/src/helpers/mediaSendRetry.ts +59 -0
  342. package/src/helpers/mediaWireData.ts +57 -0
  343. package/src/helpers/mentionSpans.ts +17 -0
  344. package/src/helpers/mimeToExtension.ts +51 -0
  345. package/src/helpers/readReceipts.ts +219 -0
  346. package/src/helpers/repairFilenameEncoding.ts +82 -0
  347. package/src/helpers/safeJson.ts +11 -0
  348. package/src/hooks/useSendMessage.tsx +448 -249
  349. package/src/i18n/strings.ts +96 -0
  350. package/src/networking/api-requests/auth.api.ts +60 -3
  351. package/src/networking/stanzaHandlers.ts +91 -2
  352. package/src/networking/xmpp/handleStanzas.xmpp.ts +6 -2
  353. package/src/networking/xmpp/sendDisplayedMarker.xmpp.ts +31 -0
  354. package/src/networking/xmpp/sendMediaMessage.xmpp.ts +60 -3
  355. package/src/networking/xmpp/sendReceivedReceipt.xmpp.ts +31 -0
  356. package/src/networking/xmppClient.ts +48 -0
  357. package/src/roomStore/persistence.ts +94 -3
  358. package/src/roomStore/roomHeapSlice.ts +23 -1
  359. package/src/roomStore/roomsSlice.ts +112 -1
  360. package/src/types/models/attachment.model.ts +51 -0
  361. package/src/types/models/message.model.ts +29 -0
  362. package/src/types/models/modal.model.ts +7 -0
  363. package/src/types/models/room.model.ts +2 -0
  364. package/src/types/types.ts +35 -1
  365. package/src/version.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  import React, { useMemo } from 'react';
2
- import { IMessage } from '../../types/types';
2
+ import { IAttachment, IMessage } from '../../types/types';
3
3
  import FileDownload from '../styled/UnsupportedType';
4
4
  import CustomMessageImage from '../styled/MessageImage';
5
5
  import CustomMessageVideo from '../styled/VideoMessage';
@@ -11,7 +11,9 @@ import {
11
11
  Text,
12
12
  View,
13
13
  } from 'react-native';
14
- import { isLikelyAudio } from '../../helpers/mimeToExtension';
14
+ import { getAttachmentName, getMessageAttachments } from '../../helpers/attachments';
15
+ import { getMessageCaption } from '../../helpers/caption';
16
+ import { getFileKind, isAlbumKind } from '../../helpers/fileKind';
15
17
  import { getDisplayFileName } from '../../helpers/getDisplayFileName';
16
18
  import { FileIcon, PlayIcon } from '../../assets/icons';
17
19
  import { defaultMediaDims } from '../../helpers/mediaDimensions';
@@ -19,6 +21,10 @@ import { useFileToken } from '../../hooks/useFileToken';
19
21
  import { appendFileToken } from '../../helpers/secureFileUrl';
20
22
  import { useTheme } from '../../hooks/useTheme';
21
23
  import SealedAttachment from './SealedAttachment';
24
+ import AlbumGrid from './AlbumGrid';
25
+ import MediaCaption from '../MessageBubble/MediaCaption';
26
+ import MediaUploadOverlay from './MediaUploadOverlay';
27
+ import { requestMediaRetry } from '../../helpers/mediaSendRetry';
22
28
 
23
29
  interface MediaMessageProps {
24
30
  mimeType?: string;
@@ -156,160 +162,270 @@ const PendingMediaMessage: React.FC<{
156
162
  );
157
163
  };
158
164
 
159
- const MediaMessage: React.FC<MediaMessageProps> = ({
160
- mimeType,
161
- location: rawLocation,
162
- messageText: rawMessageText,
165
+ interface AttachmentCardProps {
166
+ attachment: IAttachment;
167
+ /** Position in the message; the voice waveform belongs to #0 only. */
168
+ index: number;
169
+ message?: IMessage;
170
+ isUser: boolean;
171
+ fileToken?: string | null;
172
+ }
173
+
174
+ /** One file of a message: the picture, video, player or file card it gets. */
175
+ const AttachmentCard: React.FC<AttachmentCardProps> = ({
176
+ attachment,
177
+ index,
163
178
  message,
164
179
  isUser,
180
+ fileToken,
165
181
  }) => {
166
- const fileToken = useFileToken();
167
- const theme = useTheme();
182
+ const mimeType = attachment.mimetype || '';
183
+ const rawLocation = attachment.location;
184
+ const rawPreview = attachment.locationPreview;
168
185
  const location = appendFileToken(rawLocation, fileToken);
169
- const messageText = appendFileToken(rawMessageText, fileToken);
186
+ const previewText = appendFileToken(rawPreview, fileToken);
187
+ const originalName = attachment.originalName;
188
+ const duration = attachment.duration ?? (message as any)?.duration;
189
+ const waveForm = index === 0 ? (message as any)?.waveForm : undefined;
170
190
 
171
- if (message?.clientEncrypted === 'true') {
172
- return <SealedAttachment message={message} isUser={isUser} />;
173
- }
191
+ const displayName = getDisplayFileName({
192
+ originalName,
193
+ fileName: attachment.fileName,
194
+ location: rawLocation || rawPreview,
195
+ mimetype: mimeType,
196
+ });
197
+ const kind = getFileKind({
198
+ mimetype: mimeType,
199
+ name: displayName,
200
+ url: rawLocation,
201
+ duration,
202
+ waveForm,
203
+ originalName,
204
+ });
174
205
 
175
- if (mimeType) {
176
- const displayName = getDisplayFileName({
177
- originalName: (message as any)?.originalName,
178
- fileName: message?.fileName,
179
- location: rawLocation || rawMessageText,
180
- mimetype: mimeType,
181
- });
182
- const isAudioPayload = isLikelyAudio(
183
- mimeType,
184
- displayName,
185
- rawLocation,
186
- {
187
- duration: (message as any)?.duration,
188
- waveForm: (message as any)?.waveForm,
189
- originalName: (message as any)?.originalName,
190
- }
206
+ // The sender's own voice note plays from its local file while it
207
+ // uploads, waveform and all - no placeholder card in between.
208
+ const localPreview = attachment.localUrl;
209
+ if (message?.pending && kind === 'audio' && (localPreview || previewText || location)) {
210
+ return (
211
+ <AudioMessage
212
+ src={localPreview || previewText || location || ''}
213
+ mimeType={mimeType}
214
+ fileName={displayName}
215
+ duration={duration}
216
+ waveForm={waveForm}
217
+ isUser={isUser}
218
+ />
191
219
  );
192
- // The sender's own voice note plays from its local file while it
193
- // uploads, waveform and all — no placeholder card in between.
194
- if (message?.pending && isAudioPayload && (messageText || location)) {
220
+ }
221
+ if (message?.pending) {
222
+ return (
223
+ <PendingMediaMessage
224
+ fileName={displayName}
225
+ mimeType={mimeType}
226
+ previewUri={localPreview || previewText || location || ''}
227
+ isUser={isUser}
228
+ size={index === 0 ? message?.size : attachment.size}
229
+ />
230
+ );
231
+ }
232
+
233
+ switch (kind) {
234
+ case 'image':
235
+ return (
236
+ <CustomMessageImage
237
+ fileName={displayName}
238
+ originalName={originalName}
239
+ // Full-size original: opened by the preview modal.
240
+ fileURL={location || previewText || ''}
241
+ // ...and the thumbnail the bubble actually renders, which is
242
+ // what the web client shows too.
243
+ locationPreview={previewText}
244
+ mimetype={mimeType}
245
+ />
246
+ );
247
+ case 'video':
248
+ return (
249
+ <CustomMessageVideo
250
+ fileName={displayName}
251
+ originalName={originalName}
252
+ fileURL={location || ''}
253
+ // The message's locationPreview is the poster frame the backend
254
+ // generated for this video. Handing it over lets the bubble render
255
+ // as a still + play badge instead of mounting a video surface per
256
+ // row (see VideoMessage).
257
+ previewURL={previewText}
258
+ mimetype={mimeType}
259
+ />
260
+ );
261
+ case 'audio':
262
+ // Covers audio/*, octet-stream voice notes and the voice-message
263
+ // heuristics of isLikelyAudio (customer-reported #9 voicemail fix):
264
+ // a voicemail shipped as octet-stream with no audio extension still
265
+ // gets the player instead of a broken `.bin` file card.
195
266
  return (
196
267
  <AudioMessage
197
- src={messageText || location || ''}
268
+ src={location || ''}
198
269
  mimeType={mimeType}
199
270
  fileName={displayName}
200
- duration={(message as any)?.duration}
201
- waveForm={(message as any)?.waveForm}
271
+ originalName={originalName}
272
+ duration={duration}
273
+ waveForm={waveForm}
202
274
  isUser={isUser}
203
275
  />
204
276
  );
205
- }
206
- if (message?.pending) {
277
+ default:
207
278
  return (
208
- <PendingMediaMessage
279
+ <FileDownload
280
+ fileURL={location ? location : ''}
209
281
  fileName={displayName}
210
- mimeType={mimeType}
211
- previewUri={messageText || location || ''}
282
+ mimetype={mimeType}
212
283
  isUser={isUser}
213
- size={message?.size}
284
+ originalName={originalName}
285
+ duration={duration}
286
+ waveForm={waveForm}
214
287
  />
215
288
  );
216
- }
217
- switch (true) {
218
- case mimeType.startsWith('image/'):
219
- return (
220
- <CustomMessageImage
221
- fileName={displayName}
222
- originalName={(message as any)?.originalName}
223
- // Full-size original: opened by the preview modal.
224
- fileURL={location || messageText || ''}
225
- // ...and the thumbnail the bubble actually renders, which is
226
- // what the web client shows too.
227
- locationPreview={messageText}
228
- mimetype={mimeType}
229
- />
230
- );
231
- case mimeType.startsWith('video/'):
232
- return (
233
- <CustomMessageVideo
234
- fileName={displayName}
235
- originalName={(message as any)?.originalName}
236
- fileURL={location || ''}
237
- // `messageText` is the message's locationPreview — the poster
238
- // frame the backend generated for this video. Handing it over
239
- // lets the bubble render as a still + play badge instead of
240
- // mounting a video surface per row (see VideoMessage).
241
- previewURL={messageText}
242
- mimetype={mimeType}
243
- />
244
- );
245
- case mimeType.startsWith('audio/'): {
246
- return (
247
- <AudioMessage
248
- src={location || ''}
249
- mimeType={mimeType}
250
- fileName={displayName}
251
- originalName={(message as any)?.originalName}
252
- duration={(message as any)?.duration}
253
- waveForm={(message as any)?.waveForm}
254
- isUser={isUser}
255
- />
256
- );
257
- }
258
- case mimeType.includes('application/octet-stream'): {
259
- return (
260
- <AudioMessage
261
- src={location || ''}
262
- mimeType={mimeType}
263
- fileName={displayName}
264
- originalName={(message as any)?.originalName}
265
- duration={(message as any)?.duration}
266
- waveForm={(message as any)?.waveForm}
267
- isUser={isUser}
268
- />
269
- );
270
- }
271
- default: {
272
- // Some senders (notably the web app's voice-message uploader)
273
- // ship audio as `application/octet-stream` with no audio file
274
- // extension. `isLikelyAudio` recognises those via the URL path,
275
- // filename, or voice-message naming hints (voicemail-,
276
- // voice-note-, recording-, …) so we still render the AudioMessage
277
- // player instead of falling through to the generic FileDownload
278
- // card (where the voicemail rendered as a broken `.bin`
279
- // attachment). Customer-reported #9 voicemail fix.
280
- if (isAudioPayload) {
281
- return (
282
- <AudioMessage
283
- src={location || ''}
284
- mimeType={mimeType}
285
- fileName={displayName}
286
- originalName={(message as any)?.originalName}
287
- duration={(message as any)?.duration}
288
- waveForm={(message as any)?.waveForm}
289
- isUser={isUser}
290
- />
291
- );
289
+ }
290
+ };
291
+
292
+ const MediaMessage: React.FC<MediaMessageProps> = ({
293
+ mimeType,
294
+ location: rawLocation,
295
+ messageText: rawMessageText,
296
+ message,
297
+ isUser,
298
+ }) => {
299
+ const fileToken = useFileToken();
300
+ const theme = useTheme();
301
+
302
+ // Order of truth: the message's own attachments, else its flat fields, else
303
+ // the props the bubble was handed (a bare file with no message around it).
304
+ const attachments = useMemo(
305
+ () =>
306
+ getMessageAttachments(
307
+ message ?? {
308
+ location: rawLocation,
309
+ locationPreview: rawMessageText,
310
+ mimetype: mimeType,
292
311
  }
293
- return (
294
- <FileDownload
295
- fileURL={location ? location : ''}
296
- fileName={displayName}
297
- mimetype={mimeType}
298
- isUser={isUser}
299
- originalName={(message as any)?.originalName}
300
- duration={(message as any)?.duration}
301
- waveForm={(message as any)?.waveForm}
302
- />
303
- );
304
- }
305
- }
312
+ ),
313
+ [message, rawLocation, rawMessageText, mimeType]
314
+ );
315
+ const caption = message ? getMessageCaption(message) : undefined;
316
+
317
+ if (message?.clientEncrypted === 'true') {
318
+ // A sealed file has nothing to show until it is opened, so each one is a
319
+ // card of its own (SealedAttachment); the caption came out of the
320
+ // encrypted body.
321
+ return (
322
+ <>
323
+ <SealedAttachment message={message} isUser={isUser} />
324
+ <MediaCaption text={caption} isUser={isUser} />
325
+ </>
326
+ );
327
+ }
328
+
329
+ if (attachments.length === 0) {
330
+ return <Text style={{ color: theme.text }}>Unsupported media type</Text>;
306
331
  }
307
- return <Text style={{ color: theme.text }}>Unsupported media type</Text>;
332
+
333
+ // A file of an outgoing multi-file message carries its upload state. It is
334
+ // "reachable" through its local copy, not a server one, until the send lands.
335
+ const isOutgoingFile = (attachment: IAttachment) =>
336
+ Boolean(message?.pending) &&
337
+ Boolean(attachment.uploadStatus || attachment.localUrl);
338
+
339
+ const card = (attachment: IAttachment, index: number) => {
340
+ const body = (
341
+ <AttachmentCard
342
+ key={attachment.attachmentId || `${index}-${attachment.location}`}
343
+ attachment={attachment}
344
+ index={index}
345
+ message={message}
346
+ isUser={isUser}
347
+ fileToken={fileToken}
348
+ />
349
+ );
350
+ // In a multi-file message each file gets its own progress and Retry.
351
+ if (!attachment.uploadStatus || attachments.length < 2) {return body;}
352
+ return (
353
+ <View
354
+ key={`tile-${attachment.attachmentId || index}`}
355
+ style={styles.uploadTile}
356
+ testID={`media-upload-tile-${index}`}
357
+ >
358
+ {body}
359
+ <MediaUploadOverlay
360
+ status={attachment.uploadStatus}
361
+ progress={attachment.uploadProgress}
362
+ onRetry={message?.id ? () => requestMediaRetry(message.id, index) : undefined}
363
+ />
364
+ </View>
365
+ );
366
+ };
367
+
368
+ // Pictures and videos share one album when there are two or more of them;
369
+ // documents and voice notes follow as one card each. A lone picture is the
370
+ // ordinary bubble picture, exactly as a single-file message always was.
371
+ const album = attachments.filter(
372
+ (attachment) =>
373
+ (Boolean(attachment.location) || isOutgoingFile(attachment)) &&
374
+ isAlbumKind(
375
+ getFileKind({
376
+ mimetype: attachment.mimetype,
377
+ name: getAttachmentName(attachment),
378
+ url: attachment.location,
379
+ duration: attachment.duration,
380
+ })
381
+ )
382
+ );
383
+ const useAlbum = album.length >= 2;
384
+ const rest = useAlbum
385
+ ? attachments.filter((attachment) => !album.includes(attachment))
386
+ : attachments;
387
+
388
+ // One file and no text: the card alone, no wrapper around it.
389
+ if (!useAlbum && attachments.length === 1 && !caption) {
390
+ return card(attachments[0]!, 0);
391
+ }
392
+
393
+ return (
394
+ <View style={styles.stack}>
395
+ {useAlbum && (
396
+ <AlbumGrid
397
+ attachments={album}
398
+ onRetryTile={
399
+ message?.id
400
+ ? (position) =>
401
+ requestMediaRetry(
402
+ message.id,
403
+ attachments.indexOf(album[position]!)
404
+ )
405
+ : undefined
406
+ }
407
+ />
408
+ )}
409
+ {rest.map((attachment) => card(attachment, attachments.indexOf(attachment)))}
410
+ <MediaCaption text={caption} isUser={isUser} />
411
+ </View>
412
+ );
308
413
  };
309
414
 
310
415
  export default MediaMessage;
311
416
 
312
417
  const styles = StyleSheet.create({
418
+ stack: {
419
+ alignItems: 'flex-start',
420
+ gap: 4,
421
+ },
422
+ // Frames one file of a multi-file message so its upload overlay covers it.
423
+ uploadTile: {
424
+ position: 'relative',
425
+ alignSelf: 'stretch',
426
+ borderRadius: 10,
427
+ overflow: 'hidden',
428
+ },
313
429
  pendingImageWrapper: {
314
430
  width: pendingImageDims.width,
315
431
  height: pendingImageDims.height,
@@ -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