@ethora/chat-component-rn 26.9.1 → 26.10.0

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 (760) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +19 -0
  3. package/lib/commonjs/assets/icons.js +2 -2
  4. package/lib/commonjs/assets/icons.js.map +1 -1
  5. package/lib/commonjs/components/Header/HeaderRoomList.js +22 -6
  6. package/lib/commonjs/components/Header/HeaderRoomList.js.map +1 -1
  7. package/lib/commonjs/components/InputComponents/Search.js +38 -1
  8. package/lib/commonjs/components/InputComponents/Search.js.map +1 -1
  9. package/lib/commonjs/components/MainComponents/ArchivedMessageCard.js +166 -0
  10. package/lib/commonjs/components/MainComponents/ArchivedMessageCard.js.map +1 -0
  11. package/lib/commonjs/components/MainComponents/ChatHeader.js +22 -5
  12. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  13. package/lib/commonjs/components/MainComponents/ChatRoom.js +56 -25
  14. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  15. package/lib/commonjs/components/MainComponents/ChatWrapper.js +31 -1
  16. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  17. package/lib/commonjs/components/MainComponents/MediaMessage.js +18 -3
  18. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  19. package/lib/commonjs/components/MainComponents/MessageList.js +739 -42
  20. package/lib/commonjs/components/MainComponents/MessageList.js.map +1 -1
  21. package/lib/commonjs/components/MainComponents/MessageSearchButton.js +58 -0
  22. package/lib/commonjs/components/MainComponents/MessageSearchButton.js.map +1 -0
  23. package/lib/commonjs/components/MainComponents/ReduxWrapper.js +15 -6
  24. package/lib/commonjs/components/MainComponents/ReduxWrapper.js.map +1 -1
  25. package/lib/commonjs/components/MainComponents/RoomList.js +110 -1
  26. package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
  27. package/lib/commonjs/components/Menu/HeaderRoomListMenu.js +14 -3
  28. package/lib/commonjs/components/Menu/HeaderRoomListMenu.js.map +1 -1
  29. package/lib/commonjs/components/MessageBubble/BubbleHighlight.js +112 -0
  30. package/lib/commonjs/components/MessageBubble/BubbleHighlight.js.map +1 -0
  31. package/lib/commonjs/components/MessageBubble/Message.js +169 -125
  32. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  33. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +98 -78
  34. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  35. package/lib/commonjs/components/MessageBubble/messageMenuLayout.js +50 -0
  36. package/lib/commonjs/components/MessageBubble/messageMenuLayout.js.map +1 -0
  37. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +73 -37
  38. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  39. package/lib/commonjs/components/Modals/MessageSearchModal/MessageHitResults.js +253 -0
  40. package/lib/commonjs/components/Modals/MessageSearchModal/MessageHitResults.js.map +1 -0
  41. package/lib/commonjs/components/Modals/MessageSearchModal/MessageSearchModal.js +488 -0
  42. package/lib/commonjs/components/Modals/MessageSearchModal/MessageSearchModal.js.map +1 -0
  43. package/lib/commonjs/components/Modals/MessageSearchModal/openHit.js +51 -0
  44. package/lib/commonjs/components/Modals/MessageSearchModal/openHit.js.map +1 -0
  45. package/lib/commonjs/components/Modals/MessageSearchModal/resolveSender.js +47 -0
  46. package/lib/commonjs/components/Modals/MessageSearchModal/resolveSender.js.map +1 -0
  47. package/lib/commonjs/components/Modals/MessageSearchModal/searchFilters.js +83 -0
  48. package/lib/commonjs/components/Modals/MessageSearchModal/searchFilters.js.map +1 -0
  49. package/lib/commonjs/components/Modals/MessageSearchModal/snippet.js +61 -0
  50. package/lib/commonjs/components/Modals/MessageSearchModal/snippet.js.map +1 -0
  51. package/lib/commonjs/components/Modals/MessageSearchModal/useMessageSearch.js +136 -0
  52. package/lib/commonjs/components/Modals/MessageSearchModal/useMessageSearch.js.map +1 -0
  53. package/lib/commonjs/components/Modals/PublicChatsModal/PublicChatsModal.js +306 -0
  54. package/lib/commonjs/components/Modals/PublicChatsModal/PublicChatsModal.js.map +1 -0
  55. package/lib/commonjs/components/Modals/PublicChatsModal/publicChatsLogic.js +36 -0
  56. package/lib/commonjs/components/Modals/PublicChatsModal/publicChatsLogic.js.map +1 -0
  57. package/lib/commonjs/components/Modals/SelectUsersModal/SelectUsersModal.js +8 -1
  58. package/lib/commonjs/components/Modals/SelectUsersModal/SelectUsersModal.js.map +1 -1
  59. package/lib/commonjs/components/RoomComponents/ChatRoomItem.js +13 -2
  60. package/lib/commonjs/components/RoomComponents/ChatRoomItem.js.map +1 -1
  61. package/lib/commonjs/components/RoomComponents/SwipeableRoomRow.js +38 -6
  62. package/lib/commonjs/components/RoomComponents/SwipeableRoomRow.js.map +1 -1
  63. package/lib/commonjs/components/Thread/ThreadHeader.js +3 -1
  64. package/lib/commonjs/components/Thread/ThreadHeader.js.map +1 -1
  65. package/lib/commonjs/components/Thread/ThreadWrapper.js +90 -10
  66. package/lib/commonjs/components/Thread/ThreadWrapper.js.map +1 -1
  67. package/lib/commonjs/components/styled/AudioDecoderHost.js +338 -0
  68. package/lib/commonjs/components/styled/AudioDecoderHost.js.map +1 -0
  69. package/lib/commonjs/components/styled/AudioMessage.js +493 -420
  70. package/lib/commonjs/components/styled/AudioMessage.js.map +1 -1
  71. package/lib/commonjs/components/styled/SendInput.js +31 -5
  72. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  73. package/lib/commonjs/context/MessageNotificationContext.js +14 -4
  74. package/lib/commonjs/context/MessageNotificationContext.js.map +1 -1
  75. package/lib/commonjs/context/xmppProvider.js +41 -77
  76. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  77. package/lib/commonjs/helpers/audioWaveform.js +165 -0
  78. package/lib/commonjs/helpers/audioWaveform.js.map +1 -0
  79. package/lib/commonjs/helpers/backgroundJoinSweep.js +36 -0
  80. package/lib/commonjs/helpers/backgroundJoinSweep.js.map +1 -0
  81. package/lib/commonjs/helpers/bubbleHighlight.js +44 -0
  82. package/lib/commonjs/helpers/bubbleHighlight.js.map +1 -0
  83. package/lib/commonjs/helpers/checkUniqueUsers.js +14 -12
  84. package/lib/commonjs/helpers/checkUniqueUsers.js.map +1 -1
  85. package/lib/commonjs/helpers/constants/MODAL_COMPONENTS.js +5 -1
  86. package/lib/commonjs/helpers/constants/MODAL_COMPONENTS.js.map +1 -1
  87. package/lib/commonjs/helpers/constants/MODAL_TYPES.js +3 -1
  88. package/lib/commonjs/helpers/constants/MODAL_TYPES.js.map +1 -1
  89. package/lib/commonjs/helpers/createRoomFromApi.js +40 -3
  90. package/lib/commonjs/helpers/createRoomFromApi.js.map +1 -1
  91. package/lib/commonjs/helpers/dedupeMembers.js +34 -0
  92. package/lib/commonjs/helpers/dedupeMembers.js.map +1 -0
  93. package/lib/commonjs/helpers/ethoraLogger.js +56 -0
  94. package/lib/commonjs/helpers/ethoraLogger.js.map +1 -0
  95. package/lib/commonjs/helpers/getUnnamedUsers.js +9 -4
  96. package/lib/commonjs/helpers/getUnnamedUsers.js.map +1 -1
  97. package/lib/commonjs/helpers/historyPreloadConfig.js +44 -0
  98. package/lib/commonjs/helpers/historyPreloadConfig.js.map +1 -0
  99. package/lib/commonjs/helpers/historyPreloadScheduler.js +372 -137
  100. package/lib/commonjs/helpers/historyPreloadScheduler.js.map +1 -1
  101. package/lib/commonjs/helpers/initRoomsPresence.js +17 -5
  102. package/lib/commonjs/helpers/initRoomsPresence.js.map +1 -1
  103. package/lib/commonjs/helpers/insertMessageWithDelimiter.js +5 -0
  104. package/lib/commonjs/helpers/insertMessageWithDelimiter.js.map +1 -1
  105. package/lib/commonjs/helpers/isLikelyMucJid.js +67 -0
  106. package/lib/commonjs/helpers/isLikelyMucJid.js.map +1 -0
  107. package/lib/commonjs/helpers/isMessageSearchEnabled.js +18 -0
  108. package/lib/commonjs/helpers/isMessageSearchEnabled.js.map +1 -0
  109. package/lib/commonjs/helpers/joiningRoom.js +14 -0
  110. package/lib/commonjs/helpers/joiningRoom.js.map +1 -0
  111. package/lib/commonjs/helpers/jumpThread.js +100 -0
  112. package/lib/commonjs/helpers/jumpThread.js.map +1 -0
  113. package/lib/commonjs/helpers/jumpToMessage.js +58 -0
  114. package/lib/commonjs/helpers/jumpToMessage.js.map +1 -0
  115. package/lib/commonjs/helpers/jumpWindow.js +187 -0
  116. package/lib/commonjs/helpers/jumpWindow.js.map +1 -0
  117. package/lib/commonjs/helpers/loadRoomHistory.js +75 -0
  118. package/lib/commonjs/helpers/loadRoomHistory.js.map +1 -0
  119. package/lib/commonjs/helpers/notificationPolicy.js +77 -0
  120. package/lib/commonjs/helpers/notificationPolicy.js.map +1 -0
  121. package/lib/commonjs/helpers/openRoomAtMessage.js +47 -0
  122. package/lib/commonjs/helpers/openRoomAtMessage.js.map +1 -0
  123. package/lib/commonjs/helpers/openThreadForJump.js +72 -0
  124. package/lib/commonjs/helpers/openThreadForJump.js.map +1 -0
  125. package/lib/commonjs/helpers/parseMessageReference.js +27 -8
  126. package/lib/commonjs/helpers/parseMessageReference.js.map +1 -1
  127. package/lib/commonjs/helpers/publicChatsEntry.js +17 -0
  128. package/lib/commonjs/helpers/publicChatsEntry.js.map +1 -0
  129. package/lib/commonjs/helpers/roomActivityScore.js +37 -0
  130. package/lib/commonjs/helpers/roomActivityScore.js.map +1 -0
  131. package/lib/commonjs/helpers/roomUserCount.js +36 -0
  132. package/lib/commonjs/helpers/roomUserCount.js.map +1 -0
  133. package/lib/commonjs/helpers/runConfiguredHistoryPreload.js +68 -0
  134. package/lib/commonjs/helpers/runConfiguredHistoryPreload.js.map +1 -0
  135. package/lib/commonjs/helpers/sessionAppId.js +24 -0
  136. package/lib/commonjs/helpers/sessionAppId.js.map +1 -0
  137. package/lib/commonjs/helpers/timestamp.js +43 -0
  138. package/lib/commonjs/helpers/timestamp.js.map +1 -0
  139. package/lib/commonjs/helpers/userResolver.js +517 -0
  140. package/lib/commonjs/helpers/userResolver.js.map +1 -0
  141. package/lib/commonjs/helpers/xmppIdShape.js +52 -0
  142. package/lib/commonjs/helpers/xmppIdShape.js.map +1 -0
  143. package/lib/commonjs/hooks/useChatWrapperInit.js +27 -5
  144. package/lib/commonjs/hooks/useChatWrapperInit.js.map +1 -1
  145. package/lib/commonjs/hooks/useHeapSender.js +54 -3
  146. package/lib/commonjs/hooks/useHeapSender.js.map +1 -1
  147. package/lib/commonjs/hooks/useJumpToMessage.js +411 -0
  148. package/lib/commonjs/hooks/useJumpToMessage.js.map +1 -0
  149. package/lib/commonjs/hooks/useMessageLoaderQueue.js +32 -5
  150. package/lib/commonjs/hooks/useMessageLoaderQueue.js.map +1 -1
  151. package/lib/commonjs/hooks/useMessageQueue.js +13 -5
  152. package/lib/commonjs/hooks/useMessageQueue.js.map +1 -1
  153. package/lib/commonjs/hooks/useRoomDirectory.js +29 -0
  154. package/lib/commonjs/hooks/useRoomDirectory.js.map +1 -0
  155. package/lib/commonjs/hooks/useRoomInitialization.js +27 -5
  156. package/lib/commonjs/hooks/useRoomInitialization.js.map +1 -1
  157. package/lib/commonjs/hooks/useRoomMute.js +62 -0
  158. package/lib/commonjs/hooks/useRoomMute.js.map +1 -0
  159. package/lib/commonjs/hooks/useRoomState.js +3 -1
  160. package/lib/commonjs/hooks/useRoomState.js.map +1 -1
  161. package/lib/commonjs/hooks/useSendMessage.js +13 -3
  162. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  163. package/lib/commonjs/i18n/strings.js +288 -6
  164. package/lib/commonjs/i18n/strings.js.map +1 -1
  165. package/lib/commonjs/networking/api-requests/messageSearch.api.js +87 -0
  166. package/lib/commonjs/networking/api-requests/messageSearch.api.js.map +1 -0
  167. package/lib/commonjs/networking/api-requests/publicChats.api.js +48 -0
  168. package/lib/commonjs/networking/api-requests/publicChats.api.js.map +1 -0
  169. package/lib/commonjs/networking/api-requests/roomMembers.api.js +253 -4
  170. package/lib/commonjs/networking/api-requests/roomMembers.api.js.map +1 -1
  171. package/lib/commonjs/networking/api-requests/rooms.api.js +48 -12
  172. package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
  173. package/lib/commonjs/networking/api-requests/xmppToken.api.js +71 -0
  174. package/lib/commonjs/networking/api-requests/xmppToken.api.js.map +1 -0
  175. package/lib/commonjs/networking/ethoraEvents.js +227 -0
  176. package/lib/commonjs/networking/ethoraEvents.js.map +1 -0
  177. package/lib/commonjs/networking/stanzaHandlers.js +153 -16
  178. package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
  179. package/lib/commonjs/networking/xmpp/allRoomPresences.xmpp.js +124 -3
  180. package/lib/commonjs/networking/xmpp/allRoomPresences.xmpp.js.map +1 -1
  181. package/lib/commonjs/networking/xmpp/getHistory.xmpp.js +319 -35
  182. package/lib/commonjs/networking/xmpp/getHistory.xmpp.js.map +1 -1
  183. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +7 -0
  184. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  185. package/lib/commonjs/networking/xmpp/historyCursor.js +101 -0
  186. package/lib/commonjs/networking/xmpp/historyCursor.js.map +1 -0
  187. package/lib/commonjs/networking/xmpp/mamQueryIds.js +17 -0
  188. package/lib/commonjs/networking/xmpp/mamQueryIds.js.map +1 -0
  189. package/lib/commonjs/networking/xmpp/mamRouter.js +24 -9
  190. package/lib/commonjs/networking/xmpp/mamRouter.js.map +1 -1
  191. package/lib/commonjs/networking/xmpp/presenceInRoom.xmpp.js +70 -65
  192. package/lib/commonjs/networking/xmpp/presenceInRoom.xmpp.js.map +1 -1
  193. package/lib/commonjs/networking/xmpp/roomJoinSweep.js +389 -0
  194. package/lib/commonjs/networking/xmpp/roomJoinSweep.js.map +1 -0
  195. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js +0 -1
  196. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  197. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js +0 -1
  198. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  199. package/lib/commonjs/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js +0 -1
  200. package/lib/commonjs/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js.map +1 -1
  201. package/lib/commonjs/networking/xmppClient.js +269 -41
  202. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  203. package/lib/commonjs/networking/xmppCredentials.js +80 -0
  204. package/lib/commonjs/networking/xmppCredentials.js.map +1 -0
  205. package/lib/commonjs/roomStore/Middleware/jumpThreadMiddleware.js +158 -0
  206. package/lib/commonjs/roomStore/Middleware/jumpThreadMiddleware.js.map +1 -0
  207. package/lib/commonjs/roomStore/Middleware/reactionsMiddleware.js +6 -0
  208. package/lib/commonjs/roomStore/Middleware/reactionsMiddleware.js.map +1 -1
  209. package/lib/commonjs/roomStore/Middleware/unreadMidlleware.js +45 -7
  210. package/lib/commonjs/roomStore/Middleware/unreadMidlleware.js.map +1 -1
  211. package/lib/commonjs/roomStore/chatSettingsSlice.js +15 -1
  212. package/lib/commonjs/roomStore/chatSettingsSlice.js.map +1 -1
  213. package/lib/commonjs/roomStore/index.js +13 -3
  214. package/lib/commonjs/roomStore/index.js.map +1 -1
  215. package/lib/commonjs/roomStore/persistence.js +48 -1
  216. package/lib/commonjs/roomStore/persistence.js.map +1 -1
  217. package/lib/commonjs/roomStore/roomsSlice.js +474 -60
  218. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  219. package/lib/commonjs/roomStore/safeKey.js +44 -0
  220. package/lib/commonjs/roomStore/safeKey.js.map +1 -0
  221. package/lib/commonjs/roomStore/usersSetCap.js +37 -0
  222. package/lib/commonjs/roomStore/usersSetCap.js.map +1 -0
  223. package/lib/commonjs/version.js +1 -1
  224. package/lib/commonjs/version.js.map +1 -1
  225. package/lib/module/assets/icons.js +1 -1
  226. package/lib/module/assets/icons.js.map +1 -1
  227. package/lib/module/components/Header/HeaderRoomList.js +23 -7
  228. package/lib/module/components/Header/HeaderRoomList.js.map +1 -1
  229. package/lib/module/components/InputComponents/Search.js +39 -2
  230. package/lib/module/components/InputComponents/Search.js.map +1 -1
  231. package/lib/module/components/MainComponents/ArchivedMessageCard.js +161 -0
  232. package/lib/module/components/MainComponents/ArchivedMessageCard.js.map +1 -0
  233. package/lib/module/components/MainComponents/ChatHeader.js +22 -5
  234. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  235. package/lib/module/components/MainComponents/ChatRoom.js +57 -26
  236. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  237. package/lib/module/components/MainComponents/ChatWrapper.js +31 -1
  238. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  239. package/lib/module/components/MainComponents/MediaMessage.js +18 -3
  240. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  241. package/lib/module/components/MainComponents/MessageList.js +740 -42
  242. package/lib/module/components/MainComponents/MessageList.js.map +1 -1
  243. package/lib/module/components/MainComponents/MessageSearchButton.js +48 -0
  244. package/lib/module/components/MainComponents/MessageSearchButton.js.map +1 -0
  245. package/lib/module/components/MainComponents/ReduxWrapper.js +15 -7
  246. package/lib/module/components/MainComponents/ReduxWrapper.js.map +1 -1
  247. package/lib/module/components/MainComponents/RoomList.js +111 -2
  248. package/lib/module/components/MainComponents/RoomList.js.map +1 -1
  249. package/lib/module/components/Menu/HeaderRoomListMenu.js +15 -4
  250. package/lib/module/components/Menu/HeaderRoomListMenu.js.map +1 -1
  251. package/lib/module/components/MessageBubble/BubbleHighlight.js +106 -0
  252. package/lib/module/components/MessageBubble/BubbleHighlight.js.map +1 -0
  253. package/lib/module/components/MessageBubble/Message.js +172 -127
  254. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  255. package/lib/module/components/MessageBubble/MessageInteractions.js +98 -78
  256. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  257. package/lib/module/components/MessageBubble/messageMenuLayout.js +45 -0
  258. package/lib/module/components/MessageBubble/messageMenuLayout.js.map +1 -0
  259. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +74 -38
  260. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  261. package/lib/module/components/Modals/MessageSearchModal/MessageHitResults.js +243 -0
  262. package/lib/module/components/Modals/MessageSearchModal/MessageHitResults.js.map +1 -0
  263. package/lib/module/components/Modals/MessageSearchModal/MessageSearchModal.js +482 -0
  264. package/lib/module/components/Modals/MessageSearchModal/MessageSearchModal.js.map +1 -0
  265. package/lib/module/components/Modals/MessageSearchModal/openHit.js +45 -0
  266. package/lib/module/components/Modals/MessageSearchModal/openHit.js.map +1 -0
  267. package/lib/module/components/Modals/MessageSearchModal/resolveSender.js +41 -0
  268. package/lib/module/components/Modals/MessageSearchModal/resolveSender.js.map +1 -0
  269. package/lib/module/components/Modals/MessageSearchModal/searchFilters.js +74 -0
  270. package/lib/module/components/Modals/MessageSearchModal/searchFilters.js.map +1 -0
  271. package/lib/module/components/Modals/MessageSearchModal/snippet.js +57 -0
  272. package/lib/module/components/Modals/MessageSearchModal/snippet.js.map +1 -0
  273. package/lib/module/components/Modals/MessageSearchModal/useMessageSearch.js +131 -0
  274. package/lib/module/components/Modals/MessageSearchModal/useMessageSearch.js.map +1 -0
  275. package/lib/module/components/Modals/PublicChatsModal/PublicChatsModal.js +300 -0
  276. package/lib/module/components/Modals/PublicChatsModal/PublicChatsModal.js.map +1 -0
  277. package/lib/module/components/Modals/PublicChatsModal/publicChatsLogic.js +30 -0
  278. package/lib/module/components/Modals/PublicChatsModal/publicChatsLogic.js.map +1 -0
  279. package/lib/module/components/Modals/SelectUsersModal/SelectUsersModal.js +8 -1
  280. package/lib/module/components/Modals/SelectUsersModal/SelectUsersModal.js.map +1 -1
  281. package/lib/module/components/RoomComponents/ChatRoomItem.js +14 -3
  282. package/lib/module/components/RoomComponents/ChatRoomItem.js.map +1 -1
  283. package/lib/module/components/RoomComponents/SwipeableRoomRow.js +39 -7
  284. package/lib/module/components/RoomComponents/SwipeableRoomRow.js.map +1 -1
  285. package/lib/module/components/Thread/ThreadHeader.js +3 -1
  286. package/lib/module/components/Thread/ThreadHeader.js.map +1 -1
  287. package/lib/module/components/Thread/ThreadWrapper.js +89 -9
  288. package/lib/module/components/Thread/ThreadWrapper.js.map +1 -1
  289. package/lib/module/components/styled/AudioDecoderHost.js +332 -0
  290. package/lib/module/components/styled/AudioDecoderHost.js.map +1 -0
  291. package/lib/module/components/styled/AudioMessage.js +495 -422
  292. package/lib/module/components/styled/AudioMessage.js.map +1 -1
  293. package/lib/module/components/styled/SendInput.js +32 -6
  294. package/lib/module/components/styled/SendInput.js.map +1 -1
  295. package/lib/module/context/MessageNotificationContext.js +15 -5
  296. package/lib/module/context/MessageNotificationContext.js.map +1 -1
  297. package/lib/module/context/xmppProvider.js +42 -78
  298. package/lib/module/context/xmppProvider.js.map +1 -1
  299. package/lib/module/helpers/audioWaveform.js +153 -0
  300. package/lib/module/helpers/audioWaveform.js.map +1 -0
  301. package/lib/module/helpers/backgroundJoinSweep.js +30 -0
  302. package/lib/module/helpers/backgroundJoinSweep.js.map +1 -0
  303. package/lib/module/helpers/bubbleHighlight.js +38 -0
  304. package/lib/module/helpers/bubbleHighlight.js.map +1 -0
  305. package/lib/module/helpers/checkUniqueUsers.js +15 -13
  306. package/lib/module/helpers/checkUniqueUsers.js.map +1 -1
  307. package/lib/module/helpers/constants/MODAL_COMPONENTS.js +5 -1
  308. package/lib/module/helpers/constants/MODAL_COMPONENTS.js.map +1 -1
  309. package/lib/module/helpers/constants/MODAL_TYPES.js +3 -1
  310. package/lib/module/helpers/constants/MODAL_TYPES.js.map +1 -1
  311. package/lib/module/helpers/createRoomFromApi.js +37 -3
  312. package/lib/module/helpers/createRoomFromApi.js.map +1 -1
  313. package/lib/module/helpers/dedupeMembers.js +28 -0
  314. package/lib/module/helpers/dedupeMembers.js.map +1 -0
  315. package/lib/module/helpers/ethoraLogger.js +52 -0
  316. package/lib/module/helpers/ethoraLogger.js.map +1 -0
  317. package/lib/module/helpers/getUnnamedUsers.js +9 -4
  318. package/lib/module/helpers/getUnnamedUsers.js.map +1 -1
  319. package/lib/module/helpers/historyPreloadConfig.js +39 -0
  320. package/lib/module/helpers/historyPreloadConfig.js.map +1 -0
  321. package/lib/module/helpers/historyPreloadScheduler.js +368 -134
  322. package/lib/module/helpers/historyPreloadScheduler.js.map +1 -1
  323. package/lib/module/helpers/initRoomsPresence.js +18 -5
  324. package/lib/module/helpers/initRoomsPresence.js.map +1 -1
  325. package/lib/module/helpers/insertMessageWithDelimiter.js +5 -0
  326. package/lib/module/helpers/insertMessageWithDelimiter.js.map +1 -1
  327. package/lib/module/helpers/isLikelyMucJid.js +60 -0
  328. package/lib/module/helpers/isLikelyMucJid.js.map +1 -0
  329. package/lib/module/helpers/isMessageSearchEnabled.js +13 -0
  330. package/lib/module/helpers/isMessageSearchEnabled.js.map +1 -0
  331. package/lib/module/helpers/joiningRoom.js +9 -0
  332. package/lib/module/helpers/joiningRoom.js.map +1 -0
  333. package/lib/module/helpers/jumpThread.js +83 -0
  334. package/lib/module/helpers/jumpThread.js.map +1 -0
  335. package/lib/module/helpers/jumpToMessage.js +52 -0
  336. package/lib/module/helpers/jumpToMessage.js.map +1 -0
  337. package/lib/module/helpers/jumpWindow.js +177 -0
  338. package/lib/module/helpers/jumpWindow.js.map +1 -0
  339. package/lib/module/helpers/loadRoomHistory.js +69 -0
  340. package/lib/module/helpers/loadRoomHistory.js.map +1 -0
  341. package/lib/module/helpers/notificationPolicy.js +70 -0
  342. package/lib/module/helpers/notificationPolicy.js.map +1 -0
  343. package/lib/module/helpers/openRoomAtMessage.js +41 -0
  344. package/lib/module/helpers/openRoomAtMessage.js.map +1 -0
  345. package/lib/module/helpers/openThreadForJump.js +68 -0
  346. package/lib/module/helpers/openThreadForJump.js.map +1 -0
  347. package/lib/module/helpers/parseMessageReference.js +26 -6
  348. package/lib/module/helpers/parseMessageReference.js.map +1 -1
  349. package/lib/module/helpers/publicChatsEntry.js +12 -0
  350. package/lib/module/helpers/publicChatsEntry.js.map +1 -0
  351. package/lib/module/helpers/roomActivityScore.js +30 -0
  352. package/lib/module/helpers/roomActivityScore.js.map +1 -0
  353. package/lib/module/helpers/roomUserCount.js +29 -0
  354. package/lib/module/helpers/roomUserCount.js.map +1 -0
  355. package/lib/module/helpers/runConfiguredHistoryPreload.js +63 -0
  356. package/lib/module/helpers/runConfiguredHistoryPreload.js.map +1 -0
  357. package/lib/module/helpers/sessionAppId.js +20 -0
  358. package/lib/module/helpers/sessionAppId.js.map +1 -0
  359. package/lib/module/helpers/timestamp.js +37 -0
  360. package/lib/module/helpers/timestamp.js.map +1 -0
  361. package/lib/module/helpers/userResolver.js +503 -0
  362. package/lib/module/helpers/userResolver.js.map +1 -0
  363. package/lib/module/helpers/xmppIdShape.js +46 -0
  364. package/lib/module/helpers/xmppIdShape.js.map +1 -0
  365. package/lib/module/hooks/useChatWrapperInit.js +16 -5
  366. package/lib/module/hooks/useChatWrapperInit.js.map +1 -1
  367. package/lib/module/hooks/useHeapSender.js +54 -3
  368. package/lib/module/hooks/useHeapSender.js.map +1 -1
  369. package/lib/module/hooks/useJumpToMessage.js +389 -0
  370. package/lib/module/hooks/useJumpToMessage.js.map +1 -0
  371. package/lib/module/hooks/useMessageLoaderQueue.js +32 -5
  372. package/lib/module/hooks/useMessageLoaderQueue.js.map +1 -1
  373. package/lib/module/hooks/useMessageQueue.js +13 -5
  374. package/lib/module/hooks/useMessageQueue.js.map +1 -1
  375. package/lib/module/hooks/useRoomDirectory.js +24 -0
  376. package/lib/module/hooks/useRoomDirectory.js.map +1 -0
  377. package/lib/module/hooks/useRoomInitialization.js +29 -7
  378. package/lib/module/hooks/useRoomInitialization.js.map +1 -1
  379. package/lib/module/hooks/useRoomMute.js +58 -0
  380. package/lib/module/hooks/useRoomMute.js.map +1 -0
  381. package/lib/module/hooks/useRoomState.js +3 -1
  382. package/lib/module/hooks/useRoomState.js.map +1 -1
  383. package/lib/module/hooks/useSendMessage.js +13 -3
  384. package/lib/module/hooks/useSendMessage.js.map +1 -1
  385. package/lib/module/i18n/strings.js +288 -6
  386. package/lib/module/i18n/strings.js.map +1 -1
  387. package/lib/module/networking/api-requests/messageSearch.api.js +81 -0
  388. package/lib/module/networking/api-requests/messageSearch.api.js.map +1 -0
  389. package/lib/module/networking/api-requests/publicChats.api.js +43 -0
  390. package/lib/module/networking/api-requests/publicChats.api.js.map +1 -0
  391. package/lib/module/networking/api-requests/roomMembers.api.js +248 -4
  392. package/lib/module/networking/api-requests/roomMembers.api.js.map +1 -1
  393. package/lib/module/networking/api-requests/rooms.api.js +48 -12
  394. package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
  395. package/lib/module/networking/api-requests/xmppToken.api.js +66 -0
  396. package/lib/module/networking/api-requests/xmppToken.api.js.map +1 -0
  397. package/lib/module/networking/ethoraEvents.js +220 -0
  398. package/lib/module/networking/ethoraEvents.js.map +1 -0
  399. package/lib/module/networking/stanzaHandlers.js +155 -18
  400. package/lib/module/networking/stanzaHandlers.js.map +1 -1
  401. package/lib/module/networking/xmpp/allRoomPresences.xmpp.js +125 -3
  402. package/lib/module/networking/xmpp/allRoomPresences.xmpp.js.map +1 -1
  403. package/lib/module/networking/xmpp/getHistory.xmpp.js +318 -35
  404. package/lib/module/networking/xmpp/getHistory.xmpp.js.map +1 -1
  405. package/lib/module/networking/xmpp/handleStanzas.xmpp.js +7 -0
  406. package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  407. package/lib/module/networking/xmpp/historyCursor.js +95 -0
  408. package/lib/module/networking/xmpp/historyCursor.js.map +1 -0
  409. package/lib/module/networking/xmpp/mamQueryIds.js +12 -0
  410. package/lib/module/networking/xmpp/mamQueryIds.js.map +1 -0
  411. package/lib/module/networking/xmpp/mamRouter.js +22 -8
  412. package/lib/module/networking/xmpp/mamRouter.js.map +1 -1
  413. package/lib/module/networking/xmpp/presenceInRoom.xmpp.js +68 -62
  414. package/lib/module/networking/xmpp/presenceInRoom.xmpp.js.map +1 -1
  415. package/lib/module/networking/xmpp/roomJoinSweep.js +385 -0
  416. package/lib/module/networking/xmpp/roomJoinSweep.js.map +1 -0
  417. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js +0 -1
  418. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  419. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js +0 -1
  420. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  421. package/lib/module/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js +0 -1
  422. package/lib/module/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js.map +1 -1
  423. package/lib/module/networking/xmppClient.js +269 -41
  424. package/lib/module/networking/xmppClient.js.map +1 -1
  425. package/lib/module/networking/xmppCredentials.js +76 -0
  426. package/lib/module/networking/xmppCredentials.js.map +1 -0
  427. package/lib/module/roomStore/Middleware/jumpThreadMiddleware.js +153 -0
  428. package/lib/module/roomStore/Middleware/jumpThreadMiddleware.js.map +1 -0
  429. package/lib/module/roomStore/Middleware/reactionsMiddleware.js +6 -0
  430. package/lib/module/roomStore/Middleware/reactionsMiddleware.js.map +1 -1
  431. package/lib/module/roomStore/Middleware/unreadMidlleware.js +46 -7
  432. package/lib/module/roomStore/Middleware/unreadMidlleware.js.map +1 -1
  433. package/lib/module/roomStore/chatSettingsSlice.js +13 -0
  434. package/lib/module/roomStore/chatSettingsSlice.js.map +1 -1
  435. package/lib/module/roomStore/index.js +7 -3
  436. package/lib/module/roomStore/index.js.map +1 -1
  437. package/lib/module/roomStore/persistence.js +46 -1
  438. package/lib/module/roomStore/persistence.js.map +1 -1
  439. package/lib/module/roomStore/roomsSlice.js +459 -58
  440. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  441. package/lib/module/roomStore/safeKey.js +37 -0
  442. package/lib/module/roomStore/safeKey.js.map +1 -0
  443. package/lib/module/roomStore/usersSetCap.js +32 -0
  444. package/lib/module/roomStore/usersSetCap.js.map +1 -0
  445. package/lib/module/version.js +1 -1
  446. package/lib/module/version.js.map +1 -1
  447. package/lib/typescript/assets/icons.d.ts +4 -0
  448. package/lib/typescript/assets/icons.d.ts.map +1 -1
  449. package/lib/typescript/components/Header/HeaderRoomList.d.ts.map +1 -1
  450. package/lib/typescript/components/InputComponents/Search.d.ts.map +1 -1
  451. package/lib/typescript/components/MainComponents/ArchivedMessageCard.d.ts +12 -0
  452. package/lib/typescript/components/MainComponents/ArchivedMessageCard.d.ts.map +1 -0
  453. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  454. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  455. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  456. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  457. package/lib/typescript/components/MainComponents/MessageList.d.ts +22 -1
  458. package/lib/typescript/components/MainComponents/MessageList.d.ts.map +1 -1
  459. package/lib/typescript/components/MainComponents/MessageSearchButton.d.ts +7 -0
  460. package/lib/typescript/components/MainComponents/MessageSearchButton.d.ts.map +1 -0
  461. package/lib/typescript/components/MainComponents/ReduxWrapper.d.ts.map +1 -1
  462. package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
  463. package/lib/typescript/components/Menu/HeaderRoomListMenu.d.ts.map +1 -1
  464. package/lib/typescript/components/MessageBubble/BubbleHighlight.d.ts +12 -0
  465. package/lib/typescript/components/MessageBubble/BubbleHighlight.d.ts.map +1 -0
  466. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  467. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts +5 -0
  468. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  469. package/lib/typescript/components/MessageBubble/messageMenuLayout.d.ts +29 -0
  470. package/lib/typescript/components/MessageBubble/messageMenuLayout.d.ts.map +1 -0
  471. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts +2 -0
  472. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
  473. package/lib/typescript/components/Modals/MessageSearchModal/MessageHitResults.d.ts +44 -0
  474. package/lib/typescript/components/Modals/MessageSearchModal/MessageHitResults.d.ts.map +1 -0
  475. package/lib/typescript/components/Modals/MessageSearchModal/MessageSearchModal.d.ts +12 -0
  476. package/lib/typescript/components/Modals/MessageSearchModal/MessageSearchModal.d.ts.map +1 -0
  477. package/lib/typescript/components/Modals/MessageSearchModal/openHit.d.ts +15 -0
  478. package/lib/typescript/components/Modals/MessageSearchModal/openHit.d.ts.map +1 -0
  479. package/lib/typescript/components/Modals/MessageSearchModal/resolveSender.d.ts +34 -0
  480. package/lib/typescript/components/Modals/MessageSearchModal/resolveSender.d.ts.map +1 -0
  481. package/lib/typescript/components/Modals/MessageSearchModal/searchFilters.d.ts +41 -0
  482. package/lib/typescript/components/Modals/MessageSearchModal/searchFilters.d.ts.map +1 -0
  483. package/lib/typescript/components/Modals/MessageSearchModal/snippet.d.ts +15 -0
  484. package/lib/typescript/components/Modals/MessageSearchModal/snippet.d.ts.map +1 -0
  485. package/lib/typescript/components/Modals/MessageSearchModal/useMessageSearch.d.ts +23 -0
  486. package/lib/typescript/components/Modals/MessageSearchModal/useMessageSearch.d.ts.map +1 -0
  487. package/lib/typescript/components/Modals/PublicChatsModal/PublicChatsModal.d.ts +16 -0
  488. package/lib/typescript/components/Modals/PublicChatsModal/PublicChatsModal.d.ts.map +1 -0
  489. package/lib/typescript/components/Modals/PublicChatsModal/publicChatsLogic.d.ts +14 -0
  490. package/lib/typescript/components/Modals/PublicChatsModal/publicChatsLogic.d.ts.map +1 -0
  491. package/lib/typescript/components/Modals/SelectUsersModal/SelectUsersModal.d.ts.map +1 -1
  492. package/lib/typescript/components/RoomComponents/ChatRoomItem.d.ts.map +1 -1
  493. package/lib/typescript/components/RoomComponents/SwipeableRoomRow.d.ts +5 -1
  494. package/lib/typescript/components/RoomComponents/SwipeableRoomRow.d.ts.map +1 -1
  495. package/lib/typescript/components/Thread/ThreadHeader.d.ts.map +1 -1
  496. package/lib/typescript/components/Thread/ThreadWrapper.d.ts +1 -0
  497. package/lib/typescript/components/Thread/ThreadWrapper.d.ts.map +1 -1
  498. package/lib/typescript/components/styled/AudioDecoderHost.d.ts +39 -0
  499. package/lib/typescript/components/styled/AudioDecoderHost.d.ts.map +1 -0
  500. package/lib/typescript/components/styled/AudioMessage.d.ts +14 -1
  501. package/lib/typescript/components/styled/AudioMessage.d.ts.map +1 -1
  502. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  503. package/lib/typescript/context/MessageNotificationContext.d.ts.map +1 -1
  504. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  505. package/lib/typescript/helpers/audioWaveform.d.ts +54 -0
  506. package/lib/typescript/helpers/audioWaveform.d.ts.map +1 -0
  507. package/lib/typescript/helpers/backgroundJoinSweep.d.ts +14 -0
  508. package/lib/typescript/helpers/backgroundJoinSweep.d.ts.map +1 -0
  509. package/lib/typescript/helpers/bubbleHighlight.d.ts +17 -0
  510. package/lib/typescript/helpers/bubbleHighlight.d.ts.map +1 -0
  511. package/lib/typescript/helpers/checkUniqueUsers.d.ts +2 -2
  512. package/lib/typescript/helpers/checkUniqueUsers.d.ts.map +1 -1
  513. package/lib/typescript/helpers/constants/MODAL_COMPONENTS.d.ts.map +1 -1
  514. package/lib/typescript/helpers/constants/MODAL_TYPES.d.ts +2 -0
  515. package/lib/typescript/helpers/constants/MODAL_TYPES.d.ts.map +1 -1
  516. package/lib/typescript/helpers/createRoomFromApi.d.ts +25 -0
  517. package/lib/typescript/helpers/createRoomFromApi.d.ts.map +1 -1
  518. package/lib/typescript/helpers/dedupeMembers.d.ts +11 -0
  519. package/lib/typescript/helpers/dedupeMembers.d.ts.map +1 -0
  520. package/lib/typescript/helpers/ethoraLogger.d.ts +13 -0
  521. package/lib/typescript/helpers/ethoraLogger.d.ts.map +1 -0
  522. package/lib/typescript/helpers/getUnnamedUsers.d.ts.map +1 -1
  523. package/lib/typescript/helpers/historyPreloadConfig.d.ts +26 -0
  524. package/lib/typescript/helpers/historyPreloadConfig.d.ts.map +1 -0
  525. package/lib/typescript/helpers/historyPreloadScheduler.d.ts +8 -4
  526. package/lib/typescript/helpers/historyPreloadScheduler.d.ts.map +1 -1
  527. package/lib/typescript/helpers/initRoomsPresence.d.ts +7 -0
  528. package/lib/typescript/helpers/initRoomsPresence.d.ts.map +1 -1
  529. package/lib/typescript/helpers/insertMessageWithDelimiter.d.ts.map +1 -1
  530. package/lib/typescript/helpers/isLikelyMucJid.d.ts +19 -0
  531. package/lib/typescript/helpers/isLikelyMucJid.d.ts.map +1 -0
  532. package/lib/typescript/helpers/isMessageSearchEnabled.d.ts +12 -0
  533. package/lib/typescript/helpers/isMessageSearchEnabled.d.ts.map +1 -0
  534. package/lib/typescript/helpers/joiningRoom.d.ts +7 -0
  535. package/lib/typescript/helpers/joiningRoom.d.ts.map +1 -0
  536. package/lib/typescript/helpers/jumpThread.d.ts +48 -0
  537. package/lib/typescript/helpers/jumpThread.d.ts.map +1 -0
  538. package/lib/typescript/helpers/jumpToMessage.d.ts +26 -0
  539. package/lib/typescript/helpers/jumpToMessage.d.ts.map +1 -0
  540. package/lib/typescript/helpers/jumpWindow.d.ts +74 -0
  541. package/lib/typescript/helpers/jumpWindow.d.ts.map +1 -0
  542. package/lib/typescript/helpers/loadRoomHistory.d.ts +34 -0
  543. package/lib/typescript/helpers/loadRoomHistory.d.ts.map +1 -0
  544. package/lib/typescript/helpers/notificationPolicy.d.ts +42 -0
  545. package/lib/typescript/helpers/notificationPolicy.d.ts.map +1 -0
  546. package/lib/typescript/helpers/openRoomAtMessage.d.ts +17 -0
  547. package/lib/typescript/helpers/openRoomAtMessage.d.ts.map +1 -0
  548. package/lib/typescript/helpers/openThreadForJump.d.ts +27 -0
  549. package/lib/typescript/helpers/openThreadForJump.d.ts.map +1 -0
  550. package/lib/typescript/helpers/parseMessageReference.d.ts +18 -5
  551. package/lib/typescript/helpers/parseMessageReference.d.ts.map +1 -1
  552. package/lib/typescript/helpers/publicChatsEntry.d.ts +11 -0
  553. package/lib/typescript/helpers/publicChatsEntry.d.ts.map +1 -0
  554. package/lib/typescript/helpers/roomActivityScore.d.ts +5 -0
  555. package/lib/typescript/helpers/roomActivityScore.d.ts.map +1 -0
  556. package/lib/typescript/helpers/roomUserCount.d.ts +20 -0
  557. package/lib/typescript/helpers/roomUserCount.d.ts.map +1 -0
  558. package/lib/typescript/helpers/runConfiguredHistoryPreload.d.ts +21 -0
  559. package/lib/typescript/helpers/runConfiguredHistoryPreload.d.ts.map +1 -0
  560. package/lib/typescript/helpers/sessionAppId.d.ts +8 -0
  561. package/lib/typescript/helpers/sessionAppId.d.ts.map +1 -0
  562. package/lib/typescript/helpers/timestamp.d.ts +3 -0
  563. package/lib/typescript/helpers/timestamp.d.ts.map +1 -0
  564. package/lib/typescript/helpers/userResolver.d.ts +58 -0
  565. package/lib/typescript/helpers/userResolver.d.ts.map +1 -0
  566. package/lib/typescript/helpers/xmppIdShape.d.ts +10 -0
  567. package/lib/typescript/helpers/xmppIdShape.d.ts.map +1 -0
  568. package/lib/typescript/hooks/useChatWrapperInit.d.ts +2 -0
  569. package/lib/typescript/hooks/useChatWrapperInit.d.ts.map +1 -1
  570. package/lib/typescript/hooks/useHeapSender.d.ts.map +1 -1
  571. package/lib/typescript/hooks/useJumpToMessage.d.ts +116 -0
  572. package/lib/typescript/hooks/useJumpToMessage.d.ts.map +1 -0
  573. package/lib/typescript/hooks/useMessageLoaderQueue.d.ts.map +1 -1
  574. package/lib/typescript/hooks/useMessageQueue.d.ts.map +1 -1
  575. package/lib/typescript/hooks/useRoomDirectory.d.ts +12 -0
  576. package/lib/typescript/hooks/useRoomDirectory.d.ts.map +1 -0
  577. package/lib/typescript/hooks/useRoomInitialization.d.ts.map +1 -1
  578. package/lib/typescript/hooks/useRoomMute.d.ts +8 -0
  579. package/lib/typescript/hooks/useRoomMute.d.ts.map +1 -0
  580. package/lib/typescript/hooks/useRoomState.d.ts +1 -0
  581. package/lib/typescript/hooks/useRoomState.d.ts.map +1 -1
  582. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  583. package/lib/typescript/i18n/strings.d.ts.map +1 -1
  584. package/lib/typescript/networking/api-requests/messageSearch.api.d.ts +57 -0
  585. package/lib/typescript/networking/api-requests/messageSearch.api.d.ts.map +1 -0
  586. package/lib/typescript/networking/api-requests/publicChats.api.d.ts +24 -0
  587. package/lib/typescript/networking/api-requests/publicChats.api.d.ts.map +1 -0
  588. package/lib/typescript/networking/api-requests/roomMembers.api.d.ts +19 -1
  589. package/lib/typescript/networking/api-requests/roomMembers.api.d.ts.map +1 -1
  590. package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
  591. package/lib/typescript/networking/api-requests/xmppToken.api.d.ts +2 -0
  592. package/lib/typescript/networking/api-requests/xmppToken.api.d.ts.map +1 -0
  593. package/lib/typescript/networking/ethoraEvents.d.ts +45 -0
  594. package/lib/typescript/networking/ethoraEvents.d.ts.map +1 -0
  595. package/lib/typescript/networking/stanzaHandlers.d.ts +1 -1
  596. package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
  597. package/lib/typescript/networking/xmpp/allRoomPresences.xmpp.d.ts +32 -1
  598. package/lib/typescript/networking/xmpp/allRoomPresences.xmpp.d.ts.map +1 -1
  599. package/lib/typescript/networking/xmpp/getHistory.xmpp.d.ts +64 -0
  600. package/lib/typescript/networking/xmpp/getHistory.xmpp.d.ts.map +1 -1
  601. package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts.map +1 -1
  602. package/lib/typescript/networking/xmpp/historyCursor.d.ts +35 -0
  603. package/lib/typescript/networking/xmpp/historyCursor.d.ts.map +1 -0
  604. package/lib/typescript/networking/xmpp/mamQueryIds.d.ts +10 -0
  605. package/lib/typescript/networking/xmpp/mamQueryIds.d.ts.map +1 -0
  606. package/lib/typescript/networking/xmpp/mamRouter.d.ts +7 -0
  607. package/lib/typescript/networking/xmpp/mamRouter.d.ts.map +1 -1
  608. package/lib/typescript/networking/xmpp/presenceInRoom.xmpp.d.ts +13 -2
  609. package/lib/typescript/networking/xmpp/presenceInRoom.xmpp.d.ts.map +1 -1
  610. package/lib/typescript/networking/xmpp/roomJoinSweep.d.ts +82 -0
  611. package/lib/typescript/networking/xmpp/roomJoinSweep.d.ts.map +1 -0
  612. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts.map +1 -1
  613. package/lib/typescript/networking/xmpp/sendTextMessage.xmpp.d.ts.map +1 -1
  614. package/lib/typescript/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.d.ts.map +1 -1
  615. package/lib/typescript/networking/xmppClient.d.ts +90 -4
  616. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  617. package/lib/typescript/networking/xmppCredentials.d.ts +12 -0
  618. package/lib/typescript/networking/xmppCredentials.d.ts.map +1 -0
  619. package/lib/typescript/roomStore/Middleware/jumpThreadMiddleware.d.ts +3 -0
  620. package/lib/typescript/roomStore/Middleware/jumpThreadMiddleware.d.ts.map +1 -0
  621. package/lib/typescript/roomStore/Middleware/reactionsMiddleware.d.ts.map +1 -1
  622. package/lib/typescript/roomStore/Middleware/unreadMidlleware.d.ts.map +1 -1
  623. package/lib/typescript/roomStore/chatSettingsSlice.d.ts +8 -1
  624. package/lib/typescript/roomStore/chatSettingsSlice.d.ts.map +1 -1
  625. package/lib/typescript/roomStore/index.d.ts +2 -1
  626. package/lib/typescript/roomStore/index.d.ts.map +1 -1
  627. package/lib/typescript/roomStore/persistence.d.ts +8 -0
  628. package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
  629. package/lib/typescript/roomStore/roomsSlice.d.ts +138 -2
  630. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  631. package/lib/typescript/roomStore/safeKey.d.ts +13 -0
  632. package/lib/typescript/roomStore/safeKey.d.ts.map +1 -0
  633. package/lib/typescript/roomStore/usersSetCap.d.ts +16 -0
  634. package/lib/typescript/roomStore/usersSetCap.d.ts.map +1 -0
  635. package/lib/typescript/types/models/media.model.d.ts +2 -0
  636. package/lib/typescript/types/models/media.model.d.ts.map +1 -1
  637. package/lib/typescript/types/models/room.model.d.ts +18 -5
  638. package/lib/typescript/types/models/room.model.d.ts.map +1 -1
  639. package/lib/typescript/types/models/xmpp.model.d.ts +8 -1
  640. package/lib/typescript/types/models/xmpp.model.d.ts.map +1 -1
  641. package/lib/typescript/types/types.d.ts +88 -5
  642. package/lib/typescript/types/types.d.ts.map +1 -1
  643. package/lib/typescript/version.d.ts +1 -1
  644. package/lib/typescript/version.d.ts.map +1 -1
  645. package/package.json +1 -1
  646. package/src/assets/icons.tsx +13 -0
  647. package/src/components/Header/HeaderRoomList.tsx +21 -4
  648. package/src/components/InputComponents/Search.tsx +33 -0
  649. package/src/components/MainComponents/ArchivedMessageCard.tsx +139 -0
  650. package/src/components/MainComponents/ChatHeader.tsx +19 -3
  651. package/src/components/MainComponents/ChatRoom.tsx +60 -23
  652. package/src/components/MainComponents/ChatWrapper.tsx +23 -4
  653. package/src/components/MainComponents/MediaMessage.tsx +17 -0
  654. package/src/components/MainComponents/MessageList.tsx +823 -84
  655. package/src/components/MainComponents/MessageSearchButton.tsx +48 -0
  656. package/src/components/MainComponents/ReduxWrapper.tsx +16 -5
  657. package/src/components/MainComponents/RoomList.tsx +125 -0
  658. package/src/components/Menu/HeaderRoomListMenu.tsx +26 -2
  659. package/src/components/MessageBubble/BubbleHighlight.tsx +106 -0
  660. package/src/components/MessageBubble/Message.tsx +265 -177
  661. package/src/components/MessageBubble/MessageInteractions.tsx +109 -86
  662. package/src/components/MessageBubble/messageMenuLayout.ts +59 -0
  663. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +92 -21
  664. package/src/components/Modals/MessageSearchModal/MessageHitResults.tsx +271 -0
  665. package/src/components/Modals/MessageSearchModal/MessageSearchModal.tsx +487 -0
  666. package/src/components/Modals/MessageSearchModal/openHit.ts +50 -0
  667. package/src/components/Modals/MessageSearchModal/resolveSender.ts +57 -0
  668. package/src/components/Modals/MessageSearchModal/searchFilters.ts +109 -0
  669. package/src/components/Modals/MessageSearchModal/snippet.ts +58 -0
  670. package/src/components/Modals/MessageSearchModal/useMessageSearch.ts +151 -0
  671. package/src/components/Modals/PublicChatsModal/PublicChatsModal.tsx +288 -0
  672. package/src/components/Modals/PublicChatsModal/publicChatsLogic.ts +36 -0
  673. package/src/components/Modals/SelectUsersModal/SelectUsersModal.tsx +8 -1
  674. package/src/components/RoomComponents/ChatRoomItem.tsx +16 -3
  675. package/src/components/RoomComponents/SwipeableRoomRow.tsx +39 -6
  676. package/src/components/Thread/ThreadHeader.tsx +3 -1
  677. package/src/components/Thread/ThreadWrapper.tsx +96 -10
  678. package/src/components/styled/AudioDecoderHost.tsx +351 -0
  679. package/src/components/styled/AudioMessage.tsx +482 -425
  680. package/src/components/styled/SendInput.tsx +42 -4
  681. package/src/context/MessageNotificationContext.tsx +19 -5
  682. package/src/context/xmppProvider.tsx +44 -91
  683. package/src/helpers/audioWaveform.ts +142 -0
  684. package/src/helpers/backgroundJoinSweep.ts +35 -0
  685. package/src/helpers/bubbleHighlight.ts +47 -0
  686. package/src/helpers/checkUniqueUsers.tsx +15 -17
  687. package/src/helpers/constants/MODAL_COMPONENTS.tsx +4 -0
  688. package/src/helpers/constants/MODAL_TYPES.ts +3 -0
  689. package/src/helpers/createRoomFromApi.tsx +45 -4
  690. package/src/helpers/dedupeMembers.ts +29 -0
  691. package/src/helpers/ethoraLogger.ts +50 -0
  692. package/src/helpers/getUnnamedUsers.ts +7 -4
  693. package/src/helpers/historyPreloadConfig.ts +68 -0
  694. package/src/helpers/historyPreloadScheduler.ts +437 -199
  695. package/src/helpers/initRoomsPresence.ts +20 -7
  696. package/src/helpers/insertMessageWithDelimiter.ts +3 -0
  697. package/src/helpers/isLikelyMucJid.ts +72 -0
  698. package/src/helpers/isMessageSearchEnabled.ts +18 -0
  699. package/src/helpers/joiningRoom.ts +13 -0
  700. package/src/helpers/jumpThread.ts +102 -0
  701. package/src/helpers/jumpToMessage.ts +57 -0
  702. package/src/helpers/jumpWindow.ts +212 -0
  703. package/src/helpers/loadRoomHistory.ts +83 -0
  704. package/src/helpers/notificationPolicy.ts +82 -0
  705. package/src/helpers/openRoomAtMessage.ts +38 -0
  706. package/src/helpers/openThreadForJump.ts +96 -0
  707. package/src/helpers/parseMessageReference.ts +37 -8
  708. package/src/helpers/publicChatsEntry.ts +15 -0
  709. package/src/helpers/roomActivityScore.ts +45 -0
  710. package/src/helpers/roomUserCount.ts +44 -0
  711. package/src/helpers/runConfiguredHistoryPreload.ts +76 -0
  712. package/src/helpers/sessionAppId.ts +17 -0
  713. package/src/helpers/timestamp.ts +44 -0
  714. package/src/helpers/userResolver.ts +542 -0
  715. package/src/helpers/xmppIdShape.ts +46 -0
  716. package/src/hooks/useChatWrapperInit.ts +22 -4
  717. package/src/hooks/useHeapSender.tsx +54 -3
  718. package/src/hooks/useJumpToMessage.ts +538 -0
  719. package/src/hooks/useMessageLoaderQueue.tsx +40 -4
  720. package/src/hooks/useMessageQueue.ts +9 -5
  721. package/src/hooks/useRoomDirectory.ts +39 -0
  722. package/src/hooks/useRoomInitialization.tsx +33 -7
  723. package/src/hooks/useRoomMute.ts +42 -0
  724. package/src/hooks/useRoomState.tsx +6 -0
  725. package/src/hooks/useSendMessage.tsx +14 -2
  726. package/src/i18n/strings.ts +282 -0
  727. package/src/networking/api-requests/messageSearch.api.ts +130 -0
  728. package/src/networking/api-requests/publicChats.api.ts +71 -0
  729. package/src/networking/api-requests/roomMembers.api.ts +263 -11
  730. package/src/networking/api-requests/rooms.api.ts +52 -12
  731. package/src/networking/api-requests/xmppToken.api.ts +62 -0
  732. package/src/networking/ethoraEvents.ts +262 -0
  733. package/src/networking/stanzaHandlers.ts +145 -19
  734. package/src/networking/xmpp/allRoomPresences.xmpp.ts +155 -5
  735. package/src/networking/xmpp/getHistory.xmpp.ts +433 -47
  736. package/src/networking/xmpp/handleStanzas.xmpp.ts +8 -0
  737. package/src/networking/xmpp/historyCursor.ts +124 -0
  738. package/src/networking/xmpp/mamQueryIds.ts +11 -0
  739. package/src/networking/xmpp/mamRouter.ts +24 -10
  740. package/src/networking/xmpp/presenceInRoom.xmpp.ts +91 -68
  741. package/src/networking/xmpp/roomJoinSweep.ts +412 -0
  742. package/src/networking/xmpp/sendMediaMessage.xmpp.ts +0 -1
  743. package/src/networking/xmpp/sendTextMessage.xmpp.ts +0 -1
  744. package/src/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.ts +0 -1
  745. package/src/networking/xmppClient.ts +323 -47
  746. package/src/networking/xmppCredentials.ts +87 -0
  747. package/src/roomStore/Middleware/jumpThreadMiddleware.ts +163 -0
  748. package/src/roomStore/Middleware/reactionsMiddleware.tsx +4 -0
  749. package/src/roomStore/Middleware/unreadMidlleware.tsx +59 -11
  750. package/src/roomStore/chatSettingsSlice.ts +11 -0
  751. package/src/roomStore/index.ts +10 -3
  752. package/src/roomStore/persistence.ts +54 -0
  753. package/src/roomStore/roomsSlice.ts +547 -65
  754. package/src/roomStore/safeKey.ts +49 -0
  755. package/src/roomStore/usersSetCap.ts +37 -0
  756. package/src/types/models/media.model.ts +4 -0
  757. package/src/types/models/room.model.ts +18 -5
  758. package/src/types/models/xmpp.model.ts +15 -1
  759. package/src/types/types.ts +93 -5
  760. package/src/version.ts +1 -1
