@ethora/chat-component-rn 26.10.9 → 26.10.10

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 (154) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/README.md +1 -1
  3. package/lib/commonjs/components/MainComponents/AlbumGrid.js +6 -2
  4. package/lib/commonjs/components/MainComponents/AlbumGrid.js.map +1 -1
  5. package/lib/commonjs/components/MainComponents/ChatHeader.js +16 -11
  6. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  7. package/lib/commonjs/components/MainComponents/MediaMessage.js +13 -0
  8. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  9. package/lib/commonjs/components/MainComponents/RoomList.js +7 -16
  10. package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
  11. package/lib/commonjs/components/MessageBubble/Message.js +27 -1
  12. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  13. package/lib/commonjs/components/Modals/ChatProfileModal/ChatMediaGrid.js +159 -0
  14. package/lib/commonjs/components/Modals/ChatProfileModal/ChatMediaGrid.js.map +1 -0
  15. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +203 -134
  16. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  17. package/lib/commonjs/components/Modals/ChatProfileModal/LeaveChatModal.js +180 -0
  18. package/lib/commonjs/components/Modals/ChatProfileModal/LeaveChatModal.js.map +1 -0
  19. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +100 -162
  20. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  21. package/lib/commonjs/components/Modals/FilePreviewModal/MediaViewer.js +824 -0
  22. package/lib/commonjs/components/Modals/FilePreviewModal/MediaViewer.js.map +1 -0
  23. package/lib/commonjs/components/Modals/FilePreviewModal/useSaveFile.js +179 -0
  24. package/lib/commonjs/components/Modals/FilePreviewModal/useSaveFile.js.map +1 -0
  25. package/lib/commonjs/components/Modals/MessageSearchModal/MessageSearchModal.js +22 -5
  26. package/lib/commonjs/components/Modals/MessageSearchModal/MessageSearchModal.js.map +1 -1
  27. package/lib/commonjs/components/styled/MessageImage.js +4 -1
  28. package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
  29. package/lib/commonjs/components/styled/VideoMessage.js +5 -1
  30. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  31. package/lib/commonjs/context/MediaSourceContext.js +18 -0
  32. package/lib/commonjs/context/MediaSourceContext.js.map +1 -0
  33. package/lib/commonjs/helpers/constants/MODAL_COMPONENTS.js +3 -1
  34. package/lib/commonjs/helpers/constants/MODAL_COMPONENTS.js.map +1 -1
  35. package/lib/commonjs/helpers/constants/MODAL_TYPES.js +2 -0
  36. package/lib/commonjs/helpers/constants/MODAL_TYPES.js.map +1 -1
  37. package/lib/commonjs/helpers/publicChatsEntry.js +8 -6
  38. package/lib/commonjs/helpers/publicChatsEntry.js.map +1 -1
  39. package/lib/commonjs/helpers/roomMedia.js +118 -0
  40. package/lib/commonjs/helpers/roomMedia.js.map +1 -0
  41. package/lib/commonjs/i18n/strings.js +36 -0
  42. package/lib/commonjs/i18n/strings.js.map +1 -1
  43. package/lib/commonjs/networking/api-requests/rooms.api.js +9 -13
  44. package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
  45. package/lib/commonjs/version.js +1 -1
  46. package/lib/commonjs/version.js.map +1 -1
  47. package/lib/module/components/MainComponents/AlbumGrid.js +6 -2
  48. package/lib/module/components/MainComponents/AlbumGrid.js.map +1 -1
  49. package/lib/module/components/MainComponents/ChatHeader.js +18 -13
  50. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  51. package/lib/module/components/MainComponents/MediaMessage.js +13 -0
  52. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  53. package/lib/module/components/MainComponents/RoomList.js +7 -16
  54. package/lib/module/components/MainComponents/RoomList.js.map +1 -1
  55. package/lib/module/components/MessageBubble/Message.js +27 -1
  56. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  57. package/lib/module/components/Modals/ChatProfileModal/ChatMediaGrid.js +154 -0
  58. package/lib/module/components/Modals/ChatProfileModal/ChatMediaGrid.js.map +1 -0
  59. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +204 -136
  60. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  61. package/lib/module/components/Modals/ChatProfileModal/LeaveChatModal.js +175 -0
  62. package/lib/module/components/Modals/ChatProfileModal/LeaveChatModal.js.map +1 -0
  63. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +100 -163
  64. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  65. package/lib/module/components/Modals/FilePreviewModal/MediaViewer.js +819 -0
  66. package/lib/module/components/Modals/FilePreviewModal/MediaViewer.js.map +1 -0
  67. package/lib/module/components/Modals/FilePreviewModal/useSaveFile.js +172 -0
  68. package/lib/module/components/Modals/FilePreviewModal/useSaveFile.js.map +1 -0
  69. package/lib/module/components/Modals/MessageSearchModal/MessageSearchModal.js +20 -4
  70. package/lib/module/components/Modals/MessageSearchModal/MessageSearchModal.js.map +1 -1
  71. package/lib/module/components/styled/MessageImage.js +4 -1
  72. package/lib/module/components/styled/MessageImage.js.map +1 -1
  73. package/lib/module/components/styled/VideoMessage.js +5 -1
  74. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  75. package/lib/module/context/MediaSourceContext.js +13 -0
  76. package/lib/module/context/MediaSourceContext.js.map +1 -0
  77. package/lib/module/helpers/constants/MODAL_COMPONENTS.js +2 -1
  78. package/lib/module/helpers/constants/MODAL_COMPONENTS.js.map +1 -1
  79. package/lib/module/helpers/constants/MODAL_TYPES.js +2 -0
  80. package/lib/module/helpers/constants/MODAL_TYPES.js.map +1 -1
  81. package/lib/module/helpers/publicChatsEntry.js +8 -6
  82. package/lib/module/helpers/publicChatsEntry.js.map +1 -1
  83. package/lib/module/helpers/roomMedia.js +112 -0
  84. package/lib/module/helpers/roomMedia.js.map +1 -0
  85. package/lib/module/i18n/strings.js +36 -0
  86. package/lib/module/i18n/strings.js.map +1 -1
  87. package/lib/module/networking/api-requests/rooms.api.js +9 -13
  88. package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
  89. package/lib/module/version.js +1 -1
  90. package/lib/module/version.js.map +1 -1
  91. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts.map +1 -1
  92. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  93. package/lib/typescript/components/MainComponents/MediaMessage.d.ts +3 -1
  94. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  95. package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
  96. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  97. package/lib/typescript/components/Modals/ChatProfileModal/ChatMediaGrid.d.ts +16 -0
  98. package/lib/typescript/components/Modals/ChatProfileModal/ChatMediaGrid.d.ts.map +1 -0
  99. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
  100. package/lib/typescript/components/Modals/ChatProfileModal/LeaveChatModal.d.ts +18 -0
  101. package/lib/typescript/components/Modals/ChatProfileModal/LeaveChatModal.d.ts.map +1 -0
  102. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts +16 -0
  103. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  104. package/lib/typescript/components/Modals/FilePreviewModal/MediaViewer.d.ts +27 -0
  105. package/lib/typescript/components/Modals/FilePreviewModal/MediaViewer.d.ts.map +1 -0
  106. package/lib/typescript/components/Modals/FilePreviewModal/useSaveFile.d.ts +9 -0
  107. package/lib/typescript/components/Modals/FilePreviewModal/useSaveFile.d.ts.map +1 -0
  108. package/lib/typescript/components/Modals/MessageSearchModal/MessageSearchModal.d.ts +8 -0
  109. package/lib/typescript/components/Modals/MessageSearchModal/MessageSearchModal.d.ts.map +1 -1
  110. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  111. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  112. package/lib/typescript/context/MediaSourceContext.d.ts +4 -0
  113. package/lib/typescript/context/MediaSourceContext.d.ts.map +1 -0
  114. package/lib/typescript/helpers/constants/MODAL_COMPONENTS.d.ts.map +1 -1
  115. package/lib/typescript/helpers/constants/MODAL_TYPES.d.ts +2 -0
  116. package/lib/typescript/helpers/constants/MODAL_TYPES.d.ts.map +1 -1
  117. package/lib/typescript/helpers/publicChatsEntry.d.ts +7 -5
  118. package/lib/typescript/helpers/publicChatsEntry.d.ts.map +1 -1
  119. package/lib/typescript/helpers/roomMedia.d.ts +26 -0
  120. package/lib/typescript/helpers/roomMedia.d.ts.map +1 -0
  121. package/lib/typescript/i18n/strings.d.ts.map +1 -1
  122. package/lib/typescript/networking/api-requests/rooms.api.d.ts +6 -5
  123. package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
  124. package/lib/typescript/types/models/modal.model.d.ts +18 -0
  125. package/lib/typescript/types/models/modal.model.d.ts.map +1 -1
  126. package/lib/typescript/types/types.d.ts +18 -0
  127. package/lib/typescript/types/types.d.ts.map +1 -1
  128. package/lib/typescript/version.d.ts +1 -1
  129. package/lib/typescript/version.d.ts.map +1 -1
  130. package/package.json +9 -2
  131. package/src/components/MainComponents/AlbumGrid.tsx +5 -1
  132. package/src/components/MainComponents/ChatHeader.tsx +16 -11
  133. package/src/components/MainComponents/MediaMessage.tsx +13 -2
  134. package/src/components/MainComponents/RoomList.tsx +8 -19
  135. package/src/components/MessageBubble/Message.tsx +24 -1
  136. package/src/components/Modals/ChatProfileModal/ChatMediaGrid.tsx +174 -0
  137. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +119 -48
  138. package/src/components/Modals/ChatProfileModal/LeaveChatModal.tsx +191 -0
  139. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +115 -185
  140. package/src/components/Modals/FilePreviewModal/MediaViewer.tsx +880 -0
  141. package/src/components/Modals/FilePreviewModal/useSaveFile.ts +207 -0
  142. package/src/components/Modals/MessageSearchModal/MessageSearchModal.tsx +56 -30
  143. package/src/components/styled/MessageImage.tsx +3 -1
  144. package/src/components/styled/VideoMessage.tsx +5 -1
  145. package/src/context/MediaSourceContext.tsx +14 -0
  146. package/src/helpers/constants/MODAL_COMPONENTS.tsx +4 -1
  147. package/src/helpers/constants/MODAL_TYPES.ts +2 -0
  148. package/src/helpers/publicChatsEntry.ts +8 -7
  149. package/src/helpers/roomMedia.ts +126 -0
  150. package/src/i18n/strings.ts +36 -0
  151. package/src/networking/api-requests/rooms.api.ts +13 -12
  152. package/src/types/models/modal.model.ts +16 -0
  153. package/src/types/types.ts +16 -0
  154. package/src/version.ts +1 -1
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+
3
+ /** @format */
4
+
5
+ import React, { useMemo, useState } from 'react';
6
+ import { ActivityIndicator, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
7
+ import { useSelector, useStore } from 'react-redux';
8
+ import { collectRoomMedia } from '../../../helpers/roomMedia';
9
+ import { loadRoomHistory } from '../../../helpers/loadRoomHistory';
10
+ import { useFileToken } from '../../../hooks/useFileToken';
11
+ import { useChatSettingState } from '../../../hooks/useChatSettingState';
12
+ import { useXmppClient } from '../../../context/xmppProvider';
13
+ import { useTheme } from '../../../hooks/useTheme';
14
+ import { getIconColor } from '../../../helpers/getIconColor';
15
+ import { useT } from '../../../i18n/useT';
16
+ import { PlayIcon } from '../../../assets/icons';
17
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
+ /** Older history pages asked for from here; one at a time per room. */
19
+ const inFlight = new Set();
20
+ const PAGE_SIZE = 30;
21
+ /**
22
+ * The chat profile's Media tab: a three-column grid of the room's pictures
23
+ * and videos. It shows what history is loaded and pages older history in on
24
+ * demand, the same MAM pages the message list asks for.
25
+ */
26
+ const ChatMediaGrid = ({
27
+ roomJid,
28
+ onOpen
29
+ }) => {
30
+ const t = useT();
31
+ const theme = useTheme();
32
+ const styles = useMemo(() => createStyles(theme), [theme]);
33
+ const store = useStore();
34
+ const {
35
+ client
36
+ } = useXmppClient();
37
+ const fileToken = useFileToken();
38
+ const {
39
+ user,
40
+ config
41
+ } = useChatSettingState();
42
+ const messages = useSelector(state => state.rooms.rooms?.[roomJid]?.messages);
43
+ const historyComplete = useSelector(state => !!state.rooms.rooms?.[roomJid]?.historyComplete);
44
+ const [loadingMore, setLoadingMore] = useState(false);
45
+ const files = useMemo(() => collectRoomMedia(messages, roomJid, {
46
+ me: user,
47
+ fileToken,
48
+ interactive: !config?.disableInteractions,
49
+ repliesOn: !config?.disableReplies
50
+ }), [messages, roomJid, user, fileToken, config?.disableInteractions, config?.disableReplies]);
51
+ const loadMore = () => loadRoomHistory({
52
+ client,
53
+ room: store.getState().rooms.rooms?.[roomJid],
54
+ inFlight,
55
+ chatJID: roomJid,
56
+ max: PAGE_SIZE,
57
+ onBusyChange: setLoadingMore
58
+ });
59
+ return /*#__PURE__*/_jsxs(View, {
60
+ children: [files.length === 0 && !loadingMore ? /*#__PURE__*/_jsx(Text, {
61
+ testID: "chat-profile-media-empty",
62
+ style: styles.empty,
63
+ children: t('modal.chatProfile.mediaEmpty')
64
+ }) : /*#__PURE__*/_jsx(View, {
65
+ style: styles.grid,
66
+ children: files.map((file, index) => {
67
+ const isVideo = file.mimetype.startsWith('video/');
68
+ const thumb = isVideo ? file.previewURL : file.fileURL;
69
+ return /*#__PURE__*/_jsxs(TouchableOpacity, {
70
+ testID: `chat-profile-media-${index}`,
71
+ activeOpacity: 0.8,
72
+ style: styles.tile,
73
+ onPress: () => onOpen({
74
+ ...file,
75
+ siblings: files,
76
+ index
77
+ }),
78
+ children: [!!thumb && /*#__PURE__*/_jsx(Image, {
79
+ source: {
80
+ uri: thumb
81
+ },
82
+ style: styles.image,
83
+ resizeMode: "cover"
84
+ }), isVideo && /*#__PURE__*/_jsx(View, {
85
+ style: styles.videoBadge,
86
+ pointerEvents: "none",
87
+ children: /*#__PURE__*/_jsx(PlayIcon, {
88
+ width: 18,
89
+ height: 18
90
+ })
91
+ })]
92
+ }, `${file.source?.messageId}-${index}`);
93
+ })
94
+ }), loadingMore ? /*#__PURE__*/_jsx(ActivityIndicator, {
95
+ style: styles.more,
96
+ color: getIconColor(config)
97
+ }) : !historyComplete && /*#__PURE__*/_jsx(TouchableOpacity, {
98
+ testID: "chat-profile-media-load-more",
99
+ activeOpacity: 0.7,
100
+ style: styles.more,
101
+ onPress: loadMore,
102
+ children: /*#__PURE__*/_jsx(Text, {
103
+ style: [styles.moreLabel, {
104
+ color: getIconColor(config)
105
+ }],
106
+ children: t('search.messages.loadMore')
107
+ })
108
+ })]
109
+ });
110
+ };
111
+ const createStyles = theme => StyleSheet.create({
112
+ grid: {
113
+ flexDirection: 'row',
114
+ flexWrap: 'wrap',
115
+ gap: 6
116
+ },
117
+ tile: {
118
+ width: '31.8%',
119
+ aspectRatio: 1,
120
+ borderRadius: 10,
121
+ overflow: 'hidden',
122
+ backgroundColor: theme.dark ? theme.surfaceSecondary : '#1F1F1F'
123
+ },
124
+ image: {
125
+ width: '100%',
126
+ height: '100%'
127
+ },
128
+ videoBadge: {
129
+ position: 'absolute',
130
+ left: 6,
131
+ bottom: 6,
132
+ width: 28,
133
+ height: 28,
134
+ borderRadius: 14,
135
+ backgroundColor: 'rgba(0, 0, 0, 0.55)',
136
+ alignItems: 'center',
137
+ justifyContent: 'center'
138
+ },
139
+ empty: {
140
+ paddingVertical: 16,
141
+ textAlign: 'center',
142
+ color: theme.textSecondary
143
+ },
144
+ more: {
145
+ paddingVertical: 14,
146
+ alignItems: 'center'
147
+ },
148
+ moreLabel: {
149
+ fontSize: 15,
150
+ fontWeight: '500'
151
+ }
152
+ });
153
+ export default ChatMediaGrid;
154
+ //# sourceMappingURL=ChatMediaGrid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["React","useMemo","useState","ActivityIndicator","Image","StyleSheet","Text","TouchableOpacity","View","useSelector","useStore","collectRoomMedia","loadRoomHistory","useFileToken","useChatSettingState","useXmppClient","useTheme","getIconColor","useT","PlayIcon","jsx","_jsx","jsxs","_jsxs","inFlight","Set","PAGE_SIZE","ChatMediaGrid","roomJid","onOpen","t","theme","styles","createStyles","store","client","fileToken","user","config","messages","state","rooms","historyComplete","loadingMore","setLoadingMore","files","me","interactive","disableInteractions","repliesOn","disableReplies","loadMore","room","getState","chatJID","max","onBusyChange","children","length","testID","style","empty","grid","map","file","index","isVideo","mimetype","startsWith","thumb","previewURL","fileURL","activeOpacity","tile","onPress","siblings","source","uri","image","resizeMode","videoBadge","pointerEvents","width","height","messageId","more","color","moreLabel","create","flexDirection","flexWrap","gap","aspectRatio","borderRadius","overflow","backgroundColor","dark","surfaceSecondary","position","left","bottom","alignItems","justifyContent","paddingVertical","textAlign","textSecondary","fontSize","fontWeight"],"sourceRoot":"../../../../../src","sources":["components/Modals/ChatProfileModal/ChatMediaGrid.tsx"],"mappings":"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","ignoreList":[]}
@@ -29,7 +29,7 @@ import { MODAL_TYPES } from '../../../helpers/constants/MODAL_TYPES';
29
29
  import SelectUsersModal from '../SelectUsersModal/SelectUsersModal';
30
30
  import { useToast } from '../../../context/ToastContext';
31
31
  import { useRoomMute } from '../../../hooks/useRoomMute';
32
- import { useLeaveRoom } from '../../../hooks/useLeaveRoom';
32
+ import LeaveChatModal from './LeaveChatModal';
33
33
  import { useT } from '../../../i18n/useT';
34
34
  import DeleteChatModal from './DeleteChatModal';
35
35
  import { isMessageSearchEnabled } from '../../../helpers/isMessageSearchEnabled';
@@ -39,9 +39,11 @@ import ProfileMenu from '../ProfileHeader/ProfileMenu';
39
39
  import { dedupeMembers } from '../../../helpers/dedupeMembers';
40
40
  import { getRoomUserCount, isRoomMembersTruncated } from '../../../helpers/roomUserCount';
41
41
  import { useRoomDirectory } from '../../../hooks/useRoomDirectory';
42
-
42
+ import ChatMediaGrid from './ChatMediaGrid';
43
+ import MediaViewer from '../FilePreviewModal/MediaViewer';
44
+ import { useSaveFile } from '../FilePreviewModal/useSaveFile';
45
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
43
46
  /** Member rows rendered per step; the rest sits behind a 'Show more' row. */
44
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
45
47
  export const MEMBER_PAGE_SIZE = 50;
46
48
 
47
49
  /**
@@ -65,6 +67,11 @@ const ChatProfileModal = ({
65
67
  const [loading, setLoading] = useState(false);
66
68
  const [isDeleteOpen, setIsDeleteOpen] = useState(false);
67
69
  const [isReportOpen, setIsReportOpen] = useState(false);
70
+ const [isLeaveOpen, setIsLeaveOpen] = useState(false);
71
+ const [activeTab, setActiveTab] = useState('members');
72
+ // A picture or video opened from the Media tab, shown over this screen.
73
+ const [viewerFile, setViewerFile] = useState(null);
74
+ const saveFile = useSaveFile();
68
75
  const [memberQuery, setMemberQuery] = useState('');
69
76
  const [isSearchOpen, setIsSearchOpen] = useState(false);
70
77
  // Measured, not guessed: this screen renders inside whatever frame the
@@ -78,7 +85,6 @@ const ChatProfileModal = ({
78
85
  const {
79
86
  client
80
87
  } = useXmppClient();
81
- const leaveRoom = useLeaveRoom();
82
88
  const {
83
89
  user: stateUser,
84
90
  config
@@ -287,25 +293,11 @@ const ChatProfileModal = ({
287
293
  // Leaving takes us out of the members on the server, then out of the MUC;
288
294
  // dropping the room from the store is what takes the user out of the chat
289
295
  // in the UI, and the `!activeRoom` effect above then closes this modal.
290
- const handleLeave = () => {
291
- Alert.alert(t('modal.leaveChat.title'), t('modal.leaveChat.description'), [{
292
- text: t('action.cancel'),
293
- style: 'cancel'
294
- }, {
295
- text: t('action.leave'),
296
- style: 'destructive',
297
- onPress: async () => {
298
- if (!(await leaveRoom(activeRoom?.jid || ''))) {
299
- Alert.alert(t('toast.error'), t('toast.leaveFailed'));
300
- return;
301
- }
302
- handleCloseModal?.();
303
- }
304
- }]);
305
- };
296
+ const handleLeave = () => setIsLeaveOpen(true);
306
297
  const isModerator = activeRoom?.role === 'moderator';
307
298
  const canEditIcon = !config?.disableChatInfo?.disableIconEdit && activeRoom?.role !== 'participant';
308
299
  const canAddMembers = isModerator && activeRoom?.type === 'group';
300
+ const showMembers = !config?.disableChatInfo?.hideMembers;
309
301
  const canDeleteChat = isModerator && activeRoom?.type !== 'private';
310
302
  const hasIcon = !!activeRoom?.icon;
311
303
  const scrollHeaderTo = y => scrollRef.current?.scrollTo?.({
@@ -322,9 +314,15 @@ const ChatProfileModal = ({
322
314
  collapsedForSearchRef.current = true;
323
315
  scrollHeaderTo(target);
324
316
  }
317
+ setActiveTab('members');
325
318
  setIsSearchOpen(true);
326
319
  setTimeout(() => searchInputRef.current?.focus(), 50);
327
320
  };
321
+
322
+ // Search looks through this chat's messages, like the room list's search
323
+ // does across chats. Where message search is off it falls back to the
324
+ // member search it used to be; members keep their own magnifier either way.
325
+ const openSearch = () => isMessageSearchEnabled(config) ? dispatch(setActiveModal(MODAL_TYPES.CHAT_MESSAGE_SEARCH)) : openMemberSearch();
328
326
  const restoreHeader = () => {
329
327
  if (!collapsedForSearchRef.current) {
330
328
  return;
@@ -380,7 +378,7 @@ const ChatProfileModal = ({
380
378
  icon: color => /*#__PURE__*/_jsx(SearchIcon, {
381
379
  color: color
382
380
  }),
383
- onPress: openMemberSearch
381
+ onPress: openSearch
384
382
  }, {
385
383
  key: 'leave',
386
384
  label: t('action.leave'),
@@ -437,7 +435,7 @@ const ChatProfileModal = ({
437
435
  width: 18,
438
436
  height: 18
439
437
  }),
440
- onPress: openMemberSearch
438
+ onPress: openSearch
441
439
  });
