@ethora/chat-component-rn 26.7.2 → 26.7.4

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 (263) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +9 -8
  3. package/lib/commonjs/assets/icons.js +7 -2
  4. package/lib/commonjs/assets/icons.js.map +1 -1
  5. package/lib/commonjs/components/Header/HeaderRoomList.js +20 -6
  6. package/lib/commonjs/components/Header/HeaderRoomList.js.map +1 -1
  7. package/lib/commonjs/components/MainComponents/ChatRoom.js +86 -7
  8. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  9. package/lib/commonjs/components/MainComponents/ChatWrapper.js +18 -0
  10. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  11. package/lib/commonjs/components/MainComponents/MessageList.js +11 -4
  12. package/lib/commonjs/components/MainComponents/MessageList.js.map +1 -1
  13. package/lib/commonjs/components/MainComponents/ProfileImagePlaceholder.js +5 -5
  14. package/lib/commonjs/components/MainComponents/ProfileImagePlaceholder.js.map +1 -1
  15. package/lib/commonjs/components/MainComponents/RoomList.js +62 -32
  16. package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
  17. package/lib/commonjs/components/Menu/HeaderRoomListMenu.js +208 -70
  18. package/lib/commonjs/components/Menu/HeaderRoomListMenu.js.map +1 -1
  19. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js +307 -99
  20. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  21. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +544 -200
  22. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  23. package/lib/commonjs/components/Modals/ChatProfileModal/ReportChatModal.js +226 -0
  24. package/lib/commonjs/components/Modals/ChatProfileModal/ReportChatModal.js.map +1 -0
  25. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +6 -2
  26. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  27. package/lib/commonjs/components/Modals/Modal/Modal.js +8 -0
  28. package/lib/commonjs/components/Modals/Modal/Modal.js.map +1 -1
  29. package/lib/commonjs/components/Modals/NewChatModal/NewChatModal.js +202 -104
  30. package/lib/commonjs/components/Modals/NewChatModal/NewChatModal.js.map +1 -1
  31. package/lib/commonjs/components/Modals/ProfileHeader/ProfileHeader.js +556 -0
  32. package/lib/commonjs/components/Modals/ProfileHeader/ProfileHeader.js.map +1 -0
  33. package/lib/commonjs/components/Modals/ProfileHeader/ProfileMenu.js +210 -0
  34. package/lib/commonjs/components/Modals/ProfileHeader/ProfileMenu.js.map +1 -0
  35. package/lib/commonjs/components/Modals/SelectUsersModal/SelectUsersModal.js +4 -2
  36. package/lib/commonjs/components/Modals/SelectUsersModal/SelectUsersModal.js.map +1 -1
  37. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +507 -244
  38. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  39. package/lib/commonjs/components/Modals/UserSettingsModal/UserSettingsModal.js +74 -46
  40. package/lib/commonjs/components/Modals/UserSettingsModal/UserSettingsModal.js.map +1 -1
  41. package/lib/commonjs/components/Modals/styledModalComponents.js +9 -2
  42. package/lib/commonjs/components/Modals/styledModalComponents.js.map +1 -1
  43. package/lib/commonjs/components/RoomComponents/ChatRoomItem.js +17 -7
  44. package/lib/commonjs/components/RoomComponents/ChatRoomItem.js.map +1 -1
  45. package/lib/commonjs/components/styled/SendInput.js +80 -1
  46. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  47. package/lib/commonjs/components/styled/StyledComponents.js +9 -3
  48. package/lib/commonjs/components/styled/StyledComponents.js.map +1 -1
  49. package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js +11 -4
  50. package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
  51. package/lib/commonjs/context/xmppProvider.js +12 -0
  52. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  53. package/lib/commonjs/helpers/getChatBackground.js +23 -0
  54. package/lib/commonjs/helpers/getChatBackground.js.map +1 -0
  55. package/lib/commonjs/helpers/historyPreloadScheduler.js +65 -2
  56. package/lib/commonjs/helpers/historyPreloadScheduler.js.map +1 -1
  57. package/lib/commonjs/helpers/mediaLibraryRuntime.js +31 -0
  58. package/lib/commonjs/helpers/mediaLibraryRuntime.js.map +1 -0
  59. package/lib/commonjs/helpers/shallowEqual.js +36 -0
  60. package/lib/commonjs/helpers/shallowEqual.js.map +1 -0
  61. package/lib/commonjs/helpers/sheetGestures.js +32 -0
  62. package/lib/commonjs/helpers/sheetGestures.js.map +1 -0
  63. package/lib/commonjs/hooks/useChatWrapperInit.js +15 -0
  64. package/lib/commonjs/hooks/useChatWrapperInit.js.map +1 -1
  65. package/lib/commonjs/hooks/useComposing.js +27 -0
  66. package/lib/commonjs/hooks/useComposing.js.map +1 -1
  67. package/lib/commonjs/hooks/useSendMessage.js +27 -1
  68. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  69. package/lib/commonjs/hooks/useUnreadMessagesCounter.js +6 -1
  70. package/lib/commonjs/hooks/useUnreadMessagesCounter.js.map +1 -1
  71. package/lib/commonjs/i18n/strings.js +121 -1
  72. package/lib/commonjs/i18n/strings.js.map +1 -1
  73. package/lib/commonjs/networking/api-requests/user.api.js +59 -0
  74. package/lib/commonjs/networking/api-requests/user.api.js.map +1 -1
  75. package/lib/commonjs/networking/xmpp/flushLastViewedToPrivateStore.js +6 -2
  76. package/lib/commonjs/networking/xmpp/flushLastViewedToPrivateStore.js.map +1 -1
  77. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  78. package/lib/commonjs/roomStore/chatSettingsSlice.js +3 -1
  79. package/lib/commonjs/roomStore/chatSettingsSlice.js.map +1 -1
  80. package/lib/commonjs/roomStore/roomsSlice.js +2 -2
  81. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  82. package/lib/module/assets/icons.js +6 -1
  83. package/lib/module/assets/icons.js.map +1 -1
  84. package/lib/module/components/Header/HeaderRoomList.js +20 -6
  85. package/lib/module/components/Header/HeaderRoomList.js.map +1 -1
  86. package/lib/module/components/MainComponents/ChatRoom.js +87 -8
  87. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  88. package/lib/module/components/MainComponents/ChatWrapper.js +19 -1
  89. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  90. package/lib/module/components/MainComponents/MessageList.js +11 -4
  91. package/lib/module/components/MainComponents/MessageList.js.map +1 -1
  92. package/lib/module/components/MainComponents/ProfileImagePlaceholder.js +5 -5
  93. package/lib/module/components/MainComponents/ProfileImagePlaceholder.js.map +1 -1
  94. package/lib/module/components/MainComponents/RoomList.js +62 -32
  95. package/lib/module/components/MainComponents/RoomList.js.map +1 -1
  96. package/lib/module/components/Menu/HeaderRoomListMenu.js +211 -73
  97. package/lib/module/components/Menu/HeaderRoomListMenu.js.map +1 -1
  98. package/lib/module/components/Modals/AttachSheet/AttachSheet.js +298 -102
  99. package/lib/module/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  100. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +547 -204
  101. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  102. package/lib/module/components/Modals/ChatProfileModal/ReportChatModal.js +223 -0
  103. package/lib/module/components/Modals/ChatProfileModal/ReportChatModal.js.map +1 -0
  104. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +6 -2
  105. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  106. package/lib/module/components/Modals/Modal/Modal.js +9 -1
  107. package/lib/module/components/Modals/Modal/Modal.js.map +1 -1
  108. package/lib/module/components/Modals/NewChatModal/NewChatModal.js +205 -106
  109. package/lib/module/components/Modals/NewChatModal/NewChatModal.js.map +1 -1
  110. package/lib/module/components/Modals/ProfileHeader/ProfileHeader.js +548 -0
  111. package/lib/module/components/Modals/ProfileHeader/ProfileHeader.js.map +1 -0
  112. package/lib/module/components/Modals/ProfileHeader/ProfileMenu.js +205 -0
  113. package/lib/module/components/Modals/ProfileHeader/ProfileMenu.js.map +1 -0
  114. package/lib/module/components/Modals/SelectUsersModal/SelectUsersModal.js +4 -2
  115. package/lib/module/components/Modals/SelectUsersModal/SelectUsersModal.js.map +1 -1
  116. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +509 -248
  117. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  118. package/lib/module/components/Modals/UserSettingsModal/UserSettingsModal.js +76 -48
  119. package/lib/module/components/Modals/UserSettingsModal/UserSettingsModal.js.map +1 -1
  120. package/lib/module/components/Modals/styledModalComponents.js +9 -2
  121. package/lib/module/components/Modals/styledModalComponents.js.map +1 -1
  122. package/lib/module/components/RoomComponents/ChatRoomItem.js +17 -7
  123. package/lib/module/components/RoomComponents/ChatRoomItem.js.map +1 -1
  124. package/lib/module/components/styled/SendInput.js +80 -1
  125. package/lib/module/components/styled/SendInput.js.map +1 -1
  126. package/lib/module/components/styled/StyledComponents.js +9 -3
  127. package/lib/module/components/styled/StyledComponents.js.map +1 -1
  128. package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js +11 -4
  129. package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
  130. package/lib/module/context/xmppProvider.js +12 -0
  131. package/lib/module/context/xmppProvider.js.map +1 -1
  132. package/lib/module/helpers/getChatBackground.js +18 -0
  133. package/lib/module/helpers/getChatBackground.js.map +1 -0
  134. package/lib/module/helpers/historyPreloadScheduler.js +66 -3
  135. package/lib/module/helpers/historyPreloadScheduler.js.map +1 -1
  136. package/lib/module/helpers/mediaLibraryRuntime.js +25 -0
  137. package/lib/module/helpers/mediaLibraryRuntime.js.map +1 -0
  138. package/lib/module/helpers/shallowEqual.js +31 -0
  139. package/lib/module/helpers/shallowEqual.js.map +1 -0
  140. package/lib/module/helpers/sheetGestures.js +26 -0
  141. package/lib/module/helpers/sheetGestures.js.map +1 -0
  142. package/lib/module/hooks/useChatWrapperInit.js +15 -0
  143. package/lib/module/hooks/useChatWrapperInit.js.map +1 -1
  144. package/lib/module/hooks/useComposing.js +28 -1
  145. package/lib/module/hooks/useComposing.js.map +1 -1
  146. package/lib/module/hooks/useSendMessage.js +28 -2
  147. package/lib/module/hooks/useSendMessage.js.map +1 -1
  148. package/lib/module/hooks/useUnreadMessagesCounter.js +6 -1
  149. package/lib/module/hooks/useUnreadMessagesCounter.js.map +1 -1
  150. package/lib/module/i18n/strings.js +121 -1
  151. package/lib/module/i18n/strings.js.map +1 -1
  152. package/lib/module/networking/api-requests/user.api.js +56 -0
  153. package/lib/module/networking/api-requests/user.api.js.map +1 -1
  154. package/lib/module/networking/xmpp/flushLastViewedToPrivateStore.js +6 -2
  155. package/lib/module/networking/xmpp/flushLastViewedToPrivateStore.js.map +1 -1
  156. package/lib/module/networking/xmppClient.js.map +1 -1
  157. package/lib/module/roomStore/chatSettingsSlice.js +3 -1
  158. package/lib/module/roomStore/chatSettingsSlice.js.map +1 -1
  159. package/lib/module/roomStore/roomsSlice.js +1 -1
  160. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  161. package/lib/typescript/assets/icons.d.ts +12 -0
  162. package/lib/typescript/assets/icons.d.ts.map +1 -1
  163. package/lib/typescript/components/Header/HeaderRoomList.d.ts.map +1 -1
  164. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  165. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  166. package/lib/typescript/components/MainComponents/MessageList.d.ts +10 -1
  167. package/lib/typescript/components/MainComponents/MessageList.d.ts.map +1 -1
  168. package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
  169. package/lib/typescript/components/Menu/HeaderRoomListMenu.d.ts.map +1 -1
  170. package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts +9 -0
  171. package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts.map +1 -1
  172. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts +10 -0
  173. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
  174. package/lib/typescript/components/Modals/ChatProfileModal/ReportChatModal.d.ts +9 -0
  175. package/lib/typescript/components/Modals/ChatProfileModal/ReportChatModal.d.ts.map +1 -0
  176. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  177. package/lib/typescript/components/Modals/Modal/Modal.d.ts.map +1 -1
  178. package/lib/typescript/components/Modals/NewChatModal/NewChatModal.d.ts +1 -1
  179. package/lib/typescript/components/Modals/NewChatModal/NewChatModal.d.ts.map +1 -1
  180. package/lib/typescript/components/Modals/ProfileHeader/ProfileHeader.d.ts +75 -0
  181. package/lib/typescript/components/Modals/ProfileHeader/ProfileHeader.d.ts.map +1 -0
  182. package/lib/typescript/components/Modals/ProfileHeader/ProfileMenu.d.ts +25 -0
  183. package/lib/typescript/components/Modals/ProfileHeader/ProfileMenu.d.ts.map +1 -0
  184. package/lib/typescript/components/Modals/SelectUsersModal/SelectUsersModal.d.ts +6 -1
  185. package/lib/typescript/components/Modals/SelectUsersModal/SelectUsersModal.d.ts.map +1 -1
  186. package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts +16 -0
  187. package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
  188. package/lib/typescript/components/Modals/UserSettingsModal/UserSettingsModal.d.ts +1 -0
  189. package/lib/typescript/components/Modals/UserSettingsModal/UserSettingsModal.d.ts.map +1 -1
  190. package/lib/typescript/components/Modals/styledModalComponents.d.ts.map +1 -1
  191. package/lib/typescript/components/RoomComponents/ChatRoomItem.d.ts.map +1 -1
  192. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  193. package/lib/typescript/components/styled/StyledComponents.d.ts.map +1 -1
  194. package/lib/typescript/components/styled/StyledInputComponents/StyledInputComponents.d.ts.map +1 -1
  195. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  196. package/lib/typescript/helpers/getChatBackground.d.ts +15 -0
  197. package/lib/typescript/helpers/getChatBackground.d.ts.map +1 -0
  198. package/lib/typescript/helpers/historyPreloadScheduler.d.ts.map +1 -1
  199. package/lib/typescript/helpers/mediaLibraryRuntime.d.ts +3 -0
  200. package/lib/typescript/helpers/mediaLibraryRuntime.d.ts.map +1 -0
  201. package/lib/typescript/helpers/shallowEqual.d.ts +11 -0
  202. package/lib/typescript/helpers/shallowEqual.d.ts.map +1 -0
  203. package/lib/typescript/helpers/sheetGestures.d.ts +20 -0
  204. package/lib/typescript/helpers/sheetGestures.d.ts.map +1 -0
  205. package/lib/typescript/hooks/useChatWrapperInit.d.ts.map +1 -1
  206. package/lib/typescript/hooks/useComposing.d.ts.map +1 -1
  207. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  208. package/lib/typescript/hooks/useUnreadMessagesCounter.d.ts +1 -0
  209. package/lib/typescript/hooks/useUnreadMessagesCounter.d.ts.map +1 -1
  210. package/lib/typescript/i18n/strings.d.ts.map +1 -1
  211. package/lib/typescript/networking/api-requests/user.api.d.ts +27 -0
  212. package/lib/typescript/networking/api-requests/user.api.d.ts.map +1 -1
  213. package/lib/typescript/networking/xmpp/flushLastViewedToPrivateStore.d.ts +1 -0
  214. package/lib/typescript/networking/xmpp/flushLastViewedToPrivateStore.d.ts.map +1 -1
  215. package/lib/typescript/networking/xmppClient.d.ts +1 -0
  216. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  217. package/lib/typescript/roomStore/chatSettingsSlice.d.ts.map +1 -1
  218. package/lib/typescript/roomStore/roomsSlice.d.ts +1 -0
  219. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  220. package/lib/typescript/types/types.d.ts +9 -5
  221. package/lib/typescript/types/types.d.ts.map +1 -1
  222. package/package.json +6 -1
  223. package/src/assets/icons.tsx +29 -9
  224. package/src/components/Header/HeaderRoomList.tsx +20 -5
  225. package/src/components/MainComponents/ChatRoom.tsx +92 -8
  226. package/src/components/MainComponents/ChatWrapper.tsx +19 -1
  227. package/src/components/MainComponents/MessageList.tsx +21 -3
  228. package/src/components/MainComponents/ProfileImagePlaceholder.tsx +5 -5
  229. package/src/components/MainComponents/RoomList.tsx +65 -34
  230. package/src/components/Menu/HeaderRoomListMenu.tsx +209 -64
  231. package/src/components/Modals/AttachSheet/AttachSheet.tsx +318 -86
  232. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +599 -278
  233. package/src/components/Modals/ChatProfileModal/ReportChatModal.tsx +244 -0
  234. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +4 -2
  235. package/src/components/Modals/Modal/Modal.tsx +7 -0
  236. package/src/components/Modals/NewChatModal/NewChatModal.tsx +219 -117
  237. package/src/components/Modals/ProfileHeader/ProfileHeader.tsx +615 -0
  238. package/src/components/Modals/ProfileHeader/ProfileMenu.tsx +217 -0
  239. package/src/components/Modals/SelectUsersModal/SelectUsersModal.tsx +14 -4
  240. package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +617 -307
  241. package/src/components/Modals/UserSettingsModal/UserSettingsModal.tsx +75 -58
  242. package/src/components/Modals/styledModalComponents.tsx +9 -2
  243. package/src/components/RoomComponents/ChatRoomItem.tsx +14 -7
  244. package/src/components/styled/SendInput.tsx +83 -2
  245. package/src/components/styled/StyledComponents.ts +9 -3
  246. package/src/components/styled/StyledInputComponents/StyledInputComponents.tsx +11 -4
  247. package/src/context/xmppProvider.tsx +14 -0
  248. package/src/helpers/getChatBackground.ts +18 -0
  249. package/src/helpers/historyPreloadScheduler.ts +80 -3
  250. package/src/helpers/mediaLibraryRuntime.ts +25 -0
  251. package/src/helpers/shallowEqual.ts +33 -0
  252. package/src/helpers/sheetGestures.ts +25 -0
  253. package/src/hooks/useChatWrapperInit.ts +15 -0
  254. package/src/hooks/useComposing.tsx +37 -1
  255. package/src/hooks/useSendMessage.tsx +32 -2
  256. package/src/hooks/useUnreadMessagesCounter.ts +11 -0
  257. package/src/i18n/strings.ts +121 -1
  258. package/src/networking/api-requests/user.api.ts +71 -0
  259. package/src/networking/xmpp/flushLastViewedToPrivateStore.ts +17 -3
  260. package/src/networking/xmppClient.ts +5 -1
  261. package/src/roomStore/chatSettingsSlice.ts +3 -1
  262. package/src/roomStore/roomsSlice.ts +1 -1
  263. package/src/types/types.ts +16 -5
