@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
@@ -13,18 +13,26 @@ import {
13
13
  StyleSheet,
14
14
  FlatList,
15
15
  Image,
16
+ LayoutChangeEvent,
16
17
  NativeSyntheticEvent,
17
18
  NativeScrollEvent,
18
19
  TouchableOpacity,
19
20
  ImageSourcePropType,
20
21
  } from 'react-native';
22
+ import { useDispatch, useSelector, useStore } from 'react-redux';
21
23
  import { IMessage, User, IConfig, IRoom } from '../../types/types';
22
- import { msgSortableMs } from '../../roomStore/roomsSlice';
24
+ import {
25
+ PendingJump,
26
+ appendJumpWindowMessages,
27
+ clearJumpWindow,
28
+ msgSortableMs,
29
+ prependJumpWindowMessages,
30
+ setJumpWindow,
31
+ } from '../../roomStore/roomsSlice';
32
+ import { RootState } from '../../roomStore';
23
33
  import Composing from '../styled/StyledInputComponents/Composing';
24
34
  import TreadLabel from '../styled/TreadLabel';
25
35
  import { MessageContainer } from './MessageContainer';
26
- import { useSelector } from 'react-redux';
27
- import type { RootState } from '../../roomStore';
28
36
  import Loader from '../styled/Loader';
29
37
  import { ArowDownIcon } from '../../assets/icons';
30
38
  import CustomTypingIndicator from '../styled/StyledInputComponents/CustomTypingIndicator';
@@ -32,6 +40,63 @@ import { getIconColor } from '../../helpers/getIconColor';
32
40
  import { getChatBackgroundColor } from '../../helpers/getChatBackground';
33
41
  import { useTheme } from '../../hooks/useTheme';
34
42
  import { isOwnMessage } from '../../helpers/isOwnMessage';
43
+ import {
44
+ OlderPage,
45
+ WindowFetchResult,
46
+ useJumpToMessage,
47
+ } from '../../hooks/useJumpToMessage';
48
+ import { scrollRetryOffset } from '../../helpers/jumpToMessage';
49
+ import {
50
+ HistoryWindowClient,
51
+ loadJumpWindow,
52
+ loadNewerWindowPage,
53
+ loadOlderWindowPage,
54
+ } from '../../helpers/jumpWindow';
55
+ import {
56
+ mergeById,
57
+ replyParentId,
58
+ useJumpThread,
59
+ } from '../../helpers/jumpThread';
60
+ import { openThreadForJump } from '../../helpers/openThreadForJump';
61
+ import { setBubbleHighlight } from '../../helpers/bubbleHighlight';
62
+ import { requestSendersOf } from '../../helpers/userResolver';
63
+ import { useT } from '../../i18n/useT';
64
+
65
+ // Older history is requested while the reader is still this far from the top
66
+ // (or 3 screens, whichever is more), so a normal scroll finds it already there
67
+ // and never sees a spinner. The spinner is for a reader who outruns the
68
+ // request and reaches the very top while it is in flight.
69
+ export const PREFETCH_MIN_DISTANCE = 1200;
70
+ export const PREFETCH_SCREENS = 3;
71
+ // FlatList's own trigger distance, in screens: generous on purpose.
72
+ const END_REACHED_THRESHOLD = 4;
73
+ export const HISTORY_PAGE_SIZE = 100;
74
+ // Consecutive history pages that added nothing to the list before auto-paging
75
+ // stops (a reader scroll re-arms it).
76
+ export const MAX_IDLE_AUTO_PAGES = 40;
77
+ // A request that settled without moving the oldest message or the cursor gets
78
+ // exactly one more try after this long.
79
+ export const NO_PROGRESS_RETRY_MS = 1000;
80
+ const AUTO_FILL_DELAY_MS = 100;
81
+ // After a page lands, the content size change it causes is not new content.
82
+ const GROWTH_GUARD_MS = 500;
83
+ // A window page that failed is not retried in a hot loop.
84
+ export const WINDOW_RETRY_AFTER_FAILURE_MS = 3000;
85
+ // The scroll that centres the target must not be read as the reader scrolling
86
+ // toward an edge (it would page, or leave the window).
87
+ export const WINDOW_SETTLE_MS = 600;
88
+ export const WINDOW_EXIT_GRACE_MS = 1500;
89
+
90
+ /** The slice of the XMPP client the list needs. */
91
+ export interface MessageListClient extends HistoryWindowClient {
92
+ getHistoryStanza: (
93
+ chatJID: string,
94
+ max: number,
95
+ before?: number,
96
+ id?: string,
97
+ options?: { source?: 'active' | 'send_ack' | 'background' | 'default' }
98
+ ) => Promise<unknown>;
99
+ }
35
100
 
