@messenger-box/platform-mobile 10.0.3-alpha.16 → 10.0.3-alpha.162

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +248 -0
  2. package/lib/compute.js +2 -3
  3. package/lib/compute.js.map +1 -1
  4. package/lib/index.js.map +1 -1
  5. package/lib/module.js.map +1 -1
  6. package/lib/queries/inboxQueries.js +65 -0
  7. package/lib/queries/inboxQueries.js.map +1 -0
  8. package/lib/routes.json +13 -1
  9. package/lib/screens/inbox/DialogMessages.js +5 -3
  10. package/lib/screens/inbox/DialogMessages.js.map +1 -1
  11. package/lib/screens/inbox/DialogThreadMessages.js +3 -7
  12. package/lib/screens/inbox/DialogThreadMessages.js.map +1 -1
  13. package/lib/screens/inbox/DialogThreads.js +57 -18
  14. package/lib/screens/inbox/DialogThreads.js.map +1 -1
  15. package/lib/screens/inbox/Inbox.js.map +1 -1
  16. package/lib/screens/inbox/components/CachedImage/consts.js +1 -1
  17. package/lib/screens/inbox/components/CachedImage/consts.js.map +1 -1
  18. package/lib/screens/inbox/components/CachedImage/index.js +168 -46
  19. package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
  20. package/lib/screens/inbox/components/DialogItem.js +169 -0
  21. package/lib/screens/inbox/components/DialogItem.js.map +1 -0
  22. package/lib/screens/inbox/components/GiftedChatInboxComponent.js +313 -0
  23. package/lib/screens/inbox/components/GiftedChatInboxComponent.js.map +1 -0
  24. package/lib/screens/inbox/components/SlackMessageContainer/ImageViewerModal.js.map +1 -1
  25. package/lib/screens/inbox/components/SlackMessageContainer/PaymentMessage.js +197 -0
  26. package/lib/screens/inbox/components/SlackMessageContainer/PaymentMessage.js.map +1 -0
  27. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +141 -31
  28. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
  29. package/lib/screens/inbox/components/SlackMessageContainer/SlackMessage.js.map +1 -1
  30. package/lib/screens/inbox/components/SubscriptionHandler.js +24 -0
  31. package/lib/screens/inbox/components/SubscriptionHandler.js.map +1 -0
  32. package/lib/screens/inbox/components/ThreadsViewItem.js +66 -44
  33. package/lib/screens/inbox/components/ThreadsViewItem.js.map +1 -1
  34. package/lib/screens/inbox/config/config.js +2 -2
  35. package/lib/screens/inbox/config/config.js.map +1 -1
  36. package/lib/screens/inbox/containers/ConversationView.js +1205 -432
  37. package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
  38. package/lib/screens/inbox/containers/Dialogs.js +193 -80
  39. package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
  40. package/lib/screens/inbox/containers/ThreadConversationView.js +724 -207
  41. package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
  42. package/lib/screens/inbox/containers/ThreadsView.js +83 -50
  43. package/lib/screens/inbox/containers/ThreadsView.js.map +1 -1
  44. package/lib/screens/inbox/hooks/useInboxMessages.js +31 -0
  45. package/lib/screens/inbox/hooks/useInboxMessages.js.map +1 -0
  46. package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js +108 -0
  47. package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js.map +1 -0
  48. package/lib/screens/inbox/workflow/dialog-threads-xstate.js +151 -0
  49. package/lib/screens/inbox/workflow/dialog-threads-xstate.js.map +1 -0
  50. package/package.json +5 -5
  51. package/src/index.ts +2 -0
  52. package/src/queries/inboxQueries.ts +299 -0
  53. package/src/queries/index.d.ts +2 -0
  54. package/src/queries/index.ts +1 -0
  55. package/src/screens/inbox/DialogMessages.tsx +2 -1
  56. package/src/screens/inbox/DialogThreadMessages.tsx +3 -11
  57. package/src/screens/inbox/DialogThreads.tsx +53 -60
  58. package/src/screens/inbox/components/Actionsheet.tsx +30 -0
  59. package/src/screens/inbox/components/CachedImage/consts.ts +4 -3
  60. package/src/screens/inbox/components/CachedImage/index.tsx +232 -61
  61. package/src/screens/inbox/components/DialogItem.tsx +306 -0
  62. package/src/screens/inbox/components/DialogsListItem.tsx +230 -170
  63. package/src/screens/inbox/components/ExpandableInput.tsx +460 -0
  64. package/src/screens/inbox/components/ExpandableInputActionSheet.tsx +518 -0
  65. package/src/screens/inbox/components/GiftedChatInboxComponent.tsx +411 -0
  66. package/src/screens/inbox/components/ServiceDialogsListItem.tsx +330 -171
  67. package/src/screens/inbox/components/SlackInput.tsx +23 -0
  68. package/src/screens/inbox/components/SlackMessageContainer/PaymentMessage.tsx +199 -0
  69. package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +233 -23
  70. package/src/screens/inbox/components/SmartLoader.tsx +61 -0
  71. package/src/screens/inbox/components/SubscriptionHandler.tsx +41 -0
  72. package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +41 -24
  73. package/src/screens/inbox/components/ThreadsViewItem.tsx +177 -265
  74. package/src/screens/inbox/components/workflow/dialogs-list-item-xstate.ts +145 -0
  75. package/src/screens/inbox/components/workflow/service-dialogs-list-item-xstate.ts +159 -0
  76. package/src/screens/inbox/config/config.ts +2 -2
  77. package/src/screens/inbox/containers/ConversationView.tsx +2004 -698
  78. package/src/screens/inbox/containers/ConversationView.tsx.bk +1467 -0
  79. package/src/screens/inbox/containers/Dialogs.tsx +402 -196
  80. package/src/screens/inbox/containers/SupportServiceDialogs.tsx +2 -2
  81. package/src/screens/inbox/containers/ThreadConversationView.tsx +1337 -301
  82. package/src/screens/inbox/containers/ThreadsView.tsx +105 -186
  83. package/src/screens/inbox/containers/workflow/apollo/handleResult.ts +20 -0
  84. package/src/screens/inbox/containers/workflow/conversation-xstate.ts +313 -0
  85. package/src/screens/inbox/containers/workflow/dialogs-xstate.ts +196 -0
  86. package/src/screens/inbox/containers/workflow/thread-conversation-xstate.ts +401 -0
  87. package/src/screens/inbox/hooks/useInboxMessages.ts +34 -0
  88. package/src/screens/inbox/hooks/useSafeDialogThreadsMachine.ts +136 -0
  89. package/src/screens/inbox/index.ts +37 -0
  90. package/src/screens/inbox/machines/threadsMachine.ts +147 -0
  91. package/src/screens/inbox/workflow/dialog-threads-xstate.ts +163 -0
  92. package/lib/screens/inbox/components/DialogsListItem.js +0 -175
  93. package/lib/screens/inbox/components/DialogsListItem.js.map +0 -1
  94. package/lib/screens/inbox/components/ServiceDialogsListItem.js +0 -165
  95. package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +0 -1
