@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.
- package/CHANGELOG.md +1 -0
- package/README.md +1 -1
- package/lib/commonjs/components/MainComponents/AlbumGrid.js +6 -2
- package/lib/commonjs/components/MainComponents/AlbumGrid.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatHeader.js +16 -11
- package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MediaMessage.js +13 -0
- package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/commonjs/components/MainComponents/RoomList.js +7 -16
- package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
- package/lib/commonjs/components/MessageBubble/Message.js +27 -1
- package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatMediaGrid.js +159 -0
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatMediaGrid.js.map +1 -0
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +203 -134
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/LeaveChatModal.js +180 -0
- package/lib/commonjs/components/Modals/ChatProfileModal/LeaveChatModal.js.map +1 -0
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +100 -162
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/commonjs/components/Modals/FilePreviewModal/MediaViewer.js +824 -0
- package/lib/commonjs/components/Modals/FilePreviewModal/MediaViewer.js.map +1 -0
- package/lib/commonjs/components/Modals/FilePreviewModal/useSaveFile.js +179 -0
- package/lib/commonjs/components/Modals/FilePreviewModal/useSaveFile.js.map +1 -0
- package/lib/commonjs/components/Modals/MessageSearchModal/MessageSearchModal.js +22 -5
- package/lib/commonjs/components/Modals/MessageSearchModal/MessageSearchModal.js.map +1 -1
- package/lib/commonjs/components/styled/MessageImage.js +4 -1
- package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
- package/lib/commonjs/components/styled/VideoMessage.js +5 -1
- package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
- package/lib/commonjs/context/MediaSourceContext.js +18 -0
- package/lib/commonjs/context/MediaSourceContext.js.map +1 -0
- package/lib/commonjs/helpers/constants/MODAL_COMPONENTS.js +3 -1
- package/lib/commonjs/helpers/constants/MODAL_COMPONENTS.js.map +1 -1
- package/lib/commonjs/helpers/constants/MODAL_TYPES.js +2 -0
- package/lib/commonjs/helpers/constants/MODAL_TYPES.js.map +1 -1
- package/lib/commonjs/helpers/publicChatsEntry.js +8 -6
- package/lib/commonjs/helpers/publicChatsEntry.js.map +1 -1
- package/lib/commonjs/helpers/roomMedia.js +118 -0
- package/lib/commonjs/helpers/roomMedia.js.map +1 -0
- package/lib/commonjs/i18n/strings.js +36 -0
- package/lib/commonjs/i18n/strings.js.map +1 -1
- package/lib/commonjs/networking/api-requests/rooms.api.js +9 -13
- package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/commonjs/version.js +1 -1
- package/lib/commonjs/version.js.map +1 -1
- package/lib/module/components/MainComponents/AlbumGrid.js +6 -2
- package/lib/module/components/MainComponents/AlbumGrid.js.map +1 -1
- package/lib/module/components/MainComponents/ChatHeader.js +18 -13
- package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/module/components/MainComponents/MediaMessage.js +13 -0
- package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/module/components/MainComponents/RoomList.js +7 -16
- package/lib/module/components/MainComponents/RoomList.js.map +1 -1
- package/lib/module/components/MessageBubble/Message.js +27 -1
- package/lib/module/components/MessageBubble/Message.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ChatMediaGrid.js +154 -0
- package/lib/module/components/Modals/ChatProfileModal/ChatMediaGrid.js.map +1 -0
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +204 -136
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/LeaveChatModal.js +175 -0
- package/lib/module/components/Modals/ChatProfileModal/LeaveChatModal.js.map +1 -0
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +100 -163
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/module/components/Modals/FilePreviewModal/MediaViewer.js +819 -0
- package/lib/module/components/Modals/FilePreviewModal/MediaViewer.js.map +1 -0
- package/lib/module/components/Modals/FilePreviewModal/useSaveFile.js +172 -0
- package/lib/module/components/Modals/FilePreviewModal/useSaveFile.js.map +1 -0
- package/lib/module/components/Modals/MessageSearchModal/MessageSearchModal.js +20 -4
- package/lib/module/components/Modals/MessageSearchModal/MessageSearchModal.js.map +1 -1
- package/lib/module/components/styled/MessageImage.js +4 -1
- package/lib/module/components/styled/MessageImage.js.map +1 -1
- package/lib/module/components/styled/VideoMessage.js +5 -1
- package/lib/module/components/styled/VideoMessage.js.map +1 -1
- package/lib/module/context/MediaSourceContext.js +13 -0
- package/lib/module/context/MediaSourceContext.js.map +1 -0
- package/lib/module/helpers/constants/MODAL_COMPONENTS.js +2 -1
- package/lib/module/helpers/constants/MODAL_COMPONENTS.js.map +1 -1
- package/lib/module/helpers/constants/MODAL_TYPES.js +2 -0
- package/lib/module/helpers/constants/MODAL_TYPES.js.map +1 -1
- package/lib/module/helpers/publicChatsEntry.js +8 -6
- package/lib/module/helpers/publicChatsEntry.js.map +1 -1
- package/lib/module/helpers/roomMedia.js +112 -0
- package/lib/module/helpers/roomMedia.js.map +1 -0
- package/lib/module/i18n/strings.js +36 -0
- package/lib/module/i18n/strings.js.map +1 -1
- package/lib/module/networking/api-requests/rooms.api.js +9 -13
- package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/module/version.js.map +1 -1
- package/lib/typescript/components/MainComponents/AlbumGrid.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MediaMessage.d.ts +3 -1
- package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ChatMediaGrid.d.ts +16 -0
- package/lib/typescript/components/Modals/ChatProfileModal/ChatMediaGrid.d.ts.map +1 -0
- package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/LeaveChatModal.d.ts +18 -0
- package/lib/typescript/components/Modals/ChatProfileModal/LeaveChatModal.d.ts.map +1 -0
- package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts +16 -0
- package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/FilePreviewModal/MediaViewer.d.ts +27 -0
- package/lib/typescript/components/Modals/FilePreviewModal/MediaViewer.d.ts.map +1 -0
- package/lib/typescript/components/Modals/FilePreviewModal/useSaveFile.d.ts +9 -0
- package/lib/typescript/components/Modals/FilePreviewModal/useSaveFile.d.ts.map +1 -0
- package/lib/typescript/components/Modals/MessageSearchModal/MessageSearchModal.d.ts +8 -0
- package/lib/typescript/components/Modals/MessageSearchModal/MessageSearchModal.d.ts.map +1 -1
- package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
- package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
- package/lib/typescript/context/MediaSourceContext.d.ts +4 -0
- package/lib/typescript/context/MediaSourceContext.d.ts.map +1 -0
- package/lib/typescript/helpers/constants/MODAL_COMPONENTS.d.ts.map +1 -1
- package/lib/typescript/helpers/constants/MODAL_TYPES.d.ts +2 -0
- package/lib/typescript/helpers/constants/MODAL_TYPES.d.ts.map +1 -1
- package/lib/typescript/helpers/publicChatsEntry.d.ts +7 -5
- package/lib/typescript/helpers/publicChatsEntry.d.ts.map +1 -1
- package/lib/typescript/helpers/roomMedia.d.ts +26 -0
- package/lib/typescript/helpers/roomMedia.d.ts.map +1 -0
- package/lib/typescript/i18n/strings.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/rooms.api.d.ts +6 -5
- package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
- package/lib/typescript/types/models/modal.model.d.ts +18 -0
- package/lib/typescript/types/models/modal.model.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +18 -0
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/lib/typescript/version.d.ts +1 -1
- package/lib/typescript/version.d.ts.map +1 -1
- package/package.json +9 -2
- package/src/components/MainComponents/AlbumGrid.tsx +5 -1
- package/src/components/MainComponents/ChatHeader.tsx +16 -11
- package/src/components/MainComponents/MediaMessage.tsx +13 -2
- package/src/components/MainComponents/RoomList.tsx +8 -19
- package/src/components/MessageBubble/Message.tsx +24 -1
- package/src/components/Modals/ChatProfileModal/ChatMediaGrid.tsx +174 -0
- package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +119 -48
- package/src/components/Modals/ChatProfileModal/LeaveChatModal.tsx +191 -0
- package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +115 -185
- package/src/components/Modals/FilePreviewModal/MediaViewer.tsx +880 -0
- package/src/components/Modals/FilePreviewModal/useSaveFile.ts +207 -0
- package/src/components/Modals/MessageSearchModal/MessageSearchModal.tsx +56 -30
- package/src/components/styled/MessageImage.tsx +3 -1
- package/src/components/styled/VideoMessage.tsx +5 -1
- package/src/context/MediaSourceContext.tsx +14 -0
- package/src/helpers/constants/MODAL_COMPONENTS.tsx +4 -1
- package/src/helpers/constants/MODAL_TYPES.ts +2 -0
- package/src/helpers/publicChatsEntry.ts +8 -7
- package/src/helpers/roomMedia.ts +126 -0
- package/src/i18n/strings.ts +36 -0
- package/src/networking/api-requests/rooms.api.ts +13 -12
- package/src/types/models/modal.model.ts +16 -0
- package/src/types/types.ts +16 -0
- package/src/version.ts +1 -1
|
@@ -49,7 +49,7 @@ import { MODAL_TYPES } from '../../../helpers/constants/MODAL_TYPES';
|
|
|
49
49
|
import SelectUsersModal from '../SelectUsersModal/SelectUsersModal';
|
|
50
50
|
import { useToast } from '../../../context/ToastContext';
|
|
51
51
|
import { useRoomMute } from '../../../hooks/useRoomMute';
|
|
52
|
-
import
|
|
52
|
+
import LeaveChatModal from './LeaveChatModal';
|
|
53
53
|
import { useT } from '../../../i18n/useT';
|
|
54
54
|
import DeleteChatModal from './DeleteChatModal';
|
|
55
55
|
import { isMessageSearchEnabled } from '../../../helpers/isMessageSearchEnabled';
|
|
@@ -67,6 +67,10 @@ import {
|
|
|
67
67
|
isRoomMembersTruncated,
|
|
68
68
|
} from '../../../helpers/roomUserCount';
|
|
69
69
|
import { useRoomDirectory } from '../../../hooks/useRoomDirectory';
|
|
70
|
+
import ChatMediaGrid from './ChatMediaGrid';
|
|
71
|
+
import MediaViewer from '../FilePreviewModal/MediaViewer';
|
|
72
|
+
import { useSaveFile } from '../FilePreviewModal/useSaveFile';
|
|
73
|
+
import type { ModalFile } from '../../../types/types';
|
|
70
74
|
|
|
71
75
|
/** Member rows rendered per step; the rest sits behind a 'Show more' row. */
|
|
72
76
|
export const MEMBER_PAGE_SIZE = 50;
|
|
@@ -103,6 +107,11 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
103
107
|
const [loading, setLoading] = useState<boolean>(false);
|
|
104
108
|
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
|
105
109
|
const [isReportOpen, setIsReportOpen] = useState(false);
|
|
110
|
+
const [isLeaveOpen, setIsLeaveOpen] = useState(false);
|
|
111
|
+
const [activeTab, setActiveTab] = useState<'members' | 'media'>('members');
|
|
112
|
+
// A picture or video opened from the Media tab, shown over this screen.
|
|
113
|
+
const [viewerFile, setViewerFile] = useState<ModalFile | null>(null);
|
|
114
|
+
const saveFile = useSaveFile();
|
|
106
115
|
const [memberQuery, setMemberQuery] = useState('');
|
|
107
116
|
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
|
108
117
|
// Measured, not guessed: this screen renders inside whatever frame the
|
|
@@ -113,7 +122,6 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
113
122
|
const { showToast } = useToast();
|
|
114
123
|
const dispatch = useDispatch();
|
|
115
124
|
const { client } = useXmppClient();
|
|
116
|
-
const leaveRoom = useLeaveRoom();
|
|
117
125
|
const { user: stateUser, config } = useChatSettingState();
|
|
118
126
|
const theme = useTheme();
|
|
119
127
|
const styles = useMemo(() => createStyles(theme), [theme]);
|
|
@@ -343,27 +351,13 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
343
351
|
// Leaving takes us out of the members on the server, then out of the MUC;
|
|
344
352
|
// dropping the room from the store is what takes the user out of the chat
|
|
345
353
|
// in the UI, and the `!activeRoom` effect above then closes this modal.
|
|
346
|
-
const handleLeave = () =>
|
|
347
|
-
Alert.alert(t('modal.leaveChat.title'), t('modal.leaveChat.description'), [
|
|
348
|
-
{ text: t('action.cancel'), style: 'cancel' },
|
|
349
|
-
{
|
|
350
|
-
text: t('action.leave'),
|
|
351
|
-
style: 'destructive',
|
|
352
|
-
onPress: async () => {
|
|
353
|
-
if (!(await leaveRoom(activeRoom?.jid || ''))) {
|
|
354
|
-
Alert.alert(t('toast.error'), t('toast.leaveFailed'));
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
handleCloseModal?.();
|
|
358
|
-
},
|
|
359
|
-
},
|
|
360
|
-
]);
|
|
361
|
-
};
|
|
354
|
+
const handleLeave = () => setIsLeaveOpen(true);
|
|
362
355
|
|
|
363
356
|
const isModerator = activeRoom?.role === 'moderator';
|
|
364
357
|
const canEditIcon =
|
|
365
358
|
!config?.disableChatInfo?.disableIconEdit && activeRoom?.role !== 'participant';
|
|
366
359
|
const canAddMembers = isModerator && activeRoom?.type === 'group';
|
|
360
|
+
const showMembers = !config?.disableChatInfo?.hideMembers;
|
|
367
361
|
const canDeleteChat = isModerator && activeRoom?.type !== 'private';
|
|
368
362
|
const hasIcon = !!activeRoom?.icon;
|
|
369
363
|
|
|
@@ -379,10 +373,19 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
379
373
|
collapsedForSearchRef.current = true;
|
|
380
374
|
scrollHeaderTo(target);
|
|
381
375
|
}
|
|
376
|
+
setActiveTab('members');
|
|
382
377
|
setIsSearchOpen(true);
|
|
383
378
|
setTimeout(() => searchInputRef.current?.focus(), 50);
|
|
384
379
|
};
|
|
385
380
|
|
|
381
|
+
// Search looks through this chat's messages, like the room list's search
|
|
382
|
+
// does across chats. Where message search is off it falls back to the
|
|
383
|
+
// member search it used to be; members keep their own magnifier either way.
|
|
384
|
+
const openSearch = () =>
|
|
385
|
+
isMessageSearchEnabled(config)
|
|
386
|
+
? dispatch(setActiveModal(MODAL_TYPES.CHAT_MESSAGE_SEARCH))
|
|
387
|
+
: openMemberSearch();
|
|
388
|
+
|
|
386
389
|
const restoreHeader = () => {
|
|
387
390
|
if (!collapsedForSearchRef.current) {return;}
|
|
388
391
|
collapsedForSearchRef.current = false;
|
|
@@ -432,7 +435,7 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
432
435
|
key: 'search',
|
|
433
436
|
label: t('action.search'),
|
|
434
437
|
icon: (color: string) => <SearchIcon color={color} />,
|
|
435
|
-
onPress:
|
|
438
|
+
onPress: openSearch,
|
|
436
439
|
},
|
|
437
440
|
{
|
|
438
441
|
key: 'leave',
|
|
@@ -478,7 +481,7 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
478
481
|
key: 'search',
|
|
479
482
|
label: t('action.search'),
|
|
480
483
|
icon: <SearchIcon color={theme.text} width={18} height={18} />,
|
|
481
|
-
onPress:
|
|
484
|
+
onPress: openSearch,
|
|
482
485
|
});
|
|
483
486
|
}
|
|
484
487
|
items.push({
|
|
@@ -593,24 +596,61 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
593
596
|
</View>
|
|
594
597
|
)}
|
|
595
598
|
|
|
596
|
-
{isMessageSearchEnabled(config) && (
|
|
597
|
-
<View style={styles.card}>
|
|
598
|
-
<TouchableOpacity
|
|
599
|
-
testID="chat-profile-search-messages"
|
|
600
|
-
activeOpacity={0.7}
|
|
601
|
-
style={styles.addMembersRow}
|
|
602
|
-
onPress={() => dispatch(setActiveModal(MODAL_TYPES.MESSAGE_SEARCH))}
|
|
603
|
-
>
|
|
604
|
-
<SearchIcon color={getIconColor(config)} />
|
|
605
|
-
<Text style={styles.addMembersLabel}>
|
|
606
|
-
{t('search.messages.title')}
|
|
607
|
-
</Text>
|
|
608
|
-
</TouchableOpacity>
|
|
609
|
-
</View>
|
|
610
|
-
)}
|
|
611
599
|
|
|
612
|
-
{
|
|
613
|
-
|
|
600
|
+
<View style={styles.card}>
|
|
601
|
+
{showMembers ? (
|
|
602
|
+
<View style={styles.tabsRow}>
|
|
603
|
+
{(['members', 'media'] as const).map((tab) => {
|
|
604
|
+
const active = tab === activeTab;
|
|
605
|
+
return (
|
|
606
|
+
<TouchableOpacity
|
|
607
|
+
key={tab}
|
|
608
|
+
testID={`chat-profile-tab-${tab}`}
|
|
609
|
+
activeOpacity={0.7}
|
|
610
|
+
style={[
|
|
611
|
+
styles.tab,
|
|
612
|
+
active && { borderBottomColor: getIconColor(config) },
|
|
613
|
+
]}
|
|
614
|
+
onPress={() => setActiveTab(tab)}
|
|
615
|
+
>
|
|
616
|
+
<Text
|
|
617
|
+
style={[
|
|
618
|
+
styles.tabLabel,
|
|
619
|
+
getElementFont(config, 'profileSectionLabel'),
|
|
620
|
+
active && { color: getIconColor(config) },
|
|
621
|
+
]}
|
|
622
|
+
>
|
|
623
|
+
{tab === 'members'
|
|
624
|
+
? t('modal.chatProfile.membersLabel', { count: memberCount })
|
|
625
|
+
: t('modal.chatProfile.media')}
|
|
626
|
+
</Text>
|
|
627
|
+
</TouchableOpacity>
|
|
628
|
+
);
|
|
629
|
+
})}
|
|
630
|
+
{activeTab === 'members' && !isSearchOpen && (
|
|
631
|
+
<TouchableOpacity
|
|
632
|
+
testID="chat-profile-member-search-open"
|
|
633
|
+
accessibilityRole="button"
|
|
634
|
+
accessibilityLabel={t('search.members')}
|
|
635
|
+
activeOpacity={0.7}
|
|
636
|
+
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
637
|
+
style={styles.tabsSearch}
|
|
638
|
+
onPress={openMemberSearch}
|
|
639
|
+
>
|
|
640
|
+
<SearchIcon color={theme.textSecondary} width={18} height={18} />
|
|
641
|
+
</TouchableOpacity>
|
|
642
|
+
)}
|
|
643
|
+
</View>
|
|
644
|
+
) : (
|
|
645
|
+
<Text style={[styles.cardLabel, getElementFont(config, 'profileSectionLabel')]}>
|
|
646
|
+
{t('modal.chatProfile.media')}
|
|
647
|
+
</Text>
|
|
648
|
+
)}
|
|
649
|
+
|
|
650
|
+
{!showMembers || activeTab === 'media' ? (
|
|
651
|
+
<ChatMediaGrid roomJid={activeRoom.jid} onOpen={setViewerFile} />
|
|
652
|
+
) : (
|
|
653
|
+
<>
|
|
614
654
|
{canAddMembers && (
|
|
615
655
|
<SelectUsersModal
|
|
616
656
|
trigger={(open) => (
|
|
@@ -629,15 +669,6 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
629
669
|
/>
|
|
630
670
|
)}
|
|
631
671
|
|
|
632
|
-
<Text
|
|
633
|
-
style={[
|
|
634
|
-
styles.cardLabel,
|
|
635
|
-
getElementFont(config, 'profileSectionLabel'),
|
|
636
|
-
]}
|
|
637
|
-
>
|
|
638
|
-
{t('modal.chatProfile.membersLabel', { count: memberCount })}
|
|
639
|
-
</Text>
|
|
640
|
-
|
|
641
672
|
{isSearchOpen && (
|
|
642
673
|
<View style={styles.searchRow}>
|
|
643
674
|
<SearchIcon color={theme.textSecondary} width={18} height={18} />
|
|
@@ -773,8 +804,9 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
773
804
|
</Text>
|
|
774
805
|
</TouchableOpacity>
|
|
775
806
|
)}
|
|
776
|
-
|
|
777
|
-
|
|
807
|
+
</>
|
|
808
|
+
)}
|
|
809
|
+
</View>
|
|
778
810
|
</View>
|
|
779
811
|
</Animated.ScrollView>
|
|
780
812
|
|
|
@@ -798,6 +830,24 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
798
830
|
visible={isReportOpen}
|
|
799
831
|
onClose={() => setIsReportOpen(false)}
|
|
800
832
|
/>
|
|
833
|
+
<LeaveChatModal
|
|
834
|
+
roomJid={isLeaveOpen && activeRoom?.jid ? activeRoom.jid : null}
|
|
835
|
+
onClose={() => setIsLeaveOpen(false)}
|
|
836
|
+
onLeft={() => handleCloseModal?.()}
|
|
837
|
+
/>
|
|
838
|
+
{viewerFile && (
|
|
839
|
+
<MediaViewer
|
|
840
|
+
file={viewerFile}
|
|
841
|
+
onClose={() => setViewerFile(null)}
|
|
842
|
+
onPage={(index) => {
|
|
843
|
+
const next = viewerFile.siblings?.[index];
|
|
844
|
+
if (next) {setViewerFile({ ...next, siblings: viewerFile.siblings, index });}
|
|
845
|
+
}}
|
|
846
|
+
onSave={() => saveFile(viewerFile)}
|
|
847
|
+
// Reply opens the thread in the chat underneath this screen.
|
|
848
|
+
onReplyOpened={() => handleCloseModal?.()}
|
|
849
|
+
/>
|
|
850
|
+
)}
|
|
801
851
|
</ModalContainerFullScreen>
|
|
802
852
|
);
|
|
803
853
|
};
|
|
@@ -818,6 +868,27 @@ const createStyles = (theme: ChatTheme) => StyleSheet.create({
|
|
|
818
868
|
padding: 12,
|
|
819
869
|
gap: 12,
|
|
820
870
|
},
|
|
871
|
+
tabsRow: {
|
|
872
|
+
flexDirection: 'row',
|
|
873
|
+
gap: 20,
|
|
874
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
875
|
+
borderBottomColor: theme.border,
|
|
876
|
+
marginBottom: 12,
|
|
877
|
+
},
|
|
878
|
+
tabsSearch: {
|
|
879
|
+
marginLeft: 'auto',
|
|
880
|
+
paddingBottom: 10,
|
|
881
|
+
},
|
|
882
|
+
tab: {
|
|
883
|
+
paddingBottom: 10,
|
|
884
|
+
borderBottomWidth: 2,
|
|
885
|
+
borderBottomColor: 'transparent',
|
|
886
|
+
},
|
|
887
|
+
tabLabel: {
|
|
888
|
+
fontSize: 15,
|
|
889
|
+
fontWeight: '500',
|
|
890
|
+
color: theme.textSecondary,
|
|
891
|
+
},
|
|
821
892
|
card: {
|
|
822
893
|
backgroundColor: theme.surface,
|
|
823
894
|
borderRadius: 14,
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/** @format */
|
|
2
|
+
|
|
3
|
+
import React, { useMemo, useState } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
ActivityIndicator,
|
|
6
|
+
Alert,
|
|
7
|
+
Modal,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
Text,
|
|
10
|
+
TouchableOpacity,
|
|
11
|
+
View,
|
|
12
|
+
} from 'react-native';
|
|
13
|
+
import { useSelector } from 'react-redux';
|
|
14
|
+
import type { RootState } from '../../../roomStore';
|
|
15
|
+
import { ProfileImagePlaceholder } from '../../MainComponents/ProfileImagePlaceholder';
|
|
16
|
+
import { useLeaveRoom } from '../../../hooks/useLeaveRoom';
|
|
17
|
+
import { useFileToken } from '../../../hooks/useFileToken';
|
|
18
|
+
import { appendFileToken } from '../../../helpers/secureFileUrl';
|
|
19
|
+
import { useTheme } from '../../../hooks/useTheme';
|
|
20
|
+
import { useT } from '../../../i18n/useT';
|
|
21
|
+
import type { ChatTheme } from '../../../theme/theme';
|
|
22
|
+
|
|
23
|
+
interface LeaveChatModalProps {
|
|
24
|
+
/** The room to leave; the modal is shown while this is set. */
|
|
25
|
+
roomJid: string | null;
|
|
26
|
+
onClose: () => void;
|
|
27
|
+
/** Called after the server took us out, before the modal closes. */
|
|
28
|
+
onLeft?: (jid: string) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Telegram-style confirmation in front of every Leave: the chat's avatar and
|
|
33
|
+
* name, the question, Cancel and a red Leave. The leave itself runs here, so
|
|
34
|
+
* the card stays up with a spinner until the server answers and a failure is
|
|
35
|
+
* reported over it instead of after it has gone.
|
|
36
|
+
*/
|
|
37
|
+
const LeaveChatModal: React.FC<LeaveChatModalProps> = ({
|
|
38
|
+
roomJid,
|
|
39
|
+
onClose,
|
|
40
|
+
onLeft,
|
|
41
|
+
}) => {
|
|
42
|
+
const t = useT();
|
|
43
|
+
const theme = useTheme();
|
|
44
|
+
const styles = useMemo(() => createStyles(theme), [theme]);
|
|
45
|
+
const fileToken = useFileToken();
|
|
46
|
+
const leaveRoom = useLeaveRoom();
|
|
47
|
+
const room = useSelector((state: RootState) =>
|
|
48
|
+
roomJid ? state.rooms.rooms?.[roomJid] : undefined
|
|
49
|
+
);
|
|
50
|
+
const [leaving, setLeaving] = useState(false);
|
|
51
|
+
|
|
52
|
+
const name = room?.title || room?.name || '';
|
|
53
|
+
|
|
54
|
+
const close = () => {
|
|
55
|
+
if (!leaving) {onClose();}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const confirm = async () => {
|
|
59
|
+
if (!roomJid || leaving) {return;}
|
|
60
|
+
setLeaving(true);
|
|
61
|
+
const ok = await leaveRoom(roomJid);
|
|
62
|
+
setLeaving(false);
|
|
63
|
+
if (!ok) {
|
|
64
|
+
Alert.alert(t('toast.error'), t('toast.leaveFailed'));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
onLeft?.(roomJid);
|
|
68
|
+
onClose();
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Modal
|
|
73
|
+
transparent
|
|
74
|
+
visible={roomJid !== null}
|
|
75
|
+
animationType="fade"
|
|
76
|
+
onRequestClose={close}
|
|
77
|
+
>
|
|
78
|
+
<View style={styles.backdrop}>
|
|
79
|
+
<TouchableOpacity
|
|
80
|
+
testID="leave-backdrop"
|
|
81
|
+
style={StyleSheet.absoluteFill}
|
|
82
|
+
activeOpacity={1}
|
|
83
|
+
onPress={close}
|
|
84
|
+
/>
|
|
85
|
+
<View style={styles.card}>
|
|
86
|
+
<View style={styles.header}>
|
|
87
|
+
<ProfileImagePlaceholder
|
|
88
|
+
name={name}
|
|
89
|
+
size={40}
|
|
90
|
+
icon={appendFileToken(room?.icon, fileToken)}
|
|
91
|
+
disableOverlay
|
|
92
|
+
/>
|
|
93
|
+
<Text style={styles.title} numberOfLines={1}>
|
|
94
|
+
{t('modal.leaveChat.title')}
|
|
95
|
+
</Text>
|
|
96
|
+
</View>
|
|
97
|
+
<Text style={styles.description}>
|
|
98
|
+
{name
|
|
99
|
+
? t('modal.leaveChat.descriptionNamed', { name })
|
|
100
|
+
: t('modal.leaveChat.description')}
|
|
101
|
+
</Text>
|
|
102
|
+
<View style={styles.buttons}>
|
|
103
|
+
<TouchableOpacity
|
|
104
|
+
testID="leave-cancel"
|
|
105
|
+
activeOpacity={0.7}
|
|
106
|
+
disabled={leaving}
|
|
107
|
+
style={[styles.button, styles.cancel]}
|
|
108
|
+
onPress={close}
|
|
109
|
+
>
|
|
110
|
+
<Text style={styles.cancelLabel}>{t('action.cancel')}</Text>
|
|
111
|
+
</TouchableOpacity>
|
|
112
|
+
<TouchableOpacity
|
|
113
|
+
testID="leave-confirm"
|
|
114
|
+
activeOpacity={0.7}
|
|
115
|
+
disabled={leaving}
|
|
116
|
+
style={[styles.button, { backgroundColor: theme.danger }]}
|
|
117
|
+
onPress={confirm}
|
|
118
|
+
>
|
|
119
|
+
{leaving ? (
|
|
120
|
+
<ActivityIndicator color={theme.textOnPrimary} />
|
|
121
|
+
) : (
|
|
122
|
+
<Text style={styles.leaveLabel}>{t('action.leave')}</Text>
|
|
123
|
+
)}
|
|
124
|
+
</TouchableOpacity>
|
|
125
|
+
</View>
|
|
126
|
+
</View>
|
|
127
|
+
</View>
|
|
128
|
+
</Modal>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const createStyles = (theme: ChatTheme) => StyleSheet.create({
|
|
133
|
+
backdrop: {
|
|
134
|
+
flex: 1,
|
|
135
|
+
backgroundColor: theme.overlay,
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
justifyContent: 'center',
|
|
138
|
+
padding: 24,
|
|
139
|
+
},
|
|
140
|
+
card: {
|
|
141
|
+
width: '100%',
|
|
142
|
+
maxWidth: 400,
|
|
143
|
+
borderRadius: 16,
|
|
144
|
+
backgroundColor: theme.surface,
|
|
145
|
+
padding: 20,
|
|
146
|
+
},
|
|
147
|
+
header: {
|
|
148
|
+
flexDirection: 'row',
|
|
149
|
+
alignItems: 'center',
|
|
150
|
+
gap: 12,
|
|
151
|
+
marginBottom: 12,
|
|
152
|
+
},
|
|
153
|
+
title: {
|
|
154
|
+
flex: 1,
|
|
155
|
+
fontSize: 18,
|
|
156
|
+
fontWeight: '600',
|
|
157
|
+
color: theme.text,
|
|
158
|
+
},
|
|
159
|
+
description: {
|
|
160
|
+
fontSize: 15,
|
|
161
|
+
lineHeight: 21,
|
|
162
|
+
color: theme.text,
|
|
163
|
+
},
|
|
164
|
+
buttons: {
|
|
165
|
+
flexDirection: 'row',
|
|
166
|
+
gap: 12,
|
|
167
|
+
marginTop: 20,
|
|
168
|
+
},
|
|
169
|
+
button: {
|
|
170
|
+
flex: 1,
|
|
171
|
+
height: 44,
|
|
172
|
+
borderRadius: 12,
|
|
173
|
+
alignItems: 'center',
|
|
174
|
+
justifyContent: 'center',
|
|
175
|
+
},
|
|
176
|
+
cancel: {
|
|
177
|
+
backgroundColor: theme.surfaceSecondary,
|
|
178
|
+
},
|
|
179
|
+
cancelLabel: {
|
|
180
|
+
fontSize: 16,
|
|
181
|
+
fontWeight: '500',
|
|
182
|
+
color: theme.text,
|
|
183
|
+
},
|
|
184
|
+
leaveLabel: {
|
|
185
|
+
fontSize: 16,
|
|
186
|
+
fontWeight: '600',
|
|
187
|
+
color: theme.textOnPrimary,
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
export default LeaveChatModal;
|