@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
@@ -1,18 +1,16 @@
1
- import React, { useCallback, useMemo } from 'react';
1
+ /** @format */
2
2
 
3
- import {
4
- ModalContainerFullScreen,
5
- Viewider,
6
- Label,
7
- LabelData,
8
- BorderedContainer,
9
- CenterContainer,
10
- } from '../styledModalComponents';
11
- import ModalHeaderComponent from '../ModalHeaderComponent';
12
- import Button from '../../styled/Button';
3
+ import React, { useCallback, useMemo } from 'react';
4
+ import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
13
5
  import { useDispatch } from 'react-redux';
6
+ import { ModalContainerFullScreen } from '../styledModalComponents';
7
+ import ModalHeaderComponent from '../ModalHeaderComponent';
8
+ import { ArowDownIcon } from '../../../assets/icons';
14
9
  import { setActiveModal } from '../../../roomStore/chatSettingsSlice';
15
10
  import { MODAL_TYPES } from '../../../helpers/constants/MODAL_TYPES';
11
+ import { useChatSettingState } from '../../../hooks/useChatSettingState';
12
+ import { chatTextStyle } from '../../../helpers/typography';
13
+ import { useT } from '../../../i18n/useT';
16
14
 
17
15
  interface UserSettingsModalProps {
18
16
  handleCloseModal: any;
@@ -21,65 +19,84 @@ interface UserSettingsModalProps {
21
19
  const UserSettingsModal: React.FC<UserSettingsModalProps> = ({
22
20
  handleCloseModal,
23
21
  }) => {
22
+ const t = useT();
23
+ const dispatch = useDispatch();
24
+ const { config } = useChatSettingState();
25
+
24
26
  const options = useMemo(
25
27
  () => [
26
- { value: 'Manage Data', key: MODAL_TYPES.MANAGE_DATA },
27
- { value: 'Visiblility', key: MODAL_TYPES.VISIBILITY },
28
- // { value: 'Profile Shares', key: MODAL_TYPES.PROFILE_SHARES },
29
- // { value: 'Document Shares', key: MODAL_TYPES.DOCUMENT_SHARES },
30
- // { value: 'Blocked Users', key: MODAL_TYPES.BLOCKED_USERS },
31
- // { value: 'Referrals', key: MODAL_TYPES.REFERRALS },
28
+ { label: t('settings.manageData.title'), key: MODAL_TYPES.MANAGE_DATA },
29
+ { label: t('settings.visibility.title'), key: MODAL_TYPES.VISIBILITY },
32
30
  ],
33
- []
31
+ [t]
34
32
  );
35
33
 
36
- const dispatch = useDispatch();
37
-
38
- const handleClick = useCallback((key: string) => {
39
- dispatch(setActiveModal(key as any));
40
- }, []);
34
+ const handleClick = useCallback(
35
+ (key: string) => {
36
+ dispatch(setActiveModal(key as any));
37
+ },
38
+ [dispatch]
39
+ );
41
40
 
42
41
  return (
43
- <ModalContainerFullScreen>
42
+ <ModalContainerFullScreen style={styles.screen}>
44
43
  <ModalHeaderComponent
45
44
  handleCloseModal={handleCloseModal}
46
- headerTitle={'Settings'}
45
+ headerTitle={t('settings.menu.title')}
46
+ titleStyle={chatTextStyle(config?.typography?.profile?.screenTitle)}
47
47
  />
48
- <CenterContainer>
49
- <BorderedContainer
50
- style={{
51
- padding: 16,
52
- display: 'flex',
53
- flexDirection: 'column',
54
- gap: 8,
55
- }}
56
- >
57
- {options.map((option: { value: string; key: string }, index) => (
58
- <>
59
- <Button
60
- variant="default"
61
- style={{
62
- minHeight: 40,
63
- display: 'flex',
64
- flexDirection: 'column',
65
- gap: 2,
66
- width: '100%',
67
- justifyContent: 'center',
68
- borderRadius: 0,
69
- alignItems: 'flex-start',
70
- }}
71
- onPress={() => handleClick(option.key)}
72
- >
73
- <Label style={{ textAlign: 'center' }}>{option.value}</Label>
74
- {[2, 3, 4].includes(index) && <LabelData>0</LabelData>}
75
- </Button>
76
- {index < 5 && <Viewider />}
77
- </>
78
- ))}
79
- </BorderedContainer>
80
- </CenterContainer>
48
+ {/* One card per entry, rather than a single bordered block with
49
+ * hairline dividers — the rows are separate destinations, and the
50
+ * design gives each its own surface. */}
51
+ <View style={styles.body}>
52
+ {options.map((option) => (
53
+ <TouchableOpacity
54
+ key={option.key}
55
+ testID={`settings-row-${option.key}`}
56
+ activeOpacity={0.7}
57
+ style={styles.card}
58
+ onPress={() => handleClick(option.key)}
59
+ >
60
+ <Text style={styles.label}>{option.label}</Text>
61
+ <ArowDownIcon color="#8C8C8C" width={20} height={20} style={styles.chevron} />
62
+ </TouchableOpacity>
63
+ ))}
64
+ </View>
81
65
  </ModalContainerFullScreen>
82
66
  );
83
67
  };
84
68
 
69
+ const styles = StyleSheet.create({
70
+ screen: {
71
+ backgroundColor: '#E8EDF2',
72
+ },
73
+ body: {
74
+ width: '100%',
75
+ padding: 12,
76
+ gap: 12,
77
+ },
78
+ card: {
79
+ flexDirection: 'row',
80
+ alignItems: 'center',
81
+ justifyContent: 'space-between',
82
+ backgroundColor: '#FFFFFF',
83
+ borderRadius: 20,
84
+ paddingHorizontal: 20,
85
+ paddingVertical: 22,
86
+ shadowColor: '#101828',
87
+ shadowOffset: { width: 0, height: 4 },
88
+ shadowOpacity: 0.06,
89
+ shadowRadius: 12,
90
+ elevation: 3,
91
+ },
92
+ label: {
93
+ fontSize: 17,
94
+ fontWeight: '500',
95
+ color: '#141414',
96
+ },
97
+ chevron: {
98
+ transform: [{ rotate: '-90deg' }],
99
+ },
100
+ });
101
+
85
102
  export default UserSettingsModal;
@@ -86,8 +86,15 @@ export const HeaderContainer = styled.View`
86
86
  * button and 8px below — properly sized and properly centered. */
87
87
  padding: 8px 16px;
88
88
  background-color: #fff;
89
- border-bottom-width: 1px;
90
- border-bottom-color: #f0f0f0;
89
+ /* Same card treatment as the room-list header: rounded bottom corners
90
+ * and a soft drop shadow instead of a hairline rule. */
91
+ border-bottom-left-radius: 20px;
92
+ border-bottom-right-radius: 20px;
93
+ shadow-color: #101828;
94
+ shadow-offset: 0px 4px;
95
+ shadow-opacity: 0.06;
96
+ shadow-radius: 12px;
97
+ elevation: 4;
91
98
  z-index: 1;
92
99
  flex-direction: row;
93
100
  justify-content: space-between;
@@ -90,11 +90,13 @@ const ChatRoomItem: React.FC<ChatRoomItemProps> = ({
90
90
 
91
91
  return (
92
92
  <ChatItem key={index}>
93
- <ProfileImagePlaceholder
94
- name={displayName}
95
- icon={appendFileToken(chat?.icon, fileToken)}
96
- active={false}
97
- />
93
+ <View pointerEvents="none">
94
+ <ProfileImagePlaceholder
95
+ name={displayName}
96
+ icon={appendFileToken(chat?.icon, fileToken)}
97
+ active={false}
98
+ />
99
+ </View>
98
100
  <View
99
101
  style={{
100
102
  flexDirection: 'column',
@@ -104,7 +106,9 @@ const ChatRoomItem: React.FC<ChatRoomItemProps> = ({
104
106
  paddingVertical: 8,
105
107
  paddingRight: 8,
106
108
  borderBottomWidth: 1,
107
- borderBottomColor: '#F0F0F0',
109
+ // Reads against the list's #E8EDF2 ground; #F0F0F0 was lighter
110
+ // than the ground itself, so the row separators vanished.
111
+ borderBottomColor: '#C6CFDA',
108
112
  }}
109
113
  >
110
114
  <View
@@ -215,8 +219,11 @@ const ChatRoomItem: React.FC<ChatRoomItemProps> = ({
215
219
  ) : null}
216
220
  </View>
217
221
  {isDriver && (
222
+ // Darker than the list's own ground (#E8EDF2), otherwise the
223
+ // separator disappears into it — white and the old #0052CD0D
224
+ // hairline both did.
218
225
  <View
219
- style={{ height: 1, backgroundColor: '#0052CD0D', marginTop: 8 }}
226
+ style={{ height: 1, backgroundColor: '#C6CFDA', marginTop: 8 }}
220
227
  />
221
228
  )}
222
229
  </View>
@@ -10,14 +10,16 @@ import { IConfig, MediaFile } from '../../types/types';
10
10
  import Button from './Button';
11
11
  import { SendIcon, AttachIcon, RecordIcon } from '../../assets/icons';
12
12
  import { KeyboardAvoidingView, Platform, View, Text, TouchableOpacity, Alert, Linking } from 'react-native';
13
- import AttachSheet from '../Modals/AttachSheet/AttachSheet';
13
+ import AttachSheet, { PickedMedia } from '../Modals/AttachSheet/AttachSheet';
14
14
  import { MediaFilePreview } from './MediaFilePreview';
15
15
  import { getIconColor } from '../../helpers/getIconColor';
16
16
  import { getElementFont } from '../../helpers/getElementFont';
17
17
  import { useT } from '../../i18n/useT';
18
+ import { getMediaLibrary } from '../../helpers/mediaLibraryRuntime';
18
19
  import * as ImagePicker from 'expo-image-picker';
19
20
  import * as DocumentPicker from 'expo-document-picker';
20
21
  import * as ImageManipulator from 'expo-image-manipulator';
22
+ import * as FileSystem from 'expo-file-system/legacy';
21
23
  // expo-audio replaces the discontinued expo-av: `useAudioRecorder` owns a
22
24
  // single reusable recorder for this input's lifetime (prepare → record →
23
25
  // stop → prepare again), and the module-level helpers cover permissions
@@ -259,6 +261,53 @@ const SendInput: React.FC<SendInputProps> = ({
259
261
  }
260
262
  };
261
263
 
264
+ // A tap on a thumbnail in the attach sheet's recents strip. The sheet
265
+ // already resolved the asset to a real on-disk uri (iOS `ph://` ids are
266
+ // not uploadable); here we apply the same treatment the full-blown
267
+ // pickers get — HEIC→JPEG for images, and a size probe so the 50 MB cap
268
+ // is enforced before anything hits the network.
269
+ const handleRecentMediaSelection = async (media: PickedMedia) => {
270
+ try {
271
+ let size: number | undefined;
272
+ try {
273
+ const info = await FileSystem.getInfoAsync(media.uri);
274
+ if (info.exists && typeof (info as any).size === 'number') {
275
+ size = (info as any).size;
276
+ }
277
+ } catch {
278
+ // Size is a nice-to-have; the backend still rejects oversized bodies.
279
+ }
280
+
281
+ if (media.isVideo) {
282
+ handleFileSelect([
283
+ {
284
+ uri: media.uri,
285
+ type: media.mimeType || 'video/mp4',
286
+ name: media.name,
287
+ size,
288
+ },
289
+ ]);
290
+ return;
291
+ }
292
+
293
+ const normalized = await normalizeImageAsset({
294
+ uri: media.uri,
295
+ mimeType: media.mimeType,
296
+ fileName: media.name,
297
+ } as ImagePicker.ImagePickerAsset);
298
+ handleFileSelect([
299
+ {
300
+ uri: normalized.uri,
301
+ type: normalized.mime,
302
+ name: normalized.name,
303
+ size,
304
+ },
305
+ ]);
306
+ } catch (error) {
307
+ console.error('Recent media error:', error);
308
+ }
309
+ };
310
+
262
311
  const handleFileSelection = async () => {
263
312
  // Drop overlapping invocations — a second getDocumentAsync while the
264
313
  // first is still presented throws PickingInProgressException on iOS.
@@ -287,8 +336,39 @@ const SendInput: React.FC<SendInputProps> = ({
287
336
  }
288
337
  };
289
338
 
339
+ // Photo-library access is requested HERE, not inside the sheet: iOS
340
+ // cannot present the permission alert while an RN <Modal> owns the
341
+ // screen (the same UIKit limitation that used to swallow the pickers),
342
+ // so asking from inside resolved to a silent "denied" and the recents
343
+ // strip stayed empty forever. Asking on the paperclip tap puts the
344
+ // system alert over the plain chat screen, where it can actually show.
345
+ // Only the first tap ever waits on it — afterwards the status is
346
+ // already determined and `getPermissionsAsync` returns immediately.
347
+ const ensureLibraryPermission = async () => {
348
+ const MediaLibrary = getMediaLibrary();
349
+ if (!MediaLibrary?.getPermissionsAsync) {return;}
350
+ try {
351
+ const perm = await MediaLibrary.getPermissionsAsync();
352
+ if (perm?.granted || perm?.canAskAgain === false) {return;}
353
+ await MediaLibrary.requestPermissionsAsync();
354
+ } catch (err) {
355
+ // Optional peer / unsupported platform: the sheet just shows the
356
+ // camera tile and the full-library route.
357
+ console.warn('Media library permission check failed:', err);
358
+ }
359
+ };
360
+
361
+ // Only the very first tap of a session goes through the permission
362
+ // check; afterwards the sheet opens with zero added latency.
363
+ const libraryPermissionCheckedRef = useRef(false);
364
+
290
365
  const handleAttachPress = () => {
291
- setShowMediaMenu(true);
366
+ if (libraryPermissionCheckedRef.current) {
367
+ setShowMediaMenu(true);
368
+ return;
369
+ }
370
+ libraryPermissionCheckedRef.current = true;
371
+ ensureLibraryPermission().finally(() => setShowMediaMenu(true));
292
372
  };
293
373
 
294
374
  // ─── Voice recording ──────────────────────────────────────────────
@@ -692,6 +772,7 @@ const SendInput: React.FC<SendInputProps> = ({
692
772
  onCamera={handleCameraSelection}
693
773
  onGallery={handleGallerySelection}
694
774
  onDocument={handleFileSelection}
775
+ onPickMedia={handleRecentMediaSelection}
695
776
  primaryColor={getIconColor(config)}
696
777
  />
697
778
  </InputContainer>
@@ -28,9 +28,15 @@ export const ChatContainerHeader = styled.View`
28
28
  background-color: #ffffff;
29
29
  align-items: center;
30
30
  justify-content: space-between;
31
- border-bottom-width: 1px;
32
- border-bottom-color: #e5e7eb;
33
- border-style: solid;
31
+ /* Rounded bottom corners + a soft drop shadow instead of the hairline
32
+ * rule, so the header reads as a card over the conversation. */
33
+ border-bottom-left-radius: 20px;
34
+ border-bottom-right-radius: 20px;
35
+ shadow-color: #101828;
36
+ shadow-offset: 0px 4px;
37
+ shadow-opacity: 0.06;
38
+ shadow-radius: 12px;
39
+ elevation: 4;
34
40
  z-index: 1000;
35
41
  `;
36
42
 
@@ -13,14 +13,21 @@ const colors = {
13
13
 
14
14
  export const InputContainer = styled.View<{ isText?: boolean }>`
15
15
  flex-direction: column;
16
- padding: 8px 16px 4px 16px;
17
- background-color: ${colors.white};
16
+ padding: 12px 16px 8px 16px;
17
+ /* Transparent, so the dock's white surface — and its rounded top
18
+ * corners — are what shows. An opaque child painted straight over those
19
+ * corners (a parent's border radius does not clip children without
20
+ * overflow:hidden, and that would clip the dock's shadow too). */
21
+ background-color: transparent;
18
22
  z-index: 100;
19
23
  width: 100%;
20
24
  bottom: 0;
21
25
  left: 0;
22
- border-top-width: 1px;
23
- border-top-color: #e5e7eb;
26
+ /* No radius or shadow here: the dock this sits in (ChatRoom's
27
+ * InputDockTag) is the composer's outer surface and carries both. Having
28
+ * them on this inner view too drew its shadow onto the dock's white
29
+ * background — the faint seam that made the strip below look like a
30
+ * different colour. */
24
31
  `;
25
32
 
26
33
  export const MediaContainer = styled.View`
@@ -49,6 +49,7 @@ import {
49
49
  setUnreadSyncing,
50
50
  } from '../roomStore/roomsSlice';
51
51
  import { runHistoryPreloadScheduler } from '../helpers/historyPreloadScheduler';
52
+ import { updateMessagesTillLast } from '../helpers/updateMessagesTillLast';
52
53
  import { secureUserStorage } from '../helpers/secureUserStorage';
53
54
  import { clearPersistedState } from '../roomStore/persistence';
54
55
  import { pushLog as devPushLog } from '../utils/devLogger';
@@ -239,6 +240,19 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
239
240
  .catch((e: unknown) =>
240
241
  devPushLog('warn', 'reconnect: privateStore refresh failed', e)
241
242
  );
243
+ // Pull whatever landed in each room's archive while the stream was
244
+ // down. MUC presence re-join above only restores delivery for
245
+ // messages sent AFTER this reconnect — anything sent during the
246
+ // offline window has no live stanza to catch, and previously only
247
+ // came back on a full app restart (fresh useChatWrapperInit mount
248
+ // re-running its own one-shot boot sync). Safe on the very first
249
+ // connect too: rooms have no `lastMessageTimestamp` yet at that
250
+ // point, so this is a no-op until the bootstrap's own history load
251
+ // populates it. Customer-reported #32.
252
+ const rooms = store.getState().rooms?.rooms || {};
253
+ updateMessagesTillLast(rooms, created).catch((e: unknown) =>
254
+ devPushLog('warn', 'reconnect: offline catch-up sync failed', e)
255
+ );
242
256
  });
243
257
 
244
258
  setClient(created);
@@ -0,0 +1,18 @@
1
+ /** @format */
2
+
3
+ import type { IConfig } from '../types/types';
4
+
5
+ /** Ground behind the conversation, and behind the rounded corners of the
6
+ * header and the composer that sit over it. */
7
+ export const DEFAULT_CHAT_BACKGROUND = '#F3F6FC';
8
+
9
+ /**
10
+ * One place for the conversation's background colour.
11
+ *
12
+ * The message list has always painted it; the chat container behind it was
13
+ * plain white, which is what made the header's rounded bottom corners and
14
+ * the composer's rounded top corners invisible — the corners revealed the
15
+ * white parent, not the conversation.
16
+ */
17
+ export const getChatBackgroundColor = (config?: IConfig): string =>
18
+ config?.backgroundChat?.color || DEFAULT_CHAT_BACKGROUND;
@@ -1,7 +1,11 @@
1
1
  import { AppState } from 'react-native';
2
2
  import XmppClient from '../networking/xmppClient';
3
3
  import { store } from '../roomStore';
4
- import { applyRoomsPreloadBatch, setUnreadSyncing } from '../roomStore/roomsSlice';
4
+ import {
5
+ applyRoomsPreloadBatch,
6
+ setUnreadSyncing,
7
+ RUNTIME_MESSAGE_LIMIT,
8
+ } from '../roomStore/roomsSlice';
5
9
  import { IMessage, IRoom } from '../types/types';
6
10
 
7
11
  interface HistoryPreloadSchedulerOptions {
@@ -30,6 +34,13 @@ const DEFAULT_CONCURRENCY = 3;
30
34
  // `mergeHistoryIntoCache` rarely has to fall back to clear-and-replace.
31
35
  const DEFAULT_PAGE_SIZE = 20;
32
36
  const DEFAULT_RETRY_LIMIT = 2;
37
+ // When the first page is entirely unread (every fetched message is newer
38
+ // than `lastViewedTimestamp`), the true unread count could be far bigger
39
+ // than `pageSize` — `useUnread()` only ever sees what's been loaded. Page
40
+ // further back, up to this many extra fetches, until we either find the
41
+ // boundary or give up (`unreadCapped` then stays true so callers at least
42
+ // know the count is a floor, not exact). Customer-reported #34.
43
+ const MAX_UNREAD_CATCHUP_PAGES = 8;
33
44
 
34
45
  const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
35
46
 
@@ -226,9 +237,75 @@ export const runHistoryPreloadScheduler = async (
226
237
  }
227
238
 
228
239
  const nextRoom = store.getState().rooms.rooms[item.jid];
240
+ let combined: IMessage[] = fetchedMessages || [];
241
+ const lastViewed = Number(nextRoom?.lastViewedTimestamp) || 0;
242
+
243
+ // The first page was entirely unread — keep paging older until
244
+ // we find a message at/before `lastViewedTimestamp` (the true
245
+ // boundary) or run out of catch-up budget, so the count isn't
246
+ // silently truncated at `pageSize`.
247
+ if (lastViewed > 0 && computeUnreadCapped(nextRoom, combined, pageSize)) {
248
+ let lastPageLen = combined.length;
249
+ let extraPages = 0;
250
+ while (
251
+ extraPages < MAX_UNREAD_CATCHUP_PAGES &&
252
+ lastPageLen >= pageSize &&
253
+ !signal?.aborted
254
+ ) {
255
+ const oldestId = combined.reduce<number | null>((min, m) => {
256
+ const idNum = Number((m as any)?.id);
257
+ if (!Number.isFinite(idNum)) {return min;}
258
+ return min === null || idNum < min ? idNum : min;
259
+ }, null);
260
+ if (oldestId === null) {break;}
261
+
262
+ let older: IMessage[] | undefined;
263
+ try {
264
+ older = await client.getHistoryStanza(
265
+ item.jid,
266
+ pageSize,
267
+ oldestId,
268
+ undefined,
269
+ {
270
+ coalesceRoom: true,
271
+ skipIfPreloaded: !forceReload,
272
+ source: 'background',
273
+ }
274
+ );
275
+ } catch {
276
+ break;
277
+ }
278
+ if (!older || !older.length) {break;}
279
+
280
+ combined = [...older, ...combined];
281
+ lastPageLen = older.length;
282
+ extraPages++;
283
+
284
+ const oldestTs = combined.reduce<number>((minTs, m) => {
285
+ const ts = messageTimestamp(m);
286
+ return ts > 0 ? Math.min(minTs, ts) : minTs;
287
+ }, Number.MAX_SAFE_INTEGER);
288
+ if (oldestTs !== Number.MAX_SAFE_INTEGER && oldestTs <= lastViewed) {
289
+ break;
290
+ }
291
+ }
292
+ }
293
+
294
+ // `mergeHistoryIntoCache` (roomsSlice) caps whatever we dispatch
295
+ // here to the newest RUNTIME_MESSAGE_LIMIT messages before it
296
+ // ever reaches `unreadMiddleware` — so a catch-up fetch that
297
+ // pulled the true boundary can still have it silently dropped
298
+ // by that cap, leaving a falsely-confident `unreadCapped:false`
299
+ // (bug #34's failure mode again, just one layer downstream).
300
+ // Simulate that cap here so the flag reflects what will
301
+ // actually survive, not just what this fetch retrieved.
302
+ const simulatedStored =
303
+ combined.length > RUNTIME_MESSAGE_LIMIT
304
+ ? combined.slice(-RUNTIME_MESSAGE_LIMIT)
305
+ : combined;
229
306
  const unreadCapped = computeUnreadCapped(
230
307
  nextRoom,
231
- fetchedMessages || [],
308
+ simulatedStored,
232
309
  pageSize
233
310
  );
234
311
 
@@ -237,7 +314,7 @@ export const runHistoryPreloadScheduler = async (
237
314
  rooms: [
238
315
  {
239
316
  jid: item.jid,
240
- messages: fetchedMessages || [],
317
+ messages: combined,
241
318
  unreadCapped,
242
319
  historyPreloadState: 'done',
243
320
  },
@@ -0,0 +1,25 @@
1
+ let cached: any | undefined;
2
+
3
+ export const getMediaLibrary = (): any | null => {
4
+ if (cached !== undefined) {return cached;}
5
+ try {
6
+ const legacy = require('expo-media-library/legacy');
7
+
8
+ if (legacy?.getAssetsAsync) {
9
+ cached = legacy;
10
+ return cached;
11
+ }
12
+ } catch {
13
+ }
14
+
15
+ try {
16
+ cached = require('expo-media-library') ?? null;
17
+ } catch {
18
+ cached = null;
19
+ }
20
+ return cached;
21
+ };
22
+
23
+ export const __resetMediaLibraryCache = () => {
24
+ cached = undefined;
25
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * One-level shallow equality for plain objects. Used to avoid re-dispatching
3
+ * config into redux on every render just because the host passed a fresh
4
+ * object literal with the same values (e.g. `<Chat config={{...}} />`
5
+ * re-created inline) — reference equality alone would fire far too often,
6
+ * and a deep/JSON comparison risks throwing on non-serializable fields
7
+ * (functions, React elements) that configs legitimately carry.
8
+ */
9
+ export function shallowEqual(a: unknown, b: unknown): boolean {
10
+ if (a === b) {return true;}
11
+ if (
12
+ typeof a !== 'object' ||
13
+ typeof b !== 'object' ||
14
+ a === null ||
15
+ b === null
16
+ ) {
17
+ return false;
18
+ }
19
+ const aKeys = Object.keys(a as Record<string, unknown>);
20
+ const bKeys = Object.keys(b as Record<string, unknown>);
21
+ if (aKeys.length !== bKeys.length) {return false;}
22
+ for (const key of aKeys) {
23
+ if (
24
+ (a as Record<string, unknown>)[key] !==
25
+ (b as Record<string, unknown>)[key]
26
+ ) {
27
+ return false;
28
+ }
29
+ }
30
+ return true;
31
+ }
32
+
33
+ export default shallowEqual;
@@ -0,0 +1,25 @@
1
+ /** @format */
2
+
3
+ /**
4
+ * Shared thresholds for the drag-to-dismiss gesture on bottom sheets (the
5
+ * attach sheet, the profile headers, the room-list menu). Kept in one
6
+ * place so a pull feels identical wherever the user does it — and so the
7
+ * numbers are unit-testable without a touch event.
8
+ */
9
+
10
+ /** Drag past this many points and letting go dismisses the sheet. */
11
+ export const DISMISS_DISTANCE = 90;
12
+ /** …or flick faster than this, however short the drag. */
13
+ export const DISMISS_VELOCITY = 0.8;
14
+
15
+ /**
16
+ * Claim a drag only once it is clearly a downward pull, so a tap on a row
17
+ * still registers and a horizontal swipe stays with whatever scrolls
18
+ * sideways inside the sheet.
19
+ */
20
+ export const shouldClaimVerticalDrag = (dy: number, dx: number) =>
21
+ dy > 6 && Math.abs(dy) > Math.abs(dx) * 1.5;
22
+
23
+ /** Far enough / fast enough to let go of the sheet. */
24
+ export const shouldDismissOnDrag = (dy: number, vy: number) =>
25
+ dy > DISMISS_DISTANCE || vy > DISMISS_VELOCITY;
@@ -18,6 +18,7 @@ import { useChatSettingState } from './useChatSettingState';
18
18
  import { isChatIdPresentInArray } from '../helpers/isChatIdPresentInArray';
19
19
  import useGetNewArchRoom from './useGetNewArchRoom';
20
20
  import { getRoomsWithRetry } from '../helpers/getRoomsWithRetry';
21
+ import { shallowEqual } from '../helpers/shallowEqual';
21
22
 
22
23
  interface useChatWrapperInitProps {
23
24
  roomJID: string | null | undefined;
@@ -65,6 +66,20 @@ const useChatWrapperInit = ({
65
66
  };
66
67
  }, [user.xmppPassword]);
67
68
 
69
+ // Presentational config flags (e.g. `disableHeader`) can change while the
70
+ // chat stays mounted — sync them to redux whenever `config` actually
71
+ // changes, not just at init. Shallow-equal guard: an inline object
72
+ // literal from the host is a fresh reference every render even when
73
+ // nothing in it changed, and dispatching unconditionally would re-render
74
+ // every config consumer on every host render.
75
+ const lastSyncedConfigRef = useRef<IConfig | undefined>(undefined);
76
+ useEffect(() => {
77
+ if (!shallowEqual(lastSyncedConfigRef.current, config)) {
78
+ lastSyncedConfigRef.current = config;
79
+ dispatch(setConfig(config));
80
+ }
81
+ }, [config, dispatch]);
82
+
68
83
  const getRoomsWithRertyRequest = async () => {
69
84
  setIsRetrying(true);
70
85
  const retryRooms = await getRoomsWithRetry(