@ethora/chat-component-rn 26.7.3 → 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.
- package/CHANGELOG.md +45 -0
- package/README.md +9 -8
- package/lib/commonjs/assets/icons.js +7 -2
- package/lib/commonjs/assets/icons.js.map +1 -1
- package/lib/commonjs/components/Header/HeaderRoomList.js +20 -6
- package/lib/commonjs/components/Header/HeaderRoomList.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatRoom.js +26 -4
- package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MessageList.js +2 -1
- package/lib/commonjs/components/MainComponents/MessageList.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ProfileImagePlaceholder.js +5 -5
- package/lib/commonjs/components/MainComponents/ProfileImagePlaceholder.js.map +1 -1
- package/lib/commonjs/components/MainComponents/RoomList.js +62 -32
- package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
- package/lib/commonjs/components/Menu/HeaderRoomListMenu.js +208 -70
- package/lib/commonjs/components/Menu/HeaderRoomListMenu.js.map +1 -1
- package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js +307 -99
- package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +552 -213
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ReportChatModal.js +226 -0
- package/lib/commonjs/components/Modals/ChatProfileModal/ReportChatModal.js.map +1 -0
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +6 -2
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/commonjs/components/Modals/Modal/Modal.js +8 -0
- package/lib/commonjs/components/Modals/Modal/Modal.js.map +1 -1
- package/lib/commonjs/components/Modals/NewChatModal/NewChatModal.js +202 -104
- package/lib/commonjs/components/Modals/NewChatModal/NewChatModal.js.map +1 -1
- package/lib/commonjs/components/Modals/ProfileHeader/ProfileHeader.js +556 -0
- package/lib/commonjs/components/Modals/ProfileHeader/ProfileHeader.js.map +1 -0
- package/lib/commonjs/components/Modals/ProfileHeader/ProfileMenu.js +210 -0
- package/lib/commonjs/components/Modals/ProfileHeader/ProfileMenu.js.map +1 -0
- package/lib/commonjs/components/Modals/SelectUsersModal/SelectUsersModal.js +4 -2
- package/lib/commonjs/components/Modals/SelectUsersModal/SelectUsersModal.js.map +1 -1
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +507 -244
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/commonjs/components/Modals/UserSettingsModal/UserSettingsModal.js +74 -46
- package/lib/commonjs/components/Modals/UserSettingsModal/UserSettingsModal.js.map +1 -1
- package/lib/commonjs/components/Modals/styledModalComponents.js +9 -2
- package/lib/commonjs/components/Modals/styledModalComponents.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/ChatRoomItem.js +17 -7
- package/lib/commonjs/components/RoomComponents/ChatRoomItem.js.map +1 -1
- package/lib/commonjs/components/styled/SendInput.js +80 -1
- package/lib/commonjs/components/styled/SendInput.js.map +1 -1
- package/lib/commonjs/components/styled/StyledComponents.js +9 -3
- package/lib/commonjs/components/styled/StyledComponents.js.map +1 -1
- package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js +11 -4
- package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
- package/lib/commonjs/helpers/getChatBackground.js +23 -0
- package/lib/commonjs/helpers/getChatBackground.js.map +1 -0
- package/lib/commonjs/helpers/mediaLibraryRuntime.js +31 -0
- package/lib/commonjs/helpers/mediaLibraryRuntime.js.map +1 -0
- package/lib/commonjs/helpers/sheetGestures.js +32 -0
- package/lib/commonjs/helpers/sheetGestures.js.map +1 -0
- package/lib/commonjs/i18n/strings.js +121 -1
- package/lib/commonjs/i18n/strings.js.map +1 -1
- package/lib/commonjs/networking/api-requests/user.api.js +59 -0
- package/lib/commonjs/networking/api-requests/user.api.js.map +1 -1
- package/lib/commonjs/roomStore/chatSettingsSlice.js +3 -1
- package/lib/commonjs/roomStore/chatSettingsSlice.js.map +1 -1
- package/lib/module/assets/icons.js +6 -1
- package/lib/module/assets/icons.js.map +1 -1
- package/lib/module/components/Header/HeaderRoomList.js +20 -6
- package/lib/module/components/Header/HeaderRoomList.js.map +1 -1
- package/lib/module/components/MainComponents/ChatRoom.js +26 -4
- package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/module/components/MainComponents/MessageList.js +2 -1
- package/lib/module/components/MainComponents/MessageList.js.map +1 -1
- package/lib/module/components/MainComponents/ProfileImagePlaceholder.js +5 -5
- package/lib/module/components/MainComponents/ProfileImagePlaceholder.js.map +1 -1
- package/lib/module/components/MainComponents/RoomList.js +62 -32
- package/lib/module/components/MainComponents/RoomList.js.map +1 -1
- package/lib/module/components/Menu/HeaderRoomListMenu.js +211 -73
- package/lib/module/components/Menu/HeaderRoomListMenu.js.map +1 -1
- package/lib/module/components/Modals/AttachSheet/AttachSheet.js +298 -102
- package/lib/module/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +555 -217
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ReportChatModal.js +223 -0
- package/lib/module/components/Modals/ChatProfileModal/ReportChatModal.js.map +1 -0
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +6 -2
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/module/components/Modals/Modal/Modal.js +9 -1
- package/lib/module/components/Modals/Modal/Modal.js.map +1 -1
- package/lib/module/components/Modals/NewChatModal/NewChatModal.js +205 -106
- package/lib/module/components/Modals/NewChatModal/NewChatModal.js.map +1 -1
- package/lib/module/components/Modals/ProfileHeader/ProfileHeader.js +548 -0
- package/lib/module/components/Modals/ProfileHeader/ProfileHeader.js.map +1 -0
- package/lib/module/components/Modals/ProfileHeader/ProfileMenu.js +205 -0
- package/lib/module/components/Modals/ProfileHeader/ProfileMenu.js.map +1 -0
- package/lib/module/components/Modals/SelectUsersModal/SelectUsersModal.js +4 -2
- package/lib/module/components/Modals/SelectUsersModal/SelectUsersModal.js.map +1 -1
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +509 -248
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/module/components/Modals/UserSettingsModal/UserSettingsModal.js +76 -48
- package/lib/module/components/Modals/UserSettingsModal/UserSettingsModal.js.map +1 -1
- package/lib/module/components/Modals/styledModalComponents.js +9 -2
- package/lib/module/components/Modals/styledModalComponents.js.map +1 -1
- package/lib/module/components/RoomComponents/ChatRoomItem.js +17 -7
- package/lib/module/components/RoomComponents/ChatRoomItem.js.map +1 -1
- package/lib/module/components/styled/SendInput.js +80 -1
- package/lib/module/components/styled/SendInput.js.map +1 -1
- package/lib/module/components/styled/StyledComponents.js +9 -3
- package/lib/module/components/styled/StyledComponents.js.map +1 -1
- package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js +11 -4
- package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
- package/lib/module/helpers/getChatBackground.js +18 -0
- package/lib/module/helpers/getChatBackground.js.map +1 -0
- package/lib/module/helpers/mediaLibraryRuntime.js +25 -0
- package/lib/module/helpers/mediaLibraryRuntime.js.map +1 -0
- package/lib/module/helpers/sheetGestures.js +26 -0
- package/lib/module/helpers/sheetGestures.js.map +1 -0
- package/lib/module/i18n/strings.js +121 -1
- package/lib/module/i18n/strings.js.map +1 -1
- package/lib/module/networking/api-requests/user.api.js +56 -0
- package/lib/module/networking/api-requests/user.api.js.map +1 -1
- package/lib/module/roomStore/chatSettingsSlice.js +3 -1
- package/lib/module/roomStore/chatSettingsSlice.js.map +1 -1
- package/lib/typescript/assets/icons.d.ts +12 -0
- package/lib/typescript/assets/icons.d.ts.map +1 -1
- package/lib/typescript/components/Header/HeaderRoomList.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MessageList.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
- package/lib/typescript/components/Menu/HeaderRoomListMenu.d.ts.map +1 -1
- package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts +9 -0
- package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts +10 -0
- package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ReportChatModal.d.ts +9 -0
- package/lib/typescript/components/Modals/ChatProfileModal/ReportChatModal.d.ts.map +1 -0
- package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/Modal/Modal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/NewChatModal/NewChatModal.d.ts +1 -1
- package/lib/typescript/components/Modals/NewChatModal/NewChatModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ProfileHeader/ProfileHeader.d.ts +75 -0
- package/lib/typescript/components/Modals/ProfileHeader/ProfileHeader.d.ts.map +1 -0
- package/lib/typescript/components/Modals/ProfileHeader/ProfileMenu.d.ts +25 -0
- package/lib/typescript/components/Modals/ProfileHeader/ProfileMenu.d.ts.map +1 -0
- package/lib/typescript/components/Modals/SelectUsersModal/SelectUsersModal.d.ts +6 -1
- package/lib/typescript/components/Modals/SelectUsersModal/SelectUsersModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts +16 -0
- package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/UserSettingsModal/UserSettingsModal.d.ts +1 -0
- package/lib/typescript/components/Modals/UserSettingsModal/UserSettingsModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/styledModalComponents.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/ChatRoomItem.d.ts.map +1 -1
- package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
- package/lib/typescript/components/styled/StyledComponents.d.ts.map +1 -1
- package/lib/typescript/components/styled/StyledInputComponents/StyledInputComponents.d.ts.map +1 -1
- package/lib/typescript/helpers/getChatBackground.d.ts +15 -0
- package/lib/typescript/helpers/getChatBackground.d.ts.map +1 -0
- package/lib/typescript/helpers/mediaLibraryRuntime.d.ts +3 -0
- package/lib/typescript/helpers/mediaLibraryRuntime.d.ts.map +1 -0
- package/lib/typescript/helpers/sheetGestures.d.ts +20 -0
- package/lib/typescript/helpers/sheetGestures.d.ts.map +1 -0
- package/lib/typescript/i18n/strings.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/user.api.d.ts +27 -0
- package/lib/typescript/networking/api-requests/user.api.d.ts.map +1 -1
- package/lib/typescript/roomStore/chatSettingsSlice.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +8 -5
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/assets/icons.tsx +29 -9
- package/src/components/Header/HeaderRoomList.tsx +20 -5
- package/src/components/MainComponents/ChatRoom.tsx +29 -7
- package/src/components/MainComponents/MessageList.tsx +2 -1
- package/src/components/MainComponents/ProfileImagePlaceholder.tsx +5 -5
- package/src/components/MainComponents/RoomList.tsx +65 -34
- package/src/components/Menu/HeaderRoomListMenu.tsx +209 -64
- package/src/components/Modals/AttachSheet/AttachSheet.tsx +318 -86
- package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +598 -279
- package/src/components/Modals/ChatProfileModal/ReportChatModal.tsx +244 -0
- package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +4 -2
- package/src/components/Modals/Modal/Modal.tsx +7 -0
- package/src/components/Modals/NewChatModal/NewChatModal.tsx +219 -117
- package/src/components/Modals/ProfileHeader/ProfileHeader.tsx +615 -0
- package/src/components/Modals/ProfileHeader/ProfileMenu.tsx +217 -0
- package/src/components/Modals/SelectUsersModal/SelectUsersModal.tsx +14 -4
- package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +617 -307
- package/src/components/Modals/UserSettingsModal/UserSettingsModal.tsx +75 -58
- package/src/components/Modals/styledModalComponents.tsx +9 -2
- package/src/components/RoomComponents/ChatRoomItem.tsx +14 -7
- package/src/components/styled/SendInput.tsx +83 -2
- package/src/components/styled/StyledComponents.ts +9 -3
- package/src/components/styled/StyledInputComponents/StyledInputComponents.tsx +11 -4
- package/src/helpers/getChatBackground.ts +18 -0
- package/src/helpers/mediaLibraryRuntime.ts +25 -0
- package/src/helpers/sheetGestures.ts +25 -0
- package/src/i18n/strings.ts +121 -1
- package/src/networking/api-requests/user.api.ts +71 -0
- package/src/roomStore/chatSettingsSlice.ts +3 -1
- package/src/types/types.ts +8 -5
|
@@ -1,43 +1,78 @@
|
|
|
1
|
-
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
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 [
|
|
51
|
-
const [
|
|
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: '
|
|
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
|
|
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
|
-
(
|
|
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
|
|
211
|
-
|
|
212
|
-
|
|
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,270 +289,527 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
227
289
|
);
|
|
228
290
|
};
|
|
229
291
|
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
+
() => [
|
|
240
360
|
{
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
console.log('Settings clicked');
|
|
246
|
-
},
|
|
361
|
+
key: 'search',
|
|
362
|
+
label: t('action.search'),
|
|
363
|
+
icon: (color: string) => <SearchIcon color={color} />,
|
|
364
|
+
onPress: openMemberSearch,
|
|
247
365
|
},
|
|
248
366
|
{
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
367
|
+
key: 'leave',
|
|
368
|
+
label: t('action.leave'),
|
|
369
|
+
icon: (color: string) => <LeaveIcon color={color} />,
|
|
370
|
+
onPress: handleLeave,
|
|
371
|
+
},
|
|
372
|
+
{
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
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={
|
|
272
|
-
<
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
<ScrollView
|
|
299
|
-
style={{ width: '100%', flex: 1 }}
|
|
300
|
-
contentContainerStyle={{ flexGrow: 1 }}
|
|
301
|
-
showsVerticalScrollIndicator={true}
|
|
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
|
+
)}
|
|
302
487
|
>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
upload={{
|
|
314
|
-
onUpload,
|
|
315
|
-
active:
|
|
316
|
-
!config?.disableChatInfo?.disableIconEdit &&
|
|
317
|
-
activeRoom?.role !== 'participant',
|
|
318
|
-
}}
|
|
319
|
-
remove={{
|
|
320
|
-
enabled: !config?.disableChatInfo?.disableIconEdit,
|
|
321
|
-
onRemoveClick,
|
|
322
|
-
}}
|
|
323
|
-
role={activeRoom?.role}
|
|
324
|
-
size={128}
|
|
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')}
|
|
325
498
|
/>
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
>
|
|
395
|
-
<View
|
|
396
|
-
style={{
|
|
397
|
-
flexDirection: 'row',
|
|
398
|
-
justifyContent: 'flex-start',
|
|
399
|
-
paddingHorizontal: 8,
|
|
400
|
-
paddingVertical: 0,
|
|
401
|
-
alignItems: 'center',
|
|
402
|
-
width: '100%',
|
|
403
|
-
}}
|
|
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
|
+
)}
|
|
536
|
+
/>
|
|
537
|
+
)}
|
|
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}
|
|
404
567
|
>
|
|
405
|
-
<
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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]}
|
|
419
592
|
>
|
|
420
|
-
<
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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}
|
|
431
607
|
>
|
|
432
|
-
<
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
chatTextStyle(config?.typography?.profile?.memberName),
|
|
436
|
-
]}
|
|
437
|
-
>
|
|
438
|
-
{user.firstName} {user.lastName}
|
|
439
|
-
</Label>
|
|
440
|
-
{user.last_active && (
|
|
441
|
-
<LabelData style={getElementFont(config, 'profileSectionLabel')}>
|
|
442
|
-
{new Date(user.last_active * 1000).toLocaleString()}
|
|
443
|
-
</LabelData>
|
|
444
|
-
)}
|
|
445
|
-
</View>
|
|
446
|
-
</Pressable>
|
|
447
|
-
{user.role && user.role !== 'none' && (
|
|
448
|
-
<Text
|
|
449
|
-
style={{
|
|
450
|
-
backgroundColor:
|
|
451
|
-
user.ban_status !== 'banned' ? '#F3F6FC' : '#FFEBEE',
|
|
452
|
-
color:
|
|
453
|
-
user.ban_status !== 'banned' ? '#0052CD' : '#F44336',
|
|
454
|
-
padding: 5,
|
|
455
|
-
borderRadius: 16,
|
|
456
|
-
fontSize: 12,
|
|
457
|
-
}}
|
|
458
|
-
>
|
|
459
|
-
{user.role}
|
|
460
|
-
</Text>
|
|
461
|
-
)}
|
|
462
|
-
{stateUser.xmppUsername !== user.xmppUsername &&
|
|
463
|
-
activeRoom.role === 'moderator' &&
|
|
464
|
-
activeRoom.type !== 'private' && (
|
|
465
|
-
<DropdownMenu
|
|
466
|
-
options={menuOptions(user.xmppUsername) as MenuOption[]}
|
|
467
|
-
openButton={
|
|
468
|
-
<Button
|
|
469
|
-
onPress={() => {}}
|
|
470
|
-
>
|
|
471
|
-
<Text>More Options</Text>
|
|
472
|
-
</Button>
|
|
473
|
-
}
|
|
474
|
-
onClose={() => console.log('Dropdown closed')}
|
|
608
|
+
<ProfileImagePlaceholder
|
|
609
|
+
name={`${user.firstName} ${user.lastName}`}
|
|
610
|
+
size={40}
|
|
475
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>
|
|
476
642
|
)}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
|
+
)}
|
|
481
661
|
</View>
|
|
482
662
|
)}
|
|
483
|
-
</
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
+
/>
|
|
487
677
|
|
|
488
678
|
<DeleteChatModal
|
|
489
|
-
isModalOpen={
|
|
490
|
-
setIsModalOpen={
|
|
679
|
+
isModalOpen={isDeleteOpen}
|
|
680
|
+
setIsModalOpen={setIsDeleteOpen}
|
|
681
|
+
/>
|
|
682
|
+
<ReportChatModal
|
|
683
|
+
visible={isReportOpen}
|
|
684
|
+
onClose={() => setIsReportOpen(false)}
|
|
491
685
|
/>
|
|
492
686
|
</ModalContainerFullScreen>
|
|
493
687
|
);
|
|
494
688
|
};
|
|
495
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
|
+
|
|
496
815
|
export default ChatProfileModal;
|