@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,43 +1,78 @@
1
- import React, { useEffect, useMemo, useState } from 'react';
1
+ /** @format */
2
+
3
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
4
+ import {
5
+ Alert,
6
+ Animated,
7
+ Keyboard,
8
+ Linking,
9
+ Pressable,
10
+ StyleSheet,
11
+ Text,
12
+ TextInput,
13
+ TouchableOpacity,
14
+ View,
15
+ } from 'react-native';
16
+ import { useDispatch, useSelector } from 'react-redux';
17
+ import * as ImagePicker from 'expo-image-picker';
2
18
  import { useFileToken } from '../../../hooks/useFileToken';
3
19
  import { appendFileToken } from '../../../helpers/secureFileUrl';
4
- import {
5
- CenterContainer,
6
- UserInfo,
7
- UserName,
8
- UserStatus,
9
- ModalContainerFullScreen,
10
- Label,
11
- BorderedContainer,
12
- LabelData,
13
- Viewider,
14
- Divider,
15
- } from '../styledModalComponents';
16
- import { Pressable, View, Text, ScrollView, Alert, Linking } from 'react-native';
17
- import ModalHeaderComponent from '../ModalHeaderComponent';
20
+ import { ModalContainerFullScreen } from '../styledModalComponents';
18
21
  import { ProfileImagePlaceholder } from '../../MainComponents/ProfileImagePlaceholder';
19
- import { useDispatch, useSelector } from 'react-redux';
20
22
  import { RootState, getActiveRoom } from '../../../roomStore';
21
23
  import { uploadFile } from '../../../networking/api-requests/auth.api';
22
24
  import { useXmppClient } from '../../../context/xmppProvider';
23
- import { updateRoom } from '../../../roomStore/roomsSlice';
24
- import * as ImagePicker from 'expo-image-picker';
25
+ import { deleteRoom, updateRoom } from '../../../roomStore/roomsSlice';
25
26
  import Loader from '../../styled/Loader';
26
- import Button from '../../styled/Button';
27
- import { DeleteIcon, MoreIcon, QrIcon } from '../../../assets/icons';
28
- import Switch from '../../MainComponents/Switch';
27
+ import {
28
+ AddNewIcon,
29
+ DeleteIcon,
30
+ EditIcon,
31
+ LeaveIcon,
32
+ ReportIcon,
33
+ SearchIcon,
34
+ } from '../../../assets/icons';
29
35
  import { useChatSettingState } from '../../../hooks/useChatSettingState';
30
36
  import { chatTextStyle } from '../../../helpers/typography';
31
37
  import { getElementFont } from '../../../helpers/getElementFont';
38
+ import { getIconColor } from '../../../helpers/getIconColor';
32
39
  import { deleteRoomMember } from '../../../networking/api-requests/rooms.api';
33
40
  import { RoomMember } from '../../../types/models/room.model';
34
41
  import { setActiveModal, setSelectedUser } from '../../../roomStore/chatSettingsSlice';
35
42
  import { MODAL_TYPES } from '../../../helpers/constants/MODAL_TYPES';
36
- import DropdownMenu, { MenuOption } from '../../DropdownMenu/DropdownMenu';
37
43
  import SelectUsersModal from '../SelectUsersModal/SelectUsersModal';
38
44
  import { useToast } from '../../../context/ToastContext';
45
+ import { useT } from '../../../i18n/useT';
39
46
  import DeleteChatModal from './DeleteChatModal';
47
+ import ReportChatModal from './ReportChatModal';
48
+ import {
49
+ ProfileHero,
50
+ ProfileTopBar,
51
+ HeroAction,
52
+ useHeaderMetrics,
53
+ } from '../ProfileHeader/ProfileHeader';
54
+ import ProfileMenu, { ProfileMenuItem } from '../ProfileHeader/ProfileMenu';
40
55
 
56
+ /**
57
+ * Where the header should sit when search opens: tucked away at the
58
+ * collapsed offset when the picture is still expanded, and left exactly
59
+ * where it is when the user already scrolled it away.
60
+ */
61
+ export const searchScrollTarget = (
62
+ isCollapsed: boolean,
63
+ collapseDistance: number
64
+ ): number | null => (isCollapsed ? null : collapseDistance);
65
+
66
+ /** Two uppercase letters for a chat without a picture, same rule the
67
+ * avatar placeholder uses for people. */
68
+ export const chatInitials = (name?: string | null): string => {
69
+ const words = (name || '').trim().split(/\s+/).filter(Boolean);
70
+ const letters = (words.length >= 2
71
+ ? [words[0][0], words[1][0]]
72
+ : [words[0]?.[0], words[0]?.[1]]
73
+ ).filter((c) => !!c && /[\p{L}\p{N}]/u.test(c));
74
+ return letters.join('').toUpperCase();
75
+ };
41
76
 