@@ -20,6 +20,7 @@ import NewChatModal from '../Modals/NewChatModal/NewChatModal';
20
20
  import { EditWrapper } from './EditWrapper';
21
21
  import { EmptyChatIllustration } from '../../assets/EmptyChatIllustration';
22
22
  import { getIconColor } from '../../helpers/getIconColor';
23
+ import { getChatBackgroundColor } from '../../helpers/getChatBackground';
23
24
  import { ChooseChatMessage } from './ChooseChatMessage';
24
25
  import { useRoomUrl } from '../../hooks/useRoomUrl';
25
26
  import { useSendMessage } from '../../hooks/useSendMessage';
@@ -31,6 +32,9 @@ import CustomTypingIndicator from '../styled/StyledInputComponents/CustomTypingI
31
32
  // import {PanGestureHandler} from 'react-native-gesture-handler';
32
33
  import { FlatList } from 'react-native';
33
34
  import {
35
+ AppState,
36
+ AppStateStatus,
37
+ Keyboard,
34
38
  Platform,
35
39
  TouchableWithoutFeedback,
36
40
  View,
@@ -106,6 +110,45 @@ const ChatRoom: React.FC<ChatRoomProps> = React.memo(
106
110
  configWithEventHandlers || storeConfig,
107
111
  );
108
112
 
113
+ // Under the sticky keyboard strategy, the input dock's position is
114
+ // driven by KeyboardStickyView's native-animated offset. If the app
115
+ // is backgrounded while the keyboard-close animation hasn't finished,
116
+ // the OS removes the keyboard from the app-switcher snapshot but the
117
+ // shared value hasn't caught up to "closed" yet, so the input appears
118
+ // to float in that preview.
119
+ //
120
+ // NOTE on what this does and doesn't fix: iOS actually captures that
121
+ // preview during the active→inactive transition, BEFORE 'background'
122
+ // fires — so dismissing here on 'background' lands too late to affect
123
+ // the snapshot already taken, and is effectively a no-op for the exact
124
+ // iOS symptom in bug #37. An earlier version keyed this off 'inactive'
125
+ // instead, which does land in time, but 'inactive' also fires for
126
+ // Control Center, share sheets, and permission prompts — none of which
127
+ // should close the user's keyboard — so that traded a rare cosmetic
128
+ // preview glitch for a much more common false dismissal. Kept on
129
+ // 'background': it still resets the offset for genuine backgrounding
130
+ // (home button, app switch) and for Android's recents preview (RN's
131
+ // AppState has no 'inactive' there), at the cost of leaving the iOS
132
+ // switcher-preview glitch itself unresolved. Both symptoms are cosmetic
133
+ // per the QA report, which explicitly accepted leaving this imperfect
134
+ // rather than risk destabilizing the sticky strategy. Sticky-only;
135
+ // doesn't touch the other keyboard strategies.
136
+ const stickyInputEnabled =
137
+ !configWithEventHandlers?.disableKeyboardAvoidingView &&
138
+ !!configWithEventHandlers?.keyboardStickyInput;
139
+ useEffect(() => {
140
+ if (!stickyInputEnabled) {
141
+ return;
142
+ }
143
+ const onAppStateChange = (nextState: AppStateStatus) => {
144
+ if (nextState === 'background') {
145
+ Keyboard.dismiss();
146
+ }
147
+ };
148
+ const subscription = AppState.addEventListener('change', onAppStateChange);
149
+ return () => subscription.remove();
150
+ }, [stickyInputEnabled]);
151
+
109
152
  const sendMessage = useCallback(
110
153
  (message: string) => {
111
154
  if (!activeRoomJID) {
@@ -170,15 +213,28 @@ const ChatRoom: React.FC<ChatRoomProps> = React.memo(
170
213
  clientRef.current = client;
171
214
  }, [client]);
172
215
 
216
+ // Tracks what the user actually saw: `null` while they're at the
217
+ // bottom (safe to mark everything read), or the timestamp of the
218
+ // newest message visible when they scrolled away from it. Reported
219
+ // by MessageList via onReadBoundaryChange. Without this, leaving a
220
+ // room (or backgrounding) while scrolled up stamped `now()` as read
221
+ // and silently discarded genuinely-unread messages. Customer-
222
+ // reported #33.
223
+ const readBoundaryRef = useRef<number | null>(null);
224
+ const handleReadBoundaryChange = useCallback((boundaryTs: number | null) => {
225
+ readBoundaryRef.current = boundaryTs;
226
+ }, []);
227
+
173
228
  useEffect(() => {
174
229
  if (!activeRoomJID) {
175
230
  return;
176
231
  }
177
232
 
233
+ readBoundaryRef.current = null;
178
234
  dispatch(setVisibleRoom({ roomJID: activeRoomJID }));
179
235
  setIsLoadingMore(false);
180
236
  return () => {
181
- const timestamp = new Date().getTime();
237
+ const timestamp = readBoundaryRef.current ?? new Date().getTime();
182
238
  dispatch(
183
239
  setLastViewedTimestamp({
184
240
  chatJID: activeRoomJID,
@@ -191,6 +247,12 @@ const ChatRoom: React.FC<ChatRoomProps> = React.memo(
191
247
  liveClient
192
248
  .flushLastViewedToPrivateStoreStanza(store.getState().rooms?.rooms, {
193
249
  visibleRoomJID: activeRoomJID,
250
+ // Carry the same boundary to the SERVER marker. Without
251
+ // this the flush defaults to Date.now() for the visible
252
+ // room, so messages the user never scrolled down to come
253
+ // back as read on the next login — the local count was
254
+ // right but the server overrode it.
255
+ visibleRoomTs: readBoundaryRef.current,
194
256
  })
195
257
  .catch(() => {});
196
258
  }
@@ -279,7 +341,20 @@ const ChatRoom: React.FC<ChatRoomProps> = React.memo(
279
341
  ? KeyboardStickyView
280
342
  : View;
281
343
  const inputDockProps: any = {
282
- style: { paddingBottom: inputDockPaddingBottom, backgroundColor: '#fff' },
344
+ // The dock carries the composer's white surface all the way to the
345
+ // bottom edge, so it needs the same rounded top and upward shadow —
346
+ // a square white block behind a rounded composer just hid the corners.
347
+ style: {
348
+ paddingBottom: inputDockPaddingBottom,
349
+ backgroundColor: '#fff',
350
+ borderTopLeftRadius: 20,
351
+ borderTopRightRadius: 20,
352
+ shadowColor: '#101828',
353
+ shadowOffset: { width: 0, height: -4 },
354
+ shadowOpacity: 0.06,
355
+ shadowRadius: 12,
356
+ elevation: 8,
357
+ },
283
358
  ...(stickyInput
284
359
  ? { offset: { closed: 0, opened: keyboardVerticalOffset } }
285
360
  : {}),
@@ -288,11 +363,15 @@ const ChatRoom: React.FC<ChatRoomProps> = React.memo(
288
363
  return (
289
364
  <KeyboardWrapper {...keyboardWrapperProps}>
290
365
  <ChatContainer
291
- style={
292
- configWithEventHandlers?.chatRoomStyles
293
- ? (configWithEventHandlers.chatRoomStyles as import('react-native').ViewStyle)
294
- : undefined
295
- }
366
+ // The conversation's own colour, not white: the header's rounded
367
+ // bottom corners and the composer's rounded top corners reveal
368
+ // THIS view, so a white parent made both look square.
369
+ style={[
370
+ { backgroundColor: getChatBackgroundColor(configWithEventHandlers) },
371
+ configWithEventHandlers?.chatRoomStyles as
372
+ | import('react-native').ViewStyle
373
+ | undefined,
374
+ ]}
296
375
  >
297
376
  {!configWithEventHandlers?.disableHeader && (
298
377
  <ChatHeader
@@ -331,7 +410,11 @@ const ChatRoom: React.FC<ChatRoomProps> = React.memo(
331
410
  flex: 1,
332
411
  justifyContent: 'center',
333
412
  alignItems: 'center',
334
- backgroundColor: '#ffffff',
413
+ // Empty rooms used to flip to white, which broke the
414
+ // header's rounded corners in exactly the same way.
415
+ backgroundColor: getChatBackgroundColor(
416
+ configWithEventHandlers
417
+ ),
335
418
  }}
336
419
  >
337
420
  <NoMessagesPlaceholder />
@@ -404,6 +487,7 @@ const ChatRoom: React.FC<ChatRoomProps> = React.memo(
404
487
  config={configWithEventHandlers}
405
488
  loading={isLoadingMore}
406
489
  isReply={false}
490
+ onReadBoundaryChange={handleReadBoundaryChange}
407
491
  />
408
492
  )}
409
493
  </View>
@@ -1,4 +1,4 @@
1
- import React, {FC, useEffect, useMemo, useState} from 'react';
1
+ import React, {FC, useEffect, useMemo, useRef, useState} from 'react';
2
2
  import {useDispatch, useSelector} from 'react-redux';
3
3
  import ChatRoom from './ChatRoom';
4
4
  import {
@@ -34,6 +34,7 @@ import {DeviceEventEmitter, Pressable, Text, View} from 'react-native';
34
34
  import {pushLog as devPushLog} from '../../utils/devLogger';
35
35
  import {normalizeRoomJid} from '../../helpers/normalizeRoomJid';
36
36
  import {buildSeedRoom} from '../../helpers/buildSeedRoom';
37
+ import {shallowEqual} from '../../helpers/shallowEqual';
37
38
  import {InteractionsOverlayProvider} from '../MessageBubble/InteractionsOverlay';
38
39
 
39
40
  interface ChatWrapperProps {
@@ -161,6 +162,23 @@ const ChatWrapper: FC<ChatWrapperProps> = ({
161
162
  dispatch(addRoom({roomData: buildSeedRoom(seededRoomJID)}));
162
163
  }, [seededRoomJID, seededRoomMissing, dispatch]);
163
164
 
165
+ // Presentational config flags (e.g. `disableHeader`) can change while the
166
+ // chat stays mounted — sync them to redux whenever `config` actually
167
+ // changes, not just at init. Kept separate from the init effect below so
168
+ // toggling a flag doesn't re-run client/token setup. Guarded with a
169
+ // shallow-equal check (not just the `config` reference) because hosts
170
+ // commonly pass an inline object literal (`<Chat config={{...}}/>`) that
171
+ // is a new reference on every render even when nothing in it changed —
172
+ // dispatching unconditionally there would re-render every config
173
+ // consumer (ChatRoom, modals, ...) on every host render.
174
+ const lastSyncedConfigRef = useRef<IConfig | undefined>(undefined);
175
+ useEffect(() => {
176
+ if (config && !shallowEqual(lastSyncedConfigRef.current, config)) {
177
+ lastSyncedConfigRef.current = config;
178
+ dispatch(setConfig(config));
179
+ }
180
+ }, [config, dispatch]);
181
+
164
182
  useEffect(() => {
165
183
  if (roomJID) {
166
184
  dispatch(
@@ -28,6 +28,7 @@ import Loader from '../styled/Loader';
28
28
  import { ArowDownIcon } from '../../assets/icons';
29
29
  import CustomTypingIndicator from '../styled/StyledInputComponents/CustomTypingIndicator';
30
30
  import { getIconColor } from '../../helpers/getIconColor';
31
+ import { getChatBackgroundColor } from '../../helpers/getChatBackground';
31
32
 
32
33
  interface MessageListProps<TMessage extends IMessage> {
33
34
  CustomMessage?: React.ComponentType<{
@@ -53,6 +54,15 @@ interface MessageListProps<TMessage extends IMessage> {
53
54
  config?: IConfig;
54
55
  isReply: boolean;
55
56
  activeMessage?: IMessage;
57
+ /**
58
+ * Fired whenever the "read up to" boundary changes: `null` while the
59
+ * user is at the bottom (everything is read), or the timestamp of the
60
+ * newest message they'd actually seen when they scrolled away from it.
61
+ * Lets the host mark only what was actually viewed as read instead of
62
+ * stamping "now" on unmount/leave, which would wrongly clear unread
63
+ * messages the user scrolled up and never got back down to.
64
+ */
65
+ onReadBoundaryChange?: (boundaryTs: number | null) => void;
56
66
  }
57
67
 
58
68
  const MessageList = <TMessage extends IMessage>({
@@ -66,6 +76,7 @@ const MessageList = <TMessage extends IMessage>({
66
76
  loading,
67
77
  isReply,
68
78
  activeMessage,
79
+ onReadBoundaryChange,
69
80
  }: MessageListProps<TMessage>) => {
70
81
  const { composing, messages, composingList } = useRoomState(roomJID)
71
82
  .room! as IRoom;
@@ -332,6 +343,7 @@ const MessageList = <TMessage extends IMessage>({
332
343
  // counter so the badge disappears with the arrow.
333
344
  setUnreadWhileScrolledUp(0);
334
345
  newestSeenTsRef.current = null;
346
+ onReadBoundaryChange?.(null);
335
347
  } else {
336
348
  isUserAtBottomRef.current = false;
337
349
  if (hasUserScrolledRef.current) {
@@ -349,11 +361,12 @@ const MessageList = <TMessage extends IMessage>({
349
361
  },
350
362
  0
351
363
  );
364
+ onReadBoundaryChange?.(newestSeenTsRef.current);
352
365
  }
353
366
  setIsUserAtBottom(false);
354
367
  }
355
368
  },
356
- [memoizedMessages]
369
+ [memoizedMessages, onReadBoundaryChange]
357
370
  );
358
371
 
359
372
  // Keep the badge in sync with messages that arrive while the user is
@@ -382,6 +395,7 @@ const MessageList = <TMessage extends IMessage>({
382
395
  setIsUserAtBottom(true);
383
396
  setUnreadWhileScrolledUp(0);
384
397
  newestSeenTsRef.current = null;
398
+ onReadBoundaryChange?.(null);
385
399
  scrollToBottom();
386
400
  };
387
401
 
@@ -392,6 +406,8 @@ const MessageList = <TMessage extends IMessage>({
392
406
  setIsUserAtBottom(true);
393
407
  setUnreadWhileScrolledUp(0);
394
408
  newestSeenTsRef.current = null;
409
+ onReadBoundaryChange?.(null);
410
+ // eslint-disable-next-line react-hooks/exhaustive-deps
395
411
  }, [roomJID]);
396
412
 
397
413
  const BackgroundImage = useMemo(() => {
@@ -413,7 +429,7 @@ const MessageList = <TMessage extends IMessage>({
413
429
  <View
414
430
  style={[
415
431
  styles.container,
416
- { backgroundColor: config?.backgroundChat?.color || '#F3F6FC' },
432
+ { backgroundColor: getChatBackgroundColor(config) },
417
433
  ]}
418
434
  >
419
435
  <View style={styles.backgroundImageContainer}>{BackgroundImage}</View>
@@ -442,7 +458,9 @@ const MessageList = <TMessage extends IMessage>({
442
458
  onLayout={handleLayout}
443
459
  inverted={true}
444
460
  keyboardShouldPersistTaps="handled"
445
- keyboardDismissMode="interactive"
461
+ keyboardDismissMode={
462
+ config?.keepKeyboardOpenOnScroll ? 'none' : 'interactive'
463
+ }
446
464
  contentContainerStyle={styles.flatListContent}
447
465
  ListFooterComponent={
448
466
  loading && memoizedMessages.length > 15 ? (
@@ -69,27 +69,27 @@ export const ProfileImagePlaceholder: React.FC<
69
69
  if (words.length === 0) {return '';}
70
70
 
71
71
  if (words.length >= 2) {
72
- const firstLetter = /^[a-zA-Zа-яА-ЯёЁ]$/.test(words[0]?.[0] || '')
72
+ const firstLetter = /^[\p{L}\p{N}]$/u.test(words[0]?.[0] || '')
73
73
  ? words[0][0].toUpperCase()
74
74
  : '';
75
- const secondLetter = /^[a-zA-Zа-яА-ЯёЁ]$/.test(words[1]?.[0] || '')
75
+ const secondLetter = /^[\p{L}\p{N}]$/u.test(words[1]?.[0] || '')
76
76
  ? words[1][0].toUpperCase()
77
77
  : '';
78
78
  return firstLetter + secondLetter;
79
79
  }
80
80
 
81
81
  if (words.length === 1 && words[0].length >= 2) {
82
- const firstLetter = /^[a-zA-Zа-яА-ЯёЁ]$/.test(words[0][0] || '')
82
+ const firstLetter = /^[\p{L}\p{N}]$/u.test(words[0][0] || '')
83
83
  ? words[0][0].toUpperCase()
84
84
  : '';
85
- const secondLetter = /^[a-zA-Zа-яА-ЯёЁ]$/.test(words[0][1] || '')
85
+ const secondLetter = /^[\p{L}\p{N}]$/u.test(words[0][1] || '')
86
86
  ? words[0][1].toUpperCase()
87
87
  : '';
88
88
  return firstLetter + secondLetter;
89
89
  }
90
90
 
91
91
  if (words.length === 1 && words[0].length === 1) {
92
- return /^[a-zA-Zа-яА-ЯёЁ]$/.test(words[0][0] || '')
92
+ return /^[\p{L}\p{N}]$/u.test(words[0][0] || '')
93
93
  ? words[0][0].toUpperCase()
94
94
  : '';
95
95
  }
@@ -35,6 +35,14 @@ interface RoomListProps {
35
35
  onRoomClick?: (chat: IRoom) => void;
36
36
  }
37
37
 
38
+ /** Page ground behind the room list, the search field and the header's
39
+ * rounded bottom corners. */
40
+ const LIST_BACKGROUND = '#E8EDF2';
41
+
42
+ /** First-paint fallback until the floating search strip reports its real
43
+ * height; the list's top padding follows the measurement after that. */
44
+ const SEARCH_BAR_ESTIMATE = 60;
45
+
38
46
  const RoomList: React.FC<RoomListProps> = ({
39
47
  chats,
40
48
  burgerMenu = false,
@@ -120,6 +128,10 @@ const RoomList: React.FC<RoomListProps> = ({
120
128
  }
121
129
  }, [burgerMenu]);
122
130
 
131
+ // Measured, so the list's top padding always matches the floating
132
+ // search strip — including when a larger font or a taller field grows it.
133
+ const [searchBarHeight, setSearchBarHeight] = useState(SEARCH_BAR_ESTIMATE);
134
+
123
135
  const toggleDrawer = () => {
124
136
  if (isDrawerOpen) {
125
137
  closeDrawer();
@@ -180,39 +192,50 @@ const RoomList: React.FC<RoomListProps> = ({
180
192
  <>
181
193
  <View style={styles.scrollContainer}>
182
194
  <HeaderRoomList setDrawerOpen={toggleDrawer} />
183
- {/* Search sits ABOVE the list rather than in its
184
- ListHeaderComponent. As a list header it was simply the
185
- first scrolling row, so the moment you scrolled the rooms
186
- it slid up and disappeared behind the opaque header band.
187
- Fixed here it stays reachable, which is the entire point
188
- of a search field over a long list. */}
189
- <View style={styles.searchBar}>
190
- <SearchInput
191
- icon={<SearchIcon height={20} />}
192
- value={searchTerm}
193
- onChangeText={handleSearchChange}
194
- placeholder={t('search.placeholder')}
195
+ {/* The search field floats OVER the list rather than
196
+ sitting above it in the column: its strip is transparent
197
+ and the rooms scroll underneath, which is what the design
198
+ shows. It is still fixed (not a ListHeaderComponent), so
199
+ it stays reachable however far the list is scrolled — as a
200
+ list header it scrolled away behind the opaque header. */}
201
+ <View style={styles.listArea}>
202
+ <FlatList
203
+ data={filteredChats}
204
+ keyExtractor={(item) => item.jid}
205
+ // Clears the floating field on first paint; scrolling
206
+ // then slides the rooms under it.
207
+ contentContainerStyle={{ paddingTop: searchBarHeight }}
208
+ renderItem={({ item }) => (
209
+ <Pressable
210
+ // Stable testID so e2e drivers can target a room
211
+ // row by its jid local-part (e.g. Main chat under
212
+ // app id `..._...759` → testID `room-...759`).
213
+ testID={`room-${(item.jid || '').split('@')[0]}`}
214
+ accessibilityLabel={`room-${item.title || item.name}`}
215
+ onPress={() => performClick(item)}
216
+ onPressIn={handlePressIn}
217
+ onPressOut={handlePressOut}
218
+ >
219
+ <ChatRoomItem chat={item} config={config} />
220
+ </Pressable>
221
+ )}
222
+ style={styles.chatList}
195
223
  />
224
+ <View
225
+ testID="room-list-search"
226
+ style={styles.searchBar}
227
+ onLayout={(e) =>
228
+ setSearchBarHeight(e.nativeEvent.layout.height)
229
+ }
230
+ >
231
+ <SearchInput
232
+ icon={<SearchIcon height={20} />}
233
+ value={searchTerm}
234
+ onChangeText={handleSearchChange}
235
+ placeholder={t('search.placeholder')}
236
+ />
237
+ </View>
196
238
  </View>
197
- <FlatList
198
- data={filteredChats}
199
- keyExtractor={(item) => item.jid}
200
- renderItem={({ item }) => (
201
- <Pressable
202
- // Stable testID so e2e drivers can target a room
203
- // row by its jid local-part (e.g. Main chat under
204
- // app id `..._...759` → testID `room-...759`).
205
- testID={`room-${(item.jid || '').split('@')[0]}`}
206
- accessibilityLabel={`room-${item.title || item.name}`}
207
- onPress={() => performClick(item)}
208
- onPressIn={handlePressIn}
209
- onPressOut={handlePressOut}
210
- >
211
- <ChatRoomItem chat={item} config={config} />
212
- </Pressable>
213
- )}
214
- style={styles.chatList}
215
- />
216
239
 
217
240
  <HeaderRoomListMenu
218
241
  closeDrawer={closeDrawer}
@@ -238,7 +261,7 @@ const styles = StyleSheet.create({
238
261
  width: '100%',
239
262
  height: '100%',
240
263
  flex: 1,
241
- backgroundColor: '#fff',
264
+ backgroundColor: LIST_BACKGROUND,
242
265
  },
243
266
  scrollContainer: {
244
267
  flexGrow: 1,
@@ -249,6 +272,9 @@ const styles = StyleSheet.create({
249
272
  padding: 16,
250
273
  justifyContent: 'space-between',
251
274
  },
275
+ listArea: {
276
+ flex: 1,
277
+ },
252
278
  searchBar: {
253
279
  // `row` matters: SearchInputWrapper is `flex: 1` plus a fixed 44px
254
280
  // height. In a column parent that flex resolves VERTICALLY against a
@@ -256,14 +282,19 @@ const styles = StyleSheet.create({
256
282
  // but its magnifier. As a row it resolves to width, which is what it
257
283
  // meant back when the field was a list-header child.
258
284
  flexDirection: 'row',
285
+ position: 'absolute',
286
+ top: 0,
287
+ left: 0,
288
+ right: 0,
259
289
  paddingTop: 8,
290
+ paddingBottom: 8,
260
291
  paddingHorizontal: 16,
261
- backgroundColor: '#FAFAFA',
292
+ backgroundColor: 'transparent',
262
293
  },
263
294
  chatList: {
264
295
  flex: 1,
265
296
  paddingHorizontal: 16,
266
- backgroundColor: '#FAFAFA',
297
+ backgroundColor: LIST_BACKGROUND,
267
298
  },
268
299
  });
269
300