@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
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Optional-dependency bridge for the LiveKit React Native SDK.
3
+ *
4
+ * Calls are opt-in (`config.videoCalls.enabled`), and the LiveKit packages
5
+ * are NATIVE modules: they need a dev build, they cannot run in Expo Go,
6
+ * and they add camera/microphone entitlements to whatever app installs
7
+ * them. Most hosts embedding this chat SDK don't want calls at all, so
8
+ * `@livekit/react-native` and `@livekit/react-native-webrtc` are declared
9
+ * as OPTIONAL peer dependencies.
10
+ *
11
+ * That means a plain top-level `import ... from '@livekit/react-native'`
12
+ * is not allowed anywhere in the SDK: Metro resolves imports at bundle
13
+ * time, so a chat-only host would fail to build over a feature it never
14
+ * switched on. Everything call-related goes through `loadLiveKit()`, which
15
+ * requires the modules lazily at call time and returns null when they
16
+ * aren't installed. Callers surface that as a friendly config error rather
17
+ * than a redbox.
18
+ */
19
+
20
+ export interface LiveKitRuntime {
21
+ /** livekit-client `Room` constructor. */
22
+ Room: any;
23
+ /** livekit-client `RoomEvent` enum. */
24
+ RoomEvent: any;
25
+ /** livekit-client `Track` enum (Track.Source.Camera etc). */
26
+ Track: any;
27
+ /** livekit-client `ConnectionState` enum. */
28
+ ConnectionState: any;
29
+ /** RN `<VideoTrack trackRef={...} />` renderer. */
30
+ VideoTrack: any;
31
+ /** Native audio session control (routing, speaker, focus). */
32
+ AudioSession: any;
33
+ /** Installs the WebRTC globals livekit-client expects. */
34
+ registerGlobals: () => void;
35
+ }
36
+
37
+ let cached: LiveKitRuntime | null | undefined;
38
+
39
+ export const isLiveKitAvailable = (): boolean => loadLiveKit() !== null;
40
+
41
+ /**
42
+ * Resolve the LiveKit runtime, or null when the optional native packages
43
+ * aren't installed in the host app. Result is memoised (including the
44
+ * failure) so a missing dependency doesn't re-throw on every render.
45
+ */
46
+ export const loadLiveKit = (): LiveKitRuntime | null => {
47
+ if (cached !== undefined) {
48
+ return cached;
49
+ }
50
+
51
+ try {
52
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
53
+ const rn = require('@livekit/react-native');
54
+ // `livekit-client` is a hard dependency of @livekit/react-native, so
55
+ // if the first require succeeded this one will too. Read the enums
56
+ // from it rather than from the RN wrapper: the wrapper re-exports a
57
+ // moving subset across versions, the core package does not.
58
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
59
+ const core = require('livekit-client');
60
+
61
+ cached = {
62
+ Room: core.Room,
63
+ RoomEvent: core.RoomEvent,
64
+ Track: core.Track,
65
+ ConnectionState: core.ConnectionState,
66
+ VideoTrack: rn.VideoTrack,
67
+ AudioSession: rn.AudioSession,
68
+ registerGlobals: rn.registerGlobals,
69
+ };
70
+ } catch {
71
+ cached = null;
72
+ }
73
+
74
+ return cached;
75
+ };
76
+
77
+ let globalsRegistered = false;
78
+
79
+ /**
80
+ * `registerGlobals()` patches RTCPeerConnection / MediaStream / navigator
81
+ * .mediaDevices onto the JS global scope so livekit-client (written for
82
+ * browsers) works on React Native. It must run exactly once per JS
83
+ * context, before the first `new Room()`. Calling it repeatedly is
84
+ * harmless in current versions but has thrown in older ones, so guard it.
85
+ */
86
+ export const ensureLiveKitGlobals = (runtime: LiveKitRuntime): void => {
87
+ if (globalsRegistered) {return;}
88
+ try {
89
+ runtime.registerGlobals?.();
90
+ } catch {
91
+ // Already registered by the host app (it may use LiveKit elsewhere).
92
+ }
93
+ globalsRegistered = true;
94
+ };
95
+
96
+ /** Reset memoised state. Test seam only. */
97
+ export const __resetLiveKitRuntimeForTests = (): void => {
98
+ cached = undefined;
99
+ globalsRegistered = false;
100
+ };
@@ -0,0 +1,252 @@
1
+ import { useEffect, useMemo, useRef } from 'react';
2
+ import { Platform } from 'react-native';
3
+ import { useDispatch, useSelector } from 'react-redux';
4
+ import { RootState, store } from '../../roomStore';
5
+ import { acceptIncomingCall, declineIncomingCall } from '../../roomStore/callSlice';
6
+ import { sendCallStateSignal } from '../../networking/callTokenStanza';
7
+
8
+ /**
9
+ * Native incoming-call UI (Android ConnectionService / iOS CallKit) via
10
+ * the optional `react-native-callkeep` package.
11
+ *
12
+ * Why optional: CallKeep pulls a telecom-framework dependency into the
13
+ * host app and, on iOS, is only half a solution without PushKit. It is
14
+ * loaded lazily exactly like the LiveKit packages, so hosts that don't
15
+ * install it get the plain in-app ring screen and nothing breaks.
16
+ *
17
+ * Platform reality, stated plainly:
18
+ * - Android: full value. A ConnectionService call shows the system
19
+ * incoming-call screen over the lock screen.
20
+ * - iOS: this only fires while the app is running. A CallKit screen from
21
+ * a killed app requires a VoIP push over PushKit, which needs a
22
+ * separate Apple VoIP certificate and a separate send path on the
23
+ * backend (not FCM). Until that exists, iOS falls back to the regular
24
+ * data push and the in-app ring screen.
25
+ */
26
+
27
+ interface CallKeepModule {
28
+ setup: (options: any) => Promise<void>;
29
+ displayIncomingCall: (
30
+ uuid: string,
31
+ handle: string,
32
+ localizedCallerName?: string,
33
+ handleType?: string,
34
+ hasVideo?: boolean
35
+ ) => void;
36
+ endCall: (uuid: string) => void;
37
+ endAllCalls: () => void;
38
+ addEventListener: (event: string, handler: (payload: any) => void) => void;
39
+ removeEventListener: (event: string) => void;
40
+ setAvailable?: (available: boolean) => void;
41
+ backToForeground?: () => void;
42
+ }
43
+
44
+ let cachedCallKeep: CallKeepModule | null | undefined;
45
+
46
+ export const loadCallKeep = (): CallKeepModule | null => {
47
+ if (cachedCallKeep !== undefined) {
48
+ return cachedCallKeep;
49
+ }
50
+ try {
51
+ // The JS package being installed does NOT mean the native side is
52
+ // linked: a host can `npm i react-native-callkeep` and skip
53
+ // `pod install`, or (very common in this repo) node_modules can carry
54
+ // it over from another branch whose build did link it. In that state
55
+ // requiring the package builds a `new NativeEventEmitter(null)` at
56
+ // module scope, which throws an invariant that RN surfaces as an
57
+ // uncaught redbox on every render of the call overlay — the whole app
58
+ // becomes unusable over a feature the host never switched on.
59
+ // Probing NativeModules first keeps the optional dependency genuinely
60
+ // optional: no native module => behave exactly as if it isn't installed.
61
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
62
+ const { NativeModules } = require('react-native');
63
+ if (!NativeModules?.RNCallKeep) {
64
+ cachedCallKeep = null;
65
+ return cachedCallKeep;
66
+ }
67
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
68
+ const mod = require('react-native-callkeep');
69
+ cachedCallKeep = (mod?.default || mod) as CallKeepModule;
70
+ } catch {
71
+ cachedCallKeep = null;
72
+ }
73
+ return cachedCallKeep;
74
+ };
75
+
76
+ // CallKeep identifies calls by UUID. Our callIds aren't UUIDs, so derive a
77
+ // stable v4-shaped string from the callId: same call always maps to the
78
+ // same UUID, which is what endCall() needs to dismiss the right screen.
79
+ const toUuid = (seed: string): string => {
80
+ let hash = 0x811c9dc5;
81
+ const bytes: number[] = [];
82
+ for (let i = 0; i < 32; i++) {
83
+ const ch = seed.charCodeAt(i % Math.max(1, seed.length)) + i;
84
+ hash ^= ch;
85
+ hash = Math.imul(hash, 0x01000193) >>> 0;
86
+ bytes.push(hash & 0xff);
87
+ }
88
+ const hex = bytes.map((b) => b.toString(16).padStart(2, '0')).join('');
89
+ return [
90
+ hex.slice(0, 8),
91
+ hex.slice(8, 12),
92
+ '4' + hex.slice(13, 16),
93
+ ((parseInt(hex[16], 16) & 0x3) | 0x8).toString(16) + hex.slice(17, 20),
94
+ hex.slice(20, 32),
95
+ ].join('-');
96
+ };
97
+
98
+ export const useCallKeep = (): void => {
99
+ const dispatch = useDispatch();
100
+ const call = useSelector((state: RootState) => state.call);
101
+ const callKeep = useMemo(() => loadCallKeep(), []);
102
+ const activeUuidRef = useRef<string | null>(null);
103
+ const readyRef = useRef(false);
104
+
105
+ // One-time native setup.
106
+ useEffect(() => {
107
+ if (!callKeep) {return;}
108
+ let cancelled = false;
109
+
110
+ (async () => {
111
+ try {
112
+ await callKeep.setup({
113
+ ios: {
114
+ appName: 'Ethora',
115
+ supportsVideo: true,
116
+ },
117
+ android: {
118
+ alertTitle: 'Permissions required',
119
+ alertDescription:
120
+ 'This application needs to access your phone accounts to show incoming calls',
121
+ cancelButton: 'Cancel',
122
+ okButton: 'ok',
123
+ additionalPermissions: [],
124
+ foregroundService: {
125
+ channelId: 'com.ethora.chat.calls',
126
+ channelName: 'Incoming calls',
127
+ notificationTitle: 'Call in progress',
128
+ },
129
+ },
130
+ });
131
+ if (cancelled) {return;}
132
+ callKeep.setAvailable?.(true);
133
+ readyRef.current = true;
134
+ } catch {
135
+ // Host didn't grant phone-account permission, or the module isn't
136
+ // linked. The in-app ring screen still works, so stay quiet.
137
+ readyRef.current = false;
138
+ }
139
+ })();
140
+
141
+ return () => {
142
+ cancelled = true;
143
+ };
144
+ }, [callKeep]);
145
+
146
+ // Bridge native answer/end actions back into Redux.
147
+ useEffect(() => {
148
+ if (!callKeep) {return;}
149
+
150
+ const onAnswer = () => {
151
+ // The user answered from the system screen, which on Android may
152
+ // mean the app was not even in the foreground.
153
+ callKeep.backToForeground?.();
154
+ if (store.getState().call.direction === 'incoming') {
155
+ dispatch(acceptIncomingCall());
156
+ }
157
+ };
158
+
159
+ const onEnd = () => {
160
+ const snapshot = store.getState().call;
161
+ if (snapshot.phase === 'idle') {return;}
162
+ if (
163
+ snapshot.direction === 'incoming' &&
164
+ snapshot.phase === 'ringing-incoming'
165
+ ) {
166
+ sendCallStateSignal('declined');
167
+ dispatch(declineIncomingCall());
168
+ }
169
+ };
170
+
171
+ callKeep.addEventListener('answerCall', onAnswer);
172
+ callKeep.addEventListener('endCall', onEnd);
173
+
174
+ return () => {
175
+ callKeep.removeEventListener('answerCall');
176
+ callKeep.removeEventListener('endCall');
177
+ };
178
+ }, [callKeep, dispatch]);
179
+
180
+ // Show / dismiss the native screen as the call state changes.
181
+ useEffect(() => {
182
+ if (!callKeep || !readyRef.current) {return;}
183
+
184
+ const isRinging =
185
+ call.direction === 'incoming' && call.phase === 'ringing-incoming';
186
+
187
+ if (isRinging && !activeUuidRef.current) {
188
+ const uuid = toUuid(call.callId || call.roomJid || String(Date.now()));
189
+ activeUuidRef.current = uuid;
190
+ try {
191
+ callKeep.displayIncomingCall(
192
+ uuid,
193
+ call.peerXmppUsername || call.roomName || 'Ethora',
194
+ call.roomName || undefined,
195
+ 'generic',
196
+ call.kind === 'video'
197
+ );
198
+ } catch {
199
+ activeUuidRef.current = null;
200
+ }
201
+ return;
202
+ }
203
+
204
+ // Any exit from ringing (answered in-app, declined, peer cancelled)
205
+ // must tear the system screen down or it hangs around ringing.
206
+ if (!isRinging && activeUuidRef.current && call.phase !== 'connecting' && call.phase !== 'in-call') {
207
+ try {
208
+ callKeep.endCall(activeUuidRef.current);
209
+ } catch {
210
+ // Screen already gone.
211
+ }
212
+ activeUuidRef.current = null;
213
+ }
214
+
215
+ if (call.phase === 'idle' && activeUuidRef.current) {
216
+ try {
217
+ callKeep.endCall(activeUuidRef.current);
218
+ } catch {
219
+ // noop
220
+ }
221
+ activeUuidRef.current = null;
222
+ }
223
+ }, [
224
+ callKeep,
225
+ call.direction,
226
+ call.phase,
227
+ call.callId,
228
+ call.roomJid,
229
+ call.roomName,
230
+ call.peerXmppUsername,
231
+ call.kind,
232
+ ]);
233
+
234
+ // Nothing to clean up on unmount beyond the active screen: this hook
235
+ // lives for the lifetime of the provider.
236
+ useEffect(
237
+ () => () => {
238
+ if (callKeep && activeUuidRef.current) {
239
+ try {
240
+ callKeep.endCall(activeUuidRef.current);
241
+ } catch {
242
+ // noop
243
+ }
244
+ }
245
+ },
246
+ [callKeep]
247
+ );
248
+
249
+ // iOS-only note kept as a runtime no-op so the import isn't unused when
250
+ // the platform check is tree-shaken in release builds.
251
+ void Platform.OS;
252
+ };
@@ -7,7 +7,12 @@ import {
7
7
  ActivityIndicator,
8
8
  Platform,
9
9
  } from 'react-native';