42
77
  interface ChatProfileModalProps {
43
78
  handleCloseModal: any;
@@ -46,33 +81,38 @@ interface ChatProfileModalProps {
46
81
  const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
47
82
  handleCloseModal,
48
83
  }) => {
84
+ const t = useT();
49
85
  const [loading, setLoading] = useState<boolean>(false);
50
- const [visible, setVisible] = useState<boolean>(false);
51
- const [isModalOpen, setIsModalOpen] = useState(false);
86
+ const [isDeleteOpen, setIsDeleteOpen] = useState(false);
87
+ const [isReportOpen, setIsReportOpen] = useState(false);
88
+ const [memberQuery, setMemberQuery] = useState('');
89
+ const [isSearchOpen, setIsSearchOpen] = useState(false);
90
+ // Measured, not guessed: this screen renders inside whatever frame the
91
+ // host gives it (the demo app keeps its own tab bar above), so the
92
+ // window height would overshoot.
93
+ const [viewportHeight, setViewportHeight] = useState(0);
52
94
 
53
95
  const { showToast } = useToast();
54
-
55
- const chatMenuOptions = useMemo(
56
- () => [
57
- {
58
- label: 'Delete chat',
59
- icon: <DeleteIcon />,
60
- onClick: () => {
61
- setIsModalOpen(true);
62
- },
63
- styles: { color: 'red' },
64
- },
65
- ],
66
- []
67
- );
68
-
69
96
  const dispatch = useDispatch();
70
-
71
97
  const { client } = useXmppClient();
72
98
  const { user: stateUser, config } = useChatSettingState();
73
99
  const activeRoom = useSelector((state: RootState) => getActiveRoom(state));
74
100
  const fileToken = useFileToken();
75
101
 
102
+ // Drives both the hero's parallax and the collapsed bar's fade-in. One
103
+ // shared value, written natively by the ScrollView.
104
+ const scrollY = useRef(new Animated.Value(0)).current;
105
+ const scrollRef = useRef<any>(null);
106
+ const searchInputRef = useRef<TextInput>(null);
107
+ const { collapseDistance } = useHeaderMetrics();
108
+ // `scrollY` is driven natively, so its value can't be read back with
109
+ // addListener. The Animated.event's `listener` still fires on the JS
110
+ // side, which is enough to know which state the header is in.
111
+ const [isCollapsed, setIsCollapsed] = useState(false);
112
+ // Set when search collapsed the header itself, so only then does
113
+ // dismissing search (or the keyboard) expand it again.
114
+ const collapsedForSearchRef = useRef(false);
115
+
76
116
  // Pull live member list from the MUC when the modal opens — REST
77
117
  // hydration only fills the *count* (item.participants /
78
118
  // item.members.length); the actual roster comes from a `getRoomMembers`
@@ -87,6 +127,12 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
87
127
  }
88
128
  }, [client, activeRoom?.jid]);
89
129
 