@@ -1,12 +1,14 @@
1
- import React, { useEffect, useRef, useState } from 'react';
1
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { withFileToken } from '../../helpers/secureFileUrl';
3
3
  import {
4
4
  View,
5
- TouchableOpacity,
5
+ Pressable,
6
6
  Text,
7
7
  StyleSheet,
8
8
  ActivityIndicator,
9
9
  Platform,
10
+ type GestureResponderEvent,
11
+ type LayoutChangeEvent,
10
12
  } from 'react-native';
11
13
  import {
12
14
  createAudioPlayer,
@@ -15,27 +17,31 @@ import {
15
17
  type AudioStatus,
16
18
  } from 'expo-audio';
17
19
  import * as FileSystem from 'expo-file-system/legacy';
18
- import { WebView, WebViewMessageEvent } from 'react-native-webview';
19
- import { PauseIcon, PlayIcon } from '../../assets/icons';
20
+ import Svg, { Path, Rect } from 'react-native-svg';
20
21
  import { useTheme } from '../../hooks/useTheme';
22
+ import { useT } from '../../i18n/useT';
21
23
  import { getIosAudioPlaybackCacheExtension } from '../../helpers/mimeToExtension';
24
+ import {
25
+ MIN_BAR,
26
+ formatClipTime,
27
+ parseDurationSeconds,
28
+ parseWaveForm,
29
+ resamplePeaks,
30
+ } from '../../helpers/audioWaveform';
22
31
  import { pushLog as devPushLog } from '../../utils/devLogger';
23
-
24
- const formatTime = (millis: number) => {
25
- const safe = Number.isFinite(millis) && millis > 0 ? millis : 0;
26
- const totalSec = Math.floor(safe / 1000);
27
- const min = Math.floor(totalSec / 60);
28
- const sec = totalSec % 60;
29
- return `${min}:${sec.toString().padStart(2, '0')}`;
30
- };
32
+ import { MAX_DECODE_BYTES, decodeAudioClip } from './AudioDecoderHost';
31
33
 
32
34
  type AudioMessageProps = {
33
35
  src: string;
34
36
  mimeType?: string;
35
37
  fileName?: string;
36
38
  originalName?: string;
39
+ /** Seconds, when the sender attached it. */
37
40
  duration?: number | string;
41
+ /** Bars the sender attached (see helpers/audioWaveform). */
38
42
  waveForm?: string;
43
+ /** Own message: the player takes the bubble's ink instead of the brand. */
44
+ isUser?: boolean;
39
45
  };
40
46
 
41
47
  type AudioContainer =
@@ -50,19 +56,25 @@ type AudioContainer =
50
56
  type PreparedSource = {
51
57
  localUri: string;
52
58
  container: AudioContainer;
53
- base64?: string;
59
+ /** The local file is already a natively playable copy (decoded WAV). */
60
+ playable?: boolean;
54
61
  };
55
62
 
56
- type WebDecoderMessage =
57
- | { type: 'bridge_ready' }
58
- | { type: 'decoded'; durationMillis: number; wavBase64: string }
59
- | { type: 'error'; message?: string };
63
+ type Analysis = { peaks: number[]; durationMillis: number };
60
64
 
61
- // Largest payload we will pull into JS for decoding (input opus) and the
62
- // largest WAV we will ship back over the bridge (output PCM). Voice
63
- // messages are a few dozen KB / a couple hundred KB of WAV; these only
64
- // guard against a pathological multi-minute attachment.
65
- const MAX_DECODE_BYTES = 25 * 1024 * 1024;
65
+ const SPEEDS = [1, 1.5, 2];
66
+ const BAR_WIDTH = 3;
67
+ const BAR_GAP = 2;
68
+ const WAVE_HEIGHT = 24;
69
+
70
+ /** Waveforms already worked out this session, by source URL. */
71
+ const analysisCache = new Map<string, Analysis>();
72
+
73
+ /**
74
+ * Only one voice note plays at a time, as in the messengers: starting one
75
+ * pauses whichever was playing.
76
+ */
77
+ let pauseActive: (() => void) | null = null;
66
78
 
67
79
  const hashString = (value: string) => {
68
80
  let hash = 0;
@@ -210,139 +222,76 @@ const getContainerExtension = (container: AudioContainer) => {
210
222
  const requiresWebAudioDecode = (container: AudioContainer) =>
211
223
  Platform.OS === 'ios' && (container === 'webm' || container === 'ogg');
212
224
 
213
- // The WebView is used ONLY as a decoder, never as a player. Two iOS facts
214
- // force this shape:
215
- // • `decodeAudioData` is the only iOS API that understands WebM/Ogg Opus,
216
- // and it works on a suspended AudioContext (decoding needs no gesture).
217
- // • Playing audio OUT of a WebView — via AudioContext OR an <audio>
218
- // element — is gated behind an in-page user gesture that an RN-side tap
219
- // cannot supply, so both hang silently.
220
- // Therefore the page decodes Opus → PCM, re-encodes to a WAV blob, and ships
221
- // the bytes back to RN as base64. RN writes the WAV to cache and plays it
222
- // through expo-audio (native, no gesture limits, real play/pause/seek).
223
- const WEB_DECODER_HTML = `<!doctype html>
224
- <html>
225
- <head>
226
- <meta charset="utf-8" />
227
- <meta name="viewport" content="width=device-width, initial-scale=1" />
228
- <style>html,body{margin:0;padding:0;width:1px;height:1px;overflow:hidden;background:transparent}</style>
229
- </head>
230
- <body>
231
- <script>
232
- (function () {
233
- var MAX_WAV = ${MAX_DECODE_BYTES};
234
- var post = function (payload) {
235
- if (window.ReactNativeWebView) {
236
- window.ReactNativeWebView.postMessage(JSON.stringify(payload));
237
- }
238
- };
239
- var ctx = null;
240
- var encodeWav = function (buffer) {
241
- var numCh = buffer.numberOfChannels;
242
- var frames = buffer.length;
243
- var sr = buffer.sampleRate;
244
- var bytesLen = 44 + frames * numCh * 2;
245
- var ab = new ArrayBuffer(bytesLen);
246
- var view = new DataView(ab);
247
- var off = 0;
248
- var ws = function (s) { for (var i = 0; i < s.length; i += 1) { view.setUint8(off++, s.charCodeAt(i)); } };
249
- var u32 = function (d) { view.setUint32(off, d, true); off += 4; };
250
- var u16 = function (d) { view.setUint16(off, d, true); off += 2; };
251
- ws('RIFF'); u32(bytesLen - 8); ws('WAVE');
252
- ws('fmt '); u32(16); u16(1); u16(numCh); u32(sr); u32(sr * numCh * 2); u16(numCh * 2); u16(16);
253
- ws('data'); u32(frames * numCh * 2);
254
- var chans = [];
255
- for (var c = 0; c < numCh; c += 1) { chans.push(buffer.getChannelData(c)); }
256
- for (var f = 0; f < frames; f += 1) {
257
- for (var c2 = 0; c2 < numCh; c2 += 1) {
258
- var s = Math.max(-1, Math.min(1, chans[c2][f]));
259
- view.setInt16(off, s < 0 ? s * 0x8000 : s * 0x7fff, true);
260
- off += 2;
261
- }
262
- }
263
- return ab;
264
- };
265
- var abToBase64 = function (ab) {
266
- var bytes = new Uint8Array(ab);
267
- var binary = '';
268
- var chunk = 0x8000;
269
- for (var i = 0; i < bytes.length; i += chunk) {
270
- binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk));
271
- }
272
- return window.btoa(binary);
273
- };
274
- window.__decodeAudio = function (base64) {
275
- try {
276
- if (!ctx) {
277
- var Ctx = window.AudioContext || window.webkitAudioContext;
278
- ctx = new Ctx();
279
- }
280
- var bin = window.atob(base64);
281
- var len = bin.length;
282
- var bytes = new Uint8Array(len);
283
- for (var i = 0; i < len; i += 1) { bytes[i] = bin.charCodeAt(i); }
284
- ctx.decodeAudioData(
285
- bytes.buffer,
286
- function (decoded) {
287
- try {
288
- var wav = encodeWav(decoded);
289
- if (wav.byteLength > MAX_WAV) {
290
- post({ type: 'error', message: 'audio_too_long' });
291
- return;
292
- }
293
- post({
294
- type: 'decoded',
295
- durationMillis: Math.floor(decoded.duration * 1000),
296
- wavBase64: abToBase64(wav),
297
- });
298
- } catch (e) {
299
- post({ type: 'error', message: 'encode_failed:' + (e && e.message ? e.message : e) });
300
- }
301
- },
302
- function (err) {
303
- post({ type: 'error', message: 'decode_failed:' + (err && err.message ? err.message : err) });
304
- }
305
- );
306
- } catch (e) {
307
- post({ type: 'error', message: 'load_failed:' + (e && e.message ? e.message : e) });
308
- }
309
- };
310
- post({ type: 'bridge_ready' });
311
- })();
312
- </script>
313
- </body>
314
- </html>`;
315
-
225
+ const PlayGlyph = ({ color }: { color: string }) => (
226
+ <Svg width={26} height={26} viewBox="0 0 24 24">
227
+ <Path
228
+ d="M7.5 5.2v13.6c0 .95 1.05 1.5 1.8.95l9.9-6.8c.65-.45.65-1.45 0-1.9L9.3 4.25c-.75-.55-1.8 0-1.8.95z"
229
+ fill={color}
230
+ />
231
+ </Svg>
232
+ );
233
+
234
+ const PauseGlyph = ({ color }: { color: string }) => (
235
+ <Svg width={24} height={24} viewBox="0 0 24 24">
236
+ <Rect x={6.5} y={5} width={4} height={14} rx={1.4} fill={color} />
237
+ <Rect x={13.5} y={5} width={4} height={14} rx={1.4} fill={color} />
238
+ </Svg>
239
+ );
240
+
241
+ /**
242
+ * A voice message the way the messengers draw it: a round play button,
243
+ * the clip's waveform filling in as it plays (tap it to jump), and its
244
+ * length — elapsed while playing — under the bars.
245
+ *
246
+ * The bars come from the sender's `waveForm` when there is one, otherwise
247
+ * from decoding the file in the shared decoder (AudioDecoderHost), which
248
+ * is also what makes the web client's WebM/Opus playable on iOS.
249
+ */
316
250
  const AudioMessage = ({
317
251
  src,
318
252
  mimeType,
319
253
  fileName,
320
254
  originalName,
255
+ duration: durationProp,
256
+ waveForm,
257
+ isUser = false,
321
258
  }: AudioMessageProps) => {
322
259
  const theme = useTheme();
260
+ const t = useT();
323
261
  const soundRef = useRef<AudioPlayer | null>(null);
324
- // expo-audio delivers progress through an event subscription instead of
325
- // expo-av's `onPlaybackStatusUpdate` callback argument, so the handle has
326
- // to be held and removed alongside the player itself.
262
+ // expo-audio delivers progress through an event subscription, so the
263
+ // handle has to be held and removed alongside the player itself.
327
264
  const statusSubRef = useRef<{ remove: () => void } | null>(null);
328
- const webViewRef = useRef<WebView | null>(null);
329
265
  const preparedRef = useRef<PreparedSource | null>(null);
330
- const bridgeReadyRef = useRef(false);
331
- const injectedRef = useRef(false);
266
+ const preparingRef = useRef<Promise<PreparedSource> | null>(null);
332
267
  const didFinishRef = useRef(false);
333
268
  const unmountedRef = useRef(false);
334
269
  const loadingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
270
+ // Where to start once the player is up (a tap on the bars before play).
271
+ const pendingSeekRef = useRef<number | null>(null);
272
+ const rateRef = useRef(1);
273
+
274
+ // The file's identity without its (rotating) access token, so caches
275
+ // survive a token refresh.
276
+ const cacheKey = src.split(/[?#]/)[0] || src;
277
+ const metaPeaks = useMemo(() => parseWaveForm(waveForm), [waveForm]);
278
+ const metaDuration = parseDurationSeconds(durationProp) * 1000;
335
279
 
336
280
  const [isPlaying, setIsPlaying] = useState(false);
337
281
  const [isLoading, setIsLoading] = useState(false);
338
282
  const [playbackError, setPlaybackError] = useState(false);
339
283
  const [position, setPosition] = useState(0);
340
- const [duration, setDuration] = useState(0);
341
- const [webViewEnabled, setWebViewEnabled] = useState(false);
342
- const primaryColor = theme.primary;
343
- const onPrimary = theme.textOnPrimary;
344
- const trackStyle = { backgroundColor: theme.border };
345
- const timeStyle = { color: theme.textSecondary };
284
+ const [duration, setDuration] = useState(metaDuration);
285
+ const [peaks, setPeaks] = useState<number[] | null>(metaPeaks);
286
+ const [rate, setRate] = useState(1);
287
+ const [waveWidth, setWaveWidth] = useState(0);
288
+
289
+ // The brand colour from the config (the same tint as the composer's mic
290
+ // and send buttons), on own and others' bubbles alike.
291
+ const ink = theme.icon;
292
+ const buttonFill = ink;
293
+ const buttonGlyph = theme.textOnPrimary;
294
+ const metaColor = isUser ? theme.messageTextUser : theme.textSecondary;
346
295
 
347
296
  const clearLoadingGuard = () => {
348
297
  if (loadingTimerRef.current) {
@@ -353,8 +302,7 @@ const AudioMessage = ({
353
302
 
354
303
  // expo-audio players are native shared objects: they are NOT garbage
355
304
  // collected with the component, so every one we create has to be
356
- // explicitly `remove()`d (expo-av's `unloadAsync` equivalent) and its
357
- // status subscription torn down first.
305
+ // explicitly `remove()`d and its status subscription torn down first.
358
306
  const releasePlayer = () => {
359
307
  statusSubRef.current?.remove();
360
308
  statusSubRef.current = null;
@@ -374,6 +322,22 @@ const AudioMessage = ({
374
322
  }
375
323
  };
376
324
 
325
+ // Stable for this bubble's lifetime: what another bubble calls to pause
326
+ // this one when it starts playing.
327
+ const pauseSelf = useRef(() => {
328
+ try {
329
+ soundRef.current?.pause();
330
+ } catch {
331
+ /* released */
332
+ }
333
+ if (!unmountedRef.current) {setIsPlaying(false);}
334
+ }).current;
335
+
336
+ const claimPlayback = () => {
337
+ if (pauseActive && pauseActive !== pauseSelf) {pauseActive();}
338
+ pauseActive = pauseSelf;
339
+ };
340
+
377
341
  const fail = (reason: unknown) => {
378
342
  clearLoadingGuard();
379
343
  if (unmountedRef.current) {
@@ -396,76 +360,75 @@ const AudioMessage = ({
396
360
  };
397
361
 
398
362
  // Hard ceiling on the loading spinner — whatever stalls (a hung network
399
- // request, a WebView that never decodes, a player that never loads)
400
- // the control resolves to an error state instead of spinning forever
401
- // (the customer-reported "stuck spinner").
363
+ // request, a decode that never returns, a player that never loads) the
364
+ // control resolves to an error state instead of spinning forever.
402
365
  const armLoadingGuard = () => {
403
366
  clearLoadingGuard();
404
367
  loadingTimerRef.current = setTimeout(() => {
405
368
  if (!unmountedRef.current && !soundRef.current) {
406
369
  fail(new Error('audio_timeout'));
407
370
  }
408
- }, 15000);
371
+ }, 25000);
409
372
  };
410
373
 
411
- const prepareSource = async (): Promise<PreparedSource> => {
412
- if (preparedRef.current) {
413
- return preparedRef.current;
414
- }
415
- if (!/^https?:\/\//i.test(src)) {
416
- const prepared: PreparedSource = { localUri: src, container: 'unknown' };
417
- preparedRef.current = prepared;
418
- return prepared;
419
- }
420
- const cacheDirectory = FileSystem.cacheDirectory;
421
- if (!cacheDirectory) {
422
- const prepared: PreparedSource = { localUri: src, container: 'unknown' };
423
- preparedRef.current = prepared;
424
- return prepared;
425
- }
374
+ const cacheBase = () =>
375
+ FileSystem.cacheDirectory
376
+ ? `${FileSystem.cacheDirectory}ethora-audio-${hashString(cacheKey)}`
377
+ : null;
426
378
 
427
- const base = `${cacheDirectory}ethora-audio-${hashString(src)}`;
428
- const part = `${base}.part`;
429
- const partInfo = await FileSystem.getInfoAsync(part);
430
- if (!partInfo.exists) {
431
- const download = await withTimeout(
432
- FileSystem.downloadAsync(withFileToken(src), part),
433
- 12000,
434
- 'audio_download_timeout'
435
- );
436
- logAudioDebug('audio downloaded', {
437
- src,
438
- uri: download.status === 200 ? download.uri : undefined,
439
- status: download.status,
379
+ const sniff = async (uri: string): Promise<AudioContainer> => {
380
+ try {
381
+ const head = await FileSystem.readAsStringAsync(uri, {
382
+ encoding: FileSystem.EncodingType.Base64,
383
+ position: 0,
384
+ length: 32,
440
385
  });
441
- if (download.status !== 200) {
442
- throw new Error(`audio_download_status_${download.status}`);
443
- }
386
+ return detectContainer(decodeBase64Head(head, 16));
387
+ } catch {
388
+ return 'unknown';
444
389
  }
390
+ };
445
391
 
446
- const head = await FileSystem.readAsStringAsync(part, {
447
- encoding: FileSystem.EncodingType.Base64,
448
- position: 0,
449
- length: 32,
450
- });
451
- const container = detectContainer(decodeBase64Head(head, 16));
452
-
453
- let base64: string | undefined;
454
- let localUri = part;
455
- if (requiresWebAudioDecode(container)) {
456
- // WebView-decode path: extension is irrelevant, we hand over the bytes.
457
- const info = await FileSystem.getInfoAsync(part);
458
- const size = 'size' in info ? info.size ?? 0 : 0;
459
- if (size > MAX_DECODE_BYTES) {
460
- throw new Error('audio_too_large_to_decode');
392
+ // Download once (shared by the waveform and playback), sniff the real
393
+ // container, and give the cached file a real extension so AVFoundation /
394
+ // ExoPlayer pick the right demuxer instead of choking on an opaque
395
+ // `.blob` (the original `audio_timeout` cause).
396
+ const prepareSource = (): Promise<PreparedSource> => {
397
+ if (preparedRef.current) {
398
+ return Promise.resolve(preparedRef.current);
399
+ }
400
+ if (preparingRef.current) {
401
+ return preparingRef.current;
402
+ }
403
+ const run = async (): Promise<PreparedSource> => {
404
+ const base = cacheBase();
405
+ if (!/^https?:\/\//i.test(src) || !base) {
406
+ // Local file (the sender's own clip while it uploads).
407
+ return { localUri: src, container: await sniff(src) };
408
+ }
409
+ const wav = `${base}.wav`;
410
+ if ((await FileSystem.getInfoAsync(wav)).exists) {
411
+ return { localUri: wav, container: 'wav', playable: true };
412
+ }
413
+ const part = `${base}.part`;
414
+ if (!(await FileSystem.getInfoAsync(part)).exists) {
415
+ const download = await withTimeout(
416
+ FileSystem.downloadAsync(withFileToken(src), part),
417
+ 12000,
418
+ 'audio_download_timeout'
419
+ );
420
+ if (download.status !== 200) {
421
+ await FileSystem.deleteAsync(part, { idempotent: true }).catch(
422
+ () => {}
423
+ );
424
+ throw new Error(`audio_download_status_${download.status}`);
425
+ }
426
+ }
427
+ const container = await sniff(part);
428
+ if (requiresWebAudioDecode(container)) {
429
+ // Decoded to WAV before playing; the extension is irrelevant.
430
+ return { localUri: part, container };
461
431
  }
462
- base64 = await FileSystem.readAsStringAsync(part, {
463
- encoding: FileSystem.EncodingType.Base64,
464
- });
465
- } else {
466
- // Native path: give the cached file a real extension so AVFoundation
467
- // / ExoPlayer pick the right demuxer instead of choking on an opaque
468
- // `.blob` (the original `audio_timeout` cause).
469
432
  const ext =
470
433
  container !== 'unknown'
471
434
  ? getContainerExtension(container)
@@ -476,55 +439,161 @@ const AudioMessage = ({
476
439
  url: src,
477
440
  });
478
441
  const finalUri = `${base}${ext}`;
479
- const finalInfo = await FileSystem.getInfoAsync(finalUri);
480
- if (finalInfo.exists) {
481
- await FileSystem.deleteAsync(part, { idempotent: true }).catch(
482
- () => {}
483
- );
442
+ if ((await FileSystem.getInfoAsync(finalUri)).exists) {
443
+ await FileSystem.deleteAsync(part, { idempotent: true }).catch(() => {});
484
444
  } else {
485
445
  await FileSystem.moveAsync({ from: part, to: finalUri });
486
446
  }
487
- localUri = finalUri;
488
- }
447
+ return { localUri: finalUri, container };
448
+ };
449
+ const pending = run().then(
450
+ (prepared) => {
451
+ preparedRef.current = prepared;
452
+ preparingRef.current = null;
453
+ logAudioDebug('audio prepared', {
454
+ src,
455
+ container: prepared.container,
456
+ platform: Platform.OS,
457
+ });
458
+ return prepared;
459
+ },
460
+ (error) => {
461
+ preparingRef.current = null;
462
+ throw error;
463
+ }
464
+ );
465
+ preparingRef.current = pending;
466
+ return pending;
467
+ };
489
468
 
490
- const prepared: PreparedSource = { localUri, container, base64 };
491
- preparedRef.current = prepared;
492
- logAudioDebug('audio prepared', {
493
- src,
494
- mimeType,
495
- fileName,
496
- originalName,
497
- container,
498
- platform: Platform.OS,
499
- decode: requiresWebAudioDecode(container) ? 'web-audio' : 'native',
469
+ const readBase64 = async (uri: string) => {
470
+ const info = await FileSystem.getInfoAsync(uri);
471
+ const size = 'size' in info ? info.size ?? 0 : 0;
472
+ if (size > MAX_DECODE_BYTES) {
473
+ throw new Error('audio_too_large_to_decode');
474
+ }
475
+ return FileSystem.readAsStringAsync(uri, {
476
+ encoding: FileSystem.EncodingType.Base64,
500
477
  });
501
- return prepared;
502
478
  };
503
479
 
504
- const maybeInjectDecode = () => {
505
- const base64 = preparedRef.current?.base64;
506
- if (
507
- bridgeReadyRef.current &&
508
- base64 &&
509
- !injectedRef.current &&
510
- webViewRef.current
511
- ) {
512
- injectedRef.current = true;
513
- webViewRef.current.injectJavaScript(
514
- `window.__decodeAudio(${JSON.stringify(base64)}); true;`
480
+ const remember = (analysis: Analysis) => {
481
+ analysisCache.set(cacheKey, analysis);
482
+ const base = cacheBase();
483
+ if (base) {
484
+ FileSystem.writeAsStringAsync(
485
+ `${base}.wave.json`,
486
+ JSON.stringify(analysis)
487
+ ).catch(() => {});
488
+ }
489
+ };
490
+
491
+ const applyAnalysis = (analysis: Analysis) => {
492
+ if (unmountedRef.current) {return;}
493
+ if (!metaPeaks && analysis.peaks.length) {setPeaks(analysis.peaks);}
494
+ if (analysis.durationMillis > 0) {
495
+ setDuration((current) => current || analysis.durationMillis);
496
+ }
497
+ };
498
+
499
+ // Decode the clip once: its bars, its real length (MediaRecorder writes
500
+ // WebM without a duration header), and — where the native player cannot
501
+ // read it — a WAV copy to play.
502
+ const decodeClip = async (prepared: PreparedSource) => {
503
+ const wantWav = requiresWebAudioDecode(prepared.container) && !prepared.playable;
504
+ const clip = await decodeAudioClip(await readBase64(prepared.localUri), {
505
+ wantWav,
506
+ });
507
+ const analysis = { peaks: clip.peaks, durationMillis: clip.durationMillis };
508
+ remember(analysis);
509
+ applyAnalysis(analysis);
510
+ const base = cacheBase();
511
+ if (clip.wavBase64 && base) {
512
+ const wavUri = `${base}.wav`;
513
+ await FileSystem.writeAsStringAsync(wavUri, clip.wavBase64, {
514
+ encoding: FileSystem.EncodingType.Base64,
515
+ });
516
+ await FileSystem.deleteAsync(prepared.localUri, { idempotent: true }).catch(
517
+ () => {}
515
518
  );
519
+ preparedRef.current = { localUri: wavUri, container: 'wav', playable: true };
516
520
  }
521
+ return preparedRef.current ?? prepared;
517
522
  };
518
523
 
519
- // expo-audio reports times in SECONDS (expo-av used milliseconds); the
520
- // whole UI below is millisecond-based, so convert at this boundary and
521
- // nowhere else.
524
+ // Full reset whenever the source changes (component reused for a new
525
+ // message / re-render with a different src).
526
+ const lastSrcRef = useRef(src);
527
+ useEffect(() => {
528
+ if (lastSrcRef.current === src) {return;}
529
+ lastSrcRef.current = src;
530
+ releasePlayer();
531
+ preparedRef.current = null;
532
+ preparingRef.current = null;
533
+ didFinishRef.current = false;
534
+ pendingSeekRef.current = null;
535
+ clearLoadingGuard();
536
+ setIsPlaying(false);
537
+ setIsLoading(false);
538
+ setPlaybackError(false);
539
+ setPosition(0);
540
+ setDuration(metaDuration);
541
+ setPeaks(metaPeaks);
542
+ // eslint-disable-next-line react-hooks/exhaustive-deps
543
+ }, [src]);
544
+
545
+ // The waveform, as soon as the bubble shows: from the message itself,
546
+ // from this session's cache, from the disk cache, or by decoding.
547
+ useEffect(() => {
548
+ if (!src || (metaPeaks && metaDuration > 0)) {return;}
549
+ const cached = analysisCache.get(cacheKey);
550
+ if (cached) {
551
+ applyAnalysis(cached);
552
+ return;
553
+ }
554
+ let cancelled = false;
555
+ (async () => {
556
+ const base = cacheBase();
557
+ if (base) {
558
+ try {
559
+ const stored = await FileSystem.readAsStringAsync(`${base}.wave.json`);
560
+ const parsed = JSON.parse(stored) as Analysis;
561
+ if (Array.isArray(parsed?.peaks)) {
562
+ analysisCache.set(cacheKey, parsed);
563
+ if (!cancelled) {applyAnalysis(parsed);}
564
+ return;
565
+ }
566
+ } catch {
567
+ /* not analysed yet */
568
+ }
569
+ }
570
+ try {
571
+ const prepared = await prepareSource();
572
+ if (cancelled) {return;}
573
+ await decodeClip(prepared);
574
+ } catch (error) {
575
+ // Not fatal: the bubble keeps flat bars and still plays.
576
+ logAudioDebug('audio waveform unavailable', {
577
+ src,
578
+ error: error instanceof Error ? error.message : String(error),
579
+ });
580
+ }
581
+ })();
582
+ return () => {
583
+ cancelled = true;
584
+ };
585
+ // eslint-disable-next-line react-hooks/exhaustive-deps
586
+ }, [src]);
587
+
588
+ // expo-audio reports times in SECONDS; the UI is millisecond-based, so
589
+ // convert at this boundary and nowhere else.
522
590
  const onPlaybackStatusUpdate = (status: AudioStatus) => {
523
- if (!status.isLoaded) {
591
+ if (!status.isLoaded || unmountedRef.current) {
524
592
  return;
525
593
  }
526
594
  setPosition(Math.max(0, status.currentTime * 1000));
527
- setDuration(status.duration > 0 ? status.duration * 1000 : 0);
595
+ // A WebM without a duration header reports 0 here: keep the decoded one.
596
+ if (status.duration > 0) {setDuration(status.duration * 1000);}
528
597
  setIsPlaying(status.playing);
529
598
  if (status.didJustFinish) {
530
599
  didFinishRef.current = true;
@@ -544,12 +613,10 @@ const AudioMessage = ({
544
613
  } catch {
545
614
  /* non-fatal — still plays through the ringer channel */
546
615
  }
547
- // expo-audio has no awaitable `createAsync`: the player is constructed
548
- // synchronously and loads in the background, reporting `isLoaded` via
549
- // the status event. Wait for that first loaded status (same 12s budget
550
- // expo-av's createAsync had) so the spinner still resolves to either
551
- // real playback or the error state, never to a silent dead button.
552
- const player = createAudioPlayer({ uri: localUri }, { updateInterval: 250 });
616
+ // expo-audio has no awaitable create: the player loads in the
617
+ // background and reports `isLoaded` via the status event. Wait for it
618
+ // so the spinner resolves to real playback or the error state.
619
+ const player = createAudioPlayer({ uri: localUri }, { updateInterval: 100 });
553
620
  let settleLoaded: (() => void) | null = null;
554
621
  const loaded = new Promise<void>((resolve) => {
555
622
  settleLoaded = resolve;
@@ -579,67 +646,21 @@ const AudioMessage = ({
579
646
  }
580
647
  soundRef.current = player;
581
648
  statusSubRef.current = subscription;
649
+ if (rateRef.current !== 1) {
650
+ player.shouldCorrectPitch = true;
651
+ player.setPlaybackRate(rateRef.current);
652
+ }
653
+ const seek = pendingSeekRef.current;
654
+ pendingSeekRef.current = null;
655
+ if (seek !== null && player.duration > 0) {
656
+ await player.seekTo(seek * player.duration);
657
+ }
658
+ claimPlayback();
582
659
  player.play();
583
660
  clearLoadingGuard();
584
661
  setIsLoading(false);
585
662
  setPlaybackError(false);
586
663
  setIsPlaying(true);
587
- logAudioDebug('native audio playback started', {
588
- src,
589
- container: preparedRef.current?.container,
590
- localUri,
591
- });
592
- };
593
-
594
- // WebView finished decoding Opus → WAV: persist the WAV and play it
595
- // natively. From here it is indistinguishable from any other native clip.
596
- const onDecoded = async (durationMillis: number, wavBase64: string) => {
597
- try {
598
- const cacheDirectory = FileSystem.cacheDirectory || '';
599
- const wavUri = `${cacheDirectory}ethora-audio-${hashString(src)}.wav`;
600
- await FileSystem.writeAsStringAsync(wavUri, wavBase64, {
601
- encoding: FileSystem.EncodingType.Base64,
602
- });
603
- if (unmountedRef.current) {
604
- return;
605
- }
606
- preparedRef.current = {
607
- localUri: wavUri,
608
- container: preparedRef.current?.container ?? 'webm',
609
- };
610
- if (durationMillis > 0) {
611
- setDuration(durationMillis);
612
- }
613
- setWebViewEnabled(false); // decoder no longer needed
614
- await startNativePlayback(wavUri);
615
- } catch (error) {
616
- fail(error);
617
- }
618
- };
619
-
620
- const handleWebMessage = (event: WebViewMessageEvent) => {
621
- let payload: WebDecoderMessage;
622
- try {
623
- payload = JSON.parse(event.nativeEvent.data) as WebDecoderMessage;
624
- } catch {
625
- return;
626
- }
627
- if (payload.type === 'bridge_ready') {
628
- bridgeReadyRef.current = true;
629
- maybeInjectDecode();
630
- return;
631
- }
632
- if (payload.type === 'decoded') {
633
- logAudioDebug('web audio decoded', {
634
- src,
635
- durationMillis: payload.durationMillis,
636
- wavBytes: payload.wavBase64 ? payload.wavBase64.length : 0,
637
- });
638
- void onDecoded(payload.durationMillis, payload.wavBase64);
639
- return;
640
- }
641
- // type === 'error'
642
- fail(new Error(payload.message || 'web_audio_error'));
643
664
  };
644
665
 
645
666
  const togglePlayback = async () => {
@@ -647,15 +668,12 @@ const AudioMessage = ({
647
668
  return;
648
669
  }
649
670
 
650
- // Already-loaded native sound (covers both native formats AND the
651
- // decoded-WAV from the WebView path) → plain toggle.
671
+ // Already-loaded player → plain toggle.
652
672
  if (soundRef.current) {
653
673
  try {
654
- // `play()` / `pause()` emit NO status event of their own (expo-av's
655
- // playAsync/pauseAsync resolved with one). Playing state only ever
656
- // reaches us through the periodic time observer, and that observer
657
- // stops while paused — so without setting this here the button
658
- // would latch on "pause" forever and never resume.
674
+ // `play()` / `pause()` emit no status event of their own, and the
675
+ // time observer stops while paused — so set the state here or the
676
+ // button latches on "pause" forever.
659
677
  if (isPlaying) {
660
678
  soundRef.current.pause();
661
679
  setIsPlaying(false);
@@ -664,6 +682,7 @@ const AudioMessage = ({
664
682
  await soundRef.current.seekTo(0);
665
683
  didFinishRef.current = false;
666
684
  }
685
+ claimPlayback();
667
686
  soundRef.current.play();
668
687
  setIsPlaying(true);
669
688
  }
@@ -673,26 +692,21 @@ const AudioMessage = ({
673
692
  return;
674
693
  }
675
694
 
676
- // First tap → prepare (download + sniff) then route to the right engine.
695
+ // First tap → download (unless the waveform already did), decode where
696
+ // the native player cannot, then play.
677
697
  setPlaybackError(false);
678
698
  setIsLoading(true);
679
699
  armLoadingGuard();
680
700
  try {
681
- const prepared = await withTimeout(
701
+ let prepared = await withTimeout(
682
702
  prepareSource(),
683
703
  14000,
684
704
  'audio_prepare_timeout'
685
705
  );
686
- if (unmountedRef.current) {
687
- return;
706
+ if (requiresWebAudioDecode(prepared.container) && !prepared.playable) {
707
+ prepared = await decodeClip(prepared);
688
708
  }
689
- if (requiresWebAudioDecode(prepared.container)) {
690
- // Mount the decoder WebView; decoding + playback continue when the
691
- // 'decoded' message arrives (→ onDecoded → startNativePlayback).
692
- if (!webViewEnabled) {
693
- setWebViewEnabled(true);
694
- }
695
- maybeInjectDecode();
709
+ if (unmountedRef.current) {
696
710
  return;
697
711
  }
698
712
  await startNativePlayback(prepared.localUri);
@@ -701,98 +715,145 @@ const AudioMessage = ({
701
715
  }
702
716
  };
703
717
 
718
+ // A tap on the bars jumps there (and starts playing, as in Telegram).
719
+ const seekFromTap = (event: GestureResponderEvent) => {
720
+ if (!waveWidth || playbackError) {return;}
721
+ const ratio = Math.max(
722
+ 0,
723
+ Math.min(1, event.nativeEvent.locationX / waveWidth)
724
+ );
725
+ const player = soundRef.current;
726
+ if (player && player.duration > 0) {
727
+ didFinishRef.current = false;
728
+ void player.seekTo(ratio * player.duration);
729
+ setPosition(ratio * player.duration * 1000);
730
+ if (!isPlaying) {
731
+ claimPlayback();
732
+ player.play();
733
+ setIsPlaying(true);
734
+ }
735
+ return;
736
+ }
737
+ if (isLoading) {return;}
738
+ pendingSeekRef.current = ratio;
739
+ void togglePlayback();
740
+ };
741
+
742
+ const cycleSpeed = () => {
743
+ const next = SPEEDS[(SPEEDS.indexOf(rate) + 1) % SPEEDS.length]!;
744
+ rateRef.current = next;
745
+ setRate(next);
746
+ const player = soundRef.current;
747
+ if (player) {
748
+ player.shouldCorrectPitch = true;
749
+ player.setPlaybackRate(next);
750
+ }
751
+ };
752
+
704
753
  useEffect(() => {
705
754
  unmountedRef.current = false;
706
755
  return () => {
707
756
  unmountedRef.current = true;
757
+ if (pauseActive === pauseSelf) {pauseActive = null;}
708
758
  clearLoadingGuard();
709
759
  releasePlayer();
710
760
  };
711
761
  // eslint-disable-next-line react-hooks/exhaustive-deps
712
762
  }, []);
713
763
 
714
- // Full reset whenever the source changes (component reused for a new
715
- // message / re-render with a different src).
716
- useEffect(() => {
717
- releasePlayer();
718
- preparedRef.current = null;
719
- bridgeReadyRef.current = false;
720
- injectedRef.current = false;
721
- didFinishRef.current = false;
722
- clearLoadingGuard();
723
- setIsPlaying(false);
724
- setIsLoading(false);
725
- setPlaybackError(false);
726
- setPosition(0);
727
- setDuration(0);
728
- setWebViewEnabled(false);
729
- // eslint-disable-next-line react-hooks/exhaustive-deps
730
- }, [src]);
764
+ const onWaveLayout = (event: LayoutChangeEvent) => {
765
+ const width = Math.floor(event.nativeEvent.layout.width);
766
+ if (width !== waveWidth) {setWaveWidth(width);}
767
+ };
768
+
769
+ const barCount = waveWidth
770
+ ? Math.max(8, Math.floor((waveWidth + BAR_GAP) / (BAR_WIDTH + BAR_GAP)))
771
+ : 0;
772
+ const bars = useMemo(
773
+ () =>
774
+ barCount
775
+ ? resamplePeaks(peaks ?? new Array(barCount).fill(MIN_BAR), barCount)
776
+ : [],
777
+ [peaks, barCount]
778
+ );
731
779
 
732
780
  const progress = duration > 0 ? Math.min(1, position / duration) : 0;
781
+ const playedBars = Math.round(progress * bars.length);
782
+ const started = isPlaying || position > 0;
783
+ const timeLabel = playbackError
784
+ ? t('media.audioUnavailable')
785
+ : started
786
+ ? formatClipTime(position)
787
+ : duration > 0
788
+ ? formatClipTime(duration)
789
+ : '';
733
790
 
734
791
  return (
735
- <View style={styles.root}>
736
- {/* Decoder WebView lives in a zero-size, absolutely-positioned host
737
- OUTSIDE the row so mounting/unmounting it during the loading phase
738
- can never shift the button or progress line (was the flicker). */}
739
- {webViewEnabled ? (
740
- <View style={styles.decoderHost} pointerEvents="none">
741
- <WebView
742
- ref={webViewRef}
743
- source={{ html: WEB_DECODER_HTML }}
744
- originWhitelist={['*']}
745
- onMessage={handleWebMessage}
746
- javaScriptEnabled
747
- domStorageEnabled={false}
748
- scrollEnabled={false}
749
- pointerEvents="none"
750
- showsHorizontalScrollIndicator={false}
751
- showsVerticalScrollIndicator={false}
752
- style={styles.hiddenWebView}
753
- />
754
- </View>
755
- ) : null}
756
- <View style={styles.container}>
757
- <TouchableOpacity
758
- testID="audio-play-button"
759
- accessibilityLabel="audio-play-button"
760
- style={[styles.playButton, { backgroundColor: primaryColor }]}
761
- onPress={togglePlayback}
762
- disabled={isLoading}
763
- activeOpacity={0.85}
792
+ <View style={styles.container} testID="audio-message">
793
+ <Pressable
794
+ testID="audio-play-button"
795
+ accessibilityRole="button"
796
+ accessibilityLabel={t(isPlaying ? 'media.audioPause' : 'media.audioPlay')}
797
+ style={({ pressed }) => [
798
+ styles.playButton,
799
+ { backgroundColor: buttonFill, opacity: pressed ? 0.85 : 1 },
800
+ ]}
801
+ onPress={togglePlayback}
802
+ disabled={isLoading}
803
+ hitSlop={6}
804
+ >
805
+ {isLoading ? (
806
+ <ActivityIndicator size="small" color={buttonGlyph} />
807
+ ) : isPlaying ? (
808
+ <PauseGlyph color={buttonGlyph} />
809
+ ) : (
810
+ <PlayGlyph color={buttonGlyph} />
811
+ )}
812
+ </Pressable>
813
+ <View style={styles.track}>
814
+ <Pressable
815
+ testID="audio-waveform"
816
+ accessibilityElementsHidden
817
+ importantForAccessibility="no-hide-descendants"
818
+ onPress={seekFromTap}
819
+ onLayout={onWaveLayout}
820
+ style={styles.wave}
764
821
  >
765
- {isLoading ? (
766
- <ActivityIndicator size="small" color={onPrimary} />
767
- ) : (
768
- <View style={styles.iconWrap}>
769
- {isPlaying ? (
770
- <PauseIcon width={18} height={18} color={onPrimary} />
771
- ) : (
772
- <PlayIcon width={18} height={18} color={onPrimary} />
773
- )}
774
- </View>
775
- )}
776
- </TouchableOpacity>
777
- <View style={styles.progressContainer}>
778
- <View style={[styles.progressTrack, trackStyle]}>
822
+ {bars.map((value, index) => (
779
823
  <View
824
+ key={index}
780
825
  style={[
781
- styles.progressFill,
782
- { width: `${progress * 100}%`, backgroundColor: primaryColor },
826
+ styles.bar,
827
+ {
828
+ height: Math.max(BAR_WIDTH, Math.round(value * WAVE_HEIGHT)),
829
+ backgroundColor: ink,
830
+ opacity: index < playedBars ? 1 : 0.35,
831
+ },
783
832
  ]}
784
833
  />
785
- </View>
786
- <View style={styles.progressRow}>
787
- <Text style={[styles.time, timeStyle]}>
788
- {playbackError ? 'Audio unavailable' : formatTime(position)}
789
- </Text>
790
- {/* Always render the right slot (empty until known) so the row
791
- never reflows when the duration appears after decoding. */}
792
- <Text style={[styles.time, timeStyle]}>
793
- {!playbackError && duration > 0 ? formatTime(duration) : ''}
794
- </Text>
795
- </View>
834
+ ))}
835
+ </Pressable>
836
+ <View style={styles.metaRow}>
837
+ <Text
838
+ style={[styles.time, { color: metaColor }]}
839
+ numberOfLines={1}
840
+ >
841
+ {timeLabel}
842
+ </Text>
843
+ {started && !playbackError ? (
844
+ <Pressable
845
+ testID="audio-speed"
846
+ accessibilityRole="button"
847
+ accessibilityLabel={t('media.audioSpeed')}
848
+ onPress={cycleSpeed}
849
+ hitSlop={8}
850
+ style={[styles.speed, { backgroundColor: ink }]}
851
+ >
852
+ <Text style={[styles.speedText, { color: buttonGlyph }]}>
853
+ {`${rate}×`}
854
+ </Text>
855
+ </Pressable>
856
+ ) : null}
796
857
  </View>
797
858
  </View>
798
859
  </View>
@@ -802,65 +863,61 @@ const AudioMessage = ({
802
863
  export default AudioMessage;
803
864
 
804
865
  const styles = StyleSheet.create({
805
- root: {
806
- position: 'relative',
807
- },
808
- decoderHost: {
809
- position: 'absolute',
810
- top: 0,
811
- left: 0,
812
- width: 0,
813
- height: 0,
814
- overflow: 'hidden',
815
- opacity: 0,
816
- },
817
866
  container: {
818
867
  flexDirection: 'row',
819
868
  alignItems: 'center',
820
- gap: 12,
821
- paddingHorizontal: 12,
822
- paddingVertical: 10,
823
- width: 260,
869
+ gap: 10,
870
+ // Media bubbles carry no padding of their own (images run edge to
871
+ // edge), so the player brings the text bubble's inset with it.
872
+ paddingTop: 10,
873
+ paddingLeft: 10,
874
+ paddingRight: 12,
875
+ width: 250,
824
876
  maxWidth: '100%',
825
877
  },
826
878
  playButton: {
827
- width: 44,
828
- height: 44,
829
- borderRadius: 22,
879
+ width: 46,
880
+ height: 46,
881
+ borderRadius: 23,
830
882
  alignItems: 'center',
831
883
  justifyContent: 'center',
832
884
  },
833
- iconWrap: {
834
- marginLeft: 1,
835
- },
836
- progressContainer: {
885
+ track: {
837
886
  flex: 1,
838
- gap: 6,
887
+ gap: 4,
839
888
  },
840
- progressRow: {
889
+ wave: {
890
+ height: WAVE_HEIGHT,
841
891
  flexDirection: 'row',
842
- justifyContent: 'space-between',
843
- alignItems: 'center',
844
- },
845
- progressTrack: {
846
- height: 6,
847
- backgroundColor: '#D0D7E6',
848
- borderRadius: 999,
892
+ alignItems: 'flex-end',
893
+ gap: BAR_GAP,
849
894
  overflow: 'hidden',
850
895
  },
851
- hiddenWebView: {
852
- position: 'absolute',
853
- width: 1,
854
- height: 1,
855
- opacity: 0,
896
+ bar: {
897
+ width: BAR_WIDTH,
898
+ borderRadius: BAR_WIDTH / 2,
856
899
  },
857
- progressFill: {
858
- height: '100%',
859
- borderRadius: 999,
900
+ metaRow: {
901
+ flexDirection: 'row',
902
+ alignItems: 'center',
903
+ gap: 6,
904
+ minHeight: 16,
860
905
  },
861
906
  time: {
862
907
  fontSize: 12,
863
- color: '#667085',
864
- fontWeight: '500',
908
+ lineHeight: 16,
909
+ fontVariant: ['tabular-nums'],
910
+ opacity: 0.85,
911
+ },
912
+ speed: {
913
+ borderRadius: 8,
914
+ paddingHorizontal: 5,
915
+ paddingVertical: 1,
916
+ opacity: 0.85,
917
+ },
918
+ speedText: {
919
+ fontSize: 11,
920
+ lineHeight: 14,
921
+ fontWeight: '700',
865
922
  },
866
923
  });