10
- import { Audio, AVPlaybackStatus } from 'expo-av';
10
+ import {
11
+ createAudioPlayer,
12
+ setAudioModeAsync,
13
+ type AudioPlayer,
14
+ type AudioStatus,
15
+ } from 'expo-audio';
11
16
  import * as FileSystem from 'expo-file-system/legacy';
12
17
  import { WebView, WebViewMessageEvent } from 'react-native-webview';
13
18
  import { PauseIcon, PlayIcon } from '../../assets/icons';
@@ -196,11 +201,11 @@ const getContainerExtension = (container: AudioContainer) => {
196
201
  }
197
202
  };
198
203
 
199
- // AVFoundation (expo-av) and the iOS WKWebView <audio> element cannot
204
+ // AVFoundation (expo-audio) and the iOS WKWebView <audio> element cannot
200
205
  // demux/decode WebM or Ogg Opus — only `AudioContext.decodeAudioData` can
201
206
  // on iOS. So on iOS those two containers go through the WebView decoder.
202
207
  // Everything else (mp3/m4a/aac/wav), and ALL of Android (ExoPlayer handles
203
- // Opus natively), plays straight through expo-av.
208
+ // Opus natively), plays straight through expo-audio.
204
209
  const requiresWebAudioDecode = (container: AudioContainer) =>
