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