@@ -1,27 +1,20 @@
1
- import React, { useCallback, useMemo, useEffect, useState } from 'react';
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, useDispatch } from 'react-redux';
15
- import { useNavigation, useRoute, useIsFocused, useFocusEffect } from '@react-navigation/native';
16
- import { orderBy, uniqBy, startCase } from 'lodash-es';
17
- import { DialogsListItem } from '../components/DialogsListItem';
18
- import { ServiceDialogsListItem } from '../components/ServiceDialogsListItem';
19
- import { useGetChannelsByUserQuery, useGetChannelsByUserWithServiceChannelsQuery } from 'common/graphql';
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';
8
+ import {
9
+ useGetChannelsByUserQuery,
10
+ useGetChannelsByUserWithLastMessageQuery,
11
+ OnChatMessageAddedDocument,
12
+ } from 'common/graphql';
20
13
  import { RoomType } from 'common';
21
14
  import { userSelector } from '@adminide-stack/user-auth0-client';
22
- import { CHANGE_SETTINGS_ACTION } from '@admin-layout/client';
23
15
  import { config } from '../config';
24
16
  import colors from 'tailwindcss/colors';
17
+ import { SubscriptionHandler } from '../components/SubscriptionHandler';
25
18
 
26
19
  export interface InboxProps {
27
20
  channelFilters?: Record<string, unknown>;
@@ -32,218 +25,431 @@ export interface InboxProps {
32
25
  const DialogsComponent = (props: InboxProps) => {
33
26
  const { channelFilters: channelFilterProp, channelRole, supportServices } = props;
34
27
  const channelFilters = { ...channelFilterProp };
35
- 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;
36
31
  const { params } = useRoute<any>();
37
- const auth = useSelector(userSelector);
38
- const dispatch = useDispatch();
32
+ const auth = useSelector(userSelector, shallowEqual);
39
33
  const navigation = useNavigation<any>();
40
- const isFocused = useIsFocused();
41
- const [refreshing, setRefresh] = useState<boolean>(false);
42
- // const [userDirectChannel, setUserDirectChannel] = useState<any>([]);
43
-
44
- const {
45
- data: userChannels,
46
- loading: userChannelsLoading,
47
- refetch: getChannelsRefetch,
48
- } = useGetChannelsByUserWithServiceChannelsQuery({
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({
49
52
  variables: {
50
53
  role: channelRole,
51
54
  criteria: channelFilters,
52
- supportServices: supportServices ? true : false,
53
- supportServiceCriteria: {
54
- type: RoomType.Service,
55
- },
55
+ // supportServices: false,
56
+ // supportServices: supportServices ? true : false,
57
+ // supportServiceCriteria: {
58
+ // type: RoomType.Service,
59
+ // },
60
+ limit: 15,
61
+ skip: 0,
56
62
  },
63
+ notifyOnNetworkStatusChange: true,
64
+ fetchPolicy: 'cache-and-network',
57
65
  });
58
66
 
59
- // const {
60
- // data: userChannels,
61
- // loading: userChannelsLoading,
62
- // refetch: getChannelsRefetch,
63
- // } = useGetChannelsByUserQuery({
64
- // variables: {
65
- // role: channelRole,
66
- // criteria: channelFilters,
67
- // },
68
- // onCompleted: (data: any) => {
69
- // setRefresh(false);
70
- // },
71
- // });
67
+ // Subscribe to new messages to update lastMessage in channels
68
+ // useEffect(() => {
69
+ // if (!data?.channelsByUser?.length || !subscribeToMore) return;
72
70
 
73
- useFocusEffect(
74
- React.useCallback(() => {
75
- // Do something when the screen is focused
76
- setRefresh(false);
77
- //getChannelsRefetch({ role: channelRole, criteria: channelFilters });
78
- getChannelsRefetch({
79
- role: channelRole,
80
- criteria: channelFilters,
81
- supportServices: supportServices ? true : false,
82
- supportServiceCriteria: {
83
- type: RoomType.Service,
84
- },
85
- });
86
- return () => {
87
- // Do something when the screen is unfocused
88
- // Useful for cleanup functions
89
- };
90
- }, [channelFilters]),
91
- );
71
+ // const subscriptions: (() => void)[] = [];
92
72
 
93
- // const channels = React.useMemo(() => {
94
- // if (!userChannels?.channelsByUser?.length) return null;
95
- // let uChannels: any =
96
- // userChannels?.channelsByUser?.filter((c: any) =>
97
- // c.members.some((u: any) => u !== null && u?.user?.id != auth?.id && u.user.__typename == 'UserAccount'),
98
- // ) ?? [];
99
- // return (uChannels && orderBy(uChannels, ['updatedAt'], ['desc'])) || [];
100
- // }, [userChannels]);
101
-
102
- const channels = React.useMemo(() => {
103
- const allChannels = [...(userChannels?.supportServiceChannels ?? []), ...(userChannels?.channelsByUser ?? [])];
104
- let uChannels: any =
105
- allChannels?.filter((c: any) =>
106
- c.members.some((u: any) => u !== null && u?.user?.id != auth?.id && u.user.__typename == 'UserAccount'),
107
- ) ?? [];
108
- return (uChannels && orderBy(uChannels, ['updatedAt'], ['desc'])) || [];
109
- }, [userChannels]);
73
+ // // Subscribe to each channel for real-time lastMessage updates
74
+ // data.channelsByUser.forEach(channel => {
75
+ // if (!channel?.id) return;
110
76
 
111
- // useEffect(() => {
112
- // setTimeout(() => {
113
- // dispatch({
114
- // type: CHANGE_SETTINGS_ACTION,
115
- // payload: {
116
- // footerRender: false,
77
+ // const unsubscribe = subscribeToMore({
78
+ // document: OnChatMessageAddedDocument,
79
+ // variables: {
80
+ // channelId: channel.id.toString(),
117
81
  // },
118
- // } as any);
119
- // }, 0);
120
- // return () => {
121
- // dispatch({
122
- // type: CHANGE_SETTINGS_ACTION,
123
- // payload: {
124
- // footerRender: true,
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
+ // };
125
129
  // },
126
- // } as any);
130
+ // onError: (error) => {
131
+ // console.error(`LastMessage subscription error for channel ${channel.id}:`, error);
132
+ // },
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
+ // });
127
147
  // };
128
- // }, []);
148
+ // }, [data?.channelsByUser, subscribeToMore]);
129
149
 
130
- // useEffect(() => {
131
- // if (userChannels?.channelsByUser) {
132
- // if (userChannels?.channelsByUser?.length == 0) {
133
- // setUserDirectChannel([]);
134
- // }
135
- // //Direct channel
136
- // let userDirectChannels: any =
137
- // userChannels?.channelsByUser
138
- // ?.filter((i: any) => i.type == 'DIRECT')
139
- // ?.filter((c: any) =>
140
- // c.members.some((u: any) => u?.user?.id != auth?.id && u.user.__typename == 'UserAccount'),
141
- // ) ?? [];
142
-
143
- // if (userDirectChannels?.length > 0) setUserDirectChannel(userDirectChannels);
144
- // }
145
- // }, [userChannels?.channelsByUser]);
146
-
147
- const handleSelectChannel = useCallback((id: any, title: any) => {
148
- if (params?.channelId) {
149
- navigation.navigate(config.INBOX_MESSEGE_PATH as any, {
150
- channelId: params?.channelId,
151
- role: params?.role,
152
- title: params?.title ?? null,
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,
153
193
  hideTabBar: true,
194
+ timestamp: new Date().getTime(),
195
+ orgName: params?.orgName,
154
196
  });
155
- } else {
156
- navigation.navigate(config.INBOX_MESSEGE_PATH as any, {
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, {
157
216
  channelId: id,
158
217
  role: channelRole,
159
218
  title: title,
219
+ postParentId: postParentId,
160
220
  hideTabBar: true,
221
+ orgName: params?.orgName,
161
222
  });
162
- }
223
+ },
224
+ [navigation, channelRole],
225
+ );
226
+
227
+ // Handle search query changes
228
+ const handleSearchChange = useCallback((text: string) => {
229
+ setSearchQuery(text);
163
230
  }, []);
164
231
 
165
- const handleSelectServiceChannel = useCallback((id: any, title: any, postParentId: any) => {
166
- if (params?.channelId) {
167
- navigation.navigate(
168
- params?.postParentId || params?.postParentId == 0
169
- ? config.THREAD_MESSEGE_PATH
170
- : (config.THREADS_PATH as any),
171
- {
172
- channelId: params?.channelId,
173
- role: params?.role,
174
- title: params?.title ?? null,
175
- postParentId: params?.postParentId,
176
- hideTabBar: true,
177
- },
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
+ />
178
272
  );
179
- } else {
180
- navigation.navigate(
181
- postParentId || postParentId == 0 ? config.THREAD_MESSEGE_PATH : (config.THREADS_PATH as any),
182
- {
183
- channelId: id,
184
- role: channelRole,
185
- title: title,
186
- postParentId: postParentId,
187
- hideTabBar: true,
188
- },
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>
189
295
  );
190
296
  }
191
- }, []);
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]);
192
332
 
193
- const handleRefresh = useCallback(() => {
194
- //if(userChannels?.channelsByUser?.length != channels?.length)setRefresh(true);
195
- setRefresh(true);
196
- getChannelsRefetch({ role: channelRole, criteria: channelFilters })?.finally(() => setRefresh(false));
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
+ };
197
342
  }, []);
198
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
+
199
428
  return (
200
429
  <Box className="p-2">
201
430
  <FlatList
202
- data={channels && channels?.length > 0 ? channels : []}
203
- onRefresh={handleRefresh}
204
- refreshing={refreshing}
431
+ data={displayChannels}
432
+ onRefresh={handlePullToRefresh}
433
+ refreshing={loading}
205
434
  contentContainerStyle={{ minHeight: '100%' }}
206
- ItemSeparatorComponent={() => <Box className="h-0.5 bg-gray-200" />}
207
- renderItem={({ item: channel }: any) =>
208
- channel?.type === RoomType.Service ? (
209
- <ServiceDialogsListItem
210
- onOpen={handleSelectServiceChannel}
211
- currentUser={auth}
212
- channel={channel}
213
- refreshing={refreshing}
214
- selectedChannelId={params?.channelId}
215
- role={channelRole}
216
- />
217
- ) : (
218
- <DialogsListItem
219
- onOpen={handleSelectChannel}
220
- currentUser={auth}
221
- channel={channel}
222
- selectedChannelId={params?.channelId}
223
- />
224
- )
225
- }
226
- ListEmptyComponent={() => (
227
- <>
228
- {userChannelsLoading ? (
229
- <Center className="flex-1 justify-center items-center">
230
- <Spinner color={colors.blue[500]} />
231
- </Center>
232
- ) : (
233
- <Box className="p-5">
234
- <Heading>Chat</Heading>
235
- <Input className={`h-[50] mt-3 rounded-[50] border-gray-200 border `}>
236
- <InputField placeholder="Search" />
237
- </Input>
238
- <Center className="mt-6">
239
- <Ionicons name="chatbubbles" size={50} />
240
- <Text>You don't have any messages yet!</Text>
241
- </Center>
242
- </Box>
243
- )}
244
- </>
435
+ ItemSeparatorComponent={React.useCallback(
436
+ () => (
437
+ <Box className="h-0.5 bg-gray-200" />
438
+ ),
439
+ [],
245
440
  )}
246
- keyExtractor={(item, index) => 'key' + index}
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}
247
453
  />
248
454
  </Box>
249
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 { useSupportServiceChannelsQuery } from 'common/graphql';
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
- } = useSupportServiceChannelsQuery({
30
+ } = useServiceChannelsQuery({
31
31
  variables: {
32
32
  criteria: { type: 'SERVICE' },
33
33
  },