205
210
  Platform.OS === 'ios' && (container === 'webm' || container === 'ogg');
206
211
 
@@ -213,7 +218,7 @@ const requiresWebAudioDecode = (container: AudioContainer) =>
213
218
  // cannot supply, so both hang silently.
214
219
  // Therefore the page decodes Opus → PCM, re-encodes to a WAV blob, and ships
215
220
  // the bytes back to RN as base64. RN writes the WAV to cache and plays it
216
- // through expo-av (native, no gesture limits, real play/pause/seek).
221
+ // through expo-audio (native, no gesture limits, real play/pause/seek).
217
222
  const WEB_DECODER_HTML = `<!doctype html>
218
223
  <html>
219
224
  <head>
@@ -314,7 +319,11 @@ const AudioMessage = ({
314
319
  originalName,
315
320
  }: AudioMessageProps) => {
316
321
  const { config } = useChatSettingState();
317
- const soundRef = useRef<Audio.Sound | null>(null);
322
+ const soundRef = useRef<AudioPlayer | null>(null);
323
+ // expo-audio delivers progress through an event subscription instead of
324
+ // expo-av's `onPlaybackStatusUpdate` callback argument, so the handle has
325
+ // to be held and removed alongside the player itself.
326
+ const statusSubRef = useRef<{ remove: () => void } | null>(null);
318
327
  const webViewRef = useRef<WebView | null>(null);
319
328
  const preparedRef = useRef<PreparedSource | null>(null);
320
329
  const bridgeReadyRef = useRef(false);
@@ -338,6 +347,29 @@ const AudioMessage = ({
338
347
  }
339
348
  };
340
349
 
350
+ // expo-audio players are native shared objects: they are NOT garbage
351
+ // collected with the component, so every one we create has to be
352
+ // explicitly `remove()`d (expo-av's `unloadAsync` equivalent) and its
353
+ // status subscription torn down first.
354
+ const releasePlayer = () => {
355
+ statusSubRef.current?.remove();
356
+ statusSubRef.current = null;
357
+ const player = soundRef.current;
358
+ soundRef.current = null;
359
+ if (player) {
360
+ try {
361
+ player.pause();
362
+ } catch {
363
+ /* already released */
364
+ }
365
+ try {
366
+ player.remove();
367
+ } catch {
368
+ /* already released */
369
+ }
370
+ }
371
+ };
372
+
341
373
  const fail = (reason: unknown) => {
342
374
  clearLoadingGuard();
343
375
  if (unmountedRef.current) {
@@ -360,7 +392,7 @@ const AudioMessage = ({
360
392
  };
361
393
 
362
394
  // Hard ceiling on the loading spinner — whatever stalls (a hung network
363
- // request, a WebView that never decodes, expo-av wedging on a bad file)
395
+ // request, a WebView that never decodes, a player that never loads)
364
396
  // the control resolves to an error state instead of spinning forever
365
397
  // (the customer-reported "stuck spinner").
366
398
  const armLoadingGuard = () => {
@@ -480,50 +512,70 @@ const AudioMessage = ({
480
512
  }
481
513
  };
482
514
 
483
- const onPlaybackStatusUpdate = (status: AVPlaybackStatus) => {
515
+ // expo-audio reports times in SECONDS (expo-av used milliseconds); the
516
+ // whole UI below is millisecond-based, so convert at this boundary and
517
+ // nowhere else.
518
+ const onPlaybackStatusUpdate = (status: AudioStatus) => {
484
519
  if (!status.isLoaded) {
485
- if (status.error) {
486
- logAudioDebug('playback status error', { src, error: status.error });
487
- }
488
520
  return;
489
521
  }
490
- setPosition(status.positionMillis);
491
- setDuration(status.durationMillis ?? 0);
492
- setIsPlaying(status.isPlaying);
522
+ setPosition(Math.max(0, status.currentTime * 1000));
523
+ setDuration(status.duration > 0 ? status.duration * 1000 : 0);
524
+ setIsPlaying(status.playing);
493
525
  if (status.didJustFinish) {
494
526
  didFinishRef.current = true;
495
527
  setIsPlaying(false);
496
528
  setPosition(0);
497
- void soundRef.current?.setStatusAsync({
498
- shouldPlay: false,
499
- positionMillis: 0,
500
- });
529
+ const player = soundRef.current;
530
+ if (player) {
531
+ player.pause();
532
+ void player.seekTo(0);
533
+ }
501
534
  }
502
535
  };
503
536
 
504
537
  const startNativePlayback = async (localUri: string) => {
505
538
  try {
506
- await Audio.setAudioModeAsync({ playsInSilentModeIOS: true });
539
+ await setAudioModeAsync({ playsInSilentMode: true });
507
540
  } catch {
508
541
  /* non-fatal — still plays through the ringer channel */
509
542
  }
510
- const { sound } = await withTimeout<{
511
- sound: Audio.Sound;
512
- status: AVPlaybackStatus;
513
- }>(
514
- Audio.Sound.createAsync(
515
- { uri: localUri },
516
- { shouldPlay: true },
517
- onPlaybackStatusUpdate
518
- ),
519
- 12000,
520
- 'audio_create_timeout'
543
+ // expo-audio has no awaitable `createAsync`: the player is constructed
544
+ // synchronously and loads in the background, reporting `isLoaded` via
545
+ // the status event. Wait for that first loaded status (same 12s budget
546
+ // expo-av's createAsync had) so the spinner still resolves to either
547
+ // real playback or the error state, never to a silent dead button.
548
+ const player = createAudioPlayer({ uri: localUri }, { updateInterval: 250 });
549
+ let settleLoaded: (() => void) | null = null;
550
+ const loaded = new Promise<void>((resolve) => {
551
+ settleLoaded = resolve;
552
+ });
553
+ const subscription = player.addListener(
554
+ 'playbackStatusUpdate',
555
+ (status: AudioStatus) => {
556
+ if (status.isLoaded && settleLoaded) {
557
+ settleLoaded();
558
+ settleLoaded = null;
559
+ }
560
+ onPlaybackStatusUpdate(status);
561
+ }
521
562
  );
563
+
564
+ try {
565
+ await withTimeout(loaded, 12000, 'audio_create_timeout');
566
+ } catch (error) {
567
+ subscription.remove();
568
+ player.remove();
569
+ throw error;
570
+ }
522
571
  if (unmountedRef.current) {
523
- void sound.unloadAsync();
572
+ subscription.remove();
573
+ player.remove();
524
574
  return;
525
575
  }
526
- soundRef.current = sound;
576
+ soundRef.current = player;
577
+ statusSubRef.current = subscription;
578
+ player.play();
527
579
  clearLoadingGuard();
528
580
  setIsLoading(false);
529
581
  setPlaybackError(false);
@@ -595,14 +647,21 @@ const AudioMessage = ({
595
647
  // decoded-WAV from the WebView path) → plain toggle.
596
648
  if (soundRef.current) {
597
649
  try {
650
+ // `play()` / `pause()` emit NO status event of their own (expo-av's
651
+ // playAsync/pauseAsync resolved with one). Playing state only ever
652
+ // reaches us through the periodic time observer, and that observer
653
+ // stops while paused — so without setting this here the button
654
+ // would latch on "pause" forever and never resume.
598
655
  if (isPlaying) {
599
- await soundRef.current.pauseAsync();
656
+ soundRef.current.pause();
657
+ setIsPlaying(false);
600
658
  } else {
601
659
  if (didFinishRef.current) {
602
- await soundRef.current.setPositionAsync(0);
660
+ await soundRef.current.seekTo(0);
603
661
  didFinishRef.current = false;
604
662
  }
605
- await soundRef.current.playAsync();
663
+ soundRef.current.play();
664
+ setIsPlaying(true);
606
665
  }
607
666
  } catch (error) {
608
667
  fail(error);
@@ -643,7 +702,7 @@ const AudioMessage = ({
643
702
  return () => {
644
703
  unmountedRef.current = true;
645
704
  clearLoadingGuard();
646
- soundRef.current?.unloadAsync();
705
+ releasePlayer();
647
706
  };
648
707
  // eslint-disable-next-line react-hooks/exhaustive-deps
649
708
  }, []);
@@ -651,8 +710,7 @@ const AudioMessage = ({
651
710
  // Full reset whenever the source changes (component reused for a new
652
711
  // message / re-render with a different src).
653
712
  useEffect(() => {
654
- soundRef.current?.unloadAsync();
655
- soundRef.current = null;
713
+ releasePlayer();
656
714
  preparedRef.current = null;
657
715
  bridgeReadyRef.current = false;
658
716
  injectedRef.current = false;
@@ -693,6 +751,8 @@ const AudioMessage = ({
693
751
  ) : null}
694
752
  <View style={styles.container}>
695
753
  <TouchableOpacity
754
+ testID="audio-play-button"
755
+ accessibilityLabel="audio-play-button"
696
756
  style={[styles.playButton, { backgroundColor: primaryColor }]}
697
757
  onPress={togglePlayback}
698
758
  disabled={isLoading}
@@ -68,12 +68,19 @@ export const ChatInfo = ({ children }: { children: React.ReactNode }) => (
68
68
  );
69
69
 
70
70
  // Chat name and last message display
71
+ // One line, ellipsised. Without this a long room title (or a display name
72
+ // that happens to be an email) wrapped to three or four lines and pushed
73
+ // the whole row's height around.
71
74
  export const ChatName = ({ text }: { text: string }) => (
72
- <Text style={styles.chatName}>{text}</Text>
75
+ <Text style={styles.chatName} numberOfLines={1} ellipsizeMode="tail">
76
+ {text}
77
+ </Text>
73
78
  );
74
79
 
75
80
  export const LastMessage = ({ children }: { children: React.ReactNode }) => (
76
- <Text style={styles.lastMessage}>{children}</Text>
81
+ <Text style={styles.lastMessage} numberOfLines={1} ellipsizeMode="tail">
82
+ {children}
83
+ </Text>
77
84
  );
78
85
 
79
86
  // User count display for the chat
@@ -145,7 +152,12 @@ const styles = StyleSheet.create({
145
152
  flexDirection: 'column',
146
153
  },
147
154
  chatInfo: {
148
- maxWidth: '60%',
155
+ // `flexShrink` + `minWidth: 0` is what actually lets the child Text
156
+ // ellipsise: without them the row keeps its intrinsic width and the
157
+ // text wraps instead of truncating, whatever numberOfLines says.
158
+ flexShrink: 1,
159
+ minWidth: 0,
160
+ maxWidth: '80%',
149
161
  },
150
162
  chatName: {
151
163
  fontWeight: 'bold',