@messenger-box/platform-mobile 10.0.3-alpha.10 → 10.0.3-alpha.101
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 +160 -0
- package/lib/compute.js +2 -3
- package/lib/compute.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/module.js.map +1 -1
- package/lib/queries/inboxQueries.js +65 -0
- package/lib/queries/inboxQueries.js.map +1 -0
- package/lib/routes.json +2 -3
- package/lib/screens/inbox/DialogMessages.js.map +1 -1
- package/lib/screens/inbox/DialogThreadMessages.js +3 -7
- package/lib/screens/inbox/DialogThreadMessages.js.map +1 -1
- package/lib/screens/inbox/DialogThreads.js +57 -18
- package/lib/screens/inbox/DialogThreads.js.map +1 -1
- package/lib/screens/inbox/Inbox.js.map +1 -1
- package/lib/screens/inbox/components/CachedImage/consts.js +1 -1
- package/lib/screens/inbox/components/CachedImage/consts.js.map +1 -1
- package/lib/screens/inbox/components/CachedImage/index.js +168 -46
- package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
- package/lib/screens/inbox/components/DialogItem.js +169 -0
- package/lib/screens/inbox/components/DialogItem.js.map +1 -0
- package/lib/screens/inbox/components/GiftedChatInboxComponent.js +313 -0
- package/lib/screens/inbox/components/GiftedChatInboxComponent.js.map +1 -0
- package/lib/screens/inbox/components/SlackMessageContainer/ImageViewerModal.js.map +1 -1
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +147 -31
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
- package/lib/screens/inbox/components/SlackMessageContainer/SlackMessage.js +5 -0
- package/lib/screens/inbox/components/SlackMessageContainer/SlackMessage.js.map +1 -1
- package/lib/screens/inbox/components/SubscriptionHandler.js +24 -0
- package/lib/screens/inbox/components/SubscriptionHandler.js.map +1 -0
- package/lib/screens/inbox/components/ThreadsViewItem.js +66 -44
- package/lib/screens/inbox/components/ThreadsViewItem.js.map +1 -1
- package/lib/screens/inbox/config/config.js +2 -2
- package/lib/screens/inbox/config/config.js.map +1 -1
- package/lib/screens/inbox/containers/ConversationView.js +1105 -428
- package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/Dialogs.js +193 -80
- package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadConversationView.js +724 -207
- package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadsView.js +83 -50
- package/lib/screens/inbox/containers/ThreadsView.js.map +1 -1
- package/lib/screens/inbox/hooks/useInboxMessages.js +31 -0
- package/lib/screens/inbox/hooks/useInboxMessages.js.map +1 -0
- package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js +108 -0
- package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js.map +1 -0
- package/lib/screens/inbox/workflow/dialog-threads-xstate.js +151 -0
- package/lib/screens/inbox/workflow/dialog-threads-xstate.js.map +1 -0
- package/package.json +4 -4
- package/src/index.ts +2 -0
- package/src/queries/inboxQueries.ts +299 -0
- package/src/queries/index.d.ts +2 -0
- package/src/queries/index.ts +1 -0
- package/src/screens/inbox/DialogThreadMessages.tsx +3 -11
- package/src/screens/inbox/DialogThreads.tsx +53 -60
- package/src/screens/inbox/components/Actionsheet.tsx +30 -0
- package/src/screens/inbox/components/CachedImage/consts.ts +4 -3
- package/src/screens/inbox/components/CachedImage/index.tsx +232 -61
- package/src/screens/inbox/components/DialogItem.tsx +306 -0
- package/src/screens/inbox/components/DialogsListItem.tsx +257 -176
- package/src/screens/inbox/components/ExpandableInput.tsx +460 -0
- package/src/screens/inbox/components/ExpandableInputActionSheet.tsx +518 -0
- package/src/screens/inbox/components/GiftedChatInboxComponent.tsx +411 -0
- package/src/screens/inbox/components/ServiceDialogsListItem.tsx +334 -172
- package/src/screens/inbox/components/SlackInput.tsx +23 -0
- package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +233 -23
- package/src/screens/inbox/components/SmartLoader.tsx +61 -0
- package/src/screens/inbox/components/SubscriptionHandler.tsx +41 -0
- package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +41 -24
- package/src/screens/inbox/components/ThreadsViewItem.tsx +177 -265
- package/src/screens/inbox/components/workflow/dialogs-list-item-xstate.ts +145 -0
- package/src/screens/inbox/components/workflow/service-dialogs-list-item-xstate.ts +159 -0
- package/src/screens/inbox/config/config.ts +2 -2
- package/src/screens/inbox/containers/ConversationView.tsx +1843 -702
- package/src/screens/inbox/containers/ConversationView.tsx.bk +1467 -0
- package/src/screens/inbox/containers/Dialogs.tsx +400 -197
- package/src/screens/inbox/containers/SupportServiceDialogs.tsx +2 -2
- package/src/screens/inbox/containers/ThreadConversationView.tsx +1337 -301
- package/src/screens/inbox/containers/ThreadsView.tsx +105 -186
- package/src/screens/inbox/containers/workflow/apollo/handleResult.ts +20 -0
- package/src/screens/inbox/containers/workflow/conversation-xstate.ts +313 -0
- package/src/screens/inbox/containers/workflow/dialogs-xstate.ts +196 -0
- package/src/screens/inbox/containers/workflow/thread-conversation-xstate.ts +401 -0
- package/src/screens/inbox/hooks/useInboxMessages.ts +34 -0
- package/src/screens/inbox/hooks/useSafeDialogThreadsMachine.ts +136 -0
- package/src/screens/inbox/index.ts +37 -0
- package/src/screens/inbox/machines/threadsMachine.ts +147 -0
- package/src/screens/inbox/workflow/dialog-threads-xstate.ts +163 -0
- package/tsconfig.json +11 -54
- package/lib/screens/inbox/components/DialogsListItem.js +0 -159
- package/lib/screens/inbox/components/DialogsListItem.js.map +0 -1
- package/lib/screens/inbox/components/ServiceDialogsListItem.js +0 -159
- package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +0 -1
|
@@ -1,30 +1,20 @@
|
|
|
1
|
-
import React, { useCallback,
|
|
2
|
-
import {
|
|
3
|
-
FlatList,
|
|
4
|
-
Box,
|
|
5
|
-
Heading,
|
|
6
|
-
Input,
|
|
7
|
-
InputField,
|
|
8
|
-
Text,
|
|
9
|
-
Icon,
|
|
10
|
-
Center,
|
|
11
|
-
Spinner,
|
|
12
|
-
} from '@admin-layout/gluestack-ui-mobile';
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState, useMemo } from 'react';
|
|
2
|
+
import { FlatList, Box, Heading, Input, InputField, Text, Center, Spinner } from '@admin-layout/gluestack-ui-mobile';
|
|
13
3
|
import { Ionicons } from '@expo/vector-icons';
|
|
14
|
-
import { useSelector,
|
|
15
|
-
import { useNavigation, useRoute,
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { ServiceDialogsListItem } from '../components/ServiceDialogsListItem';
|
|
4
|
+
import { useSelector, shallowEqual } from 'react-redux';
|
|
5
|
+
import { useNavigation, useRoute, useFocusEffect } from '@react-navigation/native';
|
|
6
|
+
import { DialogItem } from '../components/DialogItem';
|
|
7
|
+
// import { useChannelsQuery, CHAT_MESSAGE_ADDED } from '../../../queries/inboxQueries';
|
|
19
8
|
import {
|
|
20
9
|
useGetChannelsByUserQuery,
|
|
21
|
-
|
|
22
|
-
|
|
10
|
+
useGetChannelsByUserWithLastMessageQuery,
|
|
11
|
+
OnChatMessageAddedDocument,
|
|
12
|
+
} from 'common/graphql';
|
|
23
13
|
import { RoomType } from 'common';
|
|
24
14
|
import { userSelector } from '@adminide-stack/user-auth0-client';
|
|
25
|
-
import { CHANGE_SETTINGS_ACTION } from '@admin-layout/client';
|
|
26
15
|
import { config } from '../config';
|
|
27
16
|
import colors from 'tailwindcss/colors';
|
|
17
|
+
import { SubscriptionHandler } from '../components/SubscriptionHandler';
|
|
28
18
|
|
|
29
19
|
export interface InboxProps {
|
|
30
20
|
channelFilters?: Record<string, unknown>;
|
|
@@ -35,218 +25,431 @@ export interface InboxProps {
|
|
|
35
25
|
const DialogsComponent = (props: InboxProps) => {
|
|
36
26
|
const { channelFilters: channelFilterProp, channelRole, supportServices } = props;
|
|
37
27
|
const channelFilters = { ...channelFilterProp };
|
|
38
|
-
channelFilters.type = channelFilters?.type ?? RoomType.Direct;
|
|
28
|
+
// channelFilters.type = channelFilters?.type ?? RoomType.Direct;
|
|
29
|
+
const channelType = channelFilters?.type ?? RoomType.Direct;
|
|
30
|
+
channelFilters.type = supportServices ? [channelType, RoomType.Service] : channelType;
|
|
39
31
|
const { params } = useRoute<any>();
|
|
40
|
-
const auth = useSelector(userSelector);
|
|
41
|
-
const dispatch = useDispatch();
|
|
32
|
+
const auth = useSelector(userSelector, shallowEqual);
|
|
42
33
|
const navigation = useNavigation<any>();
|
|
43
|
-
|
|
44
|
-
const [
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
// Local state for UI control - only keeping searchQuery as it's needed for local filtering
|
|
35
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
36
|
+
|
|
37
|
+
// Create a ref to track if component is mounted
|
|
38
|
+
const isMountedRef = useRef(true);
|
|
39
|
+
const focusRefreshRef = useRef<number | null>(null);
|
|
40
|
+
const lastRefreshTimeRef = useRef(Date.now());
|
|
41
|
+
const MIN_REFRESH_INTERVAL = 2000;
|
|
42
|
+
|
|
43
|
+
// Add lastNavigationTimestamp to track when the user navigates away
|
|
44
|
+
const lastNavigationTimestamp = useRef(0);
|
|
45
|
+
// Track active channel to prevent duplicate clicks on the same channel
|
|
46
|
+
const activeChannelRef = useRef<string | null>(null);
|
|
47
|
+
// Hold a timeout ref to reset active channel status
|
|
48
|
+
const resetActiveChannelTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
49
|
+
|
|
50
|
+
// Apollo query with pagination and optimistic updates
|
|
51
|
+
const { data, loading, refetch, fetchMore, subscribeToMore } = useGetChannelsByUserWithLastMessageQuery({
|
|
52
52
|
variables: {
|
|
53
53
|
role: channelRole,
|
|
54
54
|
criteria: channelFilters,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
// supportServices: false,
|
|
56
|
+
// supportServices: supportServices ? true : false,
|
|
57
|
+
// supportServiceCriteria: {
|
|
58
|
+
// type: RoomType.Service,
|
|
59
|
+
// },
|
|
60
|
+
limit: 15,
|
|
61
|
+
skip: 0,
|
|
59
62
|
},
|
|
63
|
+
notifyOnNetworkStatusChange: true,
|
|
64
|
+
fetchPolicy: 'cache-and-network',
|
|
60
65
|
});
|
|
61
66
|
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
// refetch: getChannelsRefetch,
|
|
66
|
-
// } = useGetChannelsByUserQuery({
|
|
67
|
-
// variables: {
|
|
68
|
-
// role: channelRole,
|
|
69
|
-
// criteria: channelFilters,
|
|
70
|
-
// },
|
|
71
|
-
// onCompleted: (data: any) => {
|
|
72
|
-
// setRefresh(false);
|
|
73
|
-
// },
|
|
74
|
-
// });
|
|
67
|
+
// Subscribe to new messages to update lastMessage in channels
|
|
68
|
+
// useEffect(() => {
|
|
69
|
+
// if (!data?.channelsByUser?.length || !subscribeToMore) return;
|
|
75
70
|
|
|
76
|
-
|
|
77
|
-
React.useCallback(() => {
|
|
78
|
-
// Do something when the screen is focused
|
|
79
|
-
setRefresh(false);
|
|
80
|
-
//getChannelsRefetch({ role: channelRole, criteria: channelFilters });
|
|
81
|
-
getChannelsRefetch({
|
|
82
|
-
role: channelRole,
|
|
83
|
-
criteria: channelFilters,
|
|
84
|
-
supportServices: supportServices ? true : false,
|
|
85
|
-
supportServiceCriteria: {
|
|
86
|
-
type: RoomType.Service,
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
return () => {
|
|
90
|
-
// Do something when the screen is unfocused
|
|
91
|
-
// Useful for cleanup functions
|
|
92
|
-
};
|
|
93
|
-
}, [channelFilters]),
|
|
94
|
-
);
|
|
71
|
+
// const subscriptions: (() => void)[] = [];
|
|
95
72
|
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
// userChannels?.channelsByUser?.filter((c: any) =>
|
|
100
|
-
// c.members.some((u: any) => u !== null && u?.user?.id != auth?.id && u.user.__typename == 'UserAccount'),
|
|
101
|
-
// ) ?? [];
|
|
102
|
-
// return (uChannels && orderBy(uChannels, ['updatedAt'], ['desc'])) || [];
|
|
103
|
-
// }, [userChannels]);
|
|
104
|
-
|
|
105
|
-
const channels = React.useMemo(() => {
|
|
106
|
-
const allChannels = [...(userChannels?.supportServiceChannels ?? []), ...(userChannels?.channelsByUser ?? [])];
|
|
107
|
-
let uChannels: any =
|
|
108
|
-
allChannels?.filter((c: any) =>
|
|
109
|
-
c.members.some((u: any) => u !== null && u?.user?.id != auth?.id && u.user.__typename == 'UserAccount'),
|
|
110
|
-
) ?? [];
|
|
111
|
-
return (uChannels && orderBy(uChannels, ['updatedAt'], ['desc'])) || [];
|
|
112
|
-
}, [userChannels]);
|
|
73
|
+
// // Subscribe to each channel for real-time lastMessage updates
|
|
74
|
+
// data.channelsByUser.forEach(channel => {
|
|
75
|
+
// if (!channel?.id) return;
|
|
113
76
|
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
// payload: {
|
|
119
|
-
// footerRender: false,
|
|
77
|
+
// const unsubscribe = subscribeToMore({
|
|
78
|
+
// document: OnChatMessageAddedDocument,
|
|
79
|
+
// variables: {
|
|
80
|
+
// channelId: channel.id.toString(),
|
|
120
81
|
// },
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
//
|
|
82
|
+
// updateQuery: (prev, { subscriptionData }: any) => {
|
|
83
|
+
// if (!subscriptionData?.data?.chatMessageAdded || !isMountedRef.current) {
|
|
84
|
+
// return prev;
|
|
85
|
+
// }
|
|
86
|
+
|
|
87
|
+
// const newMessage = subscriptionData.data.chatMessageAdded;
|
|
88
|
+
// const channelId = newMessage.channel?.id;
|
|
89
|
+
|
|
90
|
+
// if (!channelId) return prev;
|
|
91
|
+
|
|
92
|
+
// // Update the specific channel's lastMessage
|
|
93
|
+
// const updatedChannels = (prev.channelsByUser || []).map(ch => {
|
|
94
|
+
// if (ch?.id === channelId) {
|
|
95
|
+
// return {
|
|
96
|
+
// ...ch,
|
|
97
|
+
// lastMessage: {
|
|
98
|
+
// id: newMessage.id,
|
|
99
|
+
// message: newMessage.message || '',
|
|
100
|
+
// createdAt: newMessage.createdAt,
|
|
101
|
+
// updatedAt: newMessage.updatedAt,
|
|
102
|
+
// author: newMessage.author ? {
|
|
103
|
+
// id: newMessage.author.id,
|
|
104
|
+
// givenName: newMessage.author.givenName,
|
|
105
|
+
// familyName: newMessage.author.familyName,
|
|
106
|
+
// username: newMessage.author.username,
|
|
107
|
+
// } : null,
|
|
108
|
+
// files: newMessage.files ? {
|
|
109
|
+
// totalCount: newMessage.files.totalCount || 0,
|
|
110
|
+
// data: (newMessage.files.data || []).map(file => ({
|
|
111
|
+
// id: file.id,
|
|
112
|
+
// name: file.name,
|
|
113
|
+
// extension: file.extension,
|
|
114
|
+
// mimeType: file.mimeType,
|
|
115
|
+
// }))
|
|
116
|
+
// } : null,
|
|
117
|
+
// },
|
|
118
|
+
// updatedAt: newMessage.createdAt,
|
|
119
|
+
// lastPostAt: newMessage.createdAt,
|
|
120
|
+
// };
|
|
121
|
+
// }
|
|
122
|
+
// return ch;
|
|
123
|
+
// });
|
|
124
|
+
|
|
125
|
+
// return {
|
|
126
|
+
// ...prev,
|
|
127
|
+
// channelsByUser: updatedChannels,
|
|
128
|
+
// };
|
|
129
|
+
// },
|
|
130
|
+
// onError: (error) => {
|
|
131
|
+
// console.error(`LastMessage subscription error for channel ${channel.id}:`, error);
|
|
128
132
|
// },
|
|
129
|
-
// }
|
|
133
|
+
// });
|
|
134
|
+
|
|
135
|
+
// subscriptions.push(unsubscribe);
|
|
136
|
+
// });
|
|
137
|
+
|
|
138
|
+
// // Cleanup subscriptions
|
|
139
|
+
// return () => {
|
|
140
|
+
// subscriptions.forEach(unsub => {
|
|
141
|
+
// try {
|
|
142
|
+
// unsub?.();
|
|
143
|
+
// } catch (error) {
|
|
144
|
+
// console.error('Error unsubscribing from lastMessage updates:', error);
|
|
145
|
+
// }
|
|
146
|
+
// });
|
|
130
147
|
// };
|
|
131
|
-
// }, []);
|
|
148
|
+
// }, [data?.channelsByUser, subscribeToMore]);
|
|
132
149
|
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
150
|
+
// Memoize processChannels and sortChannels to avoid unnecessary recalculations
|
|
151
|
+
const processChannels = useCallback(
|
|
152
|
+
(rawChannels = []) => {
|
|
153
|
+
if (!rawChannels?.length) return [];
|
|
154
|
+
return rawChannels.filter((c) => {
|
|
155
|
+
if (!c?.members) return false;
|
|
156
|
+
return c.members.some(
|
|
157
|
+
(member) => member?.user && member.user.id !== auth?.id && member.user.__typename === 'UserAccount',
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
[auth?.id],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const sortChannels = useCallback((channels) => {
|
|
165
|
+
if (!channels?.length) return [];
|
|
166
|
+
return channels;
|
|
167
|
+
// return [...channels].sort((a, b) => {
|
|
168
|
+
// const dateA = new Date(a?.updatedAt || a?.createdAt).getTime();
|
|
169
|
+
// const dateB = new Date(b?.updatedAt || b?.createdAt).getTime();
|
|
170
|
+
// return dateB - dateA;
|
|
171
|
+
// });
|
|
172
|
+
}, []);
|
|
173
|
+
|
|
174
|
+
// Navigation handlers with debounce to prevent double taps
|
|
175
|
+
const handleSelectChannel = useCallback(
|
|
176
|
+
(id, title) => {
|
|
177
|
+
if (activeChannelRef.current === id) {
|
|
178
|
+
console.log('📱 Ignoring repeated tap on channel:', id);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
activeChannelRef.current = id;
|
|
182
|
+
if (resetActiveChannelTimeoutRef.current) {
|
|
183
|
+
clearTimeout(resetActiveChannelTimeoutRef.current);
|
|
184
|
+
}
|
|
185
|
+
resetActiveChannelTimeoutRef.current = setTimeout(() => {
|
|
186
|
+
activeChannelRef.current = null;
|
|
187
|
+
}, 2000);
|
|
188
|
+
console.log('📱 Navigating to channel:', id);
|
|
189
|
+
navigation.navigate(config.INBOX_MESSEGE_PATH, {
|
|
190
|
+
channelId: id,
|
|
191
|
+
role: channelRole,
|
|
192
|
+
title: title,
|
|
156
193
|
hideTabBar: true,
|
|
194
|
+
timestamp: new Date().getTime(),
|
|
195
|
+
orgName: params?.orgName,
|
|
157
196
|
});
|
|
158
|
-
}
|
|
159
|
-
|
|
197
|
+
},
|
|
198
|
+
[navigation, channelRole],
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const handleSelectServiceChannel = useCallback(
|
|
202
|
+
(id, title, postParentId) => {
|
|
203
|
+
if (activeChannelRef.current === id) {
|
|
204
|
+
console.log('📱 Ignoring repeated tap on service channel:', id);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
activeChannelRef.current = id;
|
|
208
|
+
if (resetActiveChannelTimeoutRef.current) {
|
|
209
|
+
clearTimeout(resetActiveChannelTimeoutRef.current);
|
|
210
|
+
}
|
|
211
|
+
resetActiveChannelTimeoutRef.current = setTimeout(() => {
|
|
212
|
+
activeChannelRef.current = null;
|
|
213
|
+
}, 2000);
|
|
214
|
+
console.log('📱 Navigating to service channel:', id);
|
|
215
|
+
navigation.navigate(postParentId || postParentId === 0 ? config.THREAD_MESSEGE_PATH : config.THREADS_PATH, {
|
|
160
216
|
channelId: id,
|
|
161
217
|
role: channelRole,
|
|
162
218
|
title: title,
|
|
219
|
+
postParentId: postParentId,
|
|
163
220
|
hideTabBar: true,
|
|
221
|
+
orgName: params?.orgName,
|
|
164
222
|
});
|
|
165
|
-
}
|
|
223
|
+
},
|
|
224
|
+
[navigation, channelRole],
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// Handle search query changes
|
|
228
|
+
const handleSearchChange = useCallback((text: string) => {
|
|
229
|
+
setSearchQuery(text);
|
|
166
230
|
}, []);
|
|
167
231
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
232
|
+
// Memoize allChannels and channels
|
|
233
|
+
// const allChannels = useMemo(
|
|
234
|
+
// () => [...(data?.supportServiceChannels || []), ...(data?.channelsByUser || [])],
|
|
235
|
+
// [data],
|
|
236
|
+
// );
|
|
237
|
+
const allChannels = useMemo(() => [...(data?.channelsByUser || [])], [data]);
|
|
238
|
+
const channels = useMemo(
|
|
239
|
+
() => sortChannels(processChannels(allChannels)),
|
|
240
|
+
[allChannels, processChannels, sortChannels],
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Memoize filteredChannels
|
|
244
|
+
const displayChannels = useMemo(() => {
|
|
245
|
+
if (!searchQuery.trim()) return channels;
|
|
246
|
+
const query = searchQuery.toLowerCase();
|
|
247
|
+
return channels.filter((channel) => {
|
|
248
|
+
if (channel.title && channel.title.toLowerCase().includes(query)) return true;
|
|
249
|
+
if (channel.members) {
|
|
250
|
+
return channel.members.some((member) => {
|
|
251
|
+
const user = member?.user;
|
|
252
|
+
if (!user) return false;
|
|
253
|
+
const fullName = `${user.givenName || ''} ${user.familyName || ''}`.toLowerCase();
|
|
254
|
+
return fullName.includes(query) || (user.username && user.username.toLowerCase().includes(query));
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return false;
|
|
258
|
+
});
|
|
259
|
+
}, [channels, searchQuery]);
|
|
260
|
+
|
|
261
|
+
// Memoize renderItem to avoid re-renders
|
|
262
|
+
const renderItem = useCallback(
|
|
263
|
+
({ item: channel }) => {
|
|
264
|
+
const key = `${channel.type === RoomType.Service ? 'service' : 'direct'}-${channel.id}`;
|
|
265
|
+
return (
|
|
266
|
+
<DialogItem
|
|
267
|
+
key={key}
|
|
268
|
+
onOpen={channel?.type === RoomType.Service ? handleSelectServiceChannel : handleSelectChannel}
|
|
269
|
+
currentUser={auth}
|
|
270
|
+
channel={channel}
|
|
271
|
+
/>
|
|
181
272
|
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
273
|
+
},
|
|
274
|
+
[auth, handleSelectChannel, handleSelectServiceChannel],
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// Memoize ListFooterComponent and ListEmptyComponent
|
|
278
|
+
const ListFooterComponent = useMemo(
|
|
279
|
+
() =>
|
|
280
|
+
loading ? (
|
|
281
|
+
<Center className="py-4">
|
|
282
|
+
<Spinner color={colors.blue[500]} size="small" />
|
|
283
|
+
</Center>
|
|
284
|
+
) : null,
|
|
285
|
+
[loading],
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const ListEmptyComponent = useMemo(() => {
|
|
289
|
+
if (loading && displayChannels.length === 0) {
|
|
290
|
+
return (
|
|
291
|
+
<Center className="flex-1 justify-center items-center" style={{ height: 300 }}>
|
|
292
|
+
<Spinner color={colors.blue[500]} size="large" />
|
|
293
|
+
<Text className="mt-4 text-gray-500">Loading conversations...</Text>
|
|
294
|
+
</Center>
|
|
192
295
|
);
|
|
193
296
|
}
|
|
194
|
-
|
|
297
|
+
return (
|
|
298
|
+
<Box className="p-6">
|
|
299
|
+
<Box className="mb-6">
|
|
300
|
+
<Heading className="text-2xl font-bold">Direct Messages</Heading>
|
|
301
|
+
<Text className="text-gray-600 mt-1">Private conversations with other users</Text>
|
|
302
|
+
</Box>
|
|
303
|
+
<Input
|
|
304
|
+
className="mb-8 h-[50] rounded-md border-gray-300 border"
|
|
305
|
+
size="md"
|
|
306
|
+
style={{
|
|
307
|
+
paddingVertical: 8,
|
|
308
|
+
marginBottom: 10,
|
|
309
|
+
borderColor: '#d1d5db',
|
|
310
|
+
borderRadius: 10,
|
|
311
|
+
}}
|
|
312
|
+
>
|
|
313
|
+
<InputField
|
|
314
|
+
placeholder="Search messages..."
|
|
315
|
+
onChangeText={handleSearchChange}
|
|
316
|
+
value={searchQuery}
|
|
317
|
+
/>
|
|
318
|
+
</Input>
|
|
319
|
+
<Center className="items-center" style={{ paddingVertical: 5 }}>
|
|
320
|
+
<Box className="w-16 h-16 rounded-full bg-blue-500 flex items-center justify-center mb-5">
|
|
321
|
+
<Ionicons name="chatbubble-ellipses" size={30} color="white" />
|
|
322
|
+
</Box>
|
|
323
|
+
<Text className="text-2xl font-bold text-center mb-2">No messages yet</Text>
|
|
324
|
+
<Text className="text-gray-600 text-center mb-8">
|
|
325
|
+
When you start conversations with others,{'\n'}
|
|
326
|
+
they'll appear here.
|
|
327
|
+
</Text>
|
|
328
|
+
</Center>
|
|
329
|
+
</Box>
|
|
330
|
+
);
|
|
331
|
+
}, [loading, displayChannels.length, handleSearchChange, searchQuery]);
|
|
195
332
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
333
|
+
// Handle component cleanup
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
return () => {
|
|
336
|
+
isMountedRef.current = false;
|
|
337
|
+
// Clear any active timeouts
|
|
338
|
+
if (resetActiveChannelTimeoutRef.current) {
|
|
339
|
+
clearTimeout(resetActiveChannelTimeoutRef.current);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
200
342
|
}, []);
|
|
201
343
|
|
|
344
|
+
// Reset activeChannelRef when returning to this screen
|
|
345
|
+
useFocusEffect(
|
|
346
|
+
useCallback(() => {
|
|
347
|
+
// When screen gains focus, check if we're coming back from a detail screen
|
|
348
|
+
const now = Date.now();
|
|
349
|
+
|
|
350
|
+
// Reset active channel ref if enough time has passed since last navigation
|
|
351
|
+
if (now - lastNavigationTimestamp.current > 300) {
|
|
352
|
+
activeChannelRef.current = null;
|
|
353
|
+
console.log('Reset active channel reference on focus');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return () => {
|
|
357
|
+
// When losing focus, update the timestamp
|
|
358
|
+
lastNavigationTimestamp.current = Date.now();
|
|
359
|
+
};
|
|
360
|
+
}, []),
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
// Handle refresh on focus
|
|
364
|
+
useFocusEffect(
|
|
365
|
+
useCallback(() => {
|
|
366
|
+
console.log('📱 Focus effect triggered for Dialogs screen');
|
|
367
|
+
|
|
368
|
+
// Refresh when returning to the screen if enough time has passed
|
|
369
|
+
const performRefresh = () => {
|
|
370
|
+
const now = Date.now();
|
|
371
|
+
if (now - lastRefreshTimeRef.current < MIN_REFRESH_INTERVAL) {
|
|
372
|
+
console.log('⏩ Skipping refresh: too soon after previous refresh');
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
console.log('🔄 Performing refresh on screen focus');
|
|
377
|
+
if (isMountedRef.current) {
|
|
378
|
+
lastRefreshTimeRef.current = now;
|
|
379
|
+
refetch();
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const focusRefreshTimeout = setTimeout(performRefresh, 100);
|
|
384
|
+
return () => clearTimeout(focusRefreshTimeout);
|
|
385
|
+
}, [refetch]),
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
// Handle pull-to-refresh
|
|
389
|
+
const handlePullToRefresh = useCallback(() => {
|
|
390
|
+
const now = Date.now();
|
|
391
|
+
focusRefreshRef.current = now;
|
|
392
|
+
|
|
393
|
+
console.log('🔄 Pull-to-refresh triggered');
|
|
394
|
+
refetch();
|
|
395
|
+
}, [refetch]);
|
|
396
|
+
|
|
397
|
+
// Load more channels
|
|
398
|
+
const handleLoadMore = useCallback(() => {
|
|
399
|
+
if (loading || !data || channels.length < 10) {
|
|
400
|
+
console.log('Skip loading more: already loading or all data loaded');
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
console.log('Loading more channels');
|
|
405
|
+
|
|
406
|
+
fetchMore({
|
|
407
|
+
variables: {
|
|
408
|
+
skip: channels.length,
|
|
409
|
+
},
|
|
410
|
+
updateQuery: (prev, { fetchMoreResult }) => {
|
|
411
|
+
if (!fetchMoreResult) return prev;
|
|
412
|
+
|
|
413
|
+
// Combine previous and new results
|
|
414
|
+
return {
|
|
415
|
+
...fetchMoreResult,
|
|
416
|
+
channelsByUser: [...(prev.channelsByUser || []), ...(fetchMoreResult.channelsByUser || [])],
|
|
417
|
+
// supportServiceChannels: [
|
|
418
|
+
// ...(prev.supportServiceChannels || []),
|
|
419
|
+
// ...(fetchMoreResult.supportServiceChannels || []),
|
|
420
|
+
// ],
|
|
421
|
+
};
|
|
422
|
+
},
|
|
423
|
+
}).catch((error) => {
|
|
424
|
+
console.error('Error loading more channels:', error);
|
|
425
|
+
});
|
|
426
|
+
}, [fetchMore, loading, data, channels.length]);
|
|
427
|
+
|
|
202
428
|
return (
|
|
203
429
|
<Box className="p-2">
|
|
204
430
|
<FlatList
|
|
205
|
-
data={
|
|
206
|
-
onRefresh={
|
|
207
|
-
refreshing={
|
|
431
|
+
data={displayChannels}
|
|
432
|
+
onRefresh={handlePullToRefresh}
|
|
433
|
+
refreshing={loading}
|
|
208
434
|
contentContainerStyle={{ minHeight: '100%' }}
|
|
209
|
-
ItemSeparatorComponent={(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
currentUser={auth}
|
|
215
|
-
channel={channel}
|
|
216
|
-
refreshing={refreshing}
|
|
217
|
-
selectedChannelId={params?.channelId}
|
|
218
|
-
role={channelRole}
|
|
219
|
-
/>
|
|
220
|
-
) : (
|
|
221
|
-
<DialogsListItem
|
|
222
|
-
onOpen={handleSelectChannel}
|
|
223
|
-
currentUser={auth}
|
|
224
|
-
channel={channel}
|
|
225
|
-
selectedChannelId={params?.channelId}
|
|
226
|
-
/>
|
|
227
|
-
)
|
|
228
|
-
}
|
|
229
|
-
ListEmptyComponent={() => (
|
|
230
|
-
<>
|
|
231
|
-
{userChannelsLoading ? (
|
|
232
|
-
<Center className="flex-1 justify-center items-center">
|
|
233
|
-
<Spinner color={colors.blue[500]} />
|
|
234
|
-
</Center>
|
|
235
|
-
) : (
|
|
236
|
-
<Box className="p-5">
|
|
237
|
-
<Heading>Chat</Heading>
|
|
238
|
-
<Input className={`h-[50] mt-3 rounded-[50] border-gray-200 border `}>
|
|
239
|
-
<InputField placeholder="Search" />
|
|
240
|
-
</Input>
|
|
241
|
-
<Center className="mt-6">
|
|
242
|
-
<Ionicons name="chatbubbles" size={50} />
|
|
243
|
-
<Text>You don't have any messages yet!</Text>
|
|
244
|
-
</Center>
|
|
245
|
-
</Box>
|
|
246
|
-
)}
|
|
247
|
-
</>
|
|
435
|
+
ItemSeparatorComponent={React.useCallback(
|
|
436
|
+
() => (
|
|
437
|
+
<Box className="h-0.5 bg-gray-200" />
|
|
438
|
+
),
|
|
439
|
+
[],
|
|
248
440
|
)}
|
|
249
|
-
|
|
441
|
+
renderItem={renderItem}
|
|
442
|
+
ListFooterComponent={ListFooterComponent}
|
|
443
|
+
onEndReached={handleLoadMore}
|
|
444
|
+
onEndReachedThreshold={0.5}
|
|
445
|
+
initialNumToRender={5}
|
|
446
|
+
maxToRenderPerBatch={5}
|
|
447
|
+
windowSize={5}
|
|
448
|
+
removeClippedSubviews={true}
|
|
449
|
+
updateCellsBatchingPeriod={100}
|
|
450
|
+
getItemLayout={React.useCallback((data, index) => ({ length: 80, offset: 80 * index, index }), [])}
|
|
451
|
+
keyExtractor={React.useCallback((item) => `channel-${item.id}`, [])}
|
|
452
|
+
ListEmptyComponent={ListEmptyComponent}
|
|
250
453
|
/>
|
|
251
454
|
</Box>
|
|
252
455
|
);
|
|
@@ -2,7 +2,7 @@ import React, { useCallback, useState } from 'react';
|
|
|
2
2
|
import { FlatList, Box } from '@admin-layout/gluestack-ui-mobile';
|
|
3
3
|
import { useSelector, useDispatch } from 'react-redux';
|
|
4
4
|
import { useNavigation, useRoute, useIsFocused, useFocusEffect } from '@react-navigation/native';
|
|
5
|
-
import {
|
|
5
|
+
import { useServiceChannelsQuery } from '../../../queries/inboxQueries';
|
|
6
6
|
import { userSelector } from '@adminide-stack/user-auth0-client';
|
|
7
7
|
import { CHANGE_SETTINGS_ACTION } from '@admin-layout/client';
|
|
8
8
|
import { SupportServiceDialogsListItem } from '../components/SupportServiceDialogsListItem';
|
|
@@ -27,7 +27,7 @@ const SupportServiceDialogsComponent = (props: SupportServiceInboxProps) => {
|
|
|
27
27
|
data: serviceChannels,
|
|
28
28
|
loading: serviceChannelsLoading,
|
|
29
29
|
refetch: getServiceChannelsRefetch,
|
|
30
|
-
} =
|
|
30
|
+
} = useServiceChannelsQuery({
|
|
31
31
|
variables: {
|
|
32
32
|
criteria: { type: 'SERVICE' },
|
|
33
33
|
},
|