@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.
Files changed (388) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +43 -3
  3. package/index.js +1 -1
  4. package/lib/commonjs/components/MainComponents/ChatHeader.js +34 -9
  5. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  6. package/lib/commonjs/components/MainComponents/ChatWrapper.js +17 -0
  7. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  8. package/lib/commonjs/components/MainComponents/LanguageSelectorButton.js +62 -0
  9. package/lib/commonjs/components/MainComponents/LanguageSelectorButton.js.map +1 -0
  10. package/lib/commonjs/components/MainComponents/LanguageSelectorModal.js +253 -0
  11. package/lib/commonjs/components/MainComponents/LanguageSelectorModal.js.map +1 -0
  12. package/lib/commonjs/components/MainComponents/RoomList.js +23 -9
  13. package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
  14. package/lib/commonjs/components/MessageBubble/Message.js +72 -11
  15. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  16. package/lib/commonjs/components/MessageBubble/MessageTranslate.js +160 -0
  17. package/lib/commonjs/components/MessageBubble/MessageTranslate.js.map +1 -0
  18. package/lib/commonjs/components/MessageBubble/TranslatedMessageBody.js +59 -0
  19. package/lib/commonjs/components/MessageBubble/TranslatedMessageBody.js.map +1 -0
  20. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js +1 -1
  21. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  22. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +6 -0
  23. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  24. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +26 -8
  25. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  26. package/lib/commonjs/components/Modals/ModalHeaderComponent.js +15 -1
  27. package/lib/commonjs/components/Modals/ModalHeaderComponent.js.map +1 -1
  28. package/lib/commonjs/components/UsersList/UsersList.js +3 -1
  29. package/lib/commonjs/components/UsersList/UsersList.js.map +1 -1
  30. package/lib/commonjs/components/VideoCalls/CallButtons.js +159 -0
  31. package/lib/commonjs/components/VideoCalls/CallButtons.js.map +1 -0
  32. package/lib/commonjs/components/VideoCalls/CallIcons.js +328 -0
  33. package/lib/commonjs/components/VideoCalls/CallIcons.js.map +1 -0
  34. package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js +550 -0
  35. package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js.map +1 -0
  36. package/lib/commonjs/components/VideoCalls/VideoCallSession.js +636 -0
  37. package/lib/commonjs/components/VideoCalls/VideoCallSession.js.map +1 -0
  38. package/lib/commonjs/components/VideoCalls/livekitRuntime.js +91 -0
  39. package/lib/commonjs/components/VideoCalls/livekitRuntime.js.map +1 -0
  40. package/lib/commonjs/components/VideoCalls/useCallKeep.js +215 -0
  41. package/lib/commonjs/components/VideoCalls/useCallKeep.js.map +1 -0
  42. package/lib/commonjs/components/styled/AudioMessage.js +91 -33
  43. package/lib/commonjs/components/styled/AudioMessage.js.map +1 -1
  44. package/lib/commonjs/components/styled/RoomListComponents/index.js +13 -1
  45. package/lib/commonjs/components/styled/RoomListComponents/index.js.map +1 -1
  46. package/lib/commonjs/components/styled/SendInput.js +111 -51
  47. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  48. package/lib/commonjs/components/styled/StyledComponents.js +9 -4
  49. package/lib/commonjs/components/styled/StyledComponents.js.map +1 -1
  50. package/lib/commonjs/components/styled/StyledInputComponents/Composing.js +2 -0
  51. package/lib/commonjs/components/styled/StyledInputComponents/Composing.js.map +1 -1
  52. package/lib/commonjs/context/xmppProvider.js +10 -3
  53. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  54. package/lib/commonjs/helpers/callLogMessage.js +162 -0
  55. package/lib/commonjs/helpers/callLogMessage.js.map +1 -0
  56. package/lib/commonjs/helpers/callPush.js +61 -0
  57. package/lib/commonjs/helpers/callPush.js.map +1 -0
  58. package/lib/commonjs/helpers/constants/LANGUAGE_OPTIONS.js +24 -12
  59. package/lib/commonjs/helpers/constants/LANGUAGE_OPTIONS.js.map +1 -1
  60. package/lib/commonjs/helpers/createUserNameFromSetUser.js +51 -2
  61. package/lib/commonjs/helpers/createUserNameFromSetUser.js.map +1 -1
  62. package/lib/commonjs/helpers/getDataFromXml.js +19 -1
  63. package/lib/commonjs/helpers/getDataFromXml.js.map +1 -1
  64. package/lib/commonjs/helpers/getElementFont.js +29 -0
  65. package/lib/commonjs/helpers/getElementFont.js.map +1 -0
  66. package/lib/commonjs/helpers/headerLayout.js +35 -0
  67. package/lib/commonjs/helpers/headerLayout.js.map +1 -0
  68. package/lib/commonjs/helpers/incomingCallFromPush.js +74 -0
  69. package/lib/commonjs/helpers/incomingCallFromPush.js.map +1 -0
  70. package/lib/commonjs/helpers/insertMessageWithDelimiter.js +13 -1
  71. package/lib/commonjs/helpers/insertMessageWithDelimiter.js.map +1 -1
  72. package/lib/commonjs/helpers/isUnresolvedSenderId.js +32 -0
  73. package/lib/commonjs/helpers/isUnresolvedSenderId.js.map +1 -0
  74. package/lib/commonjs/hooks/useChatSettingState.js +2 -0
  75. package/lib/commonjs/hooks/useChatSettingState.js.map +1 -1
  76. package/lib/commonjs/hooks/useMessageTranslation.js +51 -0
  77. package/lib/commonjs/hooks/useMessageTranslation.js.map +1 -0
  78. package/lib/commonjs/hooks/usePushNotifications.js +13 -3
  79. package/lib/commonjs/hooks/usePushNotifications.js.map +1 -1
  80. package/lib/commonjs/hooks/useSendMessage.js +12 -2
  81. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  82. package/lib/commonjs/i18n/strings.js +1308 -0
  83. package/lib/commonjs/i18n/strings.js.map +1 -0
  84. package/lib/commonjs/i18n/useT.js +60 -0
  85. package/lib/commonjs/i18n/useT.js.map +1 -0
  86. package/lib/commonjs/main.js +44 -0
  87. package/lib/commonjs/main.js.map +1 -1
  88. package/lib/commonjs/networking/api-requests/push.api.js +34 -5
  89. package/lib/commonjs/networking/api-requests/push.api.js.map +1 -1
  90. package/lib/commonjs/networking/api-requests/rooms.api.js +108 -0
  91. package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
  92. package/lib/commonjs/networking/callTokenStanza.js +531 -0
  93. package/lib/commonjs/networking/callTokenStanza.js.map +1 -0
  94. package/lib/commonjs/networking/outboundQueue.js.map +1 -1
  95. package/lib/commonjs/networking/stanzaHandlers.js +75 -8
  96. package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
  97. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +47 -11
  98. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  99. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js +2 -1
  100. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  101. package/lib/commonjs/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js +42 -5
  102. package/lib/commonjs/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js.map +1 -1
  103. package/lib/commonjs/networking/xmppClient.js +78 -16
  104. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  105. package/lib/commonjs/roomStore/callSlice.js +144 -0
  106. package/lib/commonjs/roomStore/callSlice.js.map +1 -0
  107. package/lib/commonjs/roomStore/chatSettingsSlice.js +8 -2
  108. package/lib/commonjs/roomStore/chatSettingsSlice.js.map +1 -1
  109. package/lib/commonjs/roomStore/index.js +7 -1
  110. package/lib/commonjs/roomStore/index.js.map +1 -1
  111. package/lib/commonjs/roomStore/persistence.js +69 -5
  112. package/lib/commonjs/roomStore/persistence.js.map +1 -1
  113. package/lib/commonjs/roomStore/roomsSlice.js +136 -6
  114. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  115. package/lib/commonjs/utils/translateModePolicy.js +33 -0
  116. package/lib/commonjs/utils/translateModePolicy.js.map +1 -0
  117. package/lib/module/components/MainComponents/ChatHeader.js +34 -9
  118. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  119. package/lib/module/components/MainComponents/ChatWrapper.js +17 -0
  120. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  121. package/lib/module/components/MainComponents/LanguageSelectorButton.js +57 -0
  122. package/lib/module/components/MainComponents/LanguageSelectorButton.js.map +1 -0
  123. package/lib/module/components/MainComponents/LanguageSelectorModal.js +246 -0
  124. package/lib/module/components/MainComponents/LanguageSelectorModal.js.map +1 -0
  125. package/lib/module/components/MainComponents/RoomList.js +23 -9
  126. package/lib/module/components/MainComponents/RoomList.js.map +1 -1
  127. package/lib/module/components/MessageBubble/Message.js +73 -12
  128. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  129. package/lib/module/components/MessageBubble/MessageTranslate.js +155 -0
  130. package/lib/module/components/MessageBubble/MessageTranslate.js.map +1 -0
  131. package/lib/module/components/MessageBubble/TranslatedMessageBody.js +53 -0
  132. package/lib/module/components/MessageBubble/TranslatedMessageBody.js.map +1 -0
  133. package/lib/module/components/Modals/AttachSheet/AttachSheet.js +1 -1
  134. package/lib/module/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  135. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +6 -0
  136. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  137. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +27 -9
  138. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  139. package/lib/module/components/Modals/ModalHeaderComponent.js +15 -1
  140. package/lib/module/components/Modals/ModalHeaderComponent.js.map +1 -1
  141. package/lib/module/components/UsersList/UsersList.js +3 -1
  142. package/lib/module/components/UsersList/UsersList.js.map +1 -1
  143. package/lib/module/components/VideoCalls/CallButtons.js +154 -0
  144. package/lib/module/components/VideoCalls/CallButtons.js.map +1 -0
  145. package/lib/module/components/VideoCalls/CallIcons.js +309 -0
  146. package/lib/module/components/VideoCalls/CallIcons.js.map +1 -0
  147. package/lib/module/components/VideoCalls/VideoCallOverlay.js +544 -0
  148. package/lib/module/components/VideoCalls/VideoCallOverlay.js.map +1 -0
  149. package/lib/module/components/VideoCalls/VideoCallSession.js +630 -0
  150. package/lib/module/components/VideoCalls/VideoCallSession.js.map +1 -0
  151. package/lib/module/components/VideoCalls/livekitRuntime.js +83 -0
  152. package/lib/module/components/VideoCalls/livekitRuntime.js.map +1 -0
  153. package/lib/module/components/VideoCalls/useCallKeep.js +210 -0
  154. package/lib/module/components/VideoCalls/useCallKeep.js.map +1 -0
  155. package/lib/module/components/styled/AudioMessage.js +91 -33
  156. package/lib/module/components/styled/AudioMessage.js.map +1 -1
  157. package/lib/module/components/styled/RoomListComponents/index.js +13 -1
  158. package/lib/module/components/styled/RoomListComponents/index.js.map +1 -1
  159. package/lib/module/components/styled/SendInput.js +112 -52
  160. package/lib/module/components/styled/SendInput.js.map +1 -1
  161. package/lib/module/components/styled/StyledComponents.js +9 -4
  162. package/lib/module/components/styled/StyledComponents.js.map +1 -1
  163. package/lib/module/components/styled/StyledInputComponents/Composing.js +2 -0
  164. package/lib/module/components/styled/StyledInputComponents/Composing.js.map +1 -1
  165. package/lib/module/context/xmppProvider.js +11 -4
  166. package/lib/module/context/xmppProvider.js.map +1 -1
  167. package/lib/module/helpers/callLogMessage.js +152 -0
  168. package/lib/module/helpers/callLogMessage.js.map +1 -0
  169. package/lib/module/helpers/callPush.js +55 -0
  170. package/lib/module/helpers/callPush.js.map +1 -0
  171. package/lib/module/helpers/constants/LANGUAGE_OPTIONS.js +24 -12
  172. package/lib/module/helpers/constants/LANGUAGE_OPTIONS.js.map +1 -1
  173. package/lib/module/helpers/createUserNameFromSetUser.js +49 -1
  174. package/lib/module/helpers/createUserNameFromSetUser.js.map +1 -1
  175. package/lib/module/helpers/getDataFromXml.js +20 -1
  176. package/lib/module/helpers/getDataFromXml.js.map +1 -1
  177. package/lib/module/helpers/getElementFont.js +24 -0
  178. package/lib/module/helpers/getElementFont.js.map +1 -0
  179. package/lib/module/helpers/headerLayout.js +30 -0
  180. package/lib/module/helpers/headerLayout.js.map +1 -0
  181. package/lib/module/helpers/incomingCallFromPush.js +70 -0
  182. package/lib/module/helpers/incomingCallFromPush.js.map +1 -0
  183. package/lib/module/helpers/insertMessageWithDelimiter.js +13 -1
  184. package/lib/module/helpers/insertMessageWithDelimiter.js.map +1 -1
  185. package/lib/module/helpers/isUnresolvedSenderId.js +27 -0
  186. package/lib/module/helpers/isUnresolvedSenderId.js.map +1 -0
  187. package/lib/module/hooks/useChatSettingState.js +2 -0
  188. package/lib/module/hooks/useChatSettingState.js.map +1 -1
  189. package/lib/module/hooks/useMessageTranslation.js +46 -0
  190. package/lib/module/hooks/useMessageTranslation.js.map +1 -0
  191. package/lib/module/hooks/usePushNotifications.js +13 -3
  192. package/lib/module/hooks/usePushNotifications.js.map +1 -1
  193. package/lib/module/hooks/useSendMessage.js +12 -2
  194. package/lib/module/hooks/useSendMessage.js.map +1 -1
  195. package/lib/module/i18n/strings.js +1300 -0
  196. package/lib/module/i18n/strings.js.map +1 -0
  197. package/lib/module/i18n/useT.js +55 -0
  198. package/lib/module/i18n/useT.js.map +1 -0
  199. package/lib/module/main.js +7 -1
  200. package/lib/module/main.js.map +1 -1
  201. package/lib/module/networking/api-requests/push.api.js +35 -5
  202. package/lib/module/networking/api-requests/push.api.js.map +1 -1
  203. package/lib/module/networking/api-requests/rooms.api.js +109 -1
  204. package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
  205. package/lib/module/networking/callTokenStanza.js +526 -0
  206. package/lib/module/networking/callTokenStanza.js.map +1 -0
  207. package/lib/module/networking/outboundQueue.js.map +1 -1
  208. package/lib/module/networking/stanzaHandlers.js +76 -9
  209. package/lib/module/networking/stanzaHandlers.js.map +1 -1
  210. package/lib/module/networking/xmpp/handleStanzas.xmpp.js +48 -11
  211. package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  212. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js +1 -1
  213. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  214. package/lib/module/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js +43 -5
  215. package/lib/module/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js.map +1 -1
  216. package/lib/module/networking/xmppClient.js +78 -16
  217. package/lib/module/networking/xmppClient.js.map +1 -1
  218. package/lib/module/roomStore/callSlice.js +130 -0
  219. package/lib/module/roomStore/callSlice.js.map +1 -0
  220. package/lib/module/roomStore/chatSettingsSlice.js +6 -1
  221. package/lib/module/roomStore/chatSettingsSlice.js.map +1 -1
  222. package/lib/module/roomStore/index.js +6 -1
  223. package/lib/module/roomStore/index.js.map +1 -1
  224. package/lib/module/roomStore/persistence.js +69 -5
  225. package/lib/module/roomStore/persistence.js.map +1 -1
  226. package/lib/module/roomStore/roomsSlice.js +134 -5
  227. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  228. package/lib/module/utils/translateModePolicy.js +27 -0
  229. package/lib/module/utils/translateModePolicy.js.map +1 -0
  230. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  231. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  232. package/lib/typescript/components/MainComponents/LanguageSelectorButton.d.ts +12 -0
  233. package/lib/typescript/components/MainComponents/LanguageSelectorButton.d.ts.map +1 -0
  234. package/lib/typescript/components/MainComponents/LanguageSelectorModal.d.ts +31 -0
  235. package/lib/typescript/components/MainComponents/LanguageSelectorModal.d.ts.map +1 -0
  236. package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
  237. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  238. package/lib/typescript/components/MessageBubble/MessageTranslate.d.ts +44 -0
  239. package/lib/typescript/components/MessageBubble/MessageTranslate.d.ts.map +1 -0
  240. package/lib/typescript/components/MessageBubble/TranslatedMessageBody.d.ts +24 -0
  241. package/lib/typescript/components/MessageBubble/TranslatedMessageBody.d.ts.map +1 -0
  242. package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts.map +1 -1
  243. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
  244. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  245. package/lib/typescript/components/Modals/ModalHeaderComponent.d.ts +3 -0
  246. package/lib/typescript/components/Modals/ModalHeaderComponent.d.ts.map +1 -1
  247. package/lib/typescript/components/UsersList/UsersList.d.ts.map +1 -1
  248. package/lib/typescript/components/VideoCalls/CallButtons.d.ts +16 -0
  249. package/lib/typescript/components/VideoCalls/CallButtons.d.ts.map +1 -0
  250. package/lib/typescript/components/VideoCalls/CallIcons.d.ts +30 -0
  251. package/lib/typescript/components/VideoCalls/CallIcons.d.ts.map +1 -0
  252. package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts +10 -0
  253. package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts.map +1 -0
  254. package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts +38 -0
  255. package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts.map +1 -0
  256. package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts +52 -0
  257. package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts.map +1 -0
  258. package/lib/typescript/components/VideoCalls/useCallKeep.d.ts +32 -0
  259. package/lib/typescript/components/VideoCalls/useCallKeep.d.ts.map +1 -0
  260. package/lib/typescript/components/styled/AudioMessage.d.ts.map +1 -1
  261. package/lib/typescript/components/styled/RoomListComponents/index.d.ts.map +1 -1
  262. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  263. package/lib/typescript/components/styled/StyledComponents.d.ts.map +1 -1
  264. package/lib/typescript/components/styled/StyledInputComponents/Composing.d.ts.map +1 -1
  265. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  266. package/lib/typescript/helpers/callLogMessage.d.ts +34 -0
  267. package/lib/typescript/helpers/callLogMessage.d.ts.map +1 -0
  268. package/lib/typescript/helpers/callPush.d.ts +23 -0
  269. package/lib/typescript/helpers/callPush.d.ts.map +1 -0
  270. package/lib/typescript/helpers/constants/LANGUAGE_OPTIONS.d.ts +14 -0
  271. package/lib/typescript/helpers/constants/LANGUAGE_OPTIONS.d.ts.map +1 -1
  272. package/lib/typescript/helpers/createUserNameFromSetUser.d.ts +33 -1
  273. package/lib/typescript/helpers/createUserNameFromSetUser.d.ts.map +1 -1
  274. package/lib/typescript/helpers/getDataFromXml.d.ts.map +1 -1
  275. package/lib/typescript/helpers/getElementFont.d.ts +13 -0
  276. package/lib/typescript/helpers/getElementFont.d.ts.map +1 -0
  277. package/lib/typescript/helpers/headerLayout.d.ts +26 -0
  278. package/lib/typescript/helpers/headerLayout.d.ts.map +1 -0
  279. package/lib/typescript/helpers/incomingCallFromPush.d.ts +14 -0
  280. package/lib/typescript/helpers/incomingCallFromPush.d.ts.map +1 -0
  281. package/lib/typescript/helpers/insertMessageWithDelimiter.d.ts.map +1 -1
  282. package/lib/typescript/helpers/isUnresolvedSenderId.d.ts +2 -0
  283. package/lib/typescript/helpers/isUnresolvedSenderId.d.ts.map +1 -0
  284. package/lib/typescript/hooks/useChatSettingState.d.ts +2 -1
  285. package/lib/typescript/hooks/useChatSettingState.d.ts.map +1 -1
  286. package/lib/typescript/hooks/useMessageTranslation.d.ts +25 -0
  287. package/lib/typescript/hooks/useMessageTranslation.d.ts.map +1 -0
  288. package/lib/typescript/hooks/usePushNotifications.d.ts.map +1 -1
  289. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  290. package/lib/typescript/i18n/strings.d.ts +16 -0
  291. package/lib/typescript/i18n/strings.d.ts.map +1 -0
  292. package/lib/typescript/i18n/useT.d.ts +34 -0
  293. package/lib/typescript/i18n/useT.d.ts.map +1 -0
  294. package/lib/typescript/main.d.ts +6 -1
  295. package/lib/typescript/main.d.ts.map +1 -1
  296. package/lib/typescript/networking/api-requests/push.api.d.ts.map +1 -1
  297. package/lib/typescript/networking/api-requests/rooms.api.d.ts +3 -0
  298. package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
  299. package/lib/typescript/networking/callTokenStanza.d.ts +14 -0
  300. package/lib/typescript/networking/callTokenStanza.d.ts.map +1 -0
  301. package/lib/typescript/networking/outboundQueue.d.ts +1 -0
  302. package/lib/typescript/networking/outboundQueue.d.ts.map +1 -1
  303. package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
  304. package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts.map +1 -1
  305. package/lib/typescript/networking/xmpp/sendTextMessage.xmpp.d.ts +10 -0
  306. package/lib/typescript/networking/xmpp/sendTextMessage.xmpp.d.ts.map +1 -1
  307. package/lib/typescript/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.d.ts +16 -1
  308. package/lib/typescript/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.d.ts.map +1 -1
  309. package/lib/typescript/networking/xmppClient.d.ts +1 -1
  310. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  311. package/lib/typescript/roomStore/callSlice.d.ts +39 -0
  312. package/lib/typescript/roomStore/callSlice.d.ts.map +1 -0
  313. package/lib/typescript/roomStore/chatSettingsSlice.d.ts +10 -1
  314. package/lib/typescript/roomStore/chatSettingsSlice.d.ts.map +1 -1
  315. package/lib/typescript/roomStore/index.d.ts +4 -0
  316. package/lib/typescript/roomStore/index.d.ts.map +1 -1
  317. package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
  318. package/lib/typescript/roomStore/roomsSlice.d.ts +6 -1
  319. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  320. package/lib/typescript/types/models/language.model.d.ts +1 -1
  321. package/lib/typescript/types/models/language.model.d.ts.map +1 -1
  322. package/lib/typescript/types/models/message.model.d.ts +7 -0
  323. package/lib/typescript/types/models/message.model.d.ts.map +1 -1
  324. package/lib/typescript/types/types.d.ts +165 -1
  325. package/lib/typescript/types/types.d.ts.map +1 -1
  326. package/lib/typescript/utils/translateModePolicy.d.ts +20 -0
  327. package/lib/typescript/utils/translateModePolicy.d.ts.map +1 -0
  328. package/package.json +20 -4
  329. package/src/components/MainComponents/ChatHeader.tsx +44 -11
  330. package/src/components/MainComponents/ChatWrapper.tsx +15 -0
  331. package/src/components/MainComponents/LanguageSelectorButton.tsx +53 -0
  332. package/src/components/MainComponents/LanguageSelectorModal.tsx +276 -0
  333. package/src/components/MainComponents/RoomList.tsx +27 -9
  334. package/src/components/MessageBubble/Message.tsx +109 -17
  335. package/src/components/MessageBubble/MessageTranslate.tsx +182 -0
  336. package/src/components/MessageBubble/TranslatedMessageBody.tsx +59 -0
  337. package/src/components/Modals/AttachSheet/AttachSheet.tsx +1 -3
  338. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +10 -4
  339. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +25 -8
  340. package/src/components/Modals/ModalHeaderComponent.tsx +20 -3
  341. package/src/components/UsersList/UsersList.tsx +3 -1
  342. package/src/components/VideoCalls/CallButtons.tsx +171 -0
  343. package/src/components/VideoCalls/CallIcons.tsx +246 -0
  344. package/src/components/VideoCalls/VideoCallOverlay.tsx +627 -0
  345. package/src/components/VideoCalls/VideoCallSession.tsx +675 -0
  346. package/src/components/VideoCalls/livekitRuntime.ts +100 -0
  347. package/src/components/VideoCalls/useCallKeep.ts +252 -0
  348. package/src/components/styled/AudioMessage.tsx +97 -37
  349. package/src/components/styled/RoomListComponents/index.tsx +15 -3
  350. package/src/components/styled/SendInput.tsx +125 -55
  351. package/src/components/styled/StyledComponents.ts +9 -6
  352. package/src/components/styled/StyledInputComponents/Composing.tsx +7 -1
  353. package/src/context/xmppProvider.tsx +14 -1
  354. package/src/helpers/callLogMessage.ts +174 -0
  355. package/src/helpers/callPush.ts +90 -0
  356. package/src/helpers/constants/LANGUAGE_OPTIONS.ts +20 -5
  357. package/src/helpers/createUserNameFromSetUser.ts +68 -2
  358. package/src/helpers/getDataFromXml.ts +40 -4
  359. package/src/helpers/getElementFont.ts +25 -0
  360. package/src/helpers/headerLayout.ts +30 -0
  361. package/src/helpers/incomingCallFromPush.ts +83 -0
  362. package/src/helpers/insertMessageWithDelimiter.ts +27 -9
  363. package/src/helpers/isUnresolvedSenderId.ts +25 -0
  364. package/src/hooks/useChatSettingState.tsx +4 -0
  365. package/src/hooks/useMessageTranslation.ts +70 -0
  366. package/src/hooks/usePushNotifications.ts +15 -3
  367. package/src/hooks/useSendMessage.tsx +15 -2
  368. package/src/i18n/strings.ts +1322 -0
  369. package/src/i18n/useT.ts +56 -0
  370. package/src/main.ts +18 -1
  371. package/src/networking/api-requests/push.api.ts +39 -8
  372. package/src/networking/api-requests/rooms.api.ts +109 -1
  373. package/src/networking/callTokenStanza.ts +636 -0
  374. package/src/networking/outboundQueue.ts +1 -0
  375. package/src/networking/stanzaHandlers.ts +74 -15
  376. package/src/networking/xmpp/handleStanzas.xmpp.ts +52 -11
  377. package/src/networking/xmpp/sendTextMessage.xmpp.ts +1 -1
  378. package/src/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.ts +41 -5
  379. package/src/networking/xmppClient.ts +97 -28
  380. package/src/roomStore/callSlice.ts +190 -0
  381. package/src/roomStore/chatSettingsSlice.ts +16 -0
  382. package/src/roomStore/index.ts +5 -0
  383. package/src/roomStore/persistence.ts +103 -3
  384. package/src/roomStore/roomsSlice.ts +149 -4
  385. package/src/types/models/language.model.ts +17 -1
  386. package/src/types/models/message.model.ts +9 -0
  387. package/src/types/types.ts +178 -2
  388. 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