36
101
  interface MessageListProps<TMessage extends IMessage> {
37
102
  CustomMessage?: React.ComponentType<{
@@ -57,6 +122,12 @@ interface MessageListProps<TMessage extends IMessage> {
57
122
  config?: IConfig;
58
123
  isReply: boolean;
59
124
  activeMessage?: IMessage;
125
+ /**
126
+ * The XMPP client, for the jump window (archive pages around a far search
127
+ * hit) and the paging fallback of a jump. Without one the list still works
128
+ * and jumps page through the room's own loader.
129
+ */
130
+ client?: MessageListClient | null;
60
131
  /**
61
132
  * Fired whenever the "read up to" boundary changes: `null` while the
62
133
  * user is at the bottom (everything is read), or the timestamp of the
@@ -116,6 +187,7 @@ const MessageList = <TMessage extends IMessage>({
116
187
  loading,
117
188
  isReply,
118
189
  activeMessage,
190
+ client,
119
191
  onReadBoundaryChange,
120
192
  }: MessageListProps<TMessage>) => {
121
193
  const room = useSelector(
@@ -124,11 +196,14 @@ const MessageList = <TMessage extends IMessage>({
124
196
  const composing = room?.composing;
125
197
  const composingList = room?.composingList;
126
198
  const messages = room?.messages || EMPTY_LIST;
199
+ const historyComplete = room?.historyComplete;
127
200
  const theme = useTheme();
201
+ const t = useT();
202
+ const dispatch = useDispatch();
203
+ const reduxStore = useStore<RootState>();
128
204
  const [isUserAtBottom, setIsUserAtBottom] = useState(true);
129
205
  const [showNewMessageIndicator, setShowNewMessageIndicator] = useState(false);
130
206
  const [isLoadingMore, setIsLoadingMore] = useState(false);
131
- const [isScrollBlocked, setIsScrollBlocked] = useState(false);
132
207
  // Count of messages that arrived while the user was scrolled up.
133
208
  // Renders as a badge on the scroll-to-bottom arrow so they know how
134
209
  // much they're missing without having to scroll to find out.
@@ -145,12 +220,65 @@ const MessageList = <TMessage extends IMessage>({
145
220
  // to jump back to the bottom when a new message landed mid-scroll.
146
221
  const isUserAtBottomRef = useRef(true);
147
222
  const hasUserScrolledRef = useRef(false);
223
+ const mountedRef = useRef(true);
224
+ useEffect(
225
+ () => () => {
226
+ mountedRef.current = false;
227
+ },
228
+ []
229
+ );
148
230
 
149
231
  const flatListRef = useRef<FlatList<IMessage>>(null);
150
232
 
233
+ // Viewport metrics, kept in refs: the loaders read them from timers and
234
+ // FlatList callbacks, never from a stale render closure.
235
+ const scrollOffsetRef = useRef(0);
236
+ const contentHeightRef = useRef(0);
237
+ const viewportHeightRef = useRef(0);
238
+
239
+ // A short slice of archive around a far jump target, shown instead of the
240
+ // live list until the reader returns to the latest messages. Not for threads.
241
+ const jumpWindow = useSelector((state: RootState) => state.rooms.jumpWindow);
242
+ const joiningRoomJID = useSelector((state: RootState) =>
243
+ typeof state.rooms.joiningRoomJID === 'string'
244
+ ? state.rooms.joiningRoomJID
245
+ : null
246
+ );
247
+ const windowActive = !isReply && jumpWindow?.roomJID === roomJID;
248
+ const windowActiveRef = useRef(windowActive);
249
+ windowActiveRef.current = windowActive;
250
+ const loadingPropRef = useRef(loading);
251
+ loadingPropRef.current = loading;
252
+
253
+ // A thread list also sees what a jump to one of its replies brought in (and
254
+ // any window on screen): those replies may be older than the live list.
255
+ const jumpThread = useJumpThread();
256
+ const threadExtras = useMemo(() => {
257
+ if (!isReply || !activeMessage) {return null;}
258
+ const fromWindow =
259
+ jumpWindow?.roomJID === roomJID ? jumpWindow.messages : [];
260
+ const fromJump =
261
+ jumpThread &&
262
+ jumpThread.roomJID === roomJID &&
263
+ jumpThread.parentId === String(activeMessage.id)
264
+ ? jumpThread.replies
265
+ : [];
266
+ if (fromWindow.length === 0 && fromJump.length === 0) {return null;}
267
+ return mergeById(fromWindow, fromJump);
268
+ }, [isReply, activeMessage, jumpWindow, jumpThread, roomJID]);
269
+
270
+ const sourceMessages: IMessage[] = useMemo(() => {
271
+ if (windowActive && jumpWindow) {return jumpWindow.messages;}
272
+ if (threadExtras) {return mergeById(messages ?? [], threadExtras);}
273
+ return messages;
274
+ }, [windowActive, jumpWindow, threadExtras, messages]);
275
+
151
276
  const addReplyMessages = useMemo(() => {
277
+ // Index replies by their parent once instead of filtering the whole list
278
+ // per message. Only a message that has replies is copied, so the others
279
+ // keep their identity (memoized rows do not re-render).
152
280
  const repliesByParent = new Map<string, IMessage[]>();
153
- for (const mess of messages) {
281
+ for (const mess of sourceMessages) {
154
282
  if (!mess?.mainMessage || mess.isDeleted) {continue;}
155
283
  const parentId = mainMessageIdOf(mess);
156
284
  if (!parentId) {continue;}
@@ -161,12 +289,12 @@ const MessageList = <TMessage extends IMessage>({
161
289
  repliesByParent.set(parentId, [mess]);
162
290
  }
163
291
  }
164
- if (repliesByParent.size === 0) {return messages;}
165
- return messages.map((message: IMessage) => {
292
+ if (repliesByParent.size === 0) {return sourceMessages;}
293
+ return sourceMessages.map((message: IMessage) => {
166
294
  const reply = repliesByParent.get(String(message.id));
167
295
  return reply ? { ...message, reply } : message;
168
296
  });
169
- }, [messages]);
297
+ }, [sourceMessages]);
170
298
 
171
299
  const memoizedMessages = useMemo(() => {
172
300
  const nonDeletedMessages = addReplyMessages.filter(
@@ -198,8 +326,8 @@ const MessageList = <TMessage extends IMessage>({
198
326
  // matching their date.toString() comparison, but redux state can
199
327
  // emit intermediate snapshots between dispatches, so the FlatList
200
328
  // briefly renders messages out-of-order until the final batch
201
- // settles. Sorting here makes each render a stable, ordered view —
202
- // even mid-stream — so the user sees them appear from oldest to
329
+ // settles. Sorting here makes each render a stable, ordered view,
330
+ // even mid-stream, so the user sees them appear from oldest to
203
331
  // newest with no swap animation.
204
332
  let sorted = filtered;
205
333
  for (let i = 1; i < filtered.length; i++) {
@@ -218,52 +346,480 @@ const MessageList = <TMessage extends IMessage>({
218
346
  }
219
347
  return deduped;
220
348
  }, [addReplyMessages, isReply, roomJID, activeMessage]);
349
+ const memoizedMessagesRef = useRef(memoizedMessages);
350
+ memoizedMessagesRef.current = memoizedMessages;
221
351
 
222
- const handleLoadMore = useCallback(async () => {
223
- if (
224
- loading ||
225
- isLoadingMore ||
226
- !memoizedMessages.length ||
227
- isScrollBlocked
228
- ) {
229
- return;
230
- }
352
+ // ---------------------------------------------------------------------
353
+ // History loading (live list)
354
+ // ---------------------------------------------------------------------
355
+
356
+ const isLoadingMoreRef = useRef(false);
357
+ const growthGuardUntilRef = useRef(0);
358
+ // The request key (oldest displayed message + cursor) last kicked off.
359
+ // Guards against a busy loop: a short conversation that never fills the
360
+ // viewport keeps asking, and a request that settles without moving anything
361
+ // would be asked again immediately, forever.
362
+ const lastRequestedKeyRef = useRef<string | null>(null);
363
+ const idleLoadsRef = useRef(0);
364
+ const idleLoadsLenRef = useRef(-1);
365
+ // Request keys that already got their one delayed retry.
366
+ const retriedKeysRef = useRef<Set<string>>(new Set());
367
+ const retryTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
368
+ const autoFillTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
369
+ const checkLoadMoreRef = useRef<(assumeNear?: boolean) => void>(() => {});
370
+ const checkWindowLoadMoreRef = useRef<(fromUserScroll?: boolean) => void>(
371
+ () => {}
372
+ );
373
+
374
+ // The paging cursor moved (or regressed through another path) or the
375
+ // history completed: the repeat guard no longer describes the room, so a key
376
+ // seen before is allowed again. The idle page cap still bounds any loop.
377
+ const pagingCursor = useSelector(
378
+ (state: RootState) =>
379
+ state.rooms.rooms[roomJID]?.messageStats?.firstMessageTimestamp
380
+ );
381
+ const roomHistoryComplete = useSelector((state: RootState) =>
382
+ Boolean(state.rooms.rooms[roomJID]?.historyComplete)
383
+ );
384
+ useEffect(() => {
385
+ lastRequestedKeyRef.current = null;
386
+ retriedKeysRef.current = new Set();
387
+ }, [pagingCursor, roomHistoryComplete]);
231
388
 
232
- const oldestMessage = memoizedMessages[0];
233
- if (!oldestMessage || !oldestMessage.id) {
234
- return;
389
+ useEffect(() => {
390
+ // A new room's oldest message is unrelated to whatever the previous room
391
+ // last attempted: do not let a stale guard block its first load.
392
+ lastRequestedKeyRef.current = null;
393
+ retriedKeysRef.current = new Set();
394
+ idleLoadsRef.current = 0;
395
+ idleLoadsLenRef.current = -1;
396
+ if (retryTimerRef.current) {
397
+ clearTimeout(retryTimerRef.current);
398
+ retryTimerRef.current = null;
235
399
  }
400
+ }, [roomJID]);
401
+
402
+ useEffect(
403
+ () => () => {
404
+ if (retryTimerRef.current) {clearTimeout(retryTimerRef.current);}
405
+ if (autoFillTimerRef.current) {clearTimeout(autoFillTimerRef.current);}
406
+ },
407
+ []
408
+ );
409
+
410
+ const prefetchDistance = () =>
411
+ Math.max(PREFETCH_MIN_DISTANCE, viewportHeightRef.current * PREFETCH_SCREENS);
412
+ // Inverted list: offset 0 is the newest end; older history is at the far end.
413
+ const distanceToOlderEdge = () =>
414
+ contentHeightRef.current - viewportHeightRef.current - scrollOffsetRef.current;
236
415
 
237
- setIsLoadingMore(true);
238
- setIsScrollBlocked(true);
416
+ // After a request settled, if the guard still holds the same key nothing
417
+ // moved (oldest id and cursor identical). Allow exactly one retry per key
418
+ // after ~1 s, then stay stopped until the cursor, historyComplete or the
419
+ // room changes (those clear the retried set).
420
+ const scheduleNoProgressRetry = useCallback(
421
+ (key: string) => {
422
+ if (retriedKeysRef.current.has(key)) {return;}
423
+ if (retryTimerRef.current) {clearTimeout(retryTimerRef.current);}
424
+ const forRoom = roomJID;
425
+ retryTimerRef.current = setTimeout(() => {
426
+ retryTimerRef.current = null;
427
+ if (forRoom !== roomJID || !mountedRef.current) {return;}
428
+ if (lastRequestedKeyRef.current !== key) {return;}
429
+ if (reduxStore.getState().rooms.rooms[forRoom]?.historyComplete) {return;}
430
+ retriedKeysRef.current.add(key);
431
+ lastRequestedKeyRef.current = null;
432
+ checkLoadMoreRef.current();
433
+ }, NO_PROGRESS_RETRY_MS);
434
+ },
435
+ [roomJID, reduxStore]
436
+ );
437
+
438
+ const checkIfLoadMore = useCallback(
439
+ (assumeNear = false) => {
440
+ // While a jump window is on screen the live list is not what the reader
441
+ // is looking at: no background prefetch of it, the window pages instead.
442
+ if (windowActiveRef.current) {
443
+ checkWindowLoadMoreRef.current();
444
+ return;
445
+ }
446
+ if (isLoadingMoreRef.current) {return;}
447
+
448
+ const state = reduxStore.getState().rooms;
449
+ // A jump is already paging this room's history. A second request in
450
+ // flight at the same time makes the server's answers interleave and one
451
+ // of them time out, which the jump reads as the history being
452
+ // unreachable.
453
+ if (state.pendingJump?.roomJID === roomJID) {return;}
454
+ // Read the room at call time: a closure over an old render would act on
455
+ // an old cursor.
456
+ const room = state.rooms[roomJID];
457
+ if (!room || room.historyComplete) {return;}
239
458
 
240
- try {
241
- await loadMoreMessages(
242
- oldestMessage.roomJid || roomJID,
243
- 15,
244
- Number(oldestMessage.id)
459
+ if (
460
+ !assumeNear &&
461
+ viewportHeightRef.current > 0 &&
462
+ distanceToOlderEdge() >= prefetchDistance()
463
+ ) {
464
+ return;
465
+ }
466
+
467
+ const displayed = memoizedMessagesRef.current;
468
+ const first = displayed.find(
469
+ (message) => !String(message.id).startsWith('delimiter-new')
245
470
  );
246
- } catch (error) {
247
- console.error(error);
248
- } finally {
249
- setTimeout(() => {
250
- setIsLoadingMore(false);
251
- setIsScrollBlocked(false);
252
- }, 500);
253
- }
471
+ // Continue from the server's own cursor (the oldest ROW the last page
472
+ // returned), never from the oldest DISPLAYED message alone: a page made
473
+ // only of receipts or reactions displays nothing, the oldest message
474
+ // does not change, and paging would stall with plenty of history left.
475
+ const serverCursor = room.messageStats?.firstMessageTimestamp;
476
+ const oldestDisplayed = first ? Number(first.id) : NaN;
477
+ let before: number;
478
+ if (Number.isFinite(oldestDisplayed)) {
479
+ before =
480
+ typeof serverCursor === 'number' && serverCursor < oldestDisplayed
481
+ ? serverCursor
482
+ : oldestDisplayed;
483
+ } else if (typeof serverCursor === 'number' && Number.isFinite(serverCursor)) {
484
+ before = serverCursor;
485
+ } else {
486
+ return;
487
+ }
488
+
489
+ // A repeat of the same request (same oldest message AND same cursor) is
490
+ // what the guard stops; a page that only moved the cursor is progress.
491
+ const key = `${first ? first.id : 'none'}|${before}`;
492
+ if (key === lastRequestedKeyRef.current) {return;}
493
+
494
+ // Hard stop for a long run of pages that add nothing to the list (an
495
+ // archive that is almost all receipts): a reader scroll re-arms it.
496
+ if (displayed.length !== idleLoadsLenRef.current) {
497
+ idleLoadsLenRef.current = displayed.length;
498
+ idleLoadsRef.current = 0;
499
+ }
500
+ if (idleLoadsRef.current >= MAX_IDLE_AUTO_PAGES) {return;}
501
+ idleLoadsRef.current += 1;
502
+
503
+ isLoadingMoreRef.current = true;
504
+ lastRequestedKeyRef.current = key;
505
+ setIsLoadingMore(true);
506
+
507
+ let request: Promise<void>;
508
+ try {
509
+ request = Promise.resolve(
510
+ loadMoreMessages(roomJID, HISTORY_PAGE_SIZE, before)
511
+ );
512
+ } catch (error) {
513
+ request = Promise.reject(error);
514
+ }
515
+ request
516
+ .catch(() => undefined)
517
+ .then(() => {
518
+ isLoadingMoreRef.current = false;
519
+ growthGuardUntilRef.current = Date.now() + GROWTH_GUARD_MS;
520
+ if (mountedRef.current) {setIsLoadingMore(false);}
521
+ scheduleNoProgressRetry(key);
522
+ // Keep the buffer above the reader topped up without waiting for the
523
+ // next scroll event: a reader still inside the prefetch distance
524
+ // once this page is in gets the next page right away, so a
525
+ // normal-speed scroll never catches up with the loader. Deferred so
526
+ // the new page has been rendered and measured first.
527
+ setTimeout(() => {
528
+ if (mountedRef.current) {checkLoadMoreRef.current();}
529
+ }, 80);
530
+ });
531
+ },
532
+ [loadMoreMessages, roomJID, reduxStore, scheduleNoProgressRetry]
533
+ );
534
+ checkLoadMoreRef.current = checkIfLoadMore;
535
+
536
+ // A list that does not fill the viewport cannot scroll, so no scroll event
537
+ // ever asks for older history (a first page made of receipts displays one
538
+ // message and the archive behind it stays unreachable). Ask without one; the
539
+ // chain in checkIfLoadMore then pages until the viewport fills. Threads load
540
+ // through their own wrapper; a jump window pages itself.
541
+ const scheduleAutoFill = useCallback(() => {
542
+ if (isReply || windowActiveRef.current || !roomJID) {return;}
543
+ if (autoFillTimerRef.current) {clearTimeout(autoFillTimerRef.current);}
544
+ autoFillTimerRef.current = setTimeout(() => {
545
+ autoFillTimerRef.current = null;
546
+ if (!mountedRef.current) {return;}
547
+ if (windowActiveRef.current || isLoadingMoreRef.current) {return;}
548
+ const state = reduxStore.getState().rooms;
549
+ if (state.pendingJump?.roomJID === roomJID) {return;}
550
+ if (state.rooms[roomJID]?.historyComplete) {return;}
551
+ // Not laid out yet: nothing to compare against.
552
+ if (viewportHeightRef.current <= 0 || contentHeightRef.current <= 0) {return;}
553
+ const unfilled =
554
+ contentHeightRef.current <= viewportHeightRef.current ||
555
+ distanceToOlderEdge() < prefetchDistance();
556
+ if (!unfilled || !isUserAtBottomRef.current) {return;}
557
+ checkLoadMoreRef.current();
558
+ }, AUTO_FILL_DELAY_MS);
559
+ }, [isReply, roomJID, reduxStore]);
560
+
561
+ useEffect(() => {
562
+ if (isReply || windowActive || loading || historyComplete) {return;}
563
+ scheduleAutoFill();
254
564
  }, [
255
- memoizedMessages,
256
- loadMoreMessages,
257
- loading,
258
- isLoadingMore,
259
- isScrollBlocked,
260
565
  roomJID,
566
+ isReply,
567
+ windowActive,
568
+ loading,
569
+ historyComplete,
570
+ memoizedMessages.length,
571
+ pagingCursor,
572
+ scheduleAutoFill,
261
573
  ]);
262
574
 
575
+ const handleLoadMore = useCallback(() => {
576
+ // FlatList says the reader is near the older end.
577
+ checkLoadMoreRef.current(true);
578
+ }, []);
579
+
580
+ // ---------------------------------------------------------------------
581
+ // Jump window (a far jump shows a short slice of the archive around it)
582
+ // ---------------------------------------------------------------------
583
+
584
+ const [windowLoading, setWindowLoading] = useState<'older' | 'newer' | null>(
585
+ null
586
+ );
587
+ const windowBusyRef = useRef<'older' | 'newer' | null>(null);
588
+ const windowFailedAtRef = useRef({ older: 0, newer: 0 });
589
+ const windowOpenedAtRef = useRef(0);
590
+ const pendingExitScrollRef = useRef(false);
591
+ const prevWindowActiveRef = useRef(windowActive);
592
+
593
+ const exitWindow = useCallback(() => {
594
+ if (!windowActiveRef.current) {return;}
595
+ isUserAtBottomRef.current = true;
596
+ hasUserScrolledRef.current = false;
597
+ pendingExitScrollRef.current = true;
598
+ setShowNewMessageIndicator(false);
599
+ setIsUserAtBottom(true);
600
+ setUnreadWhileScrolledUp(0);
601
+ newestSeenTsRef.current = null;
602
+ onReadBoundaryChange?.(null);
603
+ dispatch(clearJumpWindow());
604
+ }, [dispatch, onReadBoundaryChange]);
605
+
606
+ // Back on the live list: land on its newest message.
607
+ useEffect(() => {
608
+ const was = prevWindowActiveRef.current;
609
+ prevWindowActiveRef.current = windowActive;
610
+ if (windowActive || !was) {return;}
611
+ // A new jump is about to scroll somewhere else: leave the position to it.
612
+ if (reduxStore.getState().rooms.pendingJump?.roomJID === roomJID) {return;}
613
+ isUserAtBottomRef.current = true;
614
+ pendingExitScrollRef.current = false;
615
+ setIsUserAtBottom(true);
616
+ const toLiveEnd = () =>
617
+ flatListRef.current?.scrollToOffset({ offset: 0, animated: false });
618
+ toLiveEnd();
619
+ // The list swaps its data a frame later on a slow device: once more.
620
+ const timer = setTimeout(toLiveEnd, 50);
621
+ return () => clearTimeout(timer);
622
+ }, [windowActive, roomJID, reduxStore]);
623
+
624
+ // A window must not outlive the list that showed it.
625
+ useEffect(
626
+ () => () => {
627
+ if (windowActiveRef.current) {dispatch(clearJumpWindow());}
628
+ },
629
+ [dispatch]
630
+ );
631
+
632
+ useEffect(() => {
633
+ if (!jumpWindow?.targetId || !windowActive) {return;}
634
+ windowOpenedAtRef.current = Date.now();
635
+ windowFailedAtRef.current = { older: 0, newer: 0 };
636
+ isUserAtBottomRef.current = false;
637
+ setIsUserAtBottom(false);
638
+ setShowNewMessageIndicator(false);
639
+ // Reading a window of old history is not reading the latest messages: from
640
+ // here on the read boundary is the newest live message the reader had
641
+ // reached, never "everything".
642
+ if (newestSeenTsRef.current === null) {
643
+ const newest = (messages ?? []).reduce(
644
+ (mx: number, m: IMessage) => Math.max(mx, msgSortableMs(m)),
645
+ 0
646
+ );
647
+ if (newest > 0) {
648
+ newestSeenTsRef.current = newest;
649
+ onReadBoundaryChange?.(newest);
650
+ }
651
+ }
652
+ // A window that does not fill the viewport never scrolls, so nothing would
653
+ // ask for the next page: look once it has settled.
654
+ const timer = setTimeout(
655
+ () => checkWindowLoadMoreRef.current(),
656
+ WINDOW_SETTLE_MS + 100
657
+ );
658
+ return () => clearTimeout(timer);
659
+ // eslint-disable-next-line react-hooks/exhaustive-deps
660
+ }, [jumpWindow?.targetId, windowActive]);
661
+
662
+ const loadWindowPage = useCallback(
663
+ async (direction: 'older' | 'newer') => {
664
+ const win = reduxStore.getState().rooms.jumpWindow;
665
+ if (!win || win.roomJID !== roomJID || !client) {return;}
666
+ if (windowBusyRef.current) {return;}
667
+ windowBusyRef.current = direction;
668
+ setWindowLoading(direction);
669
+ try {
670
+ const page =
671
+ direction === 'older'
672
+ ? await loadOlderWindowPage(client, win)
673
+ : await loadNewerWindowPage(client, win);
674
+ const current = reduxStore.getState().rooms.jumpWindow;
675
+ // The reader left, or opened another window, while this was in flight.
676
+ if (!current || current.targetId !== win.targetId) {return;}
677
+ if (!page) {
678
+ windowFailedAtRef.current[direction] = Date.now();
679
+ return;
680
+ }
681
+ requestSendersOf(page.messages);
682
+ if ('olderCursor' in page) {
683
+ dispatch(prependJumpWindowMessages({ roomJID, ...page }));
684
+ } else {
685
+ dispatch(appendJumpWindowMessages({ roomJID, ...page }));
686
+ }
687
+ } catch {
688
+ windowFailedAtRef.current[direction] = Date.now();
689
+ } finally {
690
+ windowBusyRef.current = null;
691
+ if (mountedRef.current) {setWindowLoading(null);}
692
+ setTimeout(() => {
693
+ if (mountedRef.current) {checkWindowLoadMoreRef.current();}
694
+ }, 80);
695
+ }
696
+ },
697
+ [client, roomJID, dispatch, reduxStore]
698
+ );
699
+
700
+ checkWindowLoadMoreRef.current = (fromUserScroll = false) => {
701
+ const win = reduxStore.getState().rooms.jumpWindow;
702
+ if (!win || win.roomJID !== roomJID) {return;}
703
+ const now = Date.now();
704
+ if (now - windowOpenedAtRef.current < WINDOW_SETTLE_MS) {return;}
705
+ // Not laid out yet: there is nothing to measure a distance against.
706
+ if (viewportHeightRef.current <= 0 || contentHeightRef.current <= 0) {return;}
707
+ const prefetch = prefetchDistance();
708
+ const olderDistance = distanceToOlderEdge();
709
+ const newerDistance = scrollOffsetRef.current;
710
+ if (
711
+ win.hasOlder &&
712
+ olderDistance < prefetch &&
713
+ now - windowFailedAtRef.current.older > WINDOW_RETRY_AFTER_FAILURE_MS
714
+ ) {
715
+ loadWindowPage('older');
716
+ } else if (
717
+ win.hasNewer &&
718
+ newerDistance < prefetch &&
719
+ now - windowFailedAtRef.current.newer > WINDOW_RETRY_AFTER_FAILURE_MS
720
+ ) {
721
+ loadWindowPage('newer');
722
+ } else if (
723
+ !win.hasNewer &&
724
+ fromUserScroll &&
725
+ newerDistance <= 5 &&
726
+ now - windowOpenedAtRef.current > WINDOW_EXIT_GRACE_MS
727
+ ) {
728
+ // Nothing newer than this is left to load: that is the live tail.
729
+ exitWindow();
730
+ }
731
+ };
732
+
733
+ const handleStartReached = useCallback(() => {
734
+ checkWindowLoadMoreRef.current();
735
+ }, []);
736
+
737
+ // A jump whose target is a thread reply opens the parent's thread (the main
738
+ // list never shows replies); the thread list then highlights the reply.
739
+ const resolveReply = useCallback(
740
+ (jump: PendingJump, reply: IMessage, nearby?: IMessage[]) =>
741
+ openThreadForJump({
742
+ client,
743
+ dispatch: dispatch as any,
744
+ roomJID,
745
+ at: jump.at,
746
+ reply,
747
+ live: messages ?? [],
748
+ nearby,
749
+ }),
750
+ [client, dispatch, roomJID, messages]
751
+ );
752
+
753
+ const fetchWindow = useCallback(
754
+ async (jump: PendingJump): Promise<WindowFetchResult> => {
755
+ if (isReply || !client?.getHistoryWindow) {return 'unavailable';}
756
+ const result = await loadJumpWindow(client, roomJID, jump.ids, {
757
+ createdAt: jump.createdAt,
758
+ body: jump.body,
759
+ });
760
+ if (result.status !== 'found') {return result.status;}
761
+ const target = result.window.messages.find(
762
+ (message) => String(message.id) === result.window.targetId
763
+ );
764
+ if (target && replyParentId(target)) {
765
+ // Not a main-list message: the window is only used to learn what the
766
+ // reply belongs to; it is not put on screen.
767
+ const opened = await resolveReply(
768
+ jump,
769
+ target,
770
+ result.window.messages
771
+ ).catch(() => false);
772
+ return opened ? 'found' : 'missing';
773
+ }
774
+ isUserAtBottomRef.current = false;
775
+ requestSendersOf(result.window.messages);
776
+ dispatch(setJumpWindow(result.window));
777
+ return 'found';
778
+ },
779
+ [client, roomJID, isReply, dispatch, resolveReply]
780
+ );
781
+
782
+ const fetchOlderPage = useCallback(
783
+ async (jid: string, before: number, max: number): Promise<OlderPage> => {
784
+ if (!client) {return { ok: false };}
785
+ const page = await client.getHistoryStanza(jid, max, before, undefined, {
786
+ source: 'active',
787
+ });
788
+ // The page is in the store by now, and so is the server's cursor for it
789
+ // (the <fin> handler writes it to messageStats).
790
+ const room = reduxStore.getState().rooms.rooms[jid];
791
+ return {
792
+ ok: page !== undefined,
793
+ cursor: room?.messageStats?.firstMessageTimestamp,
794
+ complete: Boolean(room?.historyComplete),
795
+ };
796
+ },
797
+ [client, reduxStore]
798
+ );
799
+
800
+ // The own message that was just sent is on the live list: leave the window
801
+ // for it (the composer already clears it; this covers other send paths).
802
+ const lastNewestIdRef = useRef<string | undefined>(
803
+ messages[messages.length - 1]?.id
804
+ );
805
+ useEffect(() => {
806
+ const newest = messages[messages.length - 1];
807
+ const changed = newest && String(newest.id) !== lastNewestIdRef.current;
808
+ lastNewestIdRef.current = newest ? String(newest.id) : undefined;
809
+ if (
810
+ changed &&
811
+ windowActiveRef.current &&
812
+ isOwnMessage(newest, user.xmppUsername || user.walletAddress)
813
+ ) {
814
+ exitWindow();
815
+ }
816
+ }, [messages, user.xmppUsername, user.walletAddress, exitWindow]);
817
+
263
818
  const dataMessages = useMemo(() => {
264
819
  const base = memoizedMessages.slice().reverse();
265
820
  const boundaryTs = newestSeenTsRef.current;
266
821
  if (
822
+ windowActive ||
267
823
  isUserAtBottomRef.current ||
268
824
  boundaryTs === null ||
269
825
  unreadWhileScrolledUp === 0
@@ -296,7 +852,61 @@ const MessageList = <TMessage extends IMessage>({
296
852
  roomJid: roomJID,
297
853
  } as IMessage);
298
854
  return withDivider;
299
- }, [memoizedMessages, isUserAtBottom, roomJID, unreadWhileScrolledUp]);
855
+ // eslint-disable-next-line react-hooks/exhaustive-deps
856
+ }, [memoizedMessages, isUserAtBottom, roomJID, unreadWhileScrolledUp, windowActive]);
857
+
858
+ // Jump-to-message (search hit, notification): scroll to the row, then pulse
859
+ // the bubble. The retry budget is for scrolling to a row the virtualised
860
+ // list has not measured yet.
861
+ const scrollRetryRef = useRef(0);
862
+ const scrollToRow = useCallback((index: number) => {
863
+ scrollRetryRef.current = 0;
864
+ flatListRef.current?.scrollToIndex({
865
+ index,
866
+ viewPosition: 0.5,
867
+ animated: false,
868
+ });
869
+ }, []);
870
+ const handleScrollToIndexFailed = useCallback(
871
+ (info: { index: number; averageItemLength: number }) => {
872
+ const offset = scrollRetryOffset(info, scrollRetryRef.current);
873
+ if (offset === null) {return;}
874
+ scrollRetryRef.current += 1;
875
+ flatListRef.current?.scrollToOffset({ offset, animated: false });
876
+ setTimeout(() => {
877
+ flatListRef.current?.scrollToIndex({
878
+ index: info.index,
879
+ viewPosition: 0.5,
880
+ animated: false,
881
+ });
882
+ }, 120);
883
+ },
884
+ []
885
+ );
886
+ useJumpToMessage({
887
+ roomJID,
888
+ messages: memoizedMessages,
889
+ listData: dataMessages,
890
+ scrollToIndex: scrollToRow,
891
+ onHighlight: setBubbleHighlight,
892
+ loadMoreMessages,
893
+ fetchOlderPage: client ? fetchOlderPage : undefined,
894
+ historyComplete,
895
+ isUserAtBottomRef,
896
+ jumpWindowActive: windowActive,
897
+ fetchWindow: isReply || !client ? undefined : fetchWindow,
898
+ scope:
899
+ isReply && activeMessage ? { threadId: String(activeMessage.id) } : 'main',
900
+ allMessages: sourceMessages,
901
+ resolveReply: isReply
902
+ ? undefined
903
+ : (jump, reply) => resolveReply(jump, reply),
904
+ roomOpening:
905
+ !isReply &&
906
+ (Boolean(loading) ||
907
+ joiningRoomJID === roomJID ||
908
+ (sourceMessages.length === 0 && !historyComplete)),
909
+ });
300
910
 
301
911
  const dateLabelIds = useMemo(() => {
302
912
  const ids = new Set<string>();
@@ -331,6 +941,8 @@ const MessageList = <TMessage extends IMessage>({
331
941
 
332
942
  const showDateLabel = dateLabelIds.has(String(item.id));
333
943
 
944
+ // The jump highlight is drawn by the message bubble itself (see
945
+ // BubbleHighlight), so the row, its date label and its avatar stay put.
334
946
  return (
335
947
  <MessageContainer
336
948
  CustomMessage={CustomMessage}
@@ -367,32 +979,61 @@ const MessageList = <TMessage extends IMessage>({
367
979
  }
368
980
  }, [flatListRef]);
369
981
 
370
- const handleContentSizeChange = useCallback(() => {
371
- if (!isLoadingMore && flatListRef.current && isUserAtBottomRef.current) {
372
- scrollToBottom();
373
- } else if (!isLoadingMore && hasUserScrolledRef.current) {
374
- setShowNewMessageIndicator(true);
375
- }
376
- }, [scrollToBottom, isLoadingMore]);
982
+ const handleContentSizeChange = useCallback(
983
+ (_width?: number, height?: number) => {
984
+ if (typeof height === 'number' && height > 0) {
985
+ contentHeightRef.current = height;
986
+ }
987
+ if (windowActiveRef.current) {
988
+ // Reading a window of old history: never stick to the bottom.
989
+ checkWindowLoadMoreRef.current();
990
+ return;
991
+ }
992
+ const growing =
993
+ isLoadingMoreRef.current || Date.now() < growthGuardUntilRef.current;
994
+ if (!growing && flatListRef.current && isUserAtBottomRef.current) {
995
+ scrollToBottom();
996
+ } else if (!growing && hasUserScrolledRef.current) {
997
+ setShowNewMessageIndicator(true);
998
+ }
999
+ scheduleAutoFill();
1000
+ },
1001
+ [scrollToBottom, scheduleAutoFill]
1002
+ );
377
1003
 
378
1004
  const handleScroll = useCallback(
379
1005
  (event: NativeSyntheticEvent<NativeScrollEvent>) => {
380
- const contentOffset = event.nativeEvent.contentOffset.y;
1006
+ const { contentOffset, contentSize, layoutMeasurement } =
1007
+ event.nativeEvent;
1008
+ const offsetY = contentOffset.y;
1009
+ scrollOffsetRef.current = offsetY;
1010
+ if (contentSize?.height > 0) {contentHeightRef.current = contentSize.height;}
1011
+ if (layoutMeasurement?.height > 0) {
1012
+ viewportHeightRef.current = layoutMeasurement.height;
1013
+ }
1014
+
1015
+ if (windowActiveRef.current) {
1016
+ // Reading a window of old history: never "at the bottom", always the
1017
+ // way back to the latest messages, and page both ways.
1018
+ isUserAtBottomRef.current = false;
1019
+ checkWindowLoadMoreRef.current(true);
1020
+ return;
1021
+ }
381
1022
 
382
- // Don't force-dismiss the keyboard on every scroll event — any
1023
+ // Don't force-dismiss the keyboard on every scroll event, any
383
1024
  // touch on the message list used to close it, which made replying
384
1025
  // to a long thread feel broken. Drag-to-dismiss still works via
385
1026
  // FlatList's keyboardDismissMode="interactive" below.
386
1027
 
387
- if (contentOffset > 150 && !hasUserScrolledRef.current) {
1028
+ if (offsetY > 150 && !hasUserScrolledRef.current) {
388
1029
  hasUserScrolledRef.current = true;
389
1030
  }
390
1031
 
391
- if (contentOffset < 150) {
1032
+ if (offsetY < 150) {
392
1033
  isUserAtBottomRef.current = true;
393
1034
  setShowNewMessageIndicator(false);
394
1035
  setIsUserAtBottom(true);
395
- // Back at the bottom — clear the unread-while-scrolled-up
1036
+ // Back at the bottom, clear the unread-while-scrolled-up
396
1037
  // counter so the badge disappears with the arrow.
397
1038
  setUnreadWhileScrolledUp(0);
398
1039
  newestSeenTsRef.current = null;
@@ -409,8 +1050,8 @@ const MessageList = <TMessage extends IMessage>({
409
1050
  // back-pagination is < this and never counts.
410
1051
  newestSeenTsRef.current = memoizedMessages.reduce(
411
1052
  (mx: number, m: IMessage) => {
412
- const t = msgSortableMs(m);
413
- return t > mx ? t : mx;
1053
+ const ts = msgSortableMs(m);
1054
+ return ts > mx ? ts : mx;
414
1055
  },
415
1056
  0
416
1057
  );
@@ -424,8 +1065,9 @@ const MessageList = <TMessage extends IMessage>({
424
1065
 
425
1066
  // Keep the badge in sync with messages that arrive while the user is
426
1067
  // scrolled up. Count ONLY messages newer than the newest-seen snapshot
427
- // — so loading older history (back-pagination) doesn't inflate it.
1068
+ //, so loading older history (back-pagination) doesn't inflate it.
428
1069
  useEffect(() => {
1070
+ if (windowActiveRef.current) {return;}
429
1071
  if (isUserAtBottomRef.current) {return;}
430
1072
  if (newestSeenTsRef.current === null) {return;}
431
1073
  const since = newestSeenTsRef.current;
@@ -436,10 +1078,17 @@ const MessageList = <TMessage extends IMessage>({
436
1078
  setUnreadWhileScrolledUp(count);
437
1079
  }, [memoizedMessages, isUserAtBottom]);
438
1080
 
439
- const handleLayout = () => {
440
- isUserAtBottomRef.current = true;
441
- hasUserScrolledRef.current = false;
442
- setIsUserAtBottom(true);
1081
+ const handleLayout = (event?: LayoutChangeEvent) => {
1082
+ const height = event?.nativeEvent?.layout?.height;
1083
+ if (typeof height === 'number' && height > 0) {
1084
+ viewportHeightRef.current = height;
1085
+ }
1086
+ if (!windowActiveRef.current) {
1087
+ isUserAtBottomRef.current = true;
1088
+ hasUserScrolledRef.current = false;
1089
+ setIsUserAtBottom(true);
1090
+ scheduleAutoFill();
1091
+ }
443
1092
  };
444
1093
 
445
1094
  const handleNewMessageIndicatorPress = () => {
@@ -506,10 +1155,22 @@ const MessageList = <TMessage extends IMessage>({
506
1155
  initialNumToRender={15}
507
1156
  maxToRenderPerBatch={10}
508
1157
  windowSize={7}
509
- onEndReached={handleLoadMore}
1158
+ onEndReached={windowActive ? handleStartReached : handleLoadMore}
1159
+ onStartReached={windowActive ? handleStartReached : undefined}
510
1160
  onScroll={handleScroll}
511
1161
  onContentSizeChange={handleContentSizeChange}
512
- onEndReachedThreshold={0.1}
1162
+ onScrollToIndexFailed={handleScrollToIndexFailed}
1163
+ // Prefetch well ahead of the viewport (screens, not pixels) so a
1164
+ // normal scroll finds older history already there.
1165
+ onEndReachedThreshold={END_REACHED_THRESHOLD}
1166
+ onStartReachedThreshold={END_REACHED_THRESHOLD}
1167
+ // In a jump window newer messages arrive at the near (offset 0) end:
1168
+ // keep what the reader is looking at where it is instead of letting
1169
+ // the content slide. The live list stays unanchored on purpose, it
1170
+ // follows its own newest end (handleContentSizeChange).
1171
+ maintainVisibleContentPosition={
1172
+ windowActive ? { minIndexForVisible: 0 } : undefined
1173
+ }
513
1174
  scrollEventThrottle={16}
514
1175
  onLayout={handleLayout}
515
1176
  inverted={true}
@@ -519,39 +1180,80 @@ const MessageList = <TMessage extends IMessage>({
519
1180
  }
520
1181
  contentContainerStyle={styles.flatListContent}
521
1182
  ListFooterComponent={
522
- loading && memoizedMessages.length > 15 ? (
1183
+ (loading || isLoadingMore) &&
1184
+ !windowActive &&
1185
+ memoizedMessages.length > 15 ? (
523
1186
  <Loader color={config?.colors?.primary} />
524
1187
  ) : null
525
1188
  }
526
1189
  />
527
- {showNewMessageIndicator && (
1190
+ {windowActive && windowLoading === 'older' && (
1191
+ <View
1192
+ testID="history-loader-older"
1193
+ pointerEvents="none"
1194
+ style={styles.loaderTop}
1195
+ >
1196
+ <Loader size={24} color={config?.colors?.primary} />
1197
+ </View>
1198
+ )}
1199
+ {windowActive && windowLoading === 'newer' && (
1200
+ <View
1201
+ testID="history-loader-newer"
1202
+ pointerEvents="none"
1203
+ style={styles.loaderBottom}
1204
+ >
1205
+ <Loader size={24} color={config?.colors?.primary} />
1206
+ </View>
1207
+ )}
1208
+ {windowActive ? (
528
1209
  <TouchableOpacity
1210
+ testID="jump-to-latest"
1211
+ accessibilityRole="button"
1212
+ accessibilityLabel={t('action.jumpToLatest')}
529
1213
  style={[
530
- styles.newMessageIndicator,
1214
+ styles.jumpToLatest,
531
1215
  {
532
1216
  backgroundColor: getIconColor(config),
533
1217
  shadowColor: theme.shadow,
534
1218
  },
535
1219
  ]}
536
- onPress={handleNewMessageIndicatorPress}
1220
+ onPress={exitWindow}
537
1221
  >
538
- {/* White chevron on the saturated FAB. The icon's default
539
- color is white, which was invisible against the previous
540
- light `secondary` background — hence "no icon". */}
541
- <ArowDownIcon color={theme.textOnPrimary} width={22} height={22} />
542
- {unreadWhileScrolledUp > 0 && (
543
- <View
544
- style={[
545
- styles.newMessageBadge,
546
- { backgroundColor: theme.danger, borderColor: theme.surface },
547
- ]}
548
- >
549
- <Text style={styles.newMessageBadgeText}>
550
- {unreadWhileScrolledUp > 99 ? '99+' : unreadWhileScrolledUp}
551
- </Text>
552
- </View>
553
- )}
1222
+ <ArowDownIcon color={theme.textOnPrimary} width={18} height={18} />
1223
+ <Text style={[styles.jumpToLatestText, { color: theme.textOnPrimary }]}>
1224
+ {t('action.jumpToLatest')}
1225
+ </Text>
554
1226
  </TouchableOpacity>
1227
+ ) : (
1228
+ showNewMessageIndicator && (
1229
+ <TouchableOpacity
1230
+ style={[
1231
+ styles.newMessageIndicator,
1232
+ {
1233
+ backgroundColor: getIconColor(config),
1234
+ shadowColor: theme.shadow,
1235
+ },
1236
+ ]}
1237
+ onPress={handleNewMessageIndicatorPress}
1238
+ >
1239
+ {/* White chevron on the saturated FAB. The icon's default
1240
+ color is white, which was invisible against the previous
1241
+ light `secondary` background, hence "no icon". */}
1242
+ <ArowDownIcon color={theme.textOnPrimary} width={22} height={22} />
1243
+ {unreadWhileScrolledUp > 0 && (
1244
+ <View
1245
+ style={[
1246
+ styles.newMessageBadge,
1247
+ { backgroundColor: theme.danger, borderColor: theme.surface },
1248
+ ]}
1249
+ >
1250
+ <Text style={styles.newMessageBadgeText}>
1251
+ {unreadWhileScrolledUp > 99 ? '99+' : unreadWhileScrolledUp}
1252
+ </Text>
1253
+ </View>
1254
+ )}
1255
+ </TouchableOpacity>
1256
+ )
555
1257
  )}