130
+ useEffect(() => {
131
+ if (!activeRoom) {
132
+ dispatch(setActiveModal(undefined));
133
+ }
134
+ }, [activeRoom, dispatch]);
135
+
90
136
  const onUpload = async () => {
91
137
  let loadingSet = false;
92
138
  try {
@@ -103,7 +149,7 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
103
149
  'Photo library permission is needed to select images.',
104
150
  [
105
151
  {
106
- text: 'Cancel',
152
+ text: t('action.cancel'),
107
153
  onPress: () => setLoading(false),
108
154
  style: 'cancel',
109
155
  },
@@ -172,7 +218,13 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
172
218
  }
173
219
  };
174
220
 
175
- const handleDeleteUser = async (userId: string) => {
221
+ const onRemoveIcon = () => {
222
+ client?.setRoomImageStanza(activeRoom?.jid || '', '', 'icon', 'none');
223
+ dispatch(updateRoom({ jid: activeRoom?.jid || '', updates: { icon: null } }));
224
+ };
225
+
226
+ const handleDeleteUser = async (user: RoomMember) => {
227
+ const userId = user.xmppUsername;
176
228
  try {
177
229
  await deleteRoomMember({
178
230
  roomId: activeRoom?.jid?.split('@')[0] || '',
@@ -184,7 +236,7 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
184
236
  jid: activeRoom?.jid || '',
185
237
  updates: {
186
238
  roomMembers: activeRoom?.roomMembers?.filter(
187
- (user) => user.xmppUsername !== userId
239
+ (member) => member.xmppUsername !== userId
188
240
  ),
189
241
  },
190
242
  })
@@ -207,9 +259,19 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
207
259
  }
208
260
  };
209
261
 
210
- const onRemoveClick = async () => {
211
- client?.setRoomImageStanza(activeRoom?.jid || '', '', 'icon', 'none');
212
- dispatch(updateRoom({ jid: activeRoom?.jid || '', updates: { icon: null } }));
262
+ const confirmRemoveMember = (user: RoomMember) => {
263
+ Alert.alert(
264
+ t('modal.chatProfile.removeMember'),
265
+ `${user.firstName} ${user.lastName}`.trim(),
266
+ [
267
+ { text: t('action.cancel'), style: 'cancel' },
268
+ {
269
+ text: t('action.delete'),
270
+ style: 'destructive',
271
+ onPress: () => handleDeleteUser(user),
272
+ },
273
+ ]
274
+ );
213
275
  };
214
276
 
215
277
  const handleUserAvatarClick = (user: RoomMember): void => {
@@ -227,268 +289,527 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
227
289
  );
228
290
  };
229
291
 
230
- const menuOptions = useMemo(
231
- () => (userId: string) => [
292
+ // Leaving is XMPP-side (an unavailable presence to the MUC); dropping the
293
+ // room from the store is what actually takes the user out of the chat in
294
+ // the UI, and the `!activeRoom` effect above then closes this modal.
295
+ const handleLeave = () => {
296
+ Alert.alert(t('modal.leaveChat.title'), t('modal.leaveChat.description'), [
297
+ { text: t('action.cancel'), style: 'cancel' },
232
298
  {
233
- label: 'Appoint as an admin',
234
- icon: null,
235
- onClick: () => {
236
- dispatch(setActiveModal(MODAL_TYPES.PROFILE));
237
- console.log('Profile clicked');
299
+ text: t('action.leave'),
300
+ style: 'destructive',
301
+ onPress: () => {
302
+ try {
303
+ client?.leaveTheRoomStanza?.(activeRoom?.jid || '');
304
+ } catch (error) {
305
+ console.error('Failed to leave the room:', error);
306
+ }
307
+ dispatch(deleteRoom({ jid: activeRoom?.jid || '' }));
308
+ handleCloseModal?.();
238
309
  },
239
310
  },
311
+ ]);
312
+ };
313
+
314
+ const isModerator = activeRoom?.role === 'moderator';
315
+ const canEditIcon =
316
+ !config?.disableChatInfo?.disableIconEdit && activeRoom?.role !== 'participant';
317
+ const canAddMembers = isModerator && activeRoom?.type === 'group';
318
+ const canDeleteChat = isModerator && activeRoom?.type !== 'private';
319
+ const hasIcon = !!activeRoom?.icon;
320
+
321
+ const scrollHeaderTo = (y: number) =>
322
+ scrollRef.current?.scrollTo?.({ y, animated: true });
323
+
324
+ // Opening search from an expanded header tucks the picture away so the
325
+ // results and the keyboard have room; from an already-collapsed header
326
+ // it just opens the field. Whatever it collapsed, it puts back.
327
+ const openMemberSearch = () => {
328
+ const target = searchScrollTarget(isCollapsed, collapseDistance);
329
+ if (target !== null) {
330
+ collapsedForSearchRef.current = true;
331
+ scrollHeaderTo(target);
332
+ }
333
+ setIsSearchOpen(true);
334
+ setTimeout(() => searchInputRef.current?.focus(), 50);
335
+ };
336
+
337
+ const restoreHeader = () => {
338
+ if (!collapsedForSearchRef.current) {return;}
339
+ collapsedForSearchRef.current = false;
340
+ scrollHeaderTo(0);
341
+ };
342
+
343
+ const closeMemberSearch = () => {
344
+ setMemberQuery('');
345
+ setIsSearchOpen(false);
346
+ Keyboard.dismiss();
347
+ restoreHeader();
348
+ };
349
+
350
+ // Putting the keyboard away is the other way out of search, so it
351
+ // restores the header too — the field stays, results and all.
352
+ useEffect(() => {
353
+ if (!isSearchOpen) {return undefined;}
354
+ const sub = Keyboard.addListener('keyboardDidHide', restoreHeader);
355
+ return () => sub.remove();
356
+ }, [isSearchOpen]); // eslint-disable-line react-hooks/exhaustive-deps
357
+
358
+ const heroActions: HeroAction[] = useMemo(
359
+ () => [
360
+ {
361
+ key: 'search',
362
+ label: t('action.search'),
363
+ icon: (color: string) => <SearchIcon color={color} />,
364
+ onPress: openMemberSearch,
365
+ },
240
366
  {
241
- label: 'Unban',
242
- icon: null,
243
- onClick: () => {
244
- dispatch(setActiveModal(MODAL_TYPES.SETTINGS));
245
- console.log('Settings clicked');
246
- },
367
+ key: 'leave',
368
+ label: t('action.leave'),
369
+ icon: (color: string) => <LeaveIcon color={color} />,
370
+ onPress: handleLeave,
247
371
  },
248
372
  {
249
- label: 'Delete',
250
- icon: null,
251
- onClick: (e: any) => {
252
- e?.preventDefault();
253
- handleDeleteUser(userId);
254
- },
373
+ key: 'report',
374
+ label: t('action.report'),
375
+ icon: (color: string) => <ReportIcon color={color} />,
376
+ onPress: () => setIsReportOpen(true),
255
377
  },
256
378
  ],
257
- []
379
+ [t, activeRoom?.jid] // eslint-disable-line react-hooks/exhaustive-deps
258
380
  );
259
381
 
260
- useEffect(() => {
261
- if (!activeRoom) {
262
- dispatch(setActiveModal(undefined));
382
+ // "…" menu. Edit is the chat picture — the only chat detail this SDK can
383
+ // actually change (there is no update-chat endpoint for title/description),
384
+ // so it is only offered when the picture is editable.
385
+ const menuItems: ProfileMenuItem[] = useMemo(() => {
386
+ const items: ProfileMenuItem[] = [];
387
+ if (canEditIcon) {
388
+ items.push({
389
+ key: 'edit',
390
+ label: t('action.edit'),
391
+ icon: <EditIcon color="#141414" width={18} height={18} />,
392
+ onPress: onUpload,
393
+ });
394
+ if (hasIcon) {
395
+ items.push({
396
+ key: 'remove-photo',
397
+ label: t('modal.chatProfile.removePhoto'),
398
+ icon: <DeleteIcon width={18} height={18} />,
399
+ onPress: onRemoveIcon,
400
+ });
401
+ }
263
402
  }
264
- }, [activeRoom, dispatch]);
403
+ // The hero's Search button is off-screen once collapsed, so the menu
404
+ // carries it — the design's collapsed menu does the same.
405
+ if (isCollapsed) {
406
+ items.push({
407
+ key: 'search',
408
+ label: t('action.search'),
409
+ icon: <SearchIcon color="#141414" width={18} height={18} />,
410
+ onPress: openMemberSearch,
411
+ });
412
+ }
413
+ items.push({
414
+ key: 'report',
415
+ label: t('action.report'),
416
+ icon: <ReportIcon color="#141414" width={18} height={18} />,
417
+ onPress: () => setIsReportOpen(true),
418
+ });
419
+ if (canDeleteChat) {
420
+ items.push({
421
+ key: 'delete-and-leave',
422
+ label: t('action.deleteAndLeave'),
423
+ icon: <DeleteIcon width={18} height={18} />,
424
+ destructive: true,
425
+ onPress: () => setIsDeleteOpen(true),
426
+ });
427
+ }
428
+ return items;
429
+ }, [canEditIcon, canDeleteChat, hasIcon, isCollapsed, t]); // eslint-disable-line react-hooks/exhaustive-deps
265
430
 
266
431
  if (!activeRoom) {
267
432
  return null;
268
433
  }
269
434
 
435
+ const title = activeRoom.title || activeRoom.name;
436
+ const memberCount = activeRoom.usersCnt ?? 0;
437
+ const subtitle = t(
438
+ memberCount === 1
439
+ ? 'modal.chatProfile.memberCountSingular'
440
+ : 'modal.chatProfile.memberCountPlural',
441
+ { count: memberCount }
442
+ );
443
+ const heroImage = appendFileToken(activeRoom.icon, fileToken) || null;
444
+ // A single flat colour behind the initials — the config's avatar colour
445
+ // when set, otherwise the primary. (The old per-name hash colour is what
446
+ // made the header read as two different greens next to the members' own
447
+ // avatars.)
448
+ const heroColor = config?.colors?.avatar || getIconColor(config);
449
+ const members = activeRoom.roomMembers ?? [];
450
+ const query = memberQuery.trim().toLowerCase();
451
+ const visibleMembers = query
452
+ ? members.filter((user) =>
453
+ `${user.firstName ?? ''} ${user.lastName ?? ''}`
454
+ .toLowerCase()
455
+ .includes(query)
456
+ )
457
+ : members;
458
+
270
459
  return (
271
- <ModalContainerFullScreen style={{ position: 'relative' }}>
272
- <ModalHeaderComponent
273
- handleCloseModal={handleCloseModal}
274
- headerTitle={'Chat Profile'}
275
- titleStyle={chatTextStyle(config?.typography?.profile?.screenTitle)}
276
- rightMenu={
277
- <>
278
- {activeRoom?.type === 'public' && (
279
- <Button EndIcon={<QrIcon />} onPress={() => setVisible(true)} />
280
- )}
281
- {activeRoom.role === 'moderator' &&
282
- activeRoom.type !== 'private' && (
283
- <DropdownMenu
284
- position="right"
285
- options={chatMenuOptions}
286
- openButton={
287
- <Button
288
- style={{ padding: 8, height: 40 }}
289
- EndIcon={<MoreIcon />}
290
- unstyled
291
- />
292
- }
460
+ <ModalContainerFullScreen style={styles.screen}>
461
+ <Animated.ScrollView
462
+ ref={scrollRef}
463
+ style={styles.scroll}
464
+ // The collapsed state has to be reachable even when the member list
465
+ // is short: without this the content was shorter than the viewport,
466
+ // so the scroll had nowhere to go and simply bounced back.
467
+ contentContainerStyle={[
468
+ styles.scrollContent,
469
+ viewportHeight > 0 && {
470
+ minHeight: viewportHeight + collapseDistance,
471
+ },
472
+ ]}
473
+ onLayout={(e) => setViewportHeight(e.nativeEvent.layout.height)}
474
+ showsVerticalScrollIndicator={false}
475
+ keyboardShouldPersistTaps="handled"
476
+ scrollEventThrottle={16}
477
+ onScroll={Animated.event(
478
+ [{ nativeEvent: { contentOffset: { y: scrollY } } }],
479
+ {
480
+ useNativeDriver: true,
481
+ listener: (e: any) => {
482
+ const y = e?.nativeEvent?.contentOffset?.y ?? 0;
483
+ setIsCollapsed(y >= collapseDistance - 8);
484
+ },
485
+ }
486
+ )}
487
+ >
488
+ <ProfileHero
489
+ title={title}
490
+ subtitle={subtitle}
491
+ imageUri={heroImage}
492
+ fallbackColor={heroColor}
493
+ initials={chatInitials(title)}
494
+ scrollY={scrollY}
495
+ actions={heroActions}
496
+ titleStyle={chatTextStyle(config?.typography?.profile?.title)}
497
+ subtitleStyle={getElementFont(config, 'profileStatus')}
498
+ />
499
+
500
+ <View style={styles.body}>
501
+ {!config?.disableChatInfo?.disableDescription && (
502
+ <View style={styles.card}>
503
+ <Text style={[styles.cardLabel, getElementFont(config, 'profileSectionLabel')]}>
504
+ {t('modal.chatProfile.description')}
505
+ </Text>
506
+ <Text style={styles.cardValue}>{activeRoom.description || '—'}</Text>
507
+ </View>
508
+ )}
509
+
510
+ {!config?.disableChatInfo?.disableType && (
511
+ <View style={styles.card}>
512
+ <Text style={[styles.cardLabel, getElementFont(config, 'profileSectionLabel')]}>
513
+ {t('modal.chatProfile.chatType')}
514
+ </Text>
515
+ <Text style={styles.cardValue}>{activeRoom.type || '—'}</Text>
516
+ </View>
517
+ )}
518
+
519
+ {!config?.disableChatInfo?.hideMembers && (
520
+ <View style={styles.card}>
521
+ {canAddMembers && (
522
+ <SelectUsersModal
523
+ trigger={(open) => (
524
+ <TouchableOpacity
525
+ testID="chat-profile-add-members"
526
+ activeOpacity={0.7}
527
+ style={styles.addMembersRow}
528
+ onPress={open}
529
+ >
530
+ <AddNewIcon color={getIconColor(config)} />
531
+ <Text style={styles.addMembersLabel}>
532
+ {t('modal.chatProfile.addMembers')}
533
+ </Text>
534
+ </TouchableOpacity>
535
+ )}
293
536
  />
294
537
  )}
295
- </>
296
- }
297
- />
298
- <CenterContainer>
299
- <ProfileImagePlaceholder
300
- name={activeRoom.title || activeRoom.name}
301
- icon={appendFileToken(activeRoom.icon, fileToken)}
302
- // Two gates control whether the icon is editable:
303
- // 1. role — only non-participants could edit before.
304
- // 2. NEW: `disableChatInfo.disableIconEdit` — hard kill from
305
- // config so a consumer can make the icon read-only for
306
- // everyone (e.g. branded rooms, patient-facing apps).
307
- // Both must allow it for the picker/remove affordances to show.
308
- upload={{
309
- onUpload,
310
- active:
311
- !config?.disableChatInfo?.disableIconEdit &&
312
- activeRoom?.role !== 'participant',
313
- }}
314
- remove={{
315
- enabled: !config?.disableChatInfo?.disableIconEdit,
316
- onRemoveClick,
317
- }}
318
- role={activeRoom?.role}
319
- size={128}
320
- />
321
- <UserInfo>
322
- <UserName
323
- fontSize={config?.typography?.profile?.title?.fontSize}
324
- fontWeight={config?.typography?.profile?.title?.fontWeight as any}
325
- >
326
- {activeRoom.title || activeRoom.name}
327
- </UserName>
328
- <UserStatus style={getElementFont(config, 'profileStatus')}>
329
- {activeRoom.usersCnt}{' '}
330
- {activeRoom.usersCnt > 1 ? 'members' : 'member'}
331
- </UserStatus>
332
- </UserInfo>
333
- {activeRoom.role === 'moderator' && activeRoom.type === 'group' && (
334
- <>
335
- {/* <AddMembersModal /> */}
336
- <SelectUsersModal />
337
- </>
338
- )}
339
- {!config?.disableChatInfo?.disableDescription && (
340
- <BorderedContainer>
341
- <LabelData style={getElementFont(config, 'profileSectionLabel')}>
342
- Description
343
- </LabelData>
344
- <Label>{activeRoom?.description || '—'}</Label>
345
- </BorderedContainer>
346
- )}
347
- {!config?.disableChatInfo?.disableType && (
348
- <BorderedContainer>
349
- <LabelData style={getElementFont(config, 'profileSectionLabel')}>
350
- Chat type
351
- </LabelData>
352
- <Label>{activeRoom.type || '—'}</Label>
353
- </BorderedContainer>
354
- )}
355
- {/* <BorderedContainer
356
- style={{
357
- justifyContent: 'space-between',
358
- flexDirection: 'row',
359
- alignItems: 'center',
360
- }}
361
- >
362
- <Label>Notifications</Label>
363
- <Label>
364
- <Switch
365
- onToggle={function (isOn: boolean): void {
366
- throw new Error('Function not implemented.');
367
- }}
368
- bgColor={config?.colors?.primary}
369
- />
370
- </Label>
371
- </BorderedContainer> */}
372
- {!config?.disableChatInfo?.hideMembers && (
373
- <BorderedContainer style={{ paddingHorizontal: 16, paddingVertical: 8 }}>
374
- {loading ? (
375
- <Loader />
376
- ) : (activeRoom?.roomMembers?.length ?? 0) === 0 ? (
377
- <Label style={{ opacity: 0.6, textAlign: 'center', paddingVertical: 8 }}>
378
- Member list unavailable
379
- </Label>
380
- ) : (
381
- <ScrollView
382
- style={{ maxHeight: 400 }}
383
- showsVerticalScrollIndicator={true}
384
- nestedScrollEnabled={true}
385
- >
386
- {activeRoom?.roomMembers?.map((user, index) => (
387
- <View
388
- key={user.xmppUsername}
389
- style={{
390
- flexDirection: 'column',
391
- alignItems: 'flex-start',
392
- }}
393
- >
394
- <View
395
- style={{
396
- flexDirection: 'row',
397
- justifyContent: 'flex-start',
398
- paddingHorizontal: 8,
399
- paddingVertical: 0,
400
- alignItems: 'center',
401
- width: '100%',
402
- }}
538
+
539
+ <Text
540
+ style={[
541
+ styles.cardLabel,
542
+ getElementFont(config, 'profileSectionLabel'),
543
+ ]}
544
+ >
545
+ {t('modal.chatProfile.membersLabel', { count: memberCount })}
546
+ </Text>
547
+
548
+ {isSearchOpen && (
549
+ <View style={styles.searchRow}>
550
+ <SearchIcon color="#8C8C8C" width={18} height={18} />
551
+ <TextInput
552
+ testID="chat-profile-member-search"
553
+ ref={searchInputRef}
554
+ style={styles.searchInput}
555
+ value={memberQuery}
556
+ onChangeText={setMemberQuery}
557
+ placeholder={t('search.members')}
558
+ placeholderTextColor="#8C8C8C"
559
+ autoCorrect={false}
560
+ returnKeyType="search"
561
+ />
562
+ <TouchableOpacity
563
+ testID="chat-profile-search-close"
564
+ activeOpacity={0.7}
565
+ hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
566
+ onPress={closeMemberSearch}
403
567
  >
404
- <Pressable
405
- style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-start', gap: 16 }}
406
- // `disableChatInfo.disableMemberTap` blocks the
407
- // tap entirely so the user-profile popup never
408
- // opens. `disableMemberProfileActions` only hid
409
- // the action buttons INSIDE that popup, leaving
410
- // the popup itself to open on tap — this gate
411
- // closes the door. Customer-reported #16.
412
- onPress={
413
- config?.disableChatInfo?.disableMemberTap
414
- ? undefined
415
- : () => handleUserAvatarClick(user)
416
- }
417
- disabled={!!config?.disableChatInfo?.disableMemberTap}
568
+ <Text style={styles.searchClose}>×</Text>
569
+ </TouchableOpacity>
570
+ </View>
571
+ )}
572
+
573
+ {loading ? (
574
+ <Loader />
575
+ ) : visibleMembers.length === 0 ? (
576
+ <Text testID="chat-profile-members-empty" style={styles.emptyMembers}>
577
+ {members.length === 0
578
+ ? t('modal.chatProfile.membersUnavailable')
579
+ : t('search.noResults')}
580
+ </Text>
581
+ ) : (
582
+ visibleMembers.map((user, index) => {
583
+ const banned = user.ban_status === 'banned';
584
+ const roleLabel =
585
+ user.role && user.role !== 'none' && user.role !== 'participant'
586
+ ? user.role
587
+ : null;
588
+ return (
589
+ <View
590
+ key={user.xmppUsername}
591
+ style={[styles.memberRow, index > 0 && styles.memberDivider]}
418
592
  >
419
- <ProfileImagePlaceholder
420
- name={`${user.firstName} ${user.lastName}`}
421
- size={40}
422
- />
423
- <View
424
- style={{
425
- flexDirection: 'column',
426
- gap: 2,
427
- alignItems: 'flex-start',
428
- justifyContent: 'center',
429
- }}
430
- >
431
- <Label
432
- style={[
433
- { fontSize: 16, fontWeight: 600 },
434
- chatTextStyle(config?.typography?.profile?.memberName),
435
- ]}
436
- >
437
- {user.firstName} {user.lastName}
438
- </Label>
439
- {user.last_active && (
440
- <LabelData style={getElementFont(config, 'profileSectionLabel')}>
441
- {new Date(user.last_active * 1000).toLocaleString()}
442
- </LabelData>
443
- )}
444
- </View>
445
- </Pressable>
446
- {user.role && user.role !== 'none' && (
447
- <Text
448
- style={{
449
- backgroundColor:
450
- user.ban_status !== 'banned' ? '#F3F6FC' : '#FFEBEE',
451
- color:
452
- user.ban_status !== 'banned' ? '#0052CD' : '#F44336',
453
- padding: 5,
454
- borderRadius: 16,
455
- fontSize: 12,
456
- }}
593
+ <Pressable
594
+ style={styles.memberMain}
595
+ // `disableChatInfo.disableMemberTap` blocks the tap
596
+ // entirely so the user-profile popup never opens.
597
+ // `disableMemberProfileActions` only hid the action
598
+ // buttons INSIDE that popup, leaving the popup itself
599
+ // to open on tap — this gate closes the door.
600
+ // Customer-reported #16.
601
+ onPress={
602
+ config?.disableChatInfo?.disableMemberTap
603
+ ? undefined
604
+ : () => handleUserAvatarClick(user)
605
+ }
606
+ disabled={!!config?.disableChatInfo?.disableMemberTap}
457
607
  >
458
- {user.role}
459
- </Text>
460
- )}
461
- {stateUser.xmppUsername !== user.xmppUsername &&
462
- activeRoom.role === 'moderator' &&
463
- activeRoom.type !== 'private' && (
464
- <DropdownMenu
465
- options={menuOptions(user.xmppUsername) as MenuOption[]}
466
- openButton={
467
- <Button
468
- onPress={() => {}}
469
- >
470
- <Text>More Options</Text>
471
- </Button>
472
- }
473
- onClose={() => console.log('Dropdown closed')}
608
+ <ProfileImagePlaceholder
609
+ name={`${user.firstName} ${user.lastName}`}
610
+ size={40}
474
611
  />
612
+ <View style={styles.memberText}>
613
+ <Text
614
+ numberOfLines={1}
615
+ style={[
616
+ styles.memberName,
617
+ chatTextStyle(config?.typography?.profile?.memberName),
618
+ ]}
619
+ >
620
+ {user.firstName} {user.lastName}
621
+ </Text>
622
+ {!!user.last_active && (
623
+ <Text
624
+ numberOfLines={1}
625
+ style={[
626
+ styles.memberStatus,
627
+ getElementFont(config, 'profileSectionLabel'),
628
+ ]}
629
+ >
630
+ {new Date(user.last_active * 1000).toLocaleString()}
631
+ </Text>
632
+ )}
633
+ </View>
634
+ </Pressable>
635
+
636
+ {(banned || roleLabel) && (
637
+ <Text
638
+ style={[styles.memberRole, banned && styles.memberBanned]}
639
+ >
640
+ {banned ? t('status.banned') : roleLabel}
641
+ </Text>
475
642
  )}
476
- </View>
477
- {index < (activeRoom?.roomMembers?.length || 0) - 1 && <Divider />}
478
- </View>
479
- ))}
480
- </ScrollView>
643
+
644
+ {isModerator &&
645
+ activeRoom.type !== 'private' &&
646
+ stateUser.xmppUsername !== user.xmppUsername && (
647
+ <TouchableOpacity
648
+ testID={`chat-profile-remove-${user.xmppUsername}`}
649
+ activeOpacity={0.7}
650
+ hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
651
+ style={styles.memberRemove}
652
+ onPress={() => confirmRemoveMember(user)}
653
+ >
654
+ <DeleteIcon width={20} height={20} />
655
+ </TouchableOpacity>
656
+ )}
657
+ </View>
658
+ );
659
+ })
660
+ )}
661
+ </View>
481
662
  )}
482
- </BorderedContainer>
483
- )}
484
- </CenterContainer>
663
+ </View>
664
+ </Animated.ScrollView>
665
+
666
+ <ProfileTopBar
667
+ title={title}
668
+ subtitle={subtitle}
669
+ imageUri={heroImage}
670
+ fallbackColor={heroColor}
671
+ initials={chatInitials(title)}
672
+ scrollY={scrollY}
673
+ onBack={handleCloseModal}
674
+ titleStyle={chatTextStyle(config?.typography?.profile?.screenTitle)}
675
+ menu={<ProfileMenu items={menuItems} />}
676
+ />
485
677
 
486
678
  <DeleteChatModal
487
- isModalOpen={isModalOpen}
488
- setIsModalOpen={setIsModalOpen}
679
+ isModalOpen={isDeleteOpen}
680
+ setIsModalOpen={setIsDeleteOpen}
681
+ />
682
+ <ReportChatModal
683
+ visible={isReportOpen}
684
+ onClose={() => setIsReportOpen(false)}
489
685
  />
490
686
  </ModalContainerFullScreen>
491
687
  );
492
688
  };
493
689
 
690
+ const styles = StyleSheet.create({
691
+ screen: {
692
+ position: 'relative',
693
+ backgroundColor: '#F2F3F5',
694
+ },
695
+ scroll: {
696
+ width: '100%',
697
+ flex: 1,
698
+ },
699
+ scrollContent: {
700
+ paddingBottom: 32,
701
+ },
702
+ body: {
703
+ padding: 12,
704
+ gap: 12,
705
+ },
706
+ card: {
707
+ backgroundColor: '#FFFFFF',
708
+ borderRadius: 14,
709
+ padding: 16,
710
+ shadowColor: '#000',
711
+ shadowOffset: { width: 0, height: 1 },
712
+ shadowOpacity: 0.05,
713
+ shadowRadius: 4,
714
+ elevation: 1,
715
+ },
716
+ cardLabel: {
717
+ color: '#8C8C8C',
718
+ fontSize: 14,
719
+ },
720
+ cardValue: {
721
+ color: '#141414',
722
+ fontSize: 16,
723
+ marginTop: 4,
724
+ },
725
+ addMembersRow: {
726
+ flexDirection: 'row',
727
+ alignItems: 'center',
728
+ gap: 12,
729
+ paddingBottom: 14,
730
+ marginBottom: 10,
731
+ borderBottomWidth: StyleSheet.hairlineWidth,
732
+ borderBottomColor: '#EFEFF2',
733
+ },
734
+ addMembersLabel: {
735
+ fontSize: 16,
736
+ fontWeight: '500',
737
+ color: '#141414',
738
+ },
739
+ searchRow: {
740
+ flexDirection: 'row',
741
+ alignItems: 'center',
742
+ gap: 8,
743
+ marginTop: 10,
744
+ paddingHorizontal: 12,
745
+ height: 40,
746
+ borderRadius: 12,
747
+ backgroundColor: '#F2F3F5',
748
+ },
749
+ searchInput: {
750
+ flex: 1,
751
+ fontSize: 15,
752
+ color: '#141414',
753
+ padding: 0,
754
+ },
755
+ searchClose: {
756
+ fontSize: 22,
757
+ lineHeight: 24,
758
+ color: '#8C8C8C',
759
+ },
760
+ emptyMembers: {
761
+ opacity: 0.6,
762
+ textAlign: 'center',
763
+ paddingVertical: 8,
764
+ color: '#141414',
765
+ },
766
+ memberRow: {
767
+ flexDirection: 'row',
768
+ alignItems: 'center',
769
+ paddingVertical: 10,
770
+ },
771
+ memberDivider: {
772
+ borderTopWidth: StyleSheet.hairlineWidth,
773
+ borderTopColor: '#EFEFF2',
774
+ },
775
+ memberMain: {
776
+ flex: 1,
777
+ flexDirection: 'row',
778
+ alignItems: 'center',
779
+ gap: 12,
780
+ },
781
+ memberText: {
782
+ flex: 1,
783
+ },
784
+ memberName: {
785
+ fontSize: 16,
786
+ fontWeight: '600',
787
+ color: '#141414',
788
+ },
789
+ memberStatus: {
790
+ fontSize: 13,
791
+ color: '#8C8C8C',
792
+ marginTop: 2,
793
+ },
794
+ memberRole: {
795
+ fontSize: 13,
796
+ color: '#8C8C8C',
797
+ marginLeft: 8,
798
+ },
799
+ memberBanned: {
800
+ color: '#E53935',
801
+ },
802
+ memberRemove: {
803
+ marginLeft: 12,
804
+ },
805
+ menuButton: {
806
+ width: 40,
807
+ height: 40,
808
+ borderRadius: 20,
809
+ backgroundColor: 'rgba(0,0,0,0.28)',
810
+ alignItems: 'center',
811
+ justifyContent: 'center',
812
+ },
813
+ });
814
+
494
815
  export default ChatProfileModal;