- // `Audio` namespace gives us permissions, audio mode, presets, and the
20
- // Recording class — same pattern AudioMessage uses for Audio.Sound.
21
- import { Audio } from 'expo-av';
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-av's Audio.Recording instance for the in-progress recording.
100
- // Kept in a ref (not state) because handlers fire synchronously and we
101
- // need the latest instance without waiting for a re-render.
102
- // Typed via InstanceType<typeof Audio.Recording> because direct
103
- // `Audio.Recording` as a type-position lookup fails under bundler
104
- // resolution even though the class is in the Audio namespace at
105
- // runtime. InstanceType resolves through the typeof of the value.
106
- const recordingRef = useRef<InstanceType<typeof Audio.Recording> | null>(null);
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-av's Audio.Recording (already a peer dep
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 Audio.setAudioModeAsync({
286
- allowsRecordingIOS: false,
287
- playsInSilentModeIOS: true,
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 || recordingRef.current) {return;}
334
+ if (isRecording || isRecordingRef.current) {return;}
296
335
  try {
297
- const perm = await Audio.requestPermissionsAsync();
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: recording requires `allowsRecordingIOS:true`; we'll
305
- // flip it back in restoreAudioMode after stop/cancel.
306
- await Audio.setAudioModeAsync({
307
- allowsRecordingIOS: true,
308
- playsInSilentModeIOS: true,
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
- const rec = new Audio.Recording();
311
- await rec.prepareToRecordAsync(
312
- Audio.RecordingOptionsPresets.HIGH_QUALITY
313
- );
314
- await rec.startAsync();
315
- recordingRef.current = rec;
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
- recordingRef.current = null;
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 rec = recordingRef.current;
335
- recordingRef.current = null;
376
+ const wasRecording = isRecordingRef.current;
377
+ isRecordingRef.current = false;
336
378
  setIsRecording(false);
337
379
  setRecordingDuration(0);
338
- if (rec) {
380
+ if (wasRecording) {
339
381
  try {
340
- await rec.stopAndUnloadAsync();
382
+ await audioRecorder.stop();
341
383
  } catch {
342
- /* already stopped / unloaded — fine */
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 rec = recordingRef.current;
351
- recordingRef.current = null;
392
+ const wasRecording = isRecordingRef.current;
393
+ isRecordingRef.current = false;
352
394
  setIsRecording(false);
353
395
  const seconds = recordingDuration;
354
396
  setRecordingDuration(0);
355
- if (!rec) {
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 rec.stopAndUnloadAsync();
405
+ await audioRecorder.stop();
364
406
  } catch {}
365
407
  await restoreAudioMode();
366
408
  return;
367
409
  }
368
410
  try {
369
- await rec.stopAndUnloadAsync();
411
+ await audioRecorder.stop();
370
412
  } catch (err) {
371
- console.warn('stopAndUnloadAsync failed', err);
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
- const rec = recordingRef.current;
399
- recordingRef.current = null;
400
- if (rec) {
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="Type message"
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). minHeight keeps the 40px tap
521
- // target; the row's align-items:center vertically centres
522
- // the content-sized input.
523
- style={{
524
- minHeight: config?.typography?.input?.minHeight ?? 40,
525
- maxHeight: config?.typography?.input?.maxHeight ?? 120,
526
- flex: 1,
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
- width: ${({ rightSpace, leftSpace }) =>
44
- rightSpace && !leftSpace
45
- ? '100%'
46
- : rightSpace || !leftSpace
47
- ? '90%'
48
- : '70%'};
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 style={styles.typingText}>{typingText}</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
- if (AppState.currentState !== 'active') {return;}
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
+ };