556
1258
  {config?.customTypingIndicator?.enabled && composing && (
557
1259
  <CustomTypingIndicator
@@ -603,6 +1305,43 @@ const styles = StyleSheet.create({
603
1305
  width: '100%',
604
1306
  height: '100%',
605
1307
  },
1308
+ // Float over the list so showing or hiding them never moves the messages.
1309
+ loaderTop: {
1310
+ position: 'absolute',
1311
+ top: 8,
1312
+ left: 0,
1313
+ right: 0,
1314
+ alignItems: 'center',
1315
+ zIndex: 2,
1316
+ },
1317
+ loaderBottom: {
1318
+ position: 'absolute',
1319
+ bottom: 8,
1320
+ left: 0,
1321
+ right: 0,
1322
+ alignItems: 'center',
1323
+ zIndex: 2,
1324
+ },
1325
+ jumpToLatest: {
1326
+ position: 'absolute',
1327
+ bottom: 20,
1328
+ right: 20,
1329
+ height: 40,
1330
+ paddingHorizontal: 14,
1331
+ borderRadius: 20,
1332
+ flexDirection: 'row',
1333
+ alignItems: 'center',
1334
+ justifyContent: 'center',
1335
+ gap: 6,
1336
+ shadowOffset: { width: 0, height: 2 },
1337
+ shadowOpacity: 0.2,
1338
+ shadowRadius: 3,
1339
+ elevation: 4,
1340
+ },
1341
+ jumpToLatestText: {
1342
+ fontSize: 13,
1343
+ fontWeight: '600',
1344
+ },
606
1345
  newMessageIndicator: {
607
1346
  position: 'absolute',
608
1347
  width: 40,