@ethora/chat-component-rn 26.5.20 → 26.6.10
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 +80 -0
- package/README.md +43 -3
- package/index.js +1 -1
- package/lib/commonjs/components/MainComponents/ChatHeader.js +34 -9
- package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatWrapper.js +17 -0
- package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/commonjs/components/MainComponents/LanguageSelectorButton.js +62 -0
- package/lib/commonjs/components/MainComponents/LanguageSelectorButton.js.map +1 -0
- package/lib/commonjs/components/MainComponents/LanguageSelectorModal.js +253 -0
- package/lib/commonjs/components/MainComponents/LanguageSelectorModal.js.map +1 -0
- package/lib/commonjs/components/MainComponents/RoomList.js +23 -9
- package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
- package/lib/commonjs/components/MessageBubble/Message.js +72 -11
- package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
- package/lib/commonjs/components/MessageBubble/MessageTranslate.js +160 -0
- package/lib/commonjs/components/MessageBubble/MessageTranslate.js.map +1 -0
- package/lib/commonjs/components/MessageBubble/TranslatedMessageBody.js +59 -0
- package/lib/commonjs/components/MessageBubble/TranslatedMessageBody.js.map +1 -0
- package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js +1 -1
- package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +6 -0
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +26 -8
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/commonjs/components/Modals/ModalHeaderComponent.js +15 -1
- package/lib/commonjs/components/Modals/ModalHeaderComponent.js.map +1 -1
- package/lib/commonjs/components/UsersList/UsersList.js +3 -1
- package/lib/commonjs/components/UsersList/UsersList.js.map +1 -1
- package/lib/commonjs/components/VideoCalls/CallButtons.js +159 -0
- package/lib/commonjs/components/VideoCalls/CallButtons.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/CallIcons.js +328 -0
- package/lib/commonjs/components/VideoCalls/CallIcons.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js +550 -0
- package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/VideoCallSession.js +636 -0
- package/lib/commonjs/components/VideoCalls/VideoCallSession.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/livekitRuntime.js +91 -0
- package/lib/commonjs/components/VideoCalls/livekitRuntime.js.map +1 -0
- package/lib/commonjs/components/VideoCalls/useCallKeep.js +215 -0
- package/lib/commonjs/components/VideoCalls/useCallKeep.js.map +1 -0
- package/lib/commonjs/components/styled/AudioMessage.js +91 -33
- package/lib/commonjs/components/styled/AudioMessage.js.map +1 -1
- package/lib/commonjs/components/styled/RoomListComponents/index.js +13 -1
- package/lib/commonjs/components/styled/RoomListComponents/index.js.map +1 -1
- package/lib/commonjs/components/styled/SendInput.js +111 -51
- package/lib/commonjs/components/styled/SendInput.js.map +1 -1
- package/lib/commonjs/components/styled/StyledComponents.js +9 -4
- package/lib/commonjs/components/styled/StyledComponents.js.map +1 -1
- package/lib/commonjs/components/styled/StyledInputComponents/Composing.js +2 -0
- package/lib/commonjs/components/styled/StyledInputComponents/Composing.js.map +1 -1
- package/lib/commonjs/context/xmppProvider.js +10 -3
- package/lib/commonjs/context/xmppProvider.js.map +1 -1
- package/lib/commonjs/helpers/callLogMessage.js +162 -0
- package/lib/commonjs/helpers/callLogMessage.js.map +1 -0
- package/lib/commonjs/helpers/callPush.js +61 -0
- package/lib/commonjs/helpers/callPush.js.map +1 -0
- package/lib/commonjs/helpers/constants/LANGUAGE_OPTIONS.js +24 -12
- package/lib/commonjs/helpers/constants/LANGUAGE_OPTIONS.js.map +1 -1
- package/lib/commonjs/helpers/createUserNameFromSetUser.js +51 -2
- package/lib/commonjs/helpers/createUserNameFromSetUser.js.map +1 -1
- package/lib/commonjs/helpers/getDataFromXml.js +19 -1
- package/lib/commonjs/helpers/getDataFromXml.js.map +1 -1
- package/lib/commonjs/helpers/getElementFont.js +29 -0
- package/lib/commonjs/helpers/getElementFont.js.map +1 -0
- package/lib/commonjs/helpers/headerLayout.js +35 -0
- package/lib/commonjs/helpers/headerLayout.js.map +1 -0
- package/lib/commonjs/helpers/incomingCallFromPush.js +74 -0
- package/lib/commonjs/helpers/incomingCallFromPush.js.map +1 -0
- package/lib/commonjs/helpers/insertMessageWithDelimiter.js +13 -1
- package/lib/commonjs/helpers/insertMessageWithDelimiter.js.map +1 -1
- package/lib/commonjs/helpers/isUnresolvedSenderId.js +32 -0
- package/lib/commonjs/helpers/isUnresolvedSenderId.js.map +1 -0
- package/lib/commonjs/hooks/useChatSettingState.js +2 -0
- package/lib/commonjs/hooks/useChatSettingState.js.map +1 -1
- package/lib/commonjs/hooks/useMessageTranslation.js +51 -0
- package/lib/commonjs/hooks/useMessageTranslation.js.map +1 -0
- package/lib/commonjs/hooks/usePushNotifications.js +13 -3
- package/lib/commonjs/hooks/usePushNotifications.js.map +1 -1
- package/lib/commonjs/hooks/useSendMessage.js +12 -2
- package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
- package/lib/commonjs/i18n/strings.js +1308 -0
- package/lib/commonjs/i18n/strings.js.map +1 -0
- package/lib/commonjs/i18n/useT.js +60 -0
- package/lib/commonjs/i18n/useT.js.map +1 -0
- package/lib/commonjs/main.js +44 -0
- package/lib/commonjs/main.js.map +1 -1
- package/lib/commonjs/networking/api-requests/push.api.js +34 -5
- package/lib/commonjs/networking/api-requests/push.api.js.map +1 -1
- package/lib/commonjs/networking/api-requests/rooms.api.js +108 -0
- package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/commonjs/networking/callTokenStanza.js +531 -0
- package/lib/commonjs/networking/callTokenStanza.js.map +1 -0
- package/lib/commonjs/networking/outboundQueue.js.map +1 -1
- package/lib/commonjs/networking/stanzaHandlers.js +75 -8
- package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
- package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +47 -11
- package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
- package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js +2 -1
- package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
- package/lib/commonjs/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js +42 -5
- package/lib/commonjs/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js.map +1 -1
- package/lib/commonjs/networking/xmppClient.js +78 -16
- package/lib/commonjs/networking/xmppClient.js.map +1 -1
- package/lib/commonjs/roomStore/callSlice.js +144 -0
- package/lib/commonjs/roomStore/callSlice.js.map +1 -0
- package/lib/commonjs/roomStore/chatSettingsSlice.js +8 -2
- package/lib/commonjs/roomStore/chatSettingsSlice.js.map +1 -1
- package/lib/commonjs/roomStore/index.js +7 -1
- package/lib/commonjs/roomStore/index.js.map +1 -1
- package/lib/commonjs/roomStore/persistence.js +69 -5
- package/lib/commonjs/roomStore/persistence.js.map +1 -1
- package/lib/commonjs/roomStore/roomsSlice.js +136 -6
- package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
- package/lib/commonjs/utils/translateModePolicy.js +33 -0
- package/lib/commonjs/utils/translateModePolicy.js.map +1 -0
- package/lib/module/components/MainComponents/ChatHeader.js +34 -9
- package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/module/components/MainComponents/ChatWrapper.js +17 -0
- package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/module/components/MainComponents/LanguageSelectorButton.js +57 -0
- package/lib/module/components/MainComponents/LanguageSelectorButton.js.map +1 -0
- package/lib/module/components/MainComponents/LanguageSelectorModal.js +246 -0
- package/lib/module/components/MainComponents/LanguageSelectorModal.js.map +1 -0
- package/lib/module/components/MainComponents/RoomList.js +23 -9
- package/lib/module/components/MainComponents/RoomList.js.map +1 -1
- package/lib/module/components/MessageBubble/Message.js +73 -12
- package/lib/module/components/MessageBubble/Message.js.map +1 -1
- package/lib/module/components/MessageBubble/MessageTranslate.js +155 -0
- package/lib/module/components/MessageBubble/MessageTranslate.js.map +1 -0
- package/lib/module/components/MessageBubble/TranslatedMessageBody.js +53 -0
- package/lib/module/components/MessageBubble/TranslatedMessageBody.js.map +1 -0
- package/lib/module/components/Modals/AttachSheet/AttachSheet.js +1 -1
- package/lib/module/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +6 -0
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +27 -9
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/module/components/Modals/ModalHeaderComponent.js +15 -1
- package/lib/module/components/Modals/ModalHeaderComponent.js.map +1 -1
- package/lib/module/components/UsersList/UsersList.js +3 -1
- package/lib/module/components/UsersList/UsersList.js.map +1 -1
- package/lib/module/components/VideoCalls/CallButtons.js +154 -0
- package/lib/module/components/VideoCalls/CallButtons.js.map +1 -0
- package/lib/module/components/VideoCalls/CallIcons.js +309 -0
- package/lib/module/components/VideoCalls/CallIcons.js.map +1 -0
- package/lib/module/components/VideoCalls/VideoCallOverlay.js +544 -0
- package/lib/module/components/VideoCalls/VideoCallOverlay.js.map +1 -0
- package/lib/module/components/VideoCalls/VideoCallSession.js +630 -0
- package/lib/module/components/VideoCalls/VideoCallSession.js.map +1 -0
- package/lib/module/components/VideoCalls/livekitRuntime.js +83 -0
- package/lib/module/components/VideoCalls/livekitRuntime.js.map +1 -0
- package/lib/module/components/VideoCalls/useCallKeep.js +210 -0
- package/lib/module/components/VideoCalls/useCallKeep.js.map +1 -0
- package/lib/module/components/styled/AudioMessage.js +91 -33
- package/lib/module/components/styled/AudioMessage.js.map +1 -1
- package/lib/module/components/styled/RoomListComponents/index.js +13 -1
- package/lib/module/components/styled/RoomListComponents/index.js.map +1 -1
- package/lib/module/components/styled/SendInput.js +112 -52
- package/lib/module/components/styled/SendInput.js.map +1 -1
- package/lib/module/components/styled/StyledComponents.js +9 -4
- package/lib/module/components/styled/StyledComponents.js.map +1 -1
- package/lib/module/components/styled/StyledInputComponents/Composing.js +2 -0
- package/lib/module/components/styled/StyledInputComponents/Composing.js.map +1 -1
- package/lib/module/context/xmppProvider.js +11 -4
- package/lib/module/context/xmppProvider.js.map +1 -1
- package/lib/module/helpers/callLogMessage.js +152 -0
- package/lib/module/helpers/callLogMessage.js.map +1 -0
- package/lib/module/helpers/callPush.js +55 -0
- package/lib/module/helpers/callPush.js.map +1 -0
- package/lib/module/helpers/constants/LANGUAGE_OPTIONS.js +24 -12
- package/lib/module/helpers/constants/LANGUAGE_OPTIONS.js.map +1 -1
- package/lib/module/helpers/createUserNameFromSetUser.js +49 -1
- package/lib/module/helpers/createUserNameFromSetUser.js.map +1 -1
- package/lib/module/helpers/getDataFromXml.js +20 -1
- package/lib/module/helpers/getDataFromXml.js.map +1 -1
- package/lib/module/helpers/getElementFont.js +24 -0
- package/lib/module/helpers/getElementFont.js.map +1 -0
- package/lib/module/helpers/headerLayout.js +30 -0
- package/lib/module/helpers/headerLayout.js.map +1 -0
- package/lib/module/helpers/incomingCallFromPush.js +70 -0
- package/lib/module/helpers/incomingCallFromPush.js.map +1 -0
- package/lib/module/helpers/insertMessageWithDelimiter.js +13 -1
- package/lib/module/helpers/insertMessageWithDelimiter.js.map +1 -1
- package/lib/module/helpers/isUnresolvedSenderId.js +27 -0
- package/lib/module/helpers/isUnresolvedSenderId.js.map +1 -0
- package/lib/module/hooks/useChatSettingState.js +2 -0
- package/lib/module/hooks/useChatSettingState.js.map +1 -1
- package/lib/module/hooks/useMessageTranslation.js +46 -0
- package/lib/module/hooks/useMessageTranslation.js.map +1 -0
- package/lib/module/hooks/usePushNotifications.js +13 -3
- package/lib/module/hooks/usePushNotifications.js.map +1 -1
- package/lib/module/hooks/useSendMessage.js +12 -2
- package/lib/module/hooks/useSendMessage.js.map +1 -1
- package/lib/module/i18n/strings.js +1300 -0
- package/lib/module/i18n/strings.js.map +1 -0
- package/lib/module/i18n/useT.js +55 -0
- package/lib/module/i18n/useT.js.map +1 -0
- package/lib/module/main.js +7 -1
- package/lib/module/main.js.map +1 -1
- package/lib/module/networking/api-requests/push.api.js +35 -5
- package/lib/module/networking/api-requests/push.api.js.map +1 -1
- package/lib/module/networking/api-requests/rooms.api.js +109 -1
- package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/module/networking/callTokenStanza.js +526 -0
- package/lib/module/networking/callTokenStanza.js.map +1 -0
- package/lib/module/networking/outboundQueue.js.map +1 -1
- package/lib/module/networking/stanzaHandlers.js +76 -9
- package/lib/module/networking/stanzaHandlers.js.map +1 -1
- package/lib/module/networking/xmpp/handleStanzas.xmpp.js +48 -11
- package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
- package/lib/module/networking/xmpp/sendTextMessage.xmpp.js +1 -1
- package/lib/module/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
- package/lib/module/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js +43 -5
- package/lib/module/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js.map +1 -1
- package/lib/module/networking/xmppClient.js +78 -16
- package/lib/module/networking/xmppClient.js.map +1 -1
- package/lib/module/roomStore/callSlice.js +130 -0
- package/lib/module/roomStore/callSlice.js.map +1 -0
- package/lib/module/roomStore/chatSettingsSlice.js +6 -1
- package/lib/module/roomStore/chatSettingsSlice.js.map +1 -1
- package/lib/module/roomStore/index.js +6 -1
- package/lib/module/roomStore/index.js.map +1 -1
- package/lib/module/roomStore/persistence.js +69 -5
- package/lib/module/roomStore/persistence.js.map +1 -1
- package/lib/module/roomStore/roomsSlice.js +134 -5
- package/lib/module/roomStore/roomsSlice.js.map +1 -1
- package/lib/module/utils/translateModePolicy.js +27 -0
- package/lib/module/utils/translateModePolicy.js.map +1 -0
- package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/LanguageSelectorButton.d.ts +12 -0
- package/lib/typescript/components/MainComponents/LanguageSelectorButton.d.ts.map +1 -0
- package/lib/typescript/components/MainComponents/LanguageSelectorModal.d.ts +31 -0
- package/lib/typescript/components/MainComponents/LanguageSelectorModal.d.ts.map +1 -0
- package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble/MessageTranslate.d.ts +44 -0
- package/lib/typescript/components/MessageBubble/MessageTranslate.d.ts.map +1 -0
- package/lib/typescript/components/MessageBubble/TranslatedMessageBody.d.ts +24 -0
- package/lib/typescript/components/MessageBubble/TranslatedMessageBody.d.ts.map +1 -0
- package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ModalHeaderComponent.d.ts +3 -0
- package/lib/typescript/components/Modals/ModalHeaderComponent.d.ts.map +1 -1
- package/lib/typescript/components/UsersList/UsersList.d.ts.map +1 -1
- package/lib/typescript/components/VideoCalls/CallButtons.d.ts +16 -0
- package/lib/typescript/components/VideoCalls/CallButtons.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/CallIcons.d.ts +30 -0
- package/lib/typescript/components/VideoCalls/CallIcons.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts +10 -0
- package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts +38 -0
- package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts +52 -0
- package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts.map +1 -0
- package/lib/typescript/components/VideoCalls/useCallKeep.d.ts +32 -0
- package/lib/typescript/components/VideoCalls/useCallKeep.d.ts.map +1 -0
- package/lib/typescript/components/styled/AudioMessage.d.ts.map +1 -1
- package/lib/typescript/components/styled/RoomListComponents/index.d.ts.map +1 -1
- package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
- package/lib/typescript/components/styled/StyledComponents.d.ts.map +1 -1
- package/lib/typescript/components/styled/StyledInputComponents/Composing.d.ts.map +1 -1
- package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
- package/lib/typescript/helpers/callLogMessage.d.ts +34 -0
- package/lib/typescript/helpers/callLogMessage.d.ts.map +1 -0
- package/lib/typescript/helpers/callPush.d.ts +23 -0
- package/lib/typescript/helpers/callPush.d.ts.map +1 -0
- package/lib/typescript/helpers/constants/LANGUAGE_OPTIONS.d.ts +14 -0
- package/lib/typescript/helpers/constants/LANGUAGE_OPTIONS.d.ts.map +1 -1
- package/lib/typescript/helpers/createUserNameFromSetUser.d.ts +33 -1
- package/lib/typescript/helpers/createUserNameFromSetUser.d.ts.map +1 -1
- package/lib/typescript/helpers/getDataFromXml.d.ts.map +1 -1
- package/lib/typescript/helpers/getElementFont.d.ts +13 -0
- package/lib/typescript/helpers/getElementFont.d.ts.map +1 -0
- package/lib/typescript/helpers/headerLayout.d.ts +26 -0
- package/lib/typescript/helpers/headerLayout.d.ts.map +1 -0
- package/lib/typescript/helpers/incomingCallFromPush.d.ts +14 -0
- package/lib/typescript/helpers/incomingCallFromPush.d.ts.map +1 -0
- package/lib/typescript/helpers/insertMessageWithDelimiter.d.ts.map +1 -1
- package/lib/typescript/helpers/isUnresolvedSenderId.d.ts +2 -0
- package/lib/typescript/helpers/isUnresolvedSenderId.d.ts.map +1 -0
- package/lib/typescript/hooks/useChatSettingState.d.ts +2 -1
- package/lib/typescript/hooks/useChatSettingState.d.ts.map +1 -1
- package/lib/typescript/hooks/useMessageTranslation.d.ts +25 -0
- package/lib/typescript/hooks/useMessageTranslation.d.ts.map +1 -0
- package/lib/typescript/hooks/usePushNotifications.d.ts.map +1 -1
- package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/i18n/strings.d.ts +16 -0
- package/lib/typescript/i18n/strings.d.ts.map +1 -0
- package/lib/typescript/i18n/useT.d.ts +34 -0
- package/lib/typescript/i18n/useT.d.ts.map +1 -0
- package/lib/typescript/main.d.ts +6 -1
- package/lib/typescript/main.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/push.api.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/rooms.api.d.ts +3 -0
- package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
- package/lib/typescript/networking/callTokenStanza.d.ts +14 -0
- package/lib/typescript/networking/callTokenStanza.d.ts.map +1 -0
- package/lib/typescript/networking/outboundQueue.d.ts +1 -0
- package/lib/typescript/networking/outboundQueue.d.ts.map +1 -1
- 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/sendTextMessage.xmpp.d.ts +10 -0
- package/lib/typescript/networking/xmpp/sendTextMessage.xmpp.d.ts.map +1 -1
- package/lib/typescript/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.d.ts +16 -1
- package/lib/typescript/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.d.ts.map +1 -1
- package/lib/typescript/networking/xmppClient.d.ts +1 -1
- package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
- package/lib/typescript/roomStore/callSlice.d.ts +39 -0
- package/lib/typescript/roomStore/callSlice.d.ts.map +1 -0
- package/lib/typescript/roomStore/chatSettingsSlice.d.ts +10 -1
- package/lib/typescript/roomStore/chatSettingsSlice.d.ts.map +1 -1
- package/lib/typescript/roomStore/index.d.ts +4 -0
- package/lib/typescript/roomStore/index.d.ts.map +1 -1
- package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
- package/lib/typescript/roomStore/roomsSlice.d.ts +6 -1
- package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
- package/lib/typescript/types/models/language.model.d.ts +1 -1
- package/lib/typescript/types/models/language.model.d.ts.map +1 -1
- package/lib/typescript/types/models/message.model.d.ts +7 -0
- package/lib/typescript/types/models/message.model.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +165 -1
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/lib/typescript/utils/translateModePolicy.d.ts +20 -0
- package/lib/typescript/utils/translateModePolicy.d.ts.map +1 -0
- package/package.json +20 -4
- package/src/components/MainComponents/ChatHeader.tsx +44 -11
- package/src/components/MainComponents/ChatWrapper.tsx +15 -0
- package/src/components/MainComponents/LanguageSelectorButton.tsx +53 -0
- package/src/components/MainComponents/LanguageSelectorModal.tsx +276 -0
- package/src/components/MainComponents/RoomList.tsx +27 -9
- package/src/components/MessageBubble/Message.tsx +109 -17
- package/src/components/MessageBubble/MessageTranslate.tsx +182 -0
- package/src/components/MessageBubble/TranslatedMessageBody.tsx +59 -0
- package/src/components/Modals/AttachSheet/AttachSheet.tsx +1 -3
- package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +10 -4
- package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +25 -8
- package/src/components/Modals/ModalHeaderComponent.tsx +20 -3
- package/src/components/UsersList/UsersList.tsx +3 -1
- package/src/components/VideoCalls/CallButtons.tsx +171 -0
- package/src/components/VideoCalls/CallIcons.tsx +246 -0
- package/src/components/VideoCalls/VideoCallOverlay.tsx +627 -0
- package/src/components/VideoCalls/VideoCallSession.tsx +675 -0
- package/src/components/VideoCalls/livekitRuntime.ts +100 -0
- package/src/components/VideoCalls/useCallKeep.ts +252 -0
- package/src/components/styled/AudioMessage.tsx +97 -37
- package/src/components/styled/RoomListComponents/index.tsx +15 -3
- package/src/components/styled/SendInput.tsx +125 -55
- package/src/components/styled/StyledComponents.ts +9 -6
- package/src/components/styled/StyledInputComponents/Composing.tsx +7 -1
- package/src/context/xmppProvider.tsx +14 -1
- package/src/helpers/callLogMessage.ts +174 -0
- package/src/helpers/callPush.ts +90 -0
- package/src/helpers/constants/LANGUAGE_OPTIONS.ts +20 -5
- package/src/helpers/createUserNameFromSetUser.ts +68 -2
- package/src/helpers/getDataFromXml.ts +40 -4
- package/src/helpers/getElementFont.ts +25 -0
- package/src/helpers/headerLayout.ts +30 -0
- package/src/helpers/incomingCallFromPush.ts +83 -0
- package/src/helpers/insertMessageWithDelimiter.ts +27 -9
- package/src/helpers/isUnresolvedSenderId.ts +25 -0
- package/src/hooks/useChatSettingState.tsx +4 -0
- package/src/hooks/useMessageTranslation.ts +70 -0
- package/src/hooks/usePushNotifications.ts +15 -3
- package/src/hooks/useSendMessage.tsx +15 -2
- package/src/i18n/strings.ts +1322 -0
- package/src/i18n/useT.ts +56 -0
- package/src/main.ts +18 -1
- package/src/networking/api-requests/push.api.ts +39 -8
- package/src/networking/api-requests/rooms.api.ts +109 -1
- package/src/networking/callTokenStanza.ts +636 -0
- package/src/networking/outboundQueue.ts +1 -0
- package/src/networking/stanzaHandlers.ts +74 -15
- package/src/networking/xmpp/handleStanzas.xmpp.ts +52 -11
- package/src/networking/xmpp/sendTextMessage.xmpp.ts +1 -1
- package/src/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.ts +41 -5
- package/src/networking/xmppClient.ts +97 -28
- package/src/roomStore/callSlice.ts +190 -0
- package/src/roomStore/chatSettingsSlice.ts +16 -0
- package/src/roomStore/index.ts +5 -0
- package/src/roomStore/persistence.ts +103 -3
- package/src/roomStore/roomsSlice.ts +149 -4
- package/src/types/models/language.model.ts +17 -1
- package/src/types/models/message.model.ts +9 -0
- package/src/types/types.ts +178 -2
- package/src/utils/translateModePolicy.ts +31 -0
|
@@ -13,12 +13,21 @@ import { KeyboardAvoidingView, Platform, View, Text, TouchableOpacity, Alert, Li
|
|
|
13
13
|
import AttachSheet from '../Modals/AttachSheet/AttachSheet';
|
|
14
14
|
import { MediaFilePreview } from './MediaFilePreview';
|
|
15
15
|
import { getIconColor } from '../../helpers/getIconColor';
|
|
16
|
+
import { getElementFont } from '../../helpers/getElementFont';
|
|
17
|
+
import { useT } from '../../i18n/useT';
|
|
16
18
|
import * as ImagePicker from 'expo-image-picker';
|
|
17
19
|
import * as DocumentPicker from 'expo-document-picker';
|
|
18
20
|
import * as ImageManipulator from 'expo-image-manipulator';
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
21
|
+
// expo-audio replaces the discontinued expo-av: `useAudioRecorder` owns a
|
|
22
|
+
// single reusable recorder for this input's lifetime (prepare → record →
|
|
23
|
+
// stop → prepare again), and the module-level helpers cover permissions
|
|
24
|
+
// and the iOS audio session.
|
|
25
|
+
import {
|
|
26
|
+
useAudioRecorder,
|
|
27
|
+
setAudioModeAsync,
|
|
28
|
+
requestRecordingPermissionsAsync,
|
|
29
|
+
RecordingPresets,
|
|
30
|
+
} from 'expo-audio';
|
|
22
31
|
|
|
23
32
|
// iOS photos default to HEIC; many web backends (incl. ours) 500 on
|
|
24
33
|
// HEIC uploads because they can't decode it. Convert to JPEG before
|
|
@@ -87,6 +96,7 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
87
96
|
isLoading,
|
|
88
97
|
isMessageProcessing,
|
|
89
98
|
}) => {
|
|
99
|
+
const t = useT();
|
|
90
100
|
const [message, setMessage] = useState('');
|
|
91
101
|
const [isRecording, setIsRecording] = useState(false);
|
|
92
102
|
// Live counter (seconds) for the recording overlay. Updated by a
|
|
@@ -96,14 +106,20 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
96
106
|
const [filePreviews, setFilePreviews] = useState<MediaFile[]>([]);
|
|
97
107
|
const [showMediaMenu, setShowMediaMenu] = useState(false);
|
|
98
108
|
|
|
99
|
-
// expo-
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
|
|
109
|
+
// expo-audio's recorder is a long-lived native object owned by the hook
|
|
110
|
+
// (released automatically on unmount) and reused across recordings, so
|
|
111
|
+
// unlike expo-av's one-shot `Audio.Recording` there is no per-recording
|
|
112
|
+
// instance to hold. The hook has to run unconditionally, but the
|
|
113
|
+
// constructor is inert on both platforms — iOS allocates a bare
|
|
114
|
+
// AVAudioRecorder (no file), Android leaves its MediaRecorder null.
|
|
115
|
+
// Nothing claims the mic, writes a file, or switches the iOS session to
|
|
116
|
+
// playAndRecord until `prepareToRecordAsync`, so consumers with
|
|
117
|
+
// `enableAudio` off carry an idle object and nothing more.
|
|
118
|
+
const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
|
|
119
|
+
// Tracks whether a recording is actually in flight, so cancel/stop and
|
|
120
|
+
// the unmount teardown can tell "never started" from "needs stopping"
|
|
121
|
+
// without reading native state.
|
|
122
|
+
const isRecordingRef = useRef(false);
|
|
107
123
|
const recordingTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
108
124
|
|
|
109
125
|
// Refs shadow `message` and `filePreviews` so handleSendClick can:
|
|
@@ -120,6 +136,13 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
120
136
|
// (versus duplicating the send with stale closure state).
|
|
121
137
|
const sendingRef = useRef(false);
|
|
122
138
|
|
|
139
|
+
// Re-entry guard for the document picker. expo-document-picker keeps a
|
|
140
|
+
// single native `pickingContext`; invoking getDocumentAsync again while a
|
|
141
|
+
// pick is still in flight throws "Different document picking in progress"
|
|
142
|
+
// (iOS, bug #4). A double-fire happens on a fast double-tap of the
|
|
143
|
+
// "Document" row or a dev-mode double-run of the sheet's close callback.
|
|
144
|
+
const pickingDocRef = useRef(false);
|
|
145
|
+
|
|
123
146
|
const handleFileSelect = (files: MediaFile[]) => {
|
|
124
147
|
// Enforce the upload size cap before anything reaches the network.
|
|
125
148
|
// The backend rejects oversized bodies with HTTP 413; catching it
|
|
@@ -237,7 +260,21 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
237
260
|
};
|
|
238
261
|
|
|
239
262
|
const handleFileSelection = async () => {
|
|
263
|
+
// Drop overlapping invocations — a second getDocumentAsync while the
|
|
264
|
+
// first is still presented throws PickingInProgressException on iOS.
|
|
265
|
+
if (pickingDocRef.current) {return;}
|
|
266
|
+
pickingDocRef.current = true;
|
|
240
267
|
try {
|
|
268
|
+
// Let the attach-sheet's modal finish its NATIVE dismissal before we
|
|
269
|
+
// present the picker. The sheet fires this handler right after its
|
|
270
|
+
// slide-out animation, but the underlying RN Modal view-controller
|
|
271
|
+
// dismissal is async — presenting the document picker over a still-
|
|
272
|
+
// dismissing modal makes iOS tear it down without a cancel callback,
|
|
273
|
+
// which strands expo-document-picker's pickingContext and makes every
|
|
274
|
+
// subsequent open throw "Different document picking in progress".
|
|
275
|
+
if (Platform.OS === 'ios') {
|
|
276
|
+
await new Promise((resolve) => setTimeout(resolve, 350));
|
|
277
|
+
}
|
|
241
278
|
const result = await DocumentPicker.getDocumentAsync({
|
|
242
279
|
type: '*/*',
|
|
243
280
|
multiple: false,
|
|
@@ -255,6 +292,8 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
255
292
|
]);
|
|
256
293
|
} catch (err) {
|
|
257
294
|
console.error('DocumentPicker error:', err);
|
|
295
|
+
} finally {
|
|
296
|
+
pickingDocRef.current = false;
|
|
258
297
|
}
|
|
259
298
|
};
|
|
260
299
|
|
|
@@ -266,7 +305,7 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
266
305
|
//
|
|
267
306
|
// Mirrors the web experience: tap mic → start recording → either tap
|
|
268
307
|
// X to discard, or tap send to stop + immediately upload + post the
|
|
269
|
-
// voice message. Uses expo-
|
|
308
|
+
// voice message. Uses expo-audio's AudioRecorder (already a peer dep
|
|
270
309
|
// for AudioMessage playback). Filename is `voice-<timestamp>.m4a` with
|
|
271
310
|
// mimetype `audio/m4a` so the receiver routes through the audio
|
|
272
311
|
// branch in MediaMessage directly, no octet-stream sniffing needed.
|
|
@@ -282,9 +321,9 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
282
321
|
// playback elsewhere in the app isn't routed to the earpiece.
|
|
283
322
|
const restoreAudioMode = async () => {
|
|
284
323
|
try {
|
|
285
|
-
await
|
|
286
|
-
|
|
287
|
-
|
|
324
|
+
await setAudioModeAsync({
|
|
325
|
+
allowsRecording: false,
|
|
326
|
+
playsInSilentMode: true,
|
|
288
327
|
});
|
|
289
328
|
} catch {
|
|
290
329
|
/* non-fatal */
|
|
@@ -292,27 +331,30 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
292
331
|
};
|
|
293
332
|
|
|
294
333
|
const handleStartRecording = async () => {
|
|
295
|
-
if (isRecording ||
|
|
334
|
+
if (isRecording || isRecordingRef.current) {return;}
|
|
296
335
|
try {
|
|
297
|
-
const perm = await
|
|
336
|
+
const perm = await requestRecordingPermissionsAsync();
|
|
298
337
|
if (!perm.granted) {
|
|
299
338
|
promptOpenSettings(
|
|
300
339
|
'Microphone permission is needed to record voice messages.'
|
|
301
340
|
);
|
|
302
341
|
return;
|
|
303
342
|
}
|
|
304
|
-
// iOS
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
343
|
+
// `allowsRecording` is not just an iOS session flag: expo-audio's
|
|
344
|
+
// recorder throws RecordingDisabledException from record() when the
|
|
345
|
+
// audio mode has it off. Flipped back in restoreAudioMode after
|
|
346
|
+
// stop/cancel so playback elsewhere isn't stuck on the earpiece.
|
|
347
|
+
await setAudioModeAsync({
|
|
348
|
+
allowsRecording: true,
|
|
349
|
+
playsInSilentMode: true,
|
|
309
350
|
});
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
await
|
|
315
|
-
|
|
351
|
+
// Passing the preset (rather than preparing bare) makes the native
|
|
352
|
+
// side allocate a FRESH output file for this take. Without it the
|
|
353
|
+
// recorder reuses the previous URL, so a second voice message would
|
|
354
|
+
// overwrite the first while its upload is still in flight.
|
|
355
|
+
await audioRecorder.prepareToRecordAsync(RecordingPresets.HIGH_QUALITY);
|
|
356
|
+
audioRecorder.record();
|
|
357
|
+
isRecordingRef.current = true;
|
|
316
358
|
setIsRecording(true);
|
|
317
359
|
setRecordingDuration(0);
|
|
318
360
|
const startedAt = Date.now();
|
|
@@ -322,7 +364,7 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
322
364
|
}, 250);
|
|
323
365
|
} catch (err) {
|
|
324
366
|
console.warn('startRecording failed', err);
|
|
325
|
-
|
|
367
|
+
isRecordingRef.current = false;
|
|
326
368
|
setIsRecording(false);
|
|
327
369
|
setRecordingDuration(0);
|
|
328
370
|
await restoreAudioMode();
|
|
@@ -331,15 +373,15 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
331
373
|
|
|
332
374
|
const handleCancelRecording = async () => {
|
|
333
375
|
stopRecordingTimer();
|
|
334
|
-
const
|
|
335
|
-
|
|
376
|
+
const wasRecording = isRecordingRef.current;
|
|
377
|
+
isRecordingRef.current = false;
|
|
336
378
|
setIsRecording(false);
|
|
337
379
|
setRecordingDuration(0);
|
|
338
|
-
if (
|
|
380
|
+
if (wasRecording) {
|
|
339
381
|
try {
|
|
340
|
-
await
|
|
382
|
+
await audioRecorder.stop();
|
|
341
383
|
} catch {
|
|
342
|
-
/* already stopped
|
|
384
|
+
/* already stopped — fine */
|
|
343
385
|
}
|
|
344
386
|
}
|
|
345
387
|
await restoreAudioMode();
|
|
@@ -347,12 +389,12 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
347
389
|
|
|
348
390
|
const handleStopAndSendRecording = async () => {
|
|
349
391
|
stopRecordingTimer();
|
|
350
|
-
const
|
|
351
|
-
|
|
392
|
+
const wasRecording = isRecordingRef.current;
|
|
393
|
+
isRecordingRef.current = false;
|
|
352
394
|
setIsRecording(false);
|
|
353
395
|
const seconds = recordingDuration;
|
|
354
396
|
setRecordingDuration(0);
|
|
355
|
-
if (!
|
|
397
|
+
if (!wasRecording) {
|
|
356
398
|
await restoreAudioMode();
|
|
357
399
|
return;
|
|
358
400
|
}
|
|
@@ -360,18 +402,20 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
360
402
|
// within the same tick there's nothing audible to send.
|
|
361
403
|
if (seconds < 1) {
|
|
362
404
|
try {
|
|
363
|
-
await
|
|
405
|
+
await audioRecorder.stop();
|
|
364
406
|
} catch {}
|
|
365
407
|
await restoreAudioMode();
|
|
366
408
|
return;
|
|
367
409
|
}
|
|
368
410
|
try {
|
|
369
|
-
await
|
|
411
|
+
await audioRecorder.stop();
|
|
370
412
|
} catch (err) {
|
|
371
|
-
console.warn('
|
|
413
|
+
console.warn('recorder.stop failed', err);
|
|
372
414
|
}
|
|
415
|
+
// Read the URI while it still points at THIS take — the next
|
|
416
|
+
// prepareToRecordAsync() swaps in a new output file.
|
|
417
|
+
const uri = audioRecorder.uri;
|
|
373
418
|
await restoreAudioMode();
|
|
374
|
-
const uri = rec.getURI();
|
|
375
419
|
if (!uri) {return;}
|
|
376
420
|
// HIGH_QUALITY preset writes AAC in an M4A container on both iOS
|
|
377
421
|
// and Android, so a single mimetype works. `voice-<ts>.m4a`
|
|
@@ -395,14 +439,16 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
395
439
|
useEffect(() => {
|
|
396
440
|
return () => {
|
|
397
441
|
stopRecordingTimer();
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
rec.stopAndUnloadAsync().catch(() => {});
|
|
442
|
+
if (isRecordingRef.current) {
|
|
443
|
+
isRecordingRef.current = false;
|
|
444
|
+
audioRecorder.stop().catch(() => {});
|
|
402
445
|
}
|
|
403
446
|
restoreAudioMode();
|
|
404
447
|
};
|
|
405
|
-
|
|
448
|
+
// The hook's recorder identity is stable (its options never change),
|
|
449
|
+
// so this teardown runs on unmount only.
|
|
450
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
451
|
+
}, [audioRecorder]);
|
|
406
452
|
|
|
407
453
|
const handleRemoveImage = (index: number) => {
|
|
408
454
|
const next = filePreviewsRef.current.filter((_, i) => i !== index);
|
|
@@ -463,6 +509,24 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
463
509
|
setMessage(next);
|
|
464
510
|
}, [editMessage]);
|
|
465
511
|
|
|
512
|
+
// iOS's multiline TextInput always top-aligns its content (UITextView
|
|
513
|
+
// has no vertical-centering API RN can hook into — `textAlignVertical`
|
|
514
|
+
// is Android-only), so a `minHeight` taller than one text line leaves
|
|
515
|
+
// the placeholder/cursor pinned near the top instead of centered. Fix:
|
|
516
|
+
// derive vertical padding from the actual line height so the box's
|
|
517
|
+
// resting height already equals its content height — nothing is left
|
|
518
|
+
// to look top-aligned.
|
|
519
|
+
const inputFontSize =
|
|
520
|
+
config?.typography?.input?.fontSize ??
|
|
521
|
+
config?.typography?.elements?.inputText?.fontSize ??
|
|
522
|
+
16;
|
|
523
|
+
const inputMinHeight = config?.typography?.input?.minHeight ?? 40;
|
|
524
|
+
const estimatedLineHeight = inputFontSize * 1.2;
|
|
525
|
+
const inputVerticalPadding = Math.max(
|
|
526
|
+
4,
|
|
527
|
+
(inputMinHeight - estimatedLineHeight) / 2
|
|
528
|
+
);
|
|
529
|
+
|
|
466
530
|
return (
|
|
467
531
|
<InputContainer isText={!!message}>
|
|
468
532
|
{filePreviews.length > 0 && (
|
|
@@ -497,7 +561,7 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
497
561
|
color={config?.colors?.primary}
|
|
498
562
|
fontSize={config?.typography?.input?.fontSize}
|
|
499
563
|
fontWeight={config?.typography?.input?.fontWeight as any}
|
|
500
|
-
placeholder=
|
|
564
|
+
placeholder={t('input.placeholder')}
|
|
501
565
|
placeholderTextColor="#999"
|
|
502
566
|
value={message}
|
|
503
567
|
onChangeText={(text) => {
|
|
@@ -517,14 +581,20 @@ const SendInput: React.FC<SendInputProps> = ({
|
|
|
517
581
|
// No fixed height: a multiline TextInput auto-grows to fit
|
|
518
582
|
// its content between min/max. Locking `height` made iOS
|
|
519
583
|
// top-align the text inside the taller box (it looked like
|
|
520
|
-
// the text had dropped).
|
|
521
|
-
//
|
|
522
|
-
// the
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
584
|
+
// the text had dropped). paddingVertical (computed above)
|
|
585
|
+
// makes the resting box height equal minHeight without
|
|
586
|
+
// leaving slack above the text, which is what actually
|
|
587
|
+
// centers it on iOS.
|
|
588
|
+
style={[
|
|
589
|
+
{
|
|
590
|
+
minHeight: inputMinHeight,
|
|
591
|
+
maxHeight: config?.typography?.input?.maxHeight ?? 120,
|
|
592
|
+
paddingTop: inputVerticalPadding,
|
|
593
|
+
paddingBottom: inputVerticalPadding,
|
|
594
|
+
flex: 1,
|
|
595
|
+
},
|
|
596
|
+
getElementFont(config, 'inputText'),
|
|
597
|
+
]}
|
|
528
598
|
/>
|
|
529
599
|
</>
|
|
530
600
|
)}
|
|
@@ -40,12 +40,15 @@ export const CenterContainer = styled.View<{
|
|
|
40
40
|
}>`
|
|
41
41
|
align-items: center;
|
|
42
42
|
justify-content: center;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
/* flex:1 so the title fills the space BETWEEN the left (back/avatar) and
|
|
44
|
+
* right (globe / call / menu) clusters and shrinks to make room for them.
|
|
45
|
+
* The previous fixed 90% width overflowed the row (15% left + 90% center
|
|
46
|
+
* + 25% right = 130%), pushing the right cluster — the translate globe,
|
|
47
|
+
* call and room-menu icons — off-screen where it rendered but was clipped
|
|
48
|
+
* by the device edge. min-width:0 lets the title ellipsize instead of
|
|
49
|
+
* forcing the row wider. Mirrors the web header's flexbox layout. */
|
|
50
|
+
flex: 1;
|
|
51
|
+
min-width: 0;
|
|
49
52
|
padding-right: ${({ rightSpace }) => (rightSpace ? '16' : '0')}px;
|
|
50
53
|
padding-left: ${({ leftSpace }) => (leftSpace ? '16' : '0')}px;
|
|
51
54
|
`;
|
|
@@ -51,7 +51,13 @@ const Composing: FC<ComposingProps> = ({ usersTyping = ['User'], style }) => {
|
|
|
51
51
|
return (
|
|
52
52
|
<View style={[styles.wrapper, style]}>
|
|
53
53
|
{typingText ? (
|
|
54
|
-
<Text
|
|
54
|
+
<Text
|
|
55
|
+
style={styles.typingText}
|
|
56
|
+
numberOfLines={1}
|
|
57
|
+
ellipsizeMode="tail"
|
|
58
|
+
>
|
|
59
|
+
{typingText}
|
|
60
|
+
</Text>
|
|
55
61
|
) : (
|
|
56
62
|
<View />
|
|
57
63
|
)}
|
|
@@ -10,6 +10,7 @@ import React, {
|
|
|
10
10
|
} from 'react';
|
|
11
11
|
import { AppState, DeviceEventEmitter } from 'react-native';
|
|
12
12
|
import NetInfo from '@react-native-community/netinfo';
|
|
13
|
+
import { VideoCallOverlay } from '../components/VideoCalls/VideoCallOverlay';
|
|
13
14
|
import XmppClient, {
|
|
14
15
|
XmppCredentialsProvider,
|
|
15
16
|
} from '../networking/xmppClient';
|
|
@@ -506,7 +507,13 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
|
|
|
506
507
|
useEffect(() => {
|
|
507
508
|
if (!client) {return;}
|
|
508
509
|
const id = setInterval(() => {
|
|
509
|
-
|
|
510
|
+
// Only skip while genuinely backgrounded. iOS also reports 'inactive'
|
|
511
|
+
// for transient states the user still sees the chat through (control
|
|
512
|
+
// centre, an incoming-call banner, app-switcher preview, and — on the
|
|
513
|
+
// simulator — whenever the window is not focused). Gating on
|
|
514
|
+
// `!== 'active'` meant the watchdog silently stopped running in all of
|
|
515
|
+
// those, so a socket that dropped during one never got revived.
|
|
516
|
+
if (AppState.currentState === 'background') {return;}
|
|
510
517
|
if (client.suppressReconnect) {return;}
|
|
511
518
|
if (client.status !== 'online') {
|
|
512
519
|
devPushLog('rn', `watchdog: client ${client.status} → forceReconnect`);
|
|
@@ -770,6 +777,12 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
|
|
|
770
777
|
}}
|
|
771
778
|
>
|
|
772
779
|
{children}
|
|
780
|
+
{/* Lives here, not inside <Chat>: an incoming call-token can arrive
|
|
781
|
+
(and must still ring) while the user is on a different screen of
|
|
782
|
+
the host app, as long as the socket is up. VideoCallOverlay reads
|
|
783
|
+
config and call state from Redux itself, so it needs nothing
|
|
784
|
+
passed in, and renders null whenever no call is in flight. */}
|
|
785
|
+
<VideoCallOverlay />
|
|
773
786
|
</XmppContext.Provider>
|
|
774
787
|
);
|
|
775
788
|
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { IMessage } from '../types/types';
|
|
2
|
+
|
|
3
|
+
// A server "call-state" stanza is emitted into the room when a call session
|
|
4
|
+
// concludes (someone leaves the LiveKit room). It carries:
|
|
5
|
+
// type="call-state" callId="..." durationMs="<ms>"
|
|
6
|
+
// callerXmppUsername="<who started the call>" isSystemMessage="true"
|
|
7
|
+
// We turn it into a friendly chat-log entry, Telegram/WhatsApp style:
|
|
8
|
+
// - durationMs > 0 -> "Outgoing call · 12 sec" / "Incoming call · 1 min"
|
|
9
|
+
// - durationMs == 0 -> "No answer" (we called) / "Missed call" (they called)
|
|
10
|
+
// Direction is derived from callerXmppUsername vs the logged-in user.
|
|
11
|
+
|
|
12
|
+
export interface CallLogMeta {
|
|
13
|
+
callId: string;
|
|
14
|
+
direction: 'outgoing' | 'incoming';
|
|
15
|
+
durationMs: number;
|
|
16
|
+
missed: boolean;
|
|
17
|
+
kind: 'audio' | 'video';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const isCallLogMessage = (message: unknown): boolean => {
|
|
21
|
+
const type = String((message as any)?.type || '').toLowerCase();
|
|
22
|
+
return type === 'call-state';
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Only a call-state that actually carries log metadata (a caller and/or a
|
|
26
|
+
// duration) is worth rendering. Bare client-side signaling frames
|
|
27
|
+
// (state=cancelled/declined/ended with no caller/duration) must NOT become
|
|
28
|
+
// chat entries, they're real-time teardown only.
|
|
29
|
+
export const callStateHasLogData = (attrs: Record<string, any>): boolean => {
|
|
30
|
+
if (!attrs) {return false;}
|
|
31
|
+
const hasCaller = !!String(attrs.callerXmppUsername || '').trim();
|
|
32
|
+
const hasDuration =
|
|
33
|
+
attrs.durationMs !== undefined &&
|
|
34
|
+
attrs.durationMs !== null &&
|
|
35
|
+
attrs.durationMs !== '';
|
|
36
|
+
return hasCaller || hasDuration;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const formatCallDuration = (ms: number): string => {
|
|
40
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1000));
|
|
41
|
+
if (totalSeconds < 60) {return `${totalSeconds} sec`;}
|
|
42
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
43
|
+
const seconds = totalSeconds % 60;
|
|
44
|
+
if (seconds === 0) {return `${minutes} min`;}
|
|
45
|
+
return `${minutes} min ${seconds} sec`;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const transformCallLogMessage = (
|
|
49
|
+
message: IMessage,
|
|
50
|
+
selfXmppUsername: string
|
|
51
|
+
): IMessage => {
|
|
52
|
+
if (!isCallLogMessage(message)) {return message;}
|
|
53
|
+
|
|
54
|
+
const raw = message as any;
|
|
55
|
+
const callerLocal = String(raw.callerXmppUsername || '')
|
|
56
|
+
.split('@')[0]
|
|
57
|
+
.trim();
|
|
58
|
+
const selfLocal = String(selfXmppUsername || '').split('@')[0].trim();
|
|
59
|
+
// When we know the caller, "outgoing" means we started it. With no
|
|
60
|
+
// caller info, default to incoming (safer label than claiming we dialed).
|
|
61
|
+
const isOutgoing = !!callerLocal && callerLocal === selfLocal;
|
|
62
|
+
const durationMs = Number(raw.durationMs || 0);
|
|
63
|
+
const kind: 'audio' | 'video' =
|
|
64
|
+
String(raw.kind || '').toLowerCase() === 'audio' ? 'audio' : 'video';
|
|
65
|
+
|
|
66
|
+
let body: string;
|
|
67
|
+
if (durationMs > 0) {
|
|
68
|
+
body = `${isOutgoing ? 'Outgoing' : 'Incoming'} call · ${formatCallDuration(durationMs)}`;
|
|
69
|
+
} else {
|
|
70
|
+
body = isOutgoing ? 'No answer' : 'Missed call';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const callLog: CallLogMeta = {
|
|
74
|
+
callId: String(raw.callId || raw.callid || '').trim(),
|
|
75
|
+
direction: isOutgoing ? 'outgoing' : 'incoming',
|
|
76
|
+
durationMs,
|
|
77
|
+
missed: durationMs === 0,
|
|
78
|
+
kind,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
...message,
|
|
83
|
+
body,
|
|
84
|
+
// Render through the existing centered SystemMessage pipeline.
|
|
85
|
+
isSystemMessage: 'true',
|
|
86
|
+
callLog,
|
|
87
|
+
} as IMessage;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Build a "call ended" log entry locally (client-side) when a call finishes.
|
|
91
|
+
// The authoritative entry is a SERVER `call-state` broadcast, but that isn't
|
|
92
|
+
// guaranteed to arrive (see header comment). This fallback keeps the chat log
|
|
93
|
+
// working regardless. It's deduplicated against any server copy in
|
|
94
|
+
// `addRoomMessage` by `callLog.callId` (the entry with the largest duration
|
|
95
|
+
// wins), so if the server does broadcast, the two collapse into one.
|
|
96
|
+
export const buildLocalCallLogMessage = (params: {
|
|
97
|
+
callId: string;
|
|
98
|
+
direction: 'outgoing' | 'incoming';
|
|
99
|
+
durationMs: number;
|
|
100
|
+
kind: 'audio' | 'video';
|
|
101
|
+
selfXmppUsername: string;
|
|
102
|
+
}): IMessage => {
|
|
103
|
+
const { callId, direction, durationMs, kind, selfXmppUsername } = params;
|
|
104
|
+
const isOutgoing = direction === 'outgoing';
|
|
105
|
+
const safeDuration = Math.max(0, Math.round(durationMs || 0));
|
|
106
|
+
|
|
107
|
+
let body: string;
|
|
108
|
+
if (safeDuration > 0) {
|
|
109
|
+
body = `${isOutgoing ? 'Outgoing' : 'Incoming'} call · ${formatCallDuration(safeDuration)}`;
|
|
110
|
+
} else {
|
|
111
|
+
body = isOutgoing ? 'No answer' : 'Missed call';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const callLog: CallLogMeta = {
|
|
115
|
+
callId,
|
|
116
|
+
direction,
|
|
117
|
+
durationMs: safeDuration,
|
|
118
|
+
missed: safeDuration === 0,
|
|
119
|
+
kind,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
// Deterministic id so a re-fire for the same call collapses in place.
|
|
124
|
+
id: callId ? `calllog-${callId}` : `calllog-${Date.now()}`,
|
|
125
|
+
body,
|
|
126
|
+
date: new Date().toISOString(),
|
|
127
|
+
isSystemMessage: 'true',
|
|
128
|
+
type: 'call-state',
|
|
129
|
+
callLog,
|
|
130
|
+
user: { id: selfXmppUsername },
|
|
131
|
+
} as unknown as IMessage;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The label for a call-log entry, built at RENDER time from the callLog
|
|
136
|
+
* meta rather than read off `message.body`.
|
|
137
|
+
*
|
|
138
|
+
* transformCallLogMessage bakes an English sentence into `body` the moment
|
|
139
|
+
* the stanza arrives, which freezes the language: switch to French and
|
|
140
|
+
* every call log you already received stays English forever, because the
|
|
141
|
+
* text is data by then, not a rendering. The meta (direction/duration/
|
|
142
|
+
* missed) is the actual fact, the sentence is a view of it, so it belongs
|
|
143
|
+
* where the view is. `body` stays as the fallback for entries received
|
|
144
|
+
* before this existed, and for the notification/room-list preview paths
|
|
145
|
+
* that only have the raw message.
|
|
146
|
+
*/
|
|
147
|
+
export const formatCallLogLabel = (
|
|
148
|
+
callLog: CallLogMeta | undefined,
|
|
149
|
+
t: (key: string, vars?: Record<string, string | number>) => string,
|
|
150
|
+
fallbackBody = ''
|
|
151
|
+
): string => {
|
|
152
|
+
if (!callLog) {return fallbackBody;}
|
|
153
|
+
|
|
154
|
+
if (!callLog.durationMs || callLog.durationMs <= 0) {
|
|
155
|
+
return callLog.direction === 'outgoing'
|
|
156
|
+
? t('call.noAnswer')
|
|
157
|
+
: t('call.missed');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const totalSeconds = Math.max(0, Math.round(callLog.durationMs / 1000));
|
|
161
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
162
|
+
const seconds = totalSeconds % 60;
|
|
163
|
+
const duration =
|
|
164
|
+
totalSeconds < 60
|
|
165
|
+
? t('call.durationSec', { n: totalSeconds })
|
|
166
|
+
: seconds === 0
|
|
167
|
+
? t('call.durationMin', { n: minutes })
|
|
168
|
+
: t('call.durationMinSec', { m: minutes, s: seconds });
|
|
169
|
+
|
|
170
|
+
const direction =
|
|
171
|
+
callLog.direction === 'outgoing' ? t('call.outgoing') : t('call.incoming');
|
|
172
|
+
|
|
173
|
+
return `${direction} · ${duration}`;
|
|
174
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Recognise and parse an *incoming call* push notification.
|
|
2
|
+
//
|
|
3
|
+
// Calls normally ring via a live XMPP `call-token` stanza, which only arrives
|
|
4
|
+
// while the socket is connected. On mobile the socket is dead whenever the OS
|
|
5
|
+
// backgrounds or kills the app, so the backend also delivers the call over
|
|
6
|
+
// push, in the same `data`-payload format as message pushes. This helper lets
|
|
7
|
+
// the push layer tell a call push apart from a chat push and pull the call
|
|
8
|
+
// fields out, tolerating the field-name variations backends tend to use.
|
|
9
|
+
|
|
10
|
+
export interface ParsedCallPush {
|
|
11
|
+
callId: string | null;
|
|
12
|
+
/** LiveKit token, if the push carried one (lets us ring + accept offline). */
|
|
13
|
+
token: string | null;
|
|
14
|
+
kind: 'audio' | 'video';
|
|
15
|
+
/** Bare room name (JID localpart) the call belongs to. */
|
|
16
|
+
roomBareName: string | null;
|
|
17
|
+
/** Raw room reference from the payload (full JID or bare name). */
|
|
18
|
+
roomRef: string | null;
|
|
19
|
+
/** Display name for the ring screen (caller). */
|
|
20
|
+
callerName: string | null;
|
|
21
|
+
/** Caller's xmpp localpart, when provided. */
|
|
22
|
+
callerXmppUsername: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const pick = (data: Record<string, any>, keys: string[]): string => {
|
|
26
|
+
for (const k of keys) {
|
|
27
|
+
const v = data?.[k];
|
|
28
|
+
if (v !== undefined && v !== null && String(v).trim() !== '') {
|
|
29
|
+
return String(v).trim();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return '';
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* True when a push `data` payload represents an incoming call. Matches on an
|
|
37
|
+
* explicit call marker (`type`/`pushType`/`category`/`event` === "call"-ish)
|
|
38
|
+
* or on a callId paired with a call-specific field, so a normal message push
|
|
39
|
+
* is never misread as a call.
|
|
40
|
+
*/
|
|
41
|
+
export const isCallPush = (
|
|
42
|
+
data: Record<string, any> | null | undefined
|
|
43
|
+
): boolean => {
|
|
44
|
+
if (!data) {return false;}
|
|
45
|
+
const marker = pick(data, [
|
|
46
|
+
'type',
|
|
47
|
+
'pushType',
|
|
48
|
+
'category',
|
|
49
|
+
'messageType',
|
|
50
|
+
'event',
|
|
51
|
+
]).toLowerCase();
|
|
52
|
+
if (
|
|
53
|
+
marker === 'call' ||
|
|
54
|
+
marker === 'incoming-call' ||
|
|
55
|
+
marker === 'call-token' ||
|
|
56
|
+
marker === 'call-invite'
|
|
57
|
+
) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
const callId = pick(data, ['callId', 'callid', 'callID']);
|
|
61
|
+
const hasCallField =
|
|
62
|
+
!!pick(data, ['callToken', 'livekitToken']) ||
|
|
63
|
+
!!pick(data, ['callKind']) ||
|
|
64
|
+
pick(data, ['kind']).toLowerCase() === 'audio' ||
|
|
65
|
+
pick(data, ['kind']).toLowerCase() === 'video';
|
|
66
|
+
return !!callId && hasCallField;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const parseCallPush = (
|
|
70
|
+
data: Record<string, any> | null | undefined
|
|
71
|
+
): ParsedCallPush => {
|
|
72
|
+
const d = data || {};
|
|
73
|
+
const roomRef =
|
|
74
|
+
pick(d, ['callRoom', 'room', 'roomJid', 'jid', 'chatId']) || null;
|
|
75
|
+
const roomBareName = roomRef ? roomRef.split('@')[0] || roomRef : null;
|
|
76
|
+
const kindRaw = pick(d, ['callKind', 'kind']).toLowerCase();
|
|
77
|
+
return {
|
|
78
|
+
callId: pick(d, ['callId', 'callid', 'callID']) || null,
|
|
79
|
+
token: pick(d, ['callToken', 'livekitToken', 'token']) || null,
|
|
80
|
+
kind: kindRaw === 'audio' ? 'audio' : 'video',
|
|
81
|
+
roomBareName,
|
|
82
|
+
roomRef,
|
|
83
|
+
callerName:
|
|
84
|
+
pick(d, ['callerName', 'callerDisplayName', 'senderName', 'title']) ||
|
|
85
|
+
null,
|
|
86
|
+
callerXmppUsername:
|
|
87
|
+
pick(d, ['callerXmppUsername', 'callerUsername', 'userJid', 'senderJid'])
|
|
88
|
+
.split('@')[0] || null,
|
|
89
|
+
};
|
|
90
|
+
};
|