@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.
- package/CHANGELOG.md +8 -0
- package/README.md +15 -0
- package/app.plugin.js +2 -0
- package/lib/commonjs/assets/icons.js +2 -2
- package/lib/commonjs/assets/icons.js.map +1 -1
- package/lib/commonjs/components/MainComponents/AlbumGrid.js +152 -0
- package/lib/commonjs/components/MainComponents/AlbumGrid.js.map +1 -0
- package/lib/commonjs/components/MainComponents/ChatRoom.js +2 -2
- package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MediaMessage.js +199 -123
- package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MediaUploadOverlay.js +109 -0
- package/lib/commonjs/components/MainComponents/MediaUploadOverlay.js.map +1 -0
- package/lib/commonjs/components/MessageBubble/MediaCaption.js +51 -0
- package/lib/commonjs/components/MessageBubble/MediaCaption.js.map +1 -0
- package/lib/commonjs/components/MessageBubble/Message.js +40 -1
- package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
- package/lib/commonjs/components/MessageBubble/MessageInteractions.js +2 -1
- package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +75 -0
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/LastMessageItem.js +20 -4
- package/lib/commonjs/components/RoomComponents/LastMessageItem.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js +3 -2
- package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js +3 -2
- package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js +3 -2
- package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js +3 -2
- package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
- package/lib/commonjs/components/Thread/ThreadWrapper.js +3 -2
- package/lib/commonjs/components/Thread/ThreadWrapper.js.map +1 -1
- package/lib/commonjs/components/styled/MediaFilePreview.js +60 -47
- package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
- package/lib/commonjs/components/styled/MessageImage.js +16 -6
- package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
- package/lib/commonjs/components/styled/SendInput.js +99 -68
- package/lib/commonjs/components/styled/SendInput.js.map +1 -1
- package/lib/commonjs/components/styled/VideoMessage.js +24 -10
- package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
- package/lib/commonjs/context/xmppProvider.js +46 -0
- package/lib/commonjs/context/xmppProvider.js.map +1 -1
- package/lib/commonjs/e2ee/fileEnvelope.js +6 -0
- package/lib/commonjs/e2ee/fileEnvelope.js.map +1 -1
- package/lib/commonjs/e2ee/sealedBody.js +32 -2
- package/lib/commonjs/e2ee/sealedBody.js.map +1 -1
- package/lib/commonjs/helpers/albumLayout.js +150 -0
- package/lib/commonjs/helpers/albumLayout.js.map +1 -0
- package/lib/commonjs/helpers/attachments.js +255 -0
- package/lib/commonjs/helpers/attachments.js.map +1 -0
- package/lib/commonjs/helpers/caption.js +214 -0
- package/lib/commonjs/helpers/caption.js.map +1 -0
- package/lib/commonjs/helpers/createMainMessageForThread.js +5 -1
- package/lib/commonjs/helpers/createMainMessageForThread.js.map +1 -1
- package/lib/commonjs/helpers/createMessageFromXml.js +19 -5
- package/lib/commonjs/helpers/createMessageFromXml.js.map +1 -1
- package/lib/commonjs/helpers/fileKind.js +40 -0
- package/lib/commonjs/helpers/fileKind.js.map +1 -0
- package/lib/commonjs/helpers/getDataFromXml.js +6 -1
- package/lib/commonjs/helpers/getDataFromXml.js.map +1 -1
- package/lib/commonjs/helpers/mapWithConcurrency.js +41 -0
- package/lib/commonjs/helpers/mapWithConcurrency.js.map +1 -0
- package/lib/commonjs/helpers/mediaLimits.js +33 -0
- package/lib/commonjs/helpers/mediaLimits.js.map +1 -0
- package/lib/commonjs/helpers/mediaSendRetry.js +64 -0
- package/lib/commonjs/helpers/mediaSendRetry.js.map +1 -0
- package/lib/commonjs/helpers/mediaWireData.js +62 -0
- package/lib/commonjs/helpers/mediaWireData.js.map +1 -0
- package/lib/commonjs/helpers/mentionSpans.js +19 -0
- package/lib/commonjs/helpers/mentionSpans.js.map +1 -0
- package/lib/commonjs/helpers/mimeToExtension.js +45 -0
- package/lib/commonjs/helpers/mimeToExtension.js.map +1 -1
- package/lib/commonjs/helpers/readReceipts.js +173 -0
- package/lib/commonjs/helpers/readReceipts.js.map +1 -0
- package/lib/commonjs/helpers/repairFilenameEncoding.js +79 -0
- package/lib/commonjs/helpers/repairFilenameEncoding.js.map +1 -0
- package/lib/commonjs/helpers/safeJson.js +18 -0
- package/lib/commonjs/helpers/safeJson.js.map +1 -0
- package/lib/commonjs/hooks/useSendMessage.js +384 -193
- package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
- package/lib/commonjs/i18n/strings.js +96 -0
- package/lib/commonjs/i18n/strings.js.map +1 -1
- package/lib/commonjs/networking/api-requests/auth.api.js +62 -3
- package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
- package/lib/commonjs/networking/stanzaHandlers.js +100 -3
- package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
- package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +4 -2
- package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
- package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js +28 -0
- package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
- package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
- package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
- package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js +28 -0
- package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
- package/lib/commonjs/networking/xmppClient.js +64 -0
- package/lib/commonjs/networking/xmppClient.js.map +1 -1
- package/lib/commonjs/roomStore/persistence.js +78 -5
- package/lib/commonjs/roomStore/persistence.js.map +1 -1
- package/lib/commonjs/roomStore/roomHeapSlice.js +4 -0
- package/lib/commonjs/roomStore/roomHeapSlice.js.map +1 -1
- package/lib/commonjs/roomStore/roomsSlice.js +114 -1
- package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
- package/lib/commonjs/types/models/attachment.model.js +2 -0
- package/lib/commonjs/types/models/attachment.model.js.map +1 -0
- package/lib/commonjs/version.js +1 -1
- package/lib/module/assets/icons.js +1 -1
- package/lib/module/assets/icons.js.map +1 -1
- package/lib/module/components/MainComponents/AlbumGrid.js +146 -0
- package/lib/module/components/MainComponents/AlbumGrid.js.map +1 -0
- package/lib/module/components/MainComponents/ChatRoom.js +2 -2
- package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/module/components/MainComponents/MediaMessage.js +200 -124
- package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/module/components/MainComponents/MediaUploadOverlay.js +104 -0
- package/lib/module/components/MainComponents/MediaUploadOverlay.js.map +1 -0
- package/lib/module/components/MessageBubble/MediaCaption.js +46 -0
- package/lib/module/components/MessageBubble/MediaCaption.js.map +1 -0
- package/lib/module/components/MessageBubble/Message.js +44 -5
- package/lib/module/components/MessageBubble/Message.js.map +1 -1
- package/lib/module/components/MessageBubble/MessageInteractions.js +2 -1
- package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +75 -0
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/module/components/RoomComponents/LastMessageItem.js +20 -4
- package/lib/module/components/RoomComponents/LastMessageItem.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastAudioMessage.js +3 -2
- package/lib/module/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessageFile.js +3 -2
- package/lib/module/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js +3 -2
- package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessageVideo.js +3 -2
- package/lib/module/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
- package/lib/module/components/Thread/ThreadWrapper.js +3 -2
- package/lib/module/components/Thread/ThreadWrapper.js.map +1 -1
- package/lib/module/components/styled/MediaFilePreview.js +61 -48
- package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
- package/lib/module/components/styled/MessageImage.js +16 -6
- package/lib/module/components/styled/MessageImage.js.map +1 -1
- package/lib/module/components/styled/SendInput.js +99 -68
- package/lib/module/components/styled/SendInput.js.map +1 -1
- package/lib/module/components/styled/VideoMessage.js +24 -10
- package/lib/module/components/styled/VideoMessage.js.map +1 -1
- package/lib/module/context/xmppProvider.js +46 -0
- package/lib/module/context/xmppProvider.js.map +1 -1
- package/lib/module/e2ee/fileEnvelope.js +1 -1
- package/lib/module/e2ee/fileEnvelope.js.map +1 -1
- package/lib/module/e2ee/sealedBody.js +32 -2
- package/lib/module/e2ee/sealedBody.js.map +1 -1
- package/lib/module/helpers/albumLayout.js +143 -0
- package/lib/module/helpers/albumLayout.js.map +1 -0
- package/lib/module/helpers/attachments.js +245 -0
- package/lib/module/helpers/attachments.js.map +1 -0
- package/lib/module/helpers/caption.js +207 -0
- package/lib/module/helpers/caption.js.map +1 -0
- package/lib/module/helpers/createMainMessageForThread.js +5 -1
- package/lib/module/helpers/createMainMessageForThread.js.map +1 -1
- package/lib/module/helpers/createMessageFromXml.js +20 -6
- package/lib/module/helpers/createMessageFromXml.js.map +1 -1
- package/lib/module/helpers/fileKind.js +34 -0
- package/lib/module/helpers/fileKind.js.map +1 -0
- package/lib/module/helpers/getDataFromXml.js +6 -1
- package/lib/module/helpers/getDataFromXml.js.map +1 -1
- package/lib/module/helpers/mapWithConcurrency.js +37 -0
- package/lib/module/helpers/mapWithConcurrency.js.map +1 -0
- package/lib/module/helpers/mediaLimits.js +27 -0
- package/lib/module/helpers/mediaLimits.js.map +1 -0
- package/lib/module/helpers/mediaSendRetry.js +55 -0
- package/lib/module/helpers/mediaSendRetry.js.map +1 -0
- package/lib/module/helpers/mediaWireData.js +58 -0
- package/lib/module/helpers/mediaWireData.js.map +1 -0
- package/lib/module/helpers/mentionSpans.js +14 -0
- package/lib/module/helpers/mentionSpans.js.map +1 -0
- package/lib/module/helpers/mimeToExtension.js +43 -0
- package/lib/module/helpers/mimeToExtension.js.map +1 -1
- package/lib/module/helpers/readReceipts.js +160 -0
- package/lib/module/helpers/readReceipts.js.map +1 -0
- package/lib/module/helpers/repairFilenameEncoding.js +74 -0
- package/lib/module/helpers/repairFilenameEncoding.js.map +1 -0
- package/lib/module/helpers/safeJson.js +13 -0
- package/lib/module/helpers/safeJson.js.map +1 -0
- package/lib/module/hooks/useSendMessage.js +387 -195
- package/lib/module/hooks/useSendMessage.js.map +1 -1
- package/lib/module/i18n/strings.js +96 -0
- package/lib/module/i18n/strings.js.map +1 -1
- package/lib/module/networking/api-requests/auth.api.js +62 -3
- package/lib/module/networking/api-requests/auth.api.js.map +1 -1
- package/lib/module/networking/stanzaHandlers.js +97 -4
- package/lib/module/networking/stanzaHandlers.js.map +1 -1
- package/lib/module/networking/xmpp/handleStanzas.xmpp.js +5 -3
- package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
- package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js +25 -0
- package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
- package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
- package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
- package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js +25 -0
- package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
- package/lib/module/networking/xmppClient.js +64 -0
- package/lib/module/networking/xmppClient.js.map +1 -1
- package/lib/module/roomStore/persistence.js +78 -5
- package/lib/module/roomStore/persistence.js.map +1 -1
- package/lib/module/roomStore/roomHeapSlice.js +4 -0
- package/lib/module/roomStore/roomHeapSlice.js.map +1 -1
- package/lib/module/roomStore/roomsSlice.js +109 -0
- package/lib/module/roomStore/roomsSlice.js.map +1 -1
- package/lib/module/types/models/attachment.model.js +2 -0
- package/lib/module/types/models/attachment.model.js.map +1 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/assets/icons.d.ts +6 -1
- package/lib/typescript/assets/icons.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/AlbumGrid.d.ts +21 -0
- package/lib/typescript/components/MainComponents/AlbumGrid.d.ts.map +1 -0
- package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts +17 -0
- package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts.map +1 -0
- package/lib/typescript/components/MessageBubble/MediaCaption.d.ts +12 -0
- package/lib/typescript/components/MessageBubble/MediaCaption.d.ts.map +1 -0
- package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
- package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/LastMessageItem.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts +2 -0
- package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts +2 -0
- package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts +2 -0
- package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts +2 -0
- package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts.map +1 -1
- package/lib/typescript/components/Thread/ThreadWrapper.d.ts.map +1 -1
- package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
- package/lib/typescript/components/styled/MessageImage.d.ts +10 -0
- package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
- package/lib/typescript/components/styled/SendInput.d.ts +7 -1
- package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
- package/lib/typescript/components/styled/VideoMessage.d.ts +7 -0
- package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
- package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
- package/lib/typescript/e2ee/fileEnvelope.d.ts +1 -1
- package/lib/typescript/e2ee/fileEnvelope.d.ts.map +1 -1
- package/lib/typescript/e2ee/sealedBody.d.ts +16 -0
- package/lib/typescript/e2ee/sealedBody.d.ts.map +1 -1
- package/lib/typescript/helpers/albumLayout.d.ts +66 -0
- package/lib/typescript/helpers/albumLayout.d.ts.map +1 -0
- package/lib/typescript/helpers/attachments.d.ts +55 -0
- package/lib/typescript/helpers/attachments.d.ts.map +1 -0
- package/lib/typescript/helpers/caption.d.ts +64 -0
- package/lib/typescript/helpers/caption.d.ts.map +1 -0
- package/lib/typescript/helpers/createMainMessageForThread.d.ts.map +1 -1
- package/lib/typescript/helpers/createMessageFromXml.d.ts.map +1 -1
- package/lib/typescript/helpers/fileKind.d.ts +20 -0
- package/lib/typescript/helpers/fileKind.d.ts.map +1 -0
- package/lib/typescript/helpers/getDataFromXml.d.ts.map +1 -1
- package/lib/typescript/helpers/mapWithConcurrency.d.ts +9 -0
- package/lib/typescript/helpers/mapWithConcurrency.d.ts.map +1 -0
- package/lib/typescript/helpers/mediaLimits.d.ts +13 -0
- package/lib/typescript/helpers/mediaLimits.d.ts.map +1 -0
- package/lib/typescript/helpers/mediaSendRetry.d.ts +26 -0
- package/lib/typescript/helpers/mediaSendRetry.d.ts.map +1 -0
- package/lib/typescript/helpers/mediaWireData.d.ts +16 -0
- package/lib/typescript/helpers/mediaWireData.d.ts.map +1 -0
- package/lib/typescript/helpers/mentionSpans.d.ts +8 -0
- package/lib/typescript/helpers/mentionSpans.d.ts.map +1 -0
- package/lib/typescript/helpers/mimeToExtension.d.ts +7 -0
- package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -1
- package/lib/typescript/helpers/readReceipts.d.ts +82 -0
- package/lib/typescript/helpers/readReceipts.d.ts.map +1 -0
- package/lib/typescript/helpers/repairFilenameEncoding.d.ts +12 -0
- package/lib/typescript/helpers/repairFilenameEncoding.d.ts.map +1 -0
- package/lib/typescript/helpers/safeJson.d.ts +2 -0
- package/lib/typescript/helpers/safeJson.d.ts.map +1 -0
- package/lib/typescript/hooks/useSendMessage.d.ts +2 -1
- package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/i18n/strings.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/auth.api.d.ts +2 -0
- package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
- package/lib/typescript/networking/stanzaHandlers.d.ts +7 -3
- package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
- package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts.map +1 -1
- package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts +3 -0
- package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts.map +1 -0
- package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts +12 -0
- package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts.map +1 -1
- package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts +3 -0
- package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts.map +1 -0
- package/lib/typescript/networking/xmppClient.d.ts +15 -0
- package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
- package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
- package/lib/typescript/roomStore/roomHeapSlice.d.ts +22 -1
- package/lib/typescript/roomStore/roomHeapSlice.d.ts.map +1 -1
- package/lib/typescript/roomStore/roomsSlice.d.ts +67 -3
- package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
- package/lib/typescript/types/models/attachment.model.d.ts +52 -0
- package/lib/typescript/types/models/attachment.model.d.ts.map +1 -0
- package/lib/typescript/types/models/message.model.d.ts +26 -0
- package/lib/typescript/types/models/message.model.d.ts.map +1 -1
- package/lib/typescript/types/models/modal.model.d.ts +7 -0
- package/lib/typescript/types/models/modal.model.d.ts.map +1 -1
- package/lib/typescript/types/models/room.model.d.ts +2 -0
- package/lib/typescript/types/models/room.model.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +29 -1
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/lib/typescript/version.d.ts +1 -1
- package/package.json +4 -1
- package/plugins/notification-service/Info.plist +31 -0
- package/plugins/notification-service/NotificationService.swift +82 -0
- package/plugins/withNotificationServiceExtension.js +137 -0
- package/src/assets/icons.tsx +20 -2
- package/src/components/MainComponents/AlbumGrid.tsx +183 -0
- package/src/components/MainComponents/ChatRoom.tsx +4 -2
- package/src/components/MainComponents/MediaMessage.tsx +246 -130
- package/src/components/MainComponents/MediaUploadOverlay.tsx +102 -0
- package/src/components/MessageBubble/MediaCaption.tsx +45 -0
- package/src/components/MessageBubble/Message.tsx +71 -3
- package/src/components/MessageBubble/MessageInteractions.tsx +2 -1
- package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +64 -0
- package/src/components/RoomComponents/LastMessageItem.tsx +15 -4
- package/src/components/RoomComponents/styled/LastAudioMessage.tsx +6 -3
- package/src/components/RoomComponents/styled/LastMessageFile.tsx +6 -2
- package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +6 -2
- package/src/components/RoomComponents/styled/LastMessageVideo.tsx +6 -2
- package/src/components/Thread/ThreadWrapper.tsx +5 -9
- package/src/components/styled/MediaFilePreview.tsx +49 -34
- package/src/components/styled/MessageImage.tsx +21 -5
- package/src/components/styled/SendInput.tsx +101 -87
- package/src/components/styled/VideoMessage.tsx +34 -9
- package/src/context/xmppProvider.tsx +32 -0
- package/src/e2ee/fileEnvelope.ts +1 -1
- package/src/e2ee/sealedBody.ts +44 -5
- package/src/helpers/albumLayout.ts +207 -0
- package/src/helpers/attachments.ts +316 -0
- package/src/helpers/caption.ts +283 -0
- package/src/helpers/createMainMessageForThread.ts +6 -1
- package/src/helpers/createMessageFromXml.ts +21 -5
- package/src/helpers/fileKind.ts +38 -0
- package/src/helpers/getDataFromXml.ts +5 -1
- package/src/helpers/mapWithConcurrency.ts +32 -0
- package/src/helpers/mediaLimits.ts +27 -0
- package/src/helpers/mediaSendRetry.ts +59 -0
- package/src/helpers/mediaWireData.ts +57 -0
- package/src/helpers/mentionSpans.ts +17 -0
- package/src/helpers/mimeToExtension.ts +51 -0
- package/src/helpers/readReceipts.ts +219 -0
- package/src/helpers/repairFilenameEncoding.ts +82 -0
- package/src/helpers/safeJson.ts +11 -0
- package/src/hooks/useSendMessage.tsx +448 -249
- package/src/i18n/strings.ts +96 -0
- package/src/networking/api-requests/auth.api.ts +60 -3
- package/src/networking/stanzaHandlers.ts +91 -2
- package/src/networking/xmpp/handleStanzas.xmpp.ts +6 -2
- package/src/networking/xmpp/sendDisplayedMarker.xmpp.ts +31 -0
- package/src/networking/xmpp/sendMediaMessage.xmpp.ts +60 -3
- package/src/networking/xmpp/sendReceivedReceipt.xmpp.ts +31 -0
- package/src/networking/xmppClient.ts +48 -0
- package/src/roomStore/persistence.ts +94 -3
- package/src/roomStore/roomHeapSlice.ts +23 -1
- package/src/roomStore/roomsSlice.ts +112 -1
- package/src/types/models/attachment.model.ts +51 -0
- package/src/types/models/message.model.ts +29 -0
- package/src/types/models/modal.model.ts +7 -0
- package/src/types/models/room.model.ts +2 -0
- package/src/types/types.ts +35 -1
- 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 {
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
167
|
-
const
|
|
182
|
+
const mimeType = attachment.mimetype || '';
|
|
183
|
+
const rawLocation = attachment.location;
|
|
184
|
+
const rawPreview = attachment.locationPreview;
|
|
168
185
|
const location = appendFileToken(rawLocation, fileToken);
|
|
169
|
-
const
|
|
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
|
-
|
|
172
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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={
|
|
268
|
+
src={location || ''}
|
|
198
269
|
mimeType={mimeType}
|
|
199
270
|
fileName={displayName}
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
<
|
|
279
|
+
<FileDownload
|
|
280
|
+
fileURL={location ? location : ''}
|
|
209
281
|
fileName={displayName}
|
|
210
|
-
|
|
211
|
-
previewUri={messageText || location || ''}
|
|
282
|
+
mimetype={mimeType}
|
|
212
283
|
isUser={isUser}
|
|
213
|
-
|
|
284
|
+
originalName={originalName}
|
|
285
|
+
duration={duration}
|
|
286
|
+
waveForm={waveForm}
|
|
214
287
|
/>
|
|
215
288
|
);
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
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
|
|