442
440
  }
443
441
  items.push({
@@ -548,58 +546,32 @@ const ChatProfileModal = ({
548
546
  style: styles.cardValue,
549
547
  children: activeRoom.type || '—'
550
548
  })]
551
- }), isMessageSearchEnabled(config) && /*#__PURE__*/_jsx(View, {
549
+ }), /*#__PURE__*/_jsxs(View, {
552
550
  style: styles.card,
553
- children: /*#__PURE__*/_jsxs(TouchableOpacity, {
554
- testID: "chat-profile-search-messages",
555
- activeOpacity: 0.7,
556
- style: styles.addMembersRow,
557
- onPress: () => dispatch(setActiveModal(MODAL_TYPES.MESSAGE_SEARCH)),
558
- children: [/*#__PURE__*/_jsx(SearchIcon, {
559
- color: getIconColor(config)
560
- }), /*#__PURE__*/_jsx(Text, {
561
- style: styles.addMembersLabel,
562
- children: t('search.messages.title')
563
- })]
564
- })
565
- }), !config?.disableChatInfo?.hideMembers && /*#__PURE__*/_jsxs(View, {
566
- style: styles.card,
567
- children: [canAddMembers && /*#__PURE__*/_jsx(SelectUsersModal, {
568
- trigger: open => /*#__PURE__*/_jsxs(TouchableOpacity, {
569
- testID: "chat-profile-add-members",
570
- activeOpacity: 0.7,
571
- style: styles.addMembersRow,
572
- onPress: open,
573
- children: [/*#__PURE__*/_jsx(AddNewIcon, {
574
- color: getIconColor(config)
575
- }), /*#__PURE__*/_jsx(Text, {
576
- style: styles.addMembersLabel,
577
- children: t('modal.chatProfile.addMembers')
578
- })]
579
- })
580
- }), /*#__PURE__*/_jsx(Text, {
581
- style: [styles.cardLabel, getElementFont(config, 'profileSectionLabel')],
582
- children: t('modal.chatProfile.membersLabel', {
583
- count: memberCount
584
- })
585
- }), isSearchOpen && /*#__PURE__*/_jsxs(View, {
586
- style: styles.searchRow,
587
- children: [/*#__PURE__*/_jsx(SearchIcon, {
588
- color: theme.textSecondary,
589
- width: 18,
590
- height: 18
591
- }), /*#__PURE__*/_jsx(TextInput, {
592
- testID: "chat-profile-member-search",
593
- ref: searchInputRef,
594
- style: styles.searchInput,
595
- value: memberQuery,
596
- onChangeText: setMemberQuery,
597
- placeholder: t('search.members'),
598
- placeholderTextColor: theme.textMuted,
599
- autoCorrect: false,
600
- returnKeyType: "search"
601
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
602
- testID: "chat-profile-search-close",
551
+ children: [showMembers ? /*#__PURE__*/_jsxs(View, {
552
+ style: styles.tabsRow,
553
+ children: [['members', 'media'].map(tab => {
554
+ const active = tab === activeTab;
555
+ return /*#__PURE__*/_jsx(TouchableOpacity, {
556
+ testID: `chat-profile-tab-${tab}`,
557
+ activeOpacity: 0.7,
558
+ style: [styles.tab, active && {
559
+ borderBottomColor: getIconColor(config)
560
+ }],
561
+ onPress: () => setActiveTab(tab),
562
+ children: /*#__PURE__*/_jsx(Text, {
563
+ style: [styles.tabLabel, getElementFont(config, 'profileSectionLabel'), active && {
564
+ color: getIconColor(config)
565
+ }],
566
+ children: tab === 'members' ? t('modal.chatProfile.membersLabel', {
567
+ count: memberCount
568
+ }) : t('modal.chatProfile.media')
569
+ })
570
+ }, tab);
571
+ }), activeTab === 'members' && !isSearchOpen && /*#__PURE__*/_jsx(TouchableOpacity, {
572
+ testID: "chat-profile-member-search-open",
573
+ accessibilityRole: "button",
574
+ accessibilityLabel: t('search.members'),
603
575
  activeOpacity: 0.7,
604
576
  hitSlop: {
605
577
  top: 8,
@@ -607,57 +579,52 @@ const ChatProfileModal = ({
607
579
  left: 8,
608
580
  right: 8
609
581
  },
610
- onPress: closeMemberSearch,
611
- children: /*#__PURE__*/_jsx(Text, {
612
- style: styles.searchClose,
613
- children: "\xD7"
582
+ style: styles.tabsSearch,
583
+ onPress: openMemberSearch,
584
+ children: /*#__PURE__*/_jsx(SearchIcon, {
585
+ color: theme.textSecondary,
586
+ width: 18,
587
+ height: 18
614
588
  })
615
589
  })]
616
- }), directoryLoading && /*#__PURE__*/_jsx(Text, {
617
- testID: "chat-profile-members-loading",
618
- style: styles.emptyMembers,
619
- children: t('modal.chatProfile.membersLoadingAll')
620
- }), loading ? /*#__PURE__*/_jsx(Loader, {}) : visibleMembers.length === 0 ? /*#__PURE__*/_jsx(Text, {
621
- testID: "chat-profile-members-empty",
622
- style: styles.emptyMembers,
623
- children: members.length === 0 ? t('modal.chatProfile.membersUnavailable') : t('search.noResults')
624
- }) : visibleMembers.map((user, index) => {
625
- const banned = user.ban_status === 'banned';
626
- const roleLabel = user.role && user.role !== 'none' && user.role !== 'participant' ? user.role : null;
627
- return /*#__PURE__*/_jsxs(View, {
628
- style: [styles.memberRow, index > 0 && styles.memberDivider],
629
- children: [/*#__PURE__*/_jsxs(Pressable, {
630
- style: styles.memberMain
631
- // `disableChatInfo.disableMemberTap` blocks the tap
632
- // entirely so the user-profile popup never opens.
633
- // `disableMemberProfileActions` only hid the action
634
- // buttons INSIDE that popup, leaving the popup itself
635
- // to open on tap — this gate closes the door.
636
- // Customer-reported #16.
637
- ,
638
- onPress: config?.disableChatInfo?.disableMemberTap ? undefined : () => handleUserAvatarClick(user),
639
- disabled: !!config?.disableChatInfo?.disableMemberTap,
640
- children: [/*#__PURE__*/_jsx(ProfileImagePlaceholder, {
641
- name: composeName(user.firstName, user.lastName),
642
- icon: appendFileToken(user.profileImage, fileToken) || null,
643
- size: 40
644
- }), /*#__PURE__*/_jsxs(View, {
645
- style: styles.memberText,
646
- children: [/*#__PURE__*/_jsxs(Text, {
647
- numberOfLines: 1,
648
- style: [styles.memberName, chatTextStyle(config?.typography?.profile?.memberName)],
649
- children: [user.firstName, " ", user.lastName]
650
- }), !!user.last_active && /*#__PURE__*/_jsx(Text, {
651
- numberOfLines: 1,
652
- style: [styles.memberStatus, getElementFont(config, 'profileSectionLabel')],
653
- children: new Date(user.last_active * 1000).toLocaleString()
654
- })]
590
+ }) : /*#__PURE__*/_jsx(Text, {
591
+ style: [styles.cardLabel, getElementFont(config, 'profileSectionLabel')],
592
+ children: t('modal.chatProfile.media')
593
+ }), !showMembers || activeTab === 'media' ? /*#__PURE__*/_jsx(ChatMediaGrid, {
594
+ roomJid: activeRoom.jid,
595
+ onOpen: setViewerFile
596
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
597
+ children: [canAddMembers && /*#__PURE__*/_jsx(SelectUsersModal, {
598
+ trigger: open => /*#__PURE__*/_jsxs(TouchableOpacity, {
599
+ testID: "chat-profile-add-members",
600
+ activeOpacity: 0.7,
601
+ style: styles.addMembersRow,
602
+ onPress: open,
603
+ children: [/*#__PURE__*/_jsx(AddNewIcon, {
604
+ color: getIconColor(config)
605
+ }), /*#__PURE__*/_jsx(Text, {
606
+ style: styles.addMembersLabel,
607
+ children: t('modal.chatProfile.addMembers')
655
608
  })]
656
- }), (banned || roleLabel) && /*#__PURE__*/_jsx(Text, {
657
- style: [styles.memberRole, banned && styles.memberBanned],
658
- children: banned ? t('status.banned') : roleLabel
659
- }), isModerator && activeRoom.type !== 'private' && stateUser.xmppUsername !== user.xmppUsername && /*#__PURE__*/_jsx(TouchableOpacity, {
660
- testID: `chat-profile-remove-${user.xmppUsername}`,
609
+ })
610
+ }), isSearchOpen && /*#__PURE__*/_jsxs(View, {
611
+ style: styles.searchRow,
612
+ children: [/*#__PURE__*/_jsx(SearchIcon, {
613
+ color: theme.textSecondary,
614
+ width: 18,
615
+ height: 18
616
+ }), /*#__PURE__*/_jsx(TextInput, {
617
+ testID: "chat-profile-member-search",
618
+ ref: searchInputRef,
619
+ style: styles.searchInput,
620
+ value: memberQuery,
621
+ onChangeText: setMemberQuery,
622
+ placeholder: t('search.members'),
623
+ placeholderTextColor: theme.textMuted,
624
+ autoCorrect: false,
625
+ returnKeyType: "search"
626
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
627
+ testID: "chat-profile-search-close",
661
628
  activeOpacity: 0.7,
662
629
  hitSlop: {
663
630
  top: 8,
@@ -665,23 +632,82 @@ const ChatProfileModal = ({
665
632
  left: 8,
666
633
  right: 8
667
634
  },
668
- style: styles.memberRemove,
669
- onPress: () => confirmRemoveMember(user),
670
- children: /*#__PURE__*/_jsx(DeleteIcon, {
671
- width: 20,
672
- height: 20
635
+ onPress: closeMemberSearch,
636
+ children: /*#__PURE__*/_jsx(Text, {
637
+ style: styles.searchClose,
638
+ children: "\xD7"
673
639
  })
674
640
  })]
675
- }, user.xmppUsername);
676
- }), hiddenMemberCount > 0 && /*#__PURE__*/_jsx(TouchableOpacity, {
677
- testID: "chat-profile-members-show-more",
678
- activeOpacity: 0.7,
679
- style: styles.showMoreRow,
680
- onPress: () => setVisibleCount(c => c + MEMBER_PAGE_SIZE),
681
- children: /*#__PURE__*/_jsx(Text, {
682
- style: styles.addMembersLabel,
683
- children: t('search.messages.loadMore')
684
- })
641
+ }), directoryLoading && /*#__PURE__*/_jsx(Text, {
642
+ testID: "chat-profile-members-loading",
643
+ style: styles.emptyMembers,
644
+ children: t('modal.chatProfile.membersLoadingAll')
645
+ }), loading ? /*#__PURE__*/_jsx(Loader, {}) : visibleMembers.length === 0 ? /*#__PURE__*/_jsx(Text, {
646
+ testID: "chat-profile-members-empty",
647
+ style: styles.emptyMembers,
648
+ children: members.length === 0 ? t('modal.chatProfile.membersUnavailable') : t('search.noResults')
649
+ }) : visibleMembers.map((user, index) => {
650
+ const banned = user.ban_status === 'banned';
651
+ const roleLabel = user.role && user.role !== 'none' && user.role !== 'participant' ? user.role : null;
652
+ return /*#__PURE__*/_jsxs(View, {
653
+ style: [styles.memberRow, index > 0 && styles.memberDivider],
654
+ children: [/*#__PURE__*/_jsxs(Pressable, {
655
+ style: styles.memberMain
656
+ // `disableChatInfo.disableMemberTap` blocks the tap
657
+ // entirely so the user-profile popup never opens.
658
+ // `disableMemberProfileActions` only hid the action
659
+ // buttons INSIDE that popup, leaving the popup itself
660
+ // to open on tap — this gate closes the door.
661
+ // Customer-reported #16.
662
+ ,
663
+ onPress: config?.disableChatInfo?.disableMemberTap ? undefined : () => handleUserAvatarClick(user),
664
+ disabled: !!config?.disableChatInfo?.disableMemberTap,
665
+ children: [/*#__PURE__*/_jsx(ProfileImagePlaceholder, {
666
+ name: composeName(user.firstName, user.lastName),
667
+ icon: appendFileToken(user.profileImage, fileToken) || null,
668
+ size: 40
669
+ }), /*#__PURE__*/_jsxs(View, {
670
+ style: styles.memberText,
671
+ children: [/*#__PURE__*/_jsxs(Text, {
672
+ numberOfLines: 1,
673
+ style: [styles.memberName, chatTextStyle(config?.typography?.profile?.memberName)],
674
+ children: [user.firstName, " ", user.lastName]
675
+ }), !!user.last_active && /*#__PURE__*/_jsx(Text, {
676
+ numberOfLines: 1,
677
+ style: [styles.memberStatus, getElementFont(config, 'profileSectionLabel')],
678
+ children: new Date(user.last_active * 1000).toLocaleString()
679
+ })]
680
+ })]
681
+ }), (banned || roleLabel) && /*#__PURE__*/_jsx(Text, {
682
+ style: [styles.memberRole, banned && styles.memberBanned],
683
+ children: banned ? t('status.banned') : roleLabel
684
+ }), isModerator && activeRoom.type !== 'private' && stateUser.xmppUsername !== user.xmppUsername && /*#__PURE__*/_jsx(TouchableOpacity, {
685
+ testID: `chat-profile-remove-${user.xmppUsername}`,
686
+ activeOpacity: 0.7,
687
+ hitSlop: {
688
+ top: 8,
689
+ bottom: 8,
690
+ left: 8,
691
+ right: 8
692
+ },
693
+ style: styles.memberRemove,
694
+ onPress: () => confirmRemoveMember(user),
695
+ children: /*#__PURE__*/_jsx(DeleteIcon, {
696
+ width: 20,
697
+ height: 20
698
+ })
699
+ })]
700
+ }, user.xmppUsername);
701
+ }), hiddenMemberCount > 0 && /*#__PURE__*/_jsx(TouchableOpacity, {
702
+ testID: "chat-profile-members-show-more",
703
+ activeOpacity: 0.7,
704
+ style: styles.showMoreRow,
705
+ onPress: () => setVisibleCount(c => c + MEMBER_PAGE_SIZE),
706
+ children: /*#__PURE__*/_jsx(Text, {
707
+ style: styles.addMembersLabel,
708
+ children: t('search.messages.loadMore')
709
+ })
710
+ })]
685
711
  })]
686
712
  })]
687
713
  })]
@@ -703,6 +729,27 @@ const ChatProfileModal = ({
703
729
  }), /*#__PURE__*/_jsx(ReportChatModal, {
704
730
  visible: isReportOpen,
705
731
  onClose: () => setIsReportOpen(false)
732
+ }), /*#__PURE__*/_jsx(LeaveChatModal, {
733
+ roomJid: isLeaveOpen && activeRoom?.jid ? activeRoom.jid : null,
734
+ onClose: () => setIsLeaveOpen(false),
735
+ onLeft: () => handleCloseModal?.()
736
+ }), viewerFile && /*#__PURE__*/_jsx(MediaViewer, {
737
+ file: viewerFile,
738
+ onClose: () => setViewerFile(null),
739
+ onPage: index => {
740
+ const next = viewerFile.siblings?.[index];
741
+ if (next) {
742
+ setViewerFile({
743
+ ...next,
744
+ siblings: viewerFile.siblings,
745
+ index
746
+ });
747
+ }
748
+ },
749
+ onSave: () => saveFile(viewerFile)
750
+ // Reply opens the thread in the chat underneath this screen.
751
+ ,
752
+ onReplyOpened: () => handleCloseModal?.()
706
753
  })]
707
754
  });
708
755
  };
@@ -722,6 +769,27 @@ const createStyles = theme => StyleSheet.create({
722
769
  padding: 12,
723
770
  gap: 12
724
771
  },
772
+ tabsRow: {
773
+ flexDirection: 'row',
774
+ gap: 20,
775
+ borderBottomWidth: StyleSheet.hairlineWidth,
776
+ borderBottomColor: theme.border,
777
+ marginBottom: 12
778
+ },
779
+ tabsSearch: {
780
+ marginLeft: 'auto',
781
+ paddingBottom: 10
782
+ },
783
+ tab: {
784
+ paddingBottom: 10,
785
+ borderBottomWidth: 2,
786
+ borderBottomColor: 'transparent'
787
+ },
788
+ tabLabel: {
789
+ fontSize: 15,
790
+ fontWeight: '500',
791
+ color: theme.textSecondary
792
+ },
725
793
  card: {
726
794
  backgroundColor: theme.surface,
727
795
  borderRadius: 14,