@ethora/chat-component-rn 26.5.21 → 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 (352) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +3 -3
  3. package/index.js +1 -1
  4. package/lib/commonjs/components/MainComponents/ChatHeader.js +20 -8
  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/FilePreviewModal/FilePreviewModal.js +3 -3
  21. package/lib/commonjs/components/UsersList/UsersList.js +3 -1
  22. package/lib/commonjs/components/UsersList/UsersList.js.map +1 -1
  23. package/lib/commonjs/components/VideoCalls/CallButtons.js +159 -0
  24. package/lib/commonjs/components/VideoCalls/CallButtons.js.map +1 -0
  25. package/lib/commonjs/components/VideoCalls/CallIcons.js +328 -0
  26. package/lib/commonjs/components/VideoCalls/CallIcons.js.map +1 -0
  27. package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js +550 -0
  28. package/lib/commonjs/components/VideoCalls/VideoCallOverlay.js.map +1 -0
  29. package/lib/commonjs/components/VideoCalls/VideoCallSession.js +636 -0
  30. package/lib/commonjs/components/VideoCalls/VideoCallSession.js.map +1 -0
  31. package/lib/commonjs/components/VideoCalls/livekitRuntime.js +91 -0
  32. package/lib/commonjs/components/VideoCalls/livekitRuntime.js.map +1 -0
  33. package/lib/commonjs/components/VideoCalls/useCallKeep.js +215 -0
  34. package/lib/commonjs/components/VideoCalls/useCallKeep.js.map +1 -0
  35. package/lib/commonjs/components/styled/AudioMessage.js +91 -33
  36. package/lib/commonjs/components/styled/AudioMessage.js.map +1 -1
  37. package/lib/commonjs/components/styled/RoomListComponents/index.js +13 -1
  38. package/lib/commonjs/components/styled/RoomListComponents/index.js.map +1 -1
  39. package/lib/commonjs/components/styled/SendInput.js +83 -49
  40. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  41. package/lib/commonjs/components/styled/StyledComponents.js +9 -4
  42. package/lib/commonjs/components/styled/StyledComponents.js.map +1 -1
  43. package/lib/commonjs/components/styled/StyledInputComponents/Composing.js +2 -0
  44. package/lib/commonjs/components/styled/StyledInputComponents/Composing.js.map +1 -1
  45. package/lib/commonjs/context/xmppProvider.js +10 -3
  46. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  47. package/lib/commonjs/helpers/callLogMessage.js +162 -0
  48. package/lib/commonjs/helpers/callLogMessage.js.map +1 -0
  49. package/lib/commonjs/helpers/callPush.js +61 -0
  50. package/lib/commonjs/helpers/callPush.js.map +1 -0
  51. package/lib/commonjs/helpers/constants/LANGUAGE_OPTIONS.js +24 -12
  52. package/lib/commonjs/helpers/constants/LANGUAGE_OPTIONS.js.map +1 -1
  53. package/lib/commonjs/helpers/createUserNameFromSetUser.js +51 -2
  54. package/lib/commonjs/helpers/createUserNameFromSetUser.js.map +1 -1
  55. package/lib/commonjs/helpers/getDataFromXml.js +19 -1
  56. package/lib/commonjs/helpers/getDataFromXml.js.map +1 -1
  57. package/lib/commonjs/helpers/incomingCallFromPush.js +74 -0
  58. package/lib/commonjs/helpers/incomingCallFromPush.js.map +1 -0
  59. package/lib/commonjs/helpers/insertMessageWithDelimiter.js +13 -1
  60. package/lib/commonjs/helpers/insertMessageWithDelimiter.js.map +1 -1
  61. package/lib/commonjs/helpers/isUnresolvedSenderId.js +32 -0
  62. package/lib/commonjs/helpers/isUnresolvedSenderId.js.map +1 -0
  63. package/lib/commonjs/hooks/useChatSettingState.js +2 -0
  64. package/lib/commonjs/hooks/useChatSettingState.js.map +1 -1
  65. package/lib/commonjs/hooks/useMessageTranslation.js +51 -0
  66. package/lib/commonjs/hooks/useMessageTranslation.js.map +1 -0
  67. package/lib/commonjs/hooks/usePushNotifications.js +13 -3
  68. package/lib/commonjs/hooks/usePushNotifications.js.map +1 -1
  69. package/lib/commonjs/hooks/useSendMessage.js +12 -2
  70. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  71. package/lib/commonjs/i18n/strings.js +1308 -0
  72. package/lib/commonjs/i18n/strings.js.map +1 -0
  73. package/lib/commonjs/i18n/useT.js +60 -0
  74. package/lib/commonjs/i18n/useT.js.map +1 -0
  75. package/lib/commonjs/main.js +44 -0
  76. package/lib/commonjs/main.js.map +1 -1
  77. package/lib/commonjs/networking/api-requests/push.api.js +34 -5
  78. package/lib/commonjs/networking/api-requests/push.api.js.map +1 -1
  79. package/lib/commonjs/networking/api-requests/rooms.api.js +108 -0
  80. package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
  81. package/lib/commonjs/networking/callTokenStanza.js +531 -0
  82. package/lib/commonjs/networking/callTokenStanza.js.map +1 -0
  83. package/lib/commonjs/networking/outboundQueue.js.map +1 -1
  84. package/lib/commonjs/networking/stanzaHandlers.js +75 -8
  85. package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
  86. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +47 -11
  87. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  88. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js +2 -1
  89. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  90. package/lib/commonjs/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js +42 -5
  91. package/lib/commonjs/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js.map +1 -1
  92. package/lib/commonjs/networking/xmppClient.js +78 -16
  93. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  94. package/lib/commonjs/roomStore/callSlice.js +144 -0
  95. package/lib/commonjs/roomStore/callSlice.js.map +1 -0
  96. package/lib/commonjs/roomStore/chatSettingsSlice.js +8 -2
  97. package/lib/commonjs/roomStore/chatSettingsSlice.js.map +1 -1
  98. package/lib/commonjs/roomStore/index.js +7 -1
  99. package/lib/commonjs/roomStore/index.js.map +1 -1
  100. package/lib/commonjs/roomStore/persistence.js +69 -5
  101. package/lib/commonjs/roomStore/persistence.js.map +1 -1
  102. package/lib/commonjs/roomStore/roomsSlice.js +136 -6
  103. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  104. package/lib/commonjs/utils/translateModePolicy.js +33 -0
  105. package/lib/commonjs/utils/translateModePolicy.js.map +1 -0
  106. package/lib/module/components/MainComponents/ChatHeader.js +20 -8
  107. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  108. package/lib/module/components/MainComponents/ChatWrapper.js +17 -0
  109. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  110. package/lib/module/components/MainComponents/LanguageSelectorButton.js +57 -0
  111. package/lib/module/components/MainComponents/LanguageSelectorButton.js.map +1 -0
  112. package/lib/module/components/MainComponents/LanguageSelectorModal.js +246 -0
  113. package/lib/module/components/MainComponents/LanguageSelectorModal.js.map +1 -0
  114. package/lib/module/components/MainComponents/RoomList.js +23 -9
  115. package/lib/module/components/MainComponents/RoomList.js.map +1 -1
  116. package/lib/module/components/MessageBubble/Message.js +73 -12
  117. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  118. package/lib/module/components/MessageBubble/MessageTranslate.js +155 -0
  119. package/lib/module/components/MessageBubble/MessageTranslate.js.map +1 -0
  120. package/lib/module/components/MessageBubble/TranslatedMessageBody.js +53 -0
  121. package/lib/module/components/MessageBubble/TranslatedMessageBody.js.map +1 -0
  122. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +3 -3
  123. package/lib/module/components/UsersList/UsersList.js +3 -1
  124. package/lib/module/components/UsersList/UsersList.js.map +1 -1
  125. package/lib/module/components/VideoCalls/CallButtons.js +154 -0
  126. package/lib/module/components/VideoCalls/CallButtons.js.map +1 -0
  127. package/lib/module/components/VideoCalls/CallIcons.js +309 -0
  128. package/lib/module/components/VideoCalls/CallIcons.js.map +1 -0
  129. package/lib/module/components/VideoCalls/VideoCallOverlay.js +544 -0
  130. package/lib/module/components/VideoCalls/VideoCallOverlay.js.map +1 -0
  131. package/lib/module/components/VideoCalls/VideoCallSession.js +630 -0
  132. package/lib/module/components/VideoCalls/VideoCallSession.js.map +1 -0
  133. package/lib/module/components/VideoCalls/livekitRuntime.js +83 -0
  134. package/lib/module/components/VideoCalls/livekitRuntime.js.map +1 -0
  135. package/lib/module/components/VideoCalls/useCallKeep.js +210 -0
  136. package/lib/module/components/VideoCalls/useCallKeep.js.map +1 -0
  137. package/lib/module/components/styled/AudioMessage.js +91 -33
  138. package/lib/module/components/styled/AudioMessage.js.map +1 -1
  139. package/lib/module/components/styled/RoomListComponents/index.js +13 -1
  140. package/lib/module/components/styled/RoomListComponents/index.js.map +1 -1
  141. package/lib/module/components/styled/SendInput.js +83 -49
  142. package/lib/module/components/styled/SendInput.js.map +1 -1
  143. package/lib/module/components/styled/StyledComponents.js +9 -4
  144. package/lib/module/components/styled/StyledComponents.js.map +1 -1
  145. package/lib/module/components/styled/StyledInputComponents/Composing.js +2 -0
  146. package/lib/module/components/styled/StyledInputComponents/Composing.js.map +1 -1
  147. package/lib/module/context/xmppProvider.js +11 -4
  148. package/lib/module/context/xmppProvider.js.map +1 -1
  149. package/lib/module/helpers/callLogMessage.js +152 -0
  150. package/lib/module/helpers/callLogMessage.js.map +1 -0
  151. package/lib/module/helpers/callPush.js +55 -0
  152. package/lib/module/helpers/callPush.js.map +1 -0
  153. package/lib/module/helpers/constants/LANGUAGE_OPTIONS.js +24 -12
  154. package/lib/module/helpers/constants/LANGUAGE_OPTIONS.js.map +1 -1
  155. package/lib/module/helpers/createUserNameFromSetUser.js +49 -1
  156. package/lib/module/helpers/createUserNameFromSetUser.js.map +1 -1
  157. package/lib/module/helpers/getDataFromXml.js +20 -1
  158. package/lib/module/helpers/getDataFromXml.js.map +1 -1
  159. package/lib/module/helpers/incomingCallFromPush.js +70 -0
  160. package/lib/module/helpers/incomingCallFromPush.js.map +1 -0
  161. package/lib/module/helpers/insertMessageWithDelimiter.js +13 -1
  162. package/lib/module/helpers/insertMessageWithDelimiter.js.map +1 -1
  163. package/lib/module/helpers/isUnresolvedSenderId.js +27 -0
  164. package/lib/module/helpers/isUnresolvedSenderId.js.map +1 -0
  165. package/lib/module/hooks/useChatSettingState.js +2 -0
  166. package/lib/module/hooks/useChatSettingState.js.map +1 -1
  167. package/lib/module/hooks/useMessageTranslation.js +46 -0
  168. package/lib/module/hooks/useMessageTranslation.js.map +1 -0
  169. package/lib/module/hooks/usePushNotifications.js +13 -3
  170. package/lib/module/hooks/usePushNotifications.js.map +1 -1
  171. package/lib/module/hooks/useSendMessage.js +12 -2
  172. package/lib/module/hooks/useSendMessage.js.map +1 -1
  173. package/lib/module/i18n/strings.js +1300 -0
  174. package/lib/module/i18n/strings.js.map +1 -0
  175. package/lib/module/i18n/useT.js +55 -0
  176. package/lib/module/i18n/useT.js.map +1 -0
  177. package/lib/module/main.js +7 -1
  178. package/lib/module/main.js.map +1 -1
  179. package/lib/module/networking/api-requests/push.api.js +35 -5
  180. package/lib/module/networking/api-requests/push.api.js.map +1 -1
  181. package/lib/module/networking/api-requests/rooms.api.js +109 -1
  182. package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
  183. package/lib/module/networking/callTokenStanza.js +526 -0
  184. package/lib/module/networking/callTokenStanza.js.map +1 -0
  185. package/lib/module/networking/outboundQueue.js.map +1 -1
  186. package/lib/module/networking/stanzaHandlers.js +76 -9
  187. package/lib/module/networking/stanzaHandlers.js.map +1 -1
  188. package/lib/module/networking/xmpp/handleStanzas.xmpp.js +48 -11
  189. package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  190. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js +1 -1
  191. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  192. package/lib/module/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js +43 -5
  193. package/lib/module/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js.map +1 -1
  194. package/lib/module/networking/xmppClient.js +78 -16
  195. package/lib/module/networking/xmppClient.js.map +1 -1
  196. package/lib/module/roomStore/callSlice.js +130 -0
  197. package/lib/module/roomStore/callSlice.js.map +1 -0
  198. package/lib/module/roomStore/chatSettingsSlice.js +6 -1
  199. package/lib/module/roomStore/chatSettingsSlice.js.map +1 -1
  200. package/lib/module/roomStore/index.js +6 -1
  201. package/lib/module/roomStore/index.js.map +1 -1
  202. package/lib/module/roomStore/persistence.js +69 -5
  203. package/lib/module/roomStore/persistence.js.map +1 -1
  204. package/lib/module/roomStore/roomsSlice.js +134 -5
  205. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  206. package/lib/module/utils/translateModePolicy.js +27 -0
  207. package/lib/module/utils/translateModePolicy.js.map +1 -0
  208. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  209. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  210. package/lib/typescript/components/MainComponents/LanguageSelectorButton.d.ts +12 -0
  211. package/lib/typescript/components/MainComponents/LanguageSelectorButton.d.ts.map +1 -0
  212. package/lib/typescript/components/MainComponents/LanguageSelectorModal.d.ts +31 -0
  213. package/lib/typescript/components/MainComponents/LanguageSelectorModal.d.ts.map +1 -0
  214. package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
  215. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  216. package/lib/typescript/components/MessageBubble/MessageTranslate.d.ts +44 -0
  217. package/lib/typescript/components/MessageBubble/MessageTranslate.d.ts.map +1 -0
  218. package/lib/typescript/components/MessageBubble/TranslatedMessageBody.d.ts +24 -0
  219. package/lib/typescript/components/MessageBubble/TranslatedMessageBody.d.ts.map +1 -0
  220. package/lib/typescript/components/UsersList/UsersList.d.ts.map +1 -1
  221. package/lib/typescript/components/VideoCalls/CallButtons.d.ts +16 -0
  222. package/lib/typescript/components/VideoCalls/CallButtons.d.ts.map +1 -0
  223. package/lib/typescript/components/VideoCalls/CallIcons.d.ts +30 -0
  224. package/lib/typescript/components/VideoCalls/CallIcons.d.ts.map +1 -0
  225. package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts +10 -0
  226. package/lib/typescript/components/VideoCalls/VideoCallOverlay.d.ts.map +1 -0
  227. package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts +38 -0
  228. package/lib/typescript/components/VideoCalls/VideoCallSession.d.ts.map +1 -0
  229. package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts +52 -0
  230. package/lib/typescript/components/VideoCalls/livekitRuntime.d.ts.map +1 -0
  231. package/lib/typescript/components/VideoCalls/useCallKeep.d.ts +32 -0
  232. package/lib/typescript/components/VideoCalls/useCallKeep.d.ts.map +1 -0
  233. package/lib/typescript/components/styled/AudioMessage.d.ts.map +1 -1
  234. package/lib/typescript/components/styled/RoomListComponents/index.d.ts.map +1 -1
  235. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  236. package/lib/typescript/components/styled/StyledComponents.d.ts.map +1 -1
  237. package/lib/typescript/components/styled/StyledInputComponents/Composing.d.ts.map +1 -1
  238. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  239. package/lib/typescript/helpers/callLogMessage.d.ts +34 -0
  240. package/lib/typescript/helpers/callLogMessage.d.ts.map +1 -0
  241. package/lib/typescript/helpers/callPush.d.ts +23 -0
  242. package/lib/typescript/helpers/callPush.d.ts.map +1 -0
  243. package/lib/typescript/helpers/constants/LANGUAGE_OPTIONS.d.ts +14 -0
  244. package/lib/typescript/helpers/constants/LANGUAGE_OPTIONS.d.ts.map +1 -1
  245. package/lib/typescript/helpers/createUserNameFromSetUser.d.ts +33 -1
  246. package/lib/typescript/helpers/createUserNameFromSetUser.d.ts.map +1 -1
  247. package/lib/typescript/helpers/getDataFromXml.d.ts.map +1 -1
  248. package/lib/typescript/helpers/incomingCallFromPush.d.ts +14 -0
  249. package/lib/typescript/helpers/incomingCallFromPush.d.ts.map +1 -0
  250. package/lib/typescript/helpers/insertMessageWithDelimiter.d.ts.map +1 -1
  251. package/lib/typescript/helpers/isUnresolvedSenderId.d.ts +2 -0
  252. package/lib/typescript/helpers/isUnresolvedSenderId.d.ts.map +1 -0
  253. package/lib/typescript/hooks/useChatSettingState.d.ts +2 -1
  254. package/lib/typescript/hooks/useChatSettingState.d.ts.map +1 -1
  255. package/lib/typescript/hooks/useMessageTranslation.d.ts +25 -0
  256. package/lib/typescript/hooks/useMessageTranslation.d.ts.map +1 -0
  257. package/lib/typescript/hooks/usePushNotifications.d.ts.map +1 -1
  258. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  259. package/lib/typescript/i18n/strings.d.ts +16 -0
  260. package/lib/typescript/i18n/strings.d.ts.map +1 -0
  261. package/lib/typescript/i18n/useT.d.ts +34 -0
  262. package/lib/typescript/i18n/useT.d.ts.map +1 -0
  263. package/lib/typescript/main.d.ts +6 -1
  264. package/lib/typescript/main.d.ts.map +1 -1
  265. package/lib/typescript/networking/api-requests/push.api.d.ts.map +1 -1
  266. package/lib/typescript/networking/api-requests/rooms.api.d.ts +3 -0
  267. package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
  268. package/lib/typescript/networking/callTokenStanza.d.ts +14 -0
  269. package/lib/typescript/networking/callTokenStanza.d.ts.map +1 -0
  270. package/lib/typescript/networking/outboundQueue.d.ts +1 -0
  271. package/lib/typescript/networking/outboundQueue.d.ts.map +1 -1
  272. package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
  273. package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts.map +1 -1
  274. package/lib/typescript/networking/xmpp/sendTextMessage.xmpp.d.ts +10 -0
  275. package/lib/typescript/networking/xmpp/sendTextMessage.xmpp.d.ts.map +1 -1
  276. package/lib/typescript/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.d.ts +16 -1
  277. package/lib/typescript/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.d.ts.map +1 -1
  278. package/lib/typescript/networking/xmppClient.d.ts +1 -1
  279. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  280. package/lib/typescript/roomStore/callSlice.d.ts +39 -0
  281. package/lib/typescript/roomStore/callSlice.d.ts.map +1 -0
  282. package/lib/typescript/roomStore/chatSettingsSlice.d.ts +10 -1
  283. package/lib/typescript/roomStore/chatSettingsSlice.d.ts.map +1 -1
  284. package/lib/typescript/roomStore/index.d.ts +4 -0
  285. package/lib/typescript/roomStore/index.d.ts.map +1 -1
  286. package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
  287. package/lib/typescript/roomStore/roomsSlice.d.ts +6 -1
  288. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  289. package/lib/typescript/types/models/language.model.d.ts +1 -1
  290. package/lib/typescript/types/models/language.model.d.ts.map +1 -1
  291. package/lib/typescript/types/models/message.model.d.ts +7 -0
  292. package/lib/typescript/types/models/message.model.d.ts.map +1 -1
  293. package/lib/typescript/types/types.d.ts +135 -1
  294. package/lib/typescript/types/types.d.ts.map +1 -1
  295. package/lib/typescript/utils/translateModePolicy.d.ts +20 -0
  296. package/lib/typescript/utils/translateModePolicy.d.ts.map +1 -0
  297. package/package.json +20 -4
  298. package/src/components/MainComponents/ChatHeader.tsx +30 -9
  299. package/src/components/MainComponents/ChatWrapper.tsx +15 -0
  300. package/src/components/MainComponents/LanguageSelectorButton.tsx +53 -0
  301. package/src/components/MainComponents/LanguageSelectorModal.tsx +276 -0
  302. package/src/components/MainComponents/RoomList.tsx +27 -9
  303. package/src/components/MessageBubble/Message.tsx +109 -17
  304. package/src/components/MessageBubble/MessageTranslate.tsx +182 -0
  305. package/src/components/MessageBubble/TranslatedMessageBody.tsx +59 -0
  306. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +3 -3
  307. package/src/components/UsersList/UsersList.tsx +3 -1
  308. package/src/components/VideoCalls/CallButtons.tsx +171 -0
  309. package/src/components/VideoCalls/CallIcons.tsx +246 -0
  310. package/src/components/VideoCalls/VideoCallOverlay.tsx +627 -0
  311. package/src/components/VideoCalls/VideoCallSession.tsx +675 -0
  312. package/src/components/VideoCalls/livekitRuntime.ts +100 -0
  313. package/src/components/VideoCalls/useCallKeep.ts +252 -0
  314. package/src/components/styled/AudioMessage.tsx +97 -37
  315. package/src/components/styled/RoomListComponents/index.tsx +15 -3
  316. package/src/components/styled/SendInput.tsx +94 -51
  317. package/src/components/styled/StyledComponents.ts +9 -6
  318. package/src/components/styled/StyledInputComponents/Composing.tsx +7 -1
  319. package/src/context/xmppProvider.tsx +14 -1
  320. package/src/helpers/callLogMessage.ts +174 -0
  321. package/src/helpers/callPush.ts +90 -0
  322. package/src/helpers/constants/LANGUAGE_OPTIONS.ts +20 -5
  323. package/src/helpers/createUserNameFromSetUser.ts +68 -2
  324. package/src/helpers/getDataFromXml.ts +40 -4
  325. package/src/helpers/incomingCallFromPush.ts +83 -0
  326. package/src/helpers/insertMessageWithDelimiter.ts +27 -9
  327. package/src/helpers/isUnresolvedSenderId.ts +25 -0
  328. package/src/hooks/useChatSettingState.tsx +4 -0
  329. package/src/hooks/useMessageTranslation.ts +70 -0
  330. package/src/hooks/usePushNotifications.ts +15 -3
  331. package/src/hooks/useSendMessage.tsx +15 -2
  332. package/src/i18n/strings.ts +1322 -0
  333. package/src/i18n/useT.ts +56 -0
  334. package/src/main.ts +18 -1
  335. package/src/networking/api-requests/push.api.ts +39 -8
  336. package/src/networking/api-requests/rooms.api.ts +109 -1
  337. package/src/networking/callTokenStanza.ts +636 -0
  338. package/src/networking/outboundQueue.ts +1 -0
  339. package/src/networking/stanzaHandlers.ts +74 -15
  340. package/src/networking/xmpp/handleStanzas.xmpp.ts +52 -11
  341. package/src/networking/xmpp/sendTextMessage.xmpp.ts +1 -1
  342. package/src/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.ts +41 -5
  343. package/src/networking/xmppClient.ts +97 -28
  344. package/src/roomStore/callSlice.ts +190 -0
  345. package/src/roomStore/chatSettingsSlice.ts +16 -0
  346. package/src/roomStore/index.ts +5 -0
  347. package/src/roomStore/persistence.ts +103 -3
  348. package/src/roomStore/roomsSlice.ts +149 -4
  349. package/src/types/models/language.model.ts +17 -1
  350. package/src/types/models/message.model.ts +9 -0
  351. package/src/types/types.ts +142 -2
  352. package/src/utils/translateModePolicy.ts +31 -0
@@ -14,12 +14,20 @@ import AttachSheet from '../Modals/AttachSheet/AttachSheet';
14
14
  import { MediaFilePreview } from './MediaFilePreview';
15
15
  import { getIconColor } from '../../helpers/getIconColor';
16
16
  import { getElementFont } from '../../helpers/getElementFont';
17
+ import { useT } from '../../i18n/useT';
17
18
  import * as ImagePicker from 'expo-image-picker';
18
19
  import * as DocumentPicker from 'expo-document-picker';
19
20
  import * as ImageManipulator from 'expo-image-manipulator';
20
- // `Audio` namespace gives us permissions, audio mode, presets, and the
21
- // Recording class — same pattern AudioMessage uses for Audio.Sound.
22
- 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';
23
31
 
24
32
  // iOS photos default to HEIC; many web backends (incl. ours) 500 on
25
33
  // HEIC uploads because they can't decode it. Convert to JPEG before
@@ -88,6 +96,7 @@ const SendInput: React.FC<SendInputProps> = ({
88
96
  isLoading,
89
97
  isMessageProcessing,
90
98
  }) => {
99
+ const t = useT();
91
100
  const [message, setMessage] = useState('');
92
101
  const [isRecording, setIsRecording] = useState(false);
93
102
  // Live counter (seconds) for the recording overlay. Updated by a
@@ -97,14 +106,20 @@ const SendInput: React.FC<SendInputProps> = ({
97
106
  const [filePreviews, setFilePreviews] = useState<MediaFile[]>([]);
98
107
  const [showMediaMenu, setShowMediaMenu] = useState(false);
99
108
 
100
- // expo-av's Audio.Recording instance for the in-progress recording.
101
- // Kept in a ref (not state) because handlers fire synchronously and we
102
- // need the latest instance without waiting for a re-render.
103
- // Typed via InstanceType<typeof Audio.Recording> because direct
104
- // `Audio.Recording` as a type-position lookup fails under bundler
105
- // resolution even though the class is in the Audio namespace at
106
- // runtime. InstanceType resolves through the typeof of the value.
107
- 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);
108
123
  const recordingTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
109
124
 
110
125
  // Refs shadow `message` and `filePreviews` so handleSendClick can:
@@ -290,7 +305,7 @@ const SendInput: React.FC<SendInputProps> = ({
290
305
  //
291
306
  // Mirrors the web experience: tap mic → start recording → either tap
292
307
  // X to discard, or tap send to stop + immediately upload + post the
293
- // voice message. Uses expo-av's Audio.Recording (already a peer dep
308
+ // voice message. Uses expo-audio's AudioRecorder (already a peer dep
294
309
  // for AudioMessage playback). Filename is `voice-<timestamp>.m4a` with
295
310
  // mimetype `audio/m4a` so the receiver routes through the audio
296
311
  // branch in MediaMessage directly, no octet-stream sniffing needed.
@@ -306,9 +321,9 @@ const SendInput: React.FC<SendInputProps> = ({
306
321
  // playback elsewhere in the app isn't routed to the earpiece.
307
322
  const restoreAudioMode = async () => {
308
323
  try {
309
- await Audio.setAudioModeAsync({
310
- allowsRecordingIOS: false,
311
- playsInSilentModeIOS: true,
324
+ await setAudioModeAsync({
325
+ allowsRecording: false,
326
+ playsInSilentMode: true,
312
327
  });
313
328
  } catch {
314
329
  /* non-fatal */
@@ -316,27 +331,30 @@ const SendInput: React.FC<SendInputProps> = ({
316
331
  };
317
332
 
318
333
  const handleStartRecording = async () => {
319
- if (isRecording || recordingRef.current) {return;}
334
+ if (isRecording || isRecordingRef.current) {return;}
320
335
  try {
321
- const perm = await Audio.requestPermissionsAsync();
336
+ const perm = await requestRecordingPermissionsAsync();
322
337
  if (!perm.granted) {
323
338
  promptOpenSettings(
324
339
  'Microphone permission is needed to record voice messages.'
325
340
  );
326
341
  return;
327
342
  }
328
- // iOS: recording requires `allowsRecordingIOS:true`; we'll
329
- // flip it back in restoreAudioMode after stop/cancel.
330
- await Audio.setAudioModeAsync({
331
- allowsRecordingIOS: true,
332
- 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,
333
350
  });
334
- const rec = new Audio.Recording();
335
- await rec.prepareToRecordAsync(
336
- Audio.RecordingOptionsPresets.HIGH_QUALITY
337
- );
338
- await rec.startAsync();
339
- 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;
340
358
  setIsRecording(true);
341
359
  setRecordingDuration(0);
342
360
  const startedAt = Date.now();
@@ -346,7 +364,7 @@ const SendInput: React.FC<SendInputProps> = ({
346
364
  }, 250);
347
365
  } catch (err) {
348
366
  console.warn('startRecording failed', err);
349
- recordingRef.current = null;
367
+ isRecordingRef.current = false;
350
368
  setIsRecording(false);
351
369
  setRecordingDuration(0);
352
370
  await restoreAudioMode();
@@ -355,15 +373,15 @@ const SendInput: React.FC<SendInputProps> = ({
355
373
 
356
374
  const handleCancelRecording = async () => {
357
375
  stopRecordingTimer();
358
- const rec = recordingRef.current;
359
- recordingRef.current = null;
376
+ const wasRecording = isRecordingRef.current;
377
+ isRecordingRef.current = false;
360
378
  setIsRecording(false);
361
379
  setRecordingDuration(0);
362
- if (rec) {
380
+ if (wasRecording) {
363
381
  try {
364
- await rec.stopAndUnloadAsync();
382
+ await audioRecorder.stop();
365
383
  } catch {
366
- /* already stopped / unloaded — fine */
384
+ /* already stopped — fine */
367
385
  }
368
386
  }
369
387
  await restoreAudioMode();
@@ -371,12 +389,12 @@ const SendInput: React.FC<SendInputProps> = ({
371
389
 
372
390
  const handleStopAndSendRecording = async () => {
373
391
  stopRecordingTimer();
374
- const rec = recordingRef.current;
375
- recordingRef.current = null;
392
+ const wasRecording = isRecordingRef.current;
393
+ isRecordingRef.current = false;
376
394
  setIsRecording(false);
377
395
  const seconds = recordingDuration;
378
396
  setRecordingDuration(0);
379
- if (!rec) {
397
+ if (!wasRecording) {
380
398
  await restoreAudioMode();
381
399
  return;
382
400
  }
@@ -384,18 +402,20 @@ const SendInput: React.FC<SendInputProps> = ({
384
402
  // within the same tick there's nothing audible to send.
385
403
  if (seconds < 1) {
386
404
  try {
387
- await rec.stopAndUnloadAsync();
405
+ await audioRecorder.stop();
388
406
  } catch {}
389
407
  await restoreAudioMode();
390
408
  return;
391
409
  }
392
410
  try {
393
- await rec.stopAndUnloadAsync();
411
+ await audioRecorder.stop();
394
412
  } catch (err) {
395
- console.warn('stopAndUnloadAsync failed', err);
413
+ console.warn('recorder.stop failed', err);
396
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;
397
418
  await restoreAudioMode();
398
- const uri = rec.getURI();
399
419
  if (!uri) {return;}
400
420
  // HIGH_QUALITY preset writes AAC in an M4A container on both iOS
401
421
  // and Android, so a single mimetype works. `voice-<ts>.m4a`
@@ -419,14 +439,16 @@ const SendInput: React.FC<SendInputProps> = ({
419
439
  useEffect(() => {
420
440
  return () => {
421
441
  stopRecordingTimer();
422
- const rec = recordingRef.current;
423
- recordingRef.current = null;
424
- if (rec) {
425
- rec.stopAndUnloadAsync().catch(() => {});
442
+ if (isRecordingRef.current) {
443
+ isRecordingRef.current = false;
444
+ audioRecorder.stop().catch(() => {});
426
445
  }
427
446
  restoreAudioMode();
428
447
  };
429
- }, []);
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]);
430
452
 
431
453
  const handleRemoveImage = (index: number) => {
432
454
  const next = filePreviewsRef.current.filter((_, i) => i !== index);
@@ -487,6 +509,24 @@ const SendInput: React.FC<SendInputProps> = ({
487
509
  setMessage(next);
488
510
  }, [editMessage]);
489
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
+
490
530
  return (
491
531
  <InputContainer isText={!!message}>
492
532
  {filePreviews.length > 0 && (
@@ -521,7 +561,7 @@ const SendInput: React.FC<SendInputProps> = ({
521
561
  color={config?.colors?.primary}
522
562
  fontSize={config?.typography?.input?.fontSize}
523
563
  fontWeight={config?.typography?.input?.fontWeight as any}
524
- placeholder="Type message"
564
+ placeholder={t('input.placeholder')}
525
565
  placeholderTextColor="#999"
526
566
  value={message}
527
567
  onChangeText={(text) => {
@@ -541,13 +581,16 @@ const SendInput: React.FC<SendInputProps> = ({
541
581
  // No fixed height: a multiline TextInput auto-grows to fit
542
582
  // its content between min/max. Locking `height` made iOS
543
583
  // top-align the text inside the taller box (it looked like
544
- // the text had dropped). minHeight keeps the 40px tap
545
- // target; the row's align-items:center vertically centres
546
- // the content-sized input.
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.
547
588
  style={[
548
589
  {
549
- minHeight: config?.typography?.input?.minHeight ?? 40,
590
+ minHeight: inputMinHeight,
550
591
  maxHeight: config?.typography?.input?.maxHeight ?? 120,
592
+ paddingTop: inputVerticalPadding,
593
+ paddingBottom: inputVerticalPadding,
551
594
  flex: 1,
552
595
  },
553
596
  getElementFont(config, 'inputText'),
@@ -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
+ };
@@ -5,10 +5,25 @@ type LanguageOption = {
5
5
  id: Iso639_1Codes;
6
6
  };
7
7
 
8
+ /**
9
+ * Languages the in-chat globe picker offers.
10
+ *
11
+ * Names are AUTONYMS (each language written in itself), which is what
12
+ * every mainstream language picker does: a reader who only speaks French
13
+ * can find "Français" but not "French". Ids carry the region because the
14
+ * deployment's readers are Canadian and US, and the region is forwarded to
15
+ * the translation service so it can tell fr-CA from fr-FR. Base-language
16
+ * lookups (i18n tables, Translate-button visibility) strip it via
17
+ * toBaseLanguage, so both forms interoperate.
18
+ *
19
+ * Commented-out entries still have full string tables in i18n/strings.ts,
20
+ * they're just not offered to readers in the current deployment.
21
+ */
8
22
  export const LANGUAGE_OPTIONS: LanguageOption[] = [
9
- { name: 'English', id: 'en' },
10
- { name: 'Spanish', id: 'es' },
11
- { name: 'Portuguese', id: 'pt' },
12
- { name: 'Haitian Creole', id: 'ht' },
13
- { name: 'Chinese', id: 'zh' },
23
+ { name: 'English', id: 'en-CA' },
24
+ { name: 'Español', id: 'es-US' },
25
+ { name: 'Français', id: 'fr-CA' },
26
+ // { name: 'Português', id: 'pt' },
27
+ // { name: 'Kreyòl ayisyen', id: 'ht' },
28
+ // { name: '中文', id: 'zh' },
14
29
  ];