@messenger-box/platform-mobile 10.0.3-alpha.18 → 10.0.3-alpha.20

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 (23) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/lib/screens/inbox/components/DialogsListItem.js +423 -50
  3. package/lib/screens/inbox/components/DialogsListItem.js.map +1 -1
  4. package/lib/screens/inbox/components/ServiceDialogsListItem.js +375 -51
  5. package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +1 -1
  6. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js +175 -0
  7. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js.map +1 -0
  8. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js +191 -0
  9. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js.map +1 -0
  10. package/lib/screens/inbox/containers/Dialogs.js +382 -181
  11. package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
  12. package/lib/screens/inbox/containers/ThreadConversationView.js +536 -48
  13. package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
  14. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js +1 -25
  15. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js.map +1 -1
  16. package/package.json +2 -2
  17. package/src/screens/inbox/components/DialogsListItem.tsx +624 -107
  18. package/src/screens/inbox/components/ServiceDialogsListItem.tsx +506 -114
  19. package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +35 -17
  20. package/src/screens/inbox/components/workflow/dialogs-list-item-xstate.ts +145 -0
  21. package/src/screens/inbox/components/workflow/service-dialogs-list-item-xstate.ts +159 -0
  22. package/src/screens/inbox/containers/Dialogs.tsx +531 -281
  23. package/src/screens/inbox/containers/ThreadConversationView.tsx +812 -37
@@ -14,7 +14,7 @@ import {
14
14
  Spinner,
15
15
  Text,
16
16
  } from '@admin-layout/gluestack-ui-mobile';
17
- import { Platform, Linking, SafeAreaView, View, TouchableHighlight } from 'react-native';
17
+ import { Platform, Linking, SafeAreaView, View, TouchableHighlight, Alert } from 'react-native';
18
18
  import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
19
19
  import { useSelector } from 'react-redux';
20
20
  import { orderBy, startCase, uniqBy } from 'lodash-es';
@@ -64,10 +64,23 @@ const {
64
64
 
65
65
  const createdAtText = (value: string) => {
66
66
  if (!value) return '';
67
- let date = new Date(value);
68
- if (isToday(date)) return 'Today';
69
- if (isYesterday(date)) return 'Yesterday';
70
- return format(new Date(value), 'MMM dd, yyyy');
67
+
68
+ try {
69
+ // Validate the date before processing
70
+ const timestamp = new Date(value).getTime();
71
+ if (isNaN(timestamp)) {
72
+ console.warn(`Invalid date value in createdAtText: ${value}`);
73
+ return 'Unknown date';
74
+ }
75
+
76
+ let date = new Date(value);
77
+ if (isToday(date)) return 'Today';
78
+ if (isYesterday(date)) return 'Yesterday';
79
+ return format(date, 'MMM dd, yyyy');
80
+ } catch (error) {
81
+ console.error(`Error processing date in createdAtText: ${value}`, error);
82
+ return 'Unknown date';
83
+ }
71
84
  };
72
85
 
73
86
  // Create a safer version of useMachine to handle potential errors
@@ -214,6 +227,8 @@ function useSafeMachine(machine) {
214
227
  context: {
215
228
  ...prev.context,
216
229
  loading: false,
230
+ loadingOldMessages:
231
+ event.data?.loadingOldMessages === false ? false : prev.context.loadingOldMessages,
217
232
  },
218
233
  }));
219
234
  } else if (event.type === ThreadActions.SEND_THREAD_MESSAGE) {
@@ -287,12 +302,111 @@ function useSafeMachine(machine) {
287
302
  } else if (event.type === 'FETCH_MORE_MESSAGES_SUCCESS') {
288
303
  setState((prev) => {
289
304
  const newMessages = event.data?.messages || [];
305
+ const apiTotalCount = event.data?.totalCount;
306
+ console.log(
307
+ `Merging ${newMessages.length} older messages with ${prev.context.threadMessages.length} existing messages`,
308
+ );
309
+
310
+ // Debug: Log the dates of the messages for troubleshooting
311
+ if (newMessages.length > 0) {
312
+ try {
313
+ console.log(
314
+ 'First new message date:',
315
+ !isNaN(new Date(newMessages[0].createdAt).getTime())
316
+ ? new Date(newMessages[0].createdAt).toISOString()
317
+ : 'Invalid date',
318
+ );
319
+ console.log(
320
+ 'Last new message date:',
321
+ !isNaN(new Date(newMessages[newMessages.length - 1].createdAt).getTime())
322
+ ? new Date(newMessages[newMessages.length - 1].createdAt).toISOString()
323
+ : 'Invalid date',
324
+ );
325
+ } catch (error) {
326
+ console.error('Error logging new message dates:', error);
327
+ }
328
+ }
329
+
330
+ if (prev.context.threadMessages.length > 0) {
331
+ try {
332
+ console.log(
333
+ 'First existing message date:',
334
+ !isNaN(new Date(prev.context.threadMessages[0].createdAt).getTime())
335
+ ? new Date(prev.context.threadMessages[0].createdAt).toISOString()
336
+ : 'Invalid date',
337
+ );
338
+ console.log(
339
+ 'Last existing message date:',
340
+ !isNaN(
341
+ new Date(
342
+ prev.context.threadMessages[prev.context.threadMessages.length - 1].createdAt,
343
+ ).getTime(),
344
+ )
345
+ ? new Date(
346
+ prev.context.threadMessages[prev.context.threadMessages.length - 1].createdAt,
347
+ ).toISOString()
348
+ : 'Invalid date',
349
+ );
350
+ } catch (error) {
351
+ console.error('Error logging existing message dates:', error);
352
+ }
353
+ }
354
+
355
+ // If no new messages returned but total count says there should be more
356
+ if (newMessages.length === 0 && prev.context.totalCount > prev.context.threadMessages.length) {
357
+ console.log('No new messages found despite totalCount indicating more should exist');
358
+
359
+ // Adjust totalCount to match reality
360
+ return {
361
+ ...prev,
362
+ context: {
363
+ ...prev.context,
364
+ loadingOldMessages: false,
365
+ totalCount: prev.context.threadMessages.length,
366
+ },
367
+ value: 'active',
368
+ };
369
+ }
370
+
371
+ // Make sure we don't add duplicate messages
372
+ const combinedMessages = uniqBy([...prev.context.threadMessages, ...newMessages], 'id');
373
+
374
+ // GiftedChat expects messages sorted by date (newest first)
375
+ const sortedMessages = orderBy(
376
+ combinedMessages,
377
+ [
378
+ (msg) => {
379
+ try {
380
+ // Safely access and convert date
381
+ return !isNaN(new Date(msg.createdAt).getTime())
382
+ ? new Date(msg.createdAt).getTime()
383
+ : 0; // Default to oldest if invalid
384
+ } catch (error) {
385
+ console.error(`Error sorting message by date: ${msg.id}`, error);
386
+ return 0; // Default to oldest if error
387
+ }
388
+ },
389
+ ],
390
+ ['desc'],
391
+ );
392
+
393
+ // Use the total count from the API response if available, otherwise keep the current count
394
+ const newTotalCount =
395
+ typeof apiTotalCount === 'number'
396
+ ? apiTotalCount
397
+ : Math.max(sortedMessages.length, prev.context.totalCount);
398
+
399
+ console.log(
400
+ `Total messages after merge and sort: ${sortedMessages.length}, totalCount: ${newTotalCount}`,
401
+ );
402
+
290
403
  return {
291
404
  ...prev,
292
405
  context: {
293
406
  ...prev.context,
294
407
  loadingOldMessages: false,
295
- threadMessages: uniqBy([...prev.context.threadMessages, ...newMessages], ({ id }) => id),
408
+ threadMessages: sortedMessages,
409
+ totalCount: newTotalCount,
296
410
  },
297
411
  value: 'active',
298
412
  };
@@ -432,6 +546,64 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
432
546
  { data, loading: threadLoading, fetchMore: fetchMoreMessages, refetch: refetchThreadMessages, subscribeToMore },
433
547
  ] = useGetPostThreadLazyQuery({ fetchPolicy: 'cache-and-network' });
434
548
 
549
+ // Add a function to force refresh all messages
550
+ const forceRefreshMessages = useCallback(() => {
551
+ console.log('Force refreshing all messages');
552
+
553
+ // Clear the current messages first
554
+ safeSend({
555
+ type: ThreadActions.CLEAR_MESSAGES,
556
+ });
557
+
558
+ // Then trigger a refetch
559
+ if (channelId && parentId) {
560
+ safeSend({
561
+ type: ThreadActions.START_LOADING,
562
+ data: { loading: true },
563
+ });
564
+
565
+ getThreadMessages({
566
+ variables: {
567
+ channelId: channelId?.toString(),
568
+ role: role?.toString(),
569
+ postParentId: !parentId || parentId == 0 ? null : parentId?.toString(),
570
+ selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
571
+ limit: 50, // Use larger limit that proved to work
572
+ },
573
+ })
574
+ .then(({ data }) => {
575
+ if (data?.getPostThread) {
576
+ const threads: any = data.getPostThread;
577
+ const threadPost = threads?.post ?? [];
578
+ const threadReplies = threads?.replies ?? [];
579
+ const messageTotalCount = threads?.replyCount ?? 0;
580
+ const messages = [...threadReplies];
581
+
582
+ console.log(
583
+ `Force refresh complete. Got ${messages.length} messages of ${messageTotalCount} total`,
584
+ );
585
+
586
+ safeSend({
587
+ type: ThreadActions.SET_THREAD_MESSAGES,
588
+ data: {
589
+ messages,
590
+ totalCount: messageTotalCount,
591
+ threadPost: threadPost,
592
+ postThread: threads,
593
+ },
594
+ });
595
+ }
596
+ })
597
+ .catch((error) => {
598
+ console.error('Error during force refresh:', error);
599
+ safeSend({
600
+ type: 'ERROR',
601
+ data: { message: error.message },
602
+ });
603
+ });
604
+ }
605
+ }, [channelId, parentId, getThreadMessages, safeSend]);
606
+
435
607
  useFocusEffect(
436
608
  React.useCallback(() => {
437
609
  // navigation?.setOptions({ title: params?.title ?? 'Thread' });
@@ -443,6 +615,11 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
443
615
  <MaterialIcons size={20} name="arrow-back-ios" color={'black'} />
444
616
  </Button>
445
617
  ),
618
+ headerRight: () => (
619
+ <Button className="bg-transparent active:bg-gray-200 mr-2" onPress={forceRefreshMessages}>
620
+ <MaterialIcons size={20} name="refresh" color={'black'} />
621
+ </Button>
622
+ ),
446
623
  });
447
624
 
448
625
  // Set initial context when focused
@@ -462,7 +639,7 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
462
639
  return () => {
463
640
  safeSend({ type: ThreadActions.CLEAR_MESSAGES });
464
641
  };
465
- }, [postParentId]),
642
+ }, [postParentId, forceRefreshMessages]),
466
643
  );
467
644
 
468
645
  // Effect for when in FetchThreadMessages state
@@ -500,13 +677,14 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
500
677
  // Fetch thread messages function
501
678
  const fetchThreadMessages = useCallback(() => {
502
679
  if (channelId && parentId) {
680
+ console.log('Initial fetch of thread messages using larger limit (50)');
503
681
  getThreadMessages({
504
682
  variables: {
505
683
  channelId: channelId?.toString(),
506
684
  role: role?.toString(),
507
685
  postParentId: !parentId || parentId == 0 ? null : parentId?.toString(),
508
686
  selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
509
- limit: MESSAGES_PER_PAGE,
687
+ limit: 50, // Use larger limit that proved to work
510
688
  },
511
689
  })
512
690
  .then(({ data }) => {
@@ -517,6 +695,10 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
517
695
  const messageTotalCount = threads?.replyCount ?? 0;
518
696
  const messages = [...threadReplies];
519
697
 
698
+ console.log(
699
+ `Initial fetch complete. Got ${messages.length} messages of ${messageTotalCount} total`,
700
+ );
701
+
520
702
  safeSend({
521
703
  type: ThreadActions.SET_THREAD_MESSAGES,
522
704
  data: {
@@ -563,6 +745,74 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
563
745
  }
564
746
  }, [safeContextProperty('selectedImage')]);
565
747
 
748
+ // Add a safety timeout to clear loading state if it gets stuck
749
+ React.useEffect(() => {
750
+ const isLoading = safeContextProperty('loadingOldMessages', false);
751
+
752
+ if (isLoading) {
753
+ console.log('Message loading timeout safety started');
754
+ const timeoutId = setTimeout(() => {
755
+ // Check if we're still loading after 10 seconds
756
+ if (safeContextProperty('loadingOldMessages', false)) {
757
+ console.log('Message loading timed out - resetting state');
758
+ safeSend({
759
+ type: ThreadActions.STOP_LOADING,
760
+ data: { loadingOldMessages: false },
761
+ });
762
+ }
763
+ }, 10000); // 10 second timeout
764
+
765
+ return () => clearTimeout(timeoutId);
766
+ }
767
+ }, [safeContextProperty('loadingOldMessages')]);
768
+
769
+ // Add a safety counter to detect and fix incorrect message counts automatically
770
+ const failedLoadAttemptsRef = useRef(0);
771
+
772
+ // Track failed attempts to load more messages
773
+ const registerLoadAttemptResult = useCallback((success: boolean) => {
774
+ if (!success) {
775
+ failedLoadAttemptsRef.current += 1;
776
+ console.log(`Failed load attempt registered, count: ${failedLoadAttemptsRef.current}`);
777
+ } else {
778
+ // Reset counter on successful load
779
+ failedLoadAttemptsRef.current = 0;
780
+ }
781
+ }, []);
782
+
783
+ // Watch for failed load attempts and auto-fix the count after 3 consecutive failures
784
+ React.useEffect(() => {
785
+ const isLoading = safeContextProperty('loadingOldMessages', false);
786
+ const totalCount = safeContextProperty('totalCount', 0);
787
+ const messagesCount = safeContextProperty('threadMessages', []).length;
788
+
789
+ // If we're not loading and there's a discrepancy in message counts
790
+ if (!isLoading && totalCount > messagesCount) {
791
+ // If we've had 3 consecutive failed attempts, fix the count
792
+ if (failedLoadAttemptsRef.current >= 2) {
793
+ console.log(
794
+ `Auto-fixing incorrect message count after ${
795
+ failedLoadAttemptsRef.current + 1
796
+ } failed load attempts`,
797
+ );
798
+ console.log(`Adjusting totalCount from ${totalCount} to ${messagesCount}`);
799
+
800
+ safeSend({
801
+ type: ThreadActions.SET_THREAD_MESSAGES,
802
+ data: {
803
+ messages: safeContextProperty('threadMessages', []),
804
+ totalCount: messagesCount,
805
+ threadPost: safeContextProperty('threadPost', []),
806
+ postThread: safeContextProperty('postThread', null),
807
+ },
808
+ });
809
+
810
+ // Reset the counter
811
+ failedLoadAttemptsRef.current = 0;
812
+ }
813
+ }
814
+ }, [safeContextProperty('loadingOldMessages'), safeContextProperty('threadMessages')]);
815
+
566
816
  const scrollToBottom = React.useCallback(() => {
567
817
  if (threadMessageListRef?.current) {
568
818
  threadMessageListRef.current.scrollToBottom();
@@ -578,36 +828,111 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
578
828
 
579
829
  // Set the loading state specifically for old messages
580
830
  safeSend({
581
- type: ThreadActions.START_LOADING,
831
+ type: ThreadActions.FETCH_MORE_MESSAGES,
582
832
  data: { loadingOldMessages: true },
583
833
  });
584
834
 
835
+ // Since Skip=0, Limit=50 worked well, let's use that approach
836
+ console.log('Using proven approach: Skip=0, Limit=50');
837
+
838
+ // Try with a larger limit and skip=0, which we know works
839
+ const queryVariables = {
840
+ channelId: channelId?.toString(),
841
+ role: role?.toString(),
842
+ postParentId: !parentId || parentId == 0 ? null : parentId?.toString(),
843
+ selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
844
+ limit: 50, // Use larger limit that proved to work
845
+ skip: 0, // Start from the beginning
846
+ };
847
+
848
+ console.log('Query variables:', JSON.stringify(queryVariables));
849
+
585
850
  fetchMoreMessages({
586
- variables: {
587
- channelId: channelId?.toString(),
588
- role: role?.toString(),
589
- postParentId: parentId?.toString(),
590
- selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
591
- limit: MESSAGES_PER_PAGE,
592
- skip: threadMessages.length,
593
- },
851
+ variables: queryVariables,
594
852
  })
595
853
  .then((res: any) => {
854
+ console.log('API response received:', JSON.stringify(res?.data, null, 2));
855
+
596
856
  if (res?.data?.getPostThread) {
597
857
  const threads: any = res?.data?.getPostThread;
598
858
  const threadReplies = threads?.replies ?? [];
859
+ // Get the actual total count from the response
860
+ const actualTotalCount = threads?.replyCount ?? 0;
861
+
862
+ console.log('API response details:');
863
+ console.log('- replyCount:', threads?.replyCount);
864
+ console.log('- replies array length:', threadReplies.length);
865
+ console.log(
866
+ '- replies structure:',
867
+ threadReplies.length > 0
868
+ ? `First item has fields: ${Object.keys(threadReplies[0]).join(', ')}`
869
+ : 'No items',
870
+ );
871
+
872
+ if (threadReplies.length > 0) {
873
+ console.log('- Sample message:', JSON.stringify(threadReplies[0], null, 2));
874
+ }
875
+
876
+ console.log(
877
+ 'Successfully loaded more messages:',
878
+ threadReplies.length,
879
+ 'of total:',
880
+ actualTotalCount,
881
+ );
882
+ console.log('Thread reply IDs:', threadReplies.map((msg) => msg.id).join(', '));
883
+
884
+ // Compare with our existing messages to find the ones we're missing
885
+ const existingIds = new Set(threadMessages.map((msg) => msg.id));
886
+ const newUniqueMessages = threadReplies.filter((msg) => !existingIds.has(msg.id));
887
+
888
+ console.log(
889
+ `Found ${newUniqueMessages.length} unique new messages out of ${threadReplies.length} received`,
890
+ );
891
+
892
+ // If we've received all messages but our count doesn't match, update the total count
893
+ if (actualTotalCount !== totalCount) {
894
+ console.log(
895
+ `Updating totalCount from ${totalCount} to ${actualTotalCount} based on API response`,
896
+ );
897
+ // This will be applied below when we process the messages
898
+ }
599
899
 
600
- console.log('Successfully loaded more messages:', threadReplies.length);
900
+ // If no new unique messages, it means we already have everything
901
+ if (newUniqueMessages.length === 0) {
902
+ console.log('No new unique messages found, adjusting total count');
903
+
904
+ // Register this as a failed load attempt
905
+ registerLoadAttemptResult(false);
906
+
907
+ // Adjust total count to match what we have
908
+ safeSend({
909
+ type: ThreadActions.SET_THREAD_MESSAGES,
910
+ data: {
911
+ messages: threadMessages,
912
+ totalCount: threadMessages.length,
913
+ threadPost: safeContextProperty('threadPost', []),
914
+ postThread: safeContextProperty('postThread', null),
915
+ },
916
+ });
917
+ return;
918
+ }
919
+
920
+ // Register success since we found new messages
921
+ registerLoadAttemptResult(true);
922
+
923
+ console.log(`Adding ${newUniqueMessages.length} new messages to thread`);
601
924
 
602
925
  safeSend({
603
926
  type: 'FETCH_MORE_MESSAGES_SUCCESS',
604
927
  data: {
605
- messages: threadReplies,
928
+ messages: newUniqueMessages,
929
+ totalCount: actualTotalCount,
606
930
  loadingOldMessages: false,
607
931
  },
608
932
  });
609
933
  } else {
610
934
  console.log('No thread data returned when loading more messages');
935
+ registerLoadAttemptResult(false);
611
936
  safeSend({
612
937
  type: ThreadActions.STOP_LOADING,
613
938
  data: { loadingOldMessages: false },
@@ -616,6 +941,7 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
616
941
  })
617
942
  .catch((error: any) => {
618
943
  console.error('Error fetching more messages:', error);
944
+ registerLoadAttemptResult(false);
619
945
  safeSend({
620
946
  type: 'ERROR',
621
947
  data: {
@@ -626,18 +952,31 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
626
952
  });
627
953
  } else {
628
954
  console.log('No more messages to load or already loading');
955
+ // Make sure we're not stuck in loading state
956
+ if (safeContextProperty('loadingOldMessages', false)) {
957
+ safeSend({
958
+ type: ThreadActions.STOP_LOADING,
959
+ data: { loadingOldMessages: false },
960
+ });
961
+ }
629
962
  }
630
- }, [parentId, channelId, state.context]);
963
+ }, [parentId, channelId, state.context, registerLoadAttemptResult]);
631
964
 
632
965
  let onScroll = false;
633
966
 
634
967
  const handleScrollToTop = ({ nativeEvent }: any) => {
635
968
  // Check if we're near the top of the list
636
969
  if (isCloseToTop(nativeEvent)) {
637
- if (
638
- !safeContextProperty('loadingOldMessages', false) &&
639
- safeContextProperty('totalCount', 0) > safeContextProperty('threadMessages', []).length
640
- ) {
970
+ const isLoading = safeContextProperty('loadingOldMessages', false);
971
+ const totalCount = safeContextProperty('totalCount', 0);
972
+ const currentCount = safeContextProperty('threadMessages', []).length;
973
+ const hasMoreMessages = totalCount > currentCount;
974
+
975
+ console.log(
976
+ `Scroll near top - Loading state: ${isLoading}, Messages: ${currentCount}/${totalCount}, Has more: ${hasMoreMessages}`,
977
+ );
978
+
979
+ if (!isLoading && hasMoreMessages) {
641
980
  console.log('Near top of list - loading older messages');
642
981
  safeSend({ type: ThreadActions.FETCH_MORE_MESSAGES });
643
982
  }
@@ -650,8 +989,21 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
650
989
  };
651
990
 
652
991
  const isCloseToTop = ({ layoutMeasurement, contentOffset, contentSize }) => {
653
- const paddingToTop = 80;
654
- return contentOffset.y <= paddingToTop;
992
+ // We consider being "close to top" when scrolled to top 15% of visible area
993
+ const visibleHeight = layoutMeasurement.height;
994
+ const topThreshold = Math.min(80, visibleHeight * 0.15);
995
+
996
+ // For debugging
997
+ const distanceFromTop = contentOffset.y;
998
+ const totalContentHeight = contentSize.height;
999
+
1000
+ console.log(
1001
+ `Scroll position: ${distanceFromTop.toFixed(0)}px from top, threshold: ${topThreshold.toFixed(
1002
+ 0,
1003
+ )}px, content height: ${totalContentHeight.toFixed(0)}px`,
1004
+ );
1005
+
1006
+ return contentOffset.y <= topThreshold;
655
1007
  };
656
1008
 
657
1009
  const onSelectImages = async () => {
@@ -971,10 +1323,39 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
971
1323
  // Generate a unique _id if needed by combining id and createdAt
972
1324
  const uniqueId = msg.id || `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
973
1325
 
1326
+ // Safely create a Date object with validation
1327
+ let messageDate;
1328
+ try {
1329
+ // Check if createdAt is a valid date string
1330
+ if (msg.createdAt && !isNaN(new Date(msg.createdAt).getTime())) {
1331
+ messageDate = new Date(msg.createdAt);
1332
+ } else {
1333
+ // Use current time as fallback if date is invalid
1334
+ console.warn(`Invalid date value for message ${msg.id}: ${msg.createdAt}`);
1335
+ messageDate = new Date();
1336
+ }
1337
+ } catch (error) {
1338
+ console.error(`Error creating date for message ${msg.id}:`, error);
1339
+ messageDate = new Date(); // Fallback to current time
1340
+ }
1341
+
1342
+ // Extract image URL from files data
1343
+ let imageUrl = null;
1344
+ if (msg.files?.data && msg.files.data.length > 0) {
1345
+ const fileData = msg.files.data[0];
1346
+ if (fileData && fileData.url) {
1347
+ imageUrl = fileData.url;
1348
+ console.log('📷 Found image URL for message', msg.id, ':', imageUrl);
1349
+ }
1350
+ }
1351
+
1352
+ // Get the message text without adding "File attachment" for image-only messages
1353
+ let messageText = msg.message || '';
1354
+
974
1355
  let message: IMessageProps = {
975
1356
  _id: uniqueId,
976
- text: msg.message || '',
977
- createdAt: new Date(msg.createdAt),
1357
+ text: messageText,
1358
+ createdAt: messageDate,
978
1359
  user: {
979
1360
  _id: msg?.author?.id ?? auth?.profile?.id,
980
1361
  name:
@@ -984,7 +1365,7 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
984
1365
  avatar: msg?.author?.picture ?? auth?.profile?.picture,
985
1366
  },
986
1367
  type: msg?.type || '',
987
- image: msg?.files?.data?.[0]?.url,
1368
+ image: imageUrl,
988
1369
  sent: msg?.isDelivered || true,
989
1370
  received: msg?.isRead || false,
990
1371
  propsConfiguration: msg?.propsConfiguration,
@@ -994,7 +1375,39 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
994
1375
  }
995
1376
 
996
1377
  // Sort messages by date (newest first as required by GiftedChat)
997
- const sortedMessages = res.sort((a, b) => b.createdAt - a.createdAt);
1378
+ // Use a safer getTime() approach with error handling
1379
+ const sortedMessages = orderBy(
1380
+ res,
1381
+ [
1382
+ (msg) => {
1383
+ try {
1384
+ return msg.createdAt instanceof Date ? msg.createdAt.getTime() : new Date().getTime();
1385
+ } catch (error) {
1386
+ console.error('Error sorting message by date:', error);
1387
+ return 0; // Fallback to a default value
1388
+ }
1389
+ },
1390
+ ],
1391
+ ['desc'],
1392
+ );
1393
+
1394
+ // Log message dates to help with debugging
1395
+ if (sortedMessages.length > 0) {
1396
+ try {
1397
+ const firstMsg = sortedMessages[0];
1398
+ const lastMsg = sortedMessages[sortedMessages.length - 1];
1399
+
1400
+ console.log(
1401
+ 'Message date range:',
1402
+ lastMsg.createdAt instanceof Date ? lastMsg.createdAt.toISOString() : 'invalid date',
1403
+ 'to',
1404
+ firstMsg.createdAt instanceof Date ? firstMsg.createdAt.toISOString() : 'invalid date',
1405
+ );
1406
+ } catch (error) {
1407
+ console.error('Error logging message date range:', error);
1408
+ }
1409
+ }
1410
+
998
1411
  return sortedMessages;
999
1412
  }, [safeContextProperty('threadMessages'), auth]);
1000
1413
 
@@ -1036,6 +1449,8 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
1036
1449
 
1037
1450
  const renderMessageText = (props: any) => {
1038
1451
  const { currentMessage } = props;
1452
+
1453
+ // For ALERT type messages with call to action
1039
1454
  if (currentMessage.type === 'ALERT') {
1040
1455
  const attachment = currentMessage?.propsConfiguration?.contents?.attachment;
1041
1456
  let action: string = '';
@@ -1091,7 +1506,14 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
1091
1506
  )}
1092
1507
  </>
1093
1508
  );
1094
- } else {
1509
+ }
1510
+ // For file attachment messages, don't show the "File attachment" text
1511
+ else if (currentMessage.text === '📎 File attachment') {
1512
+ // Return null to not render any text for these messages
1513
+ return null;
1514
+ }
1515
+ // Default text rendering
1516
+ else {
1095
1517
  return <MessageText {...props} textStyle={{ left: { marginLeft: 5 } }} />;
1096
1518
  }
1097
1519
  };
@@ -1243,9 +1665,65 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
1243
1665
  );
1244
1666
  };
1245
1667
 
1668
+ // Add a function to load messages with specific skip value for debugging
1669
+ const forceLoadMessages = useCallback(
1670
+ (skipValue: number) => {
1671
+ console.log(`Force loading messages with explicit skip=${skipValue}, limit=50`);
1672
+
1673
+ if (channelId && parentId) {
1674
+ safeSend({
1675
+ type: ThreadActions.START_LOADING,
1676
+ data: { loading: true },
1677
+ });
1678
+
1679
+ getThreadMessages({
1680
+ variables: {
1681
+ channelId: channelId?.toString(),
1682
+ role: role?.toString(),
1683
+ postParentId: !parentId || parentId == 0 ? null : parentId?.toString(),
1684
+ selectedFields: 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
1685
+ skip: skipValue,
1686
+ limit: 50, // Use larger limit that proved to work
1687
+ },
1688
+ })
1689
+ .then(({ data }) => {
1690
+ if (data?.getPostThread) {
1691
+ const threads: any = data.getPostThread;
1692
+ const threadPost = threads?.post ?? [];
1693
+ const threadReplies = threads?.replies ?? [];
1694
+ const messageTotalCount = threads?.replyCount ?? 0;
1695
+ const messages = [...threadReplies];
1696
+
1697
+ console.log(
1698
+ `Force load with skip=${skipValue} complete. Got ${messages.length} messages of ${messageTotalCount} total`,
1699
+ );
1700
+
1701
+ safeSend({
1702
+ type: ThreadActions.SET_THREAD_MESSAGES,
1703
+ data: {
1704
+ messages,
1705
+ totalCount: messageTotalCount,
1706
+ threadPost: threadPost,
1707
+ postThread: threads,
1708
+ },
1709
+ });
1710
+ }
1711
+ })
1712
+ .catch((error) => {
1713
+ console.error('Error during force load:', error);
1714
+ safeSend({
1715
+ type: 'ERROR',
1716
+ data: { message: error.message },
1717
+ });
1718
+ });
1719
+ }
1720
+ },
1721
+ [channelId, parentId, getThreadMessages, safeSend],
1722
+ );
1723
+
1246
1724
  return (
1247
1725
  <SafeAreaView style={{ flex: 1 }}>
1248
- {safeContextProperty('loadingOldMessages', false) && (
1726
+ {safeContextProperty('loadingOldMessages', false) === true && (
1249
1727
  <Box className="absolute top-10 left-0 right-0 z-10 items-center">
1250
1728
  <Box className="bg-blue-500/20 rounded-full px-4 py-2 flex-row items-center">
1251
1729
  <Spinner color={colors.blue[500]} size="small" />
@@ -1253,6 +1731,294 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
1253
1731
  </Box>
1254
1732
  </Box>
1255
1733
  )}
1734
+ {!safeContextProperty('loadingOldMessages', false) &&
1735
+ safeContextProperty('totalCount', 0) > safeContextProperty('threadMessages', []).length && (
1736
+ <Box className="absolute top-10 left-0 right-0 z-10 items-center">
1737
+ <HStack space={2} className="px-2">
1738
+ <TouchableHighlight
1739
+ onPress={() => {
1740
+ console.log('Manual load more pressed');
1741
+ Alert.alert('Load Options', 'Choose loading method', [
1742
+ {
1743
+ text: 'Normal Load',
1744
+ onPress: () => safeSend({ type: ThreadActions.FETCH_MORE_MESSAGES }),
1745
+ },
1746
+ {
1747
+ text: 'Try Skip=0',
1748
+ onPress: () => forceLoadMessages(0),
1749
+ },
1750
+ {
1751
+ text: 'Try Skip=0, Limit=50',
1752
+ onPress: () => {
1753
+ // Try with a larger limit
1754
+ console.log('Force loading with explicit skip=0, limit=50');
1755
+
1756
+ safeSend({
1757
+ type: ThreadActions.START_LOADING,
1758
+ data: { loadingOldMessages: true },
1759
+ });
1760
+
1761
+ fetchMoreMessages({
1762
+ variables: {
1763
+ channelId: channelId?.toString(),
1764
+ role: role?.toString(),
1765
+ postParentId:
1766
+ !parentId || parentId == 0 ? null : parentId?.toString(),
1767
+ selectedFields:
1768
+ 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
1769
+ limit: 50, // Try a larger limit
1770
+ skip: 0,
1771
+ },
1772
+ })
1773
+ .then((res: any) => {
1774
+ console.log(
1775
+ 'LARGE LIMIT response:',
1776
+ JSON.stringify(res?.data, null, 2),
1777
+ );
1778
+
1779
+ if (res?.data?.getPostThread) {
1780
+ const threads: any = res?.data?.getPostThread;
1781
+ const threadReplies = threads?.replies ?? [];
1782
+ const actualTotalCount = threads?.replyCount ?? 0;
1783
+
1784
+ console.log(
1785
+ `Large limit load complete. Got ${threadReplies.length} messages of ${actualTotalCount} total`,
1786
+ );
1787
+
1788
+ if (threadReplies.length > 0) {
1789
+ // Reset our message list with all available messages
1790
+ safeSend({
1791
+ type: ThreadActions.SET_THREAD_MESSAGES,
1792
+ data: {
1793
+ messages: threadReplies,
1794
+ totalCount: actualTotalCount,
1795
+ threadPost: safeContextProperty(
1796
+ 'threadPost',
1797
+ [],
1798
+ ),
1799
+ postThread: threads,
1800
+ },
1801
+ });
1802
+ } else {
1803
+ // Reset count if no messages found
1804
+ safeSend({
1805
+ type: ThreadActions.SET_THREAD_MESSAGES,
1806
+ data: {
1807
+ messages: safeContextProperty(
1808
+ 'threadMessages',
1809
+ [],
1810
+ ),
1811
+ totalCount: safeContextProperty(
1812
+ 'threadMessages',
1813
+ [],
1814
+ ).length,
1815
+ threadPost: safeContextProperty(
1816
+ 'threadPost',
1817
+ [],
1818
+ ),
1819
+ postThread: safeContextProperty(
1820
+ 'postThread',
1821
+ null,
1822
+ ),
1823
+ },
1824
+ });
1825
+ }
1826
+ } else {
1827
+ safeSend({
1828
+ type: ThreadActions.STOP_LOADING,
1829
+ data: { loadingOldMessages: false },
1830
+ });
1831
+ }
1832
+ })
1833
+ .catch((error) => {
1834
+ console.error('Error in large limit load:', error);
1835
+ safeSend({
1836
+ type: ThreadActions.STOP_LOADING,
1837
+ data: { loadingOldMessages: false },
1838
+ });
1839
+ });
1840
+ },
1841
+ },
1842
+ {
1843
+ text: 'Try Direct Fetch',
1844
+ onPress: () => {
1845
+ // Get current info
1846
+ const currentCount = safeContextProperty('threadMessages', []).length;
1847
+ const totalCount = safeContextProperty('totalCount', 0);
1848
+ const missingCount = totalCount - currentCount;
1849
+
1850
+ if (missingCount <= 0) {
1851
+ Alert.alert('Info', 'No missing messages to fetch');
1852
+ return;
1853
+ }
1854
+
1855
+ console.log(
1856
+ `Attempting direct fetch of missing ${missingCount} messages`,
1857
+ );
1858
+
1859
+ // Try explicit query with exact parameters
1860
+ safeSend({
1861
+ type: ThreadActions.START_LOADING,
1862
+ data: { loadingOldMessages: true },
1863
+ });
1864
+
1865
+ // Special query directly for the missing messages
1866
+ getThreadMessages({
1867
+ variables: {
1868
+ channelId: channelId?.toString(),
1869
+ role: role?.toString(),
1870
+ postParentId:
1871
+ !parentId || parentId == 0 ? null : parentId?.toString(),
1872
+ selectedFields:
1873
+ 'id channel post replies replyCount lastReplyAt createdAt updatedAt',
1874
+ limit: missingCount, // Only get the exact number we need
1875
+ skip: 0, // Start from the beginning
1876
+ },
1877
+ })
1878
+ .then(({ data }) => {
1879
+ console.log(
1880
+ 'DIRECT FETCH response:',
1881
+ JSON.stringify(data, null, 2),
1882
+ );
1883
+
1884
+ if (data?.getPostThread) {
1885
+ const threads: any = data.getPostThread;
1886
+ const threadReplies = threads?.replies ?? [];
1887
+ const actualTotalCount = threads?.replyCount ?? 0;
1888
+
1889
+ console.log(
1890
+ `Direct fetch complete. Got ${threadReplies.length} messages of ${actualTotalCount} total`,
1891
+ );
1892
+ console.log(
1893
+ 'Message IDs:',
1894
+ threadReplies.map((msg) => msg.id).join(', '),
1895
+ );
1896
+
1897
+ // Compare with our existing messages to find the ones we're missing
1898
+ const existingIds = new Set(
1899
+ safeContextProperty('threadMessages', []).map(
1900
+ (msg) => msg.id,
1901
+ ),
1902
+ );
1903
+ const newMessages = threadReplies.filter(
1904
+ (msg) => !existingIds.has(msg.id),
1905
+ );
1906
+
1907
+ console.log(
1908
+ `Found ${newMessages.length} new messages that we didn't have before`,
1909
+ );
1910
+
1911
+ if (newMessages.length > 0) {
1912
+ safeSend({
1913
+ type: 'FETCH_MORE_MESSAGES_SUCCESS',
1914
+ data: {
1915
+ messages: newMessages,
1916
+ totalCount: actualTotalCount,
1917
+ loadingOldMessages: false,
1918
+ },
1919
+ });
1920
+
1921
+ // Show the results
1922
+ Alert.alert(
1923
+ 'Success',
1924
+ `Found ${newMessages.length} new messages`,
1925
+ );
1926
+ } else {
1927
+ // If we didn't find any new messages, adjust the total count
1928
+ console.log('No new messages found, adjusting count');
1929
+ safeSend({
1930
+ type: ThreadActions.SET_THREAD_MESSAGES,
1931
+ data: {
1932
+ messages: safeContextProperty(
1933
+ 'threadMessages',
1934
+ [],
1935
+ ),
1936
+ totalCount: safeContextProperty(
1937
+ 'threadMessages',
1938
+ [],
1939
+ ).length,
1940
+ threadPost: safeContextProperty(
1941
+ 'threadPost',
1942
+ [],
1943
+ ),
1944
+ postThread: safeContextProperty(
1945
+ 'postThread',
1946
+ null,
1947
+ ),
1948
+ },
1949
+ });
1950
+
1951
+ // Notify the user
1952
+ Alert.alert(
1953
+ 'Info',
1954
+ 'No new messages found. Count has been adjusted.',
1955
+ );
1956
+ }
1957
+ } else {
1958
+ Alert.alert('Error', 'Could not fetch thread messages');
1959
+ safeSend({
1960
+ type: ThreadActions.STOP_LOADING,
1961
+ data: { loadingOldMessages: false },
1962
+ });
1963
+ }
1964
+ })
1965
+ .catch((error) => {
1966
+ console.error('Error in direct fetch:', error);
1967
+ Alert.alert(
1968
+ 'Error',
1969
+ 'Failed to fetch messages: ' + error.message,
1970
+ );
1971
+ safeSend({
1972
+ type: ThreadActions.STOP_LOADING,
1973
+ data: { loadingOldMessages: false },
1974
+ });
1975
+ });
1976
+ },
1977
+ },
1978
+ {
1979
+ text: 'Reset Count',
1980
+ style: 'destructive',
1981
+ onPress: () => {
1982
+ console.log('Resetting message count to match reality');
1983
+ safeSend({
1984
+ type: ThreadActions.SET_THREAD_MESSAGES,
1985
+ data: {
1986
+ messages: safeContextProperty('threadMessages', []),
1987
+ totalCount: safeContextProperty('threadMessages', []).length,
1988
+ threadPost: safeContextProperty('threadPost', []),
1989
+ postThread: safeContextProperty('postThread', null),
1990
+ },
1991
+ });
1992
+ },
1993
+ },
1994
+ {
1995
+ text: 'Cancel',
1996
+ style: 'cancel',
1997
+ },
1998
+ ]);
1999
+ }}
2000
+ underlayColor="#e6e6e6"
2001
+ >
2002
+ <Box className="bg-gray-200 rounded-full px-4 py-2 flex-row items-center">
2003
+ <MaterialIcons name="arrow-upward" size={16} color={colors.blue[500]} />
2004
+ <Text className="text-sm font-medium color-blue-600 ml-2">
2005
+ Load More (
2006
+ {safeContextProperty('totalCount', 0) -
2007
+ safeContextProperty('threadMessages', []).length}
2008
+ )
2009
+ </Text>
2010
+ </Box>
2011
+ </TouchableHighlight>
2012
+
2013
+ <TouchableHighlight onPress={forceRefreshMessages} underlayColor="#e6e6e6">
2014
+ <Box className="bg-gray-200 rounded-full px-4 py-2 flex-row items-center">
2015
+ <MaterialIcons name="refresh" size={16} color={colors.blue[500]} />
2016
+ <Text className="text-sm font-medium color-blue-600 ml-2">Refresh All</Text>
2017
+ </Box>
2018
+ </TouchableHighlight>
2019
+ </HStack>
2020
+ </Box>
2021
+ )}
1256
2022
  {isPostParentIdThread && (
1257
2023
  <>
1258
2024
  {safeContextProperty('threadPost', [])?.length > 0 && (
@@ -1284,10 +2050,19 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
1284
2050
  </Text>
1285
2051
  <Text className="pl-0 color-gray-500">
1286
2052
  {createdAtText(safeContextProperty('threadPost')[0]?.createdAt)} at{' '}
1287
- {format(
1288
- new Date(safeContextProperty('threadPost')[0]?.createdAt),
1289
- 'hh:ss:a',
1290
- )}
2053
+ {(() => {
2054
+ try {
2055
+ const createdAt = safeContextProperty('threadPost')[0]?.createdAt;
2056
+ if (createdAt && !isNaN(new Date(createdAt).getTime())) {
2057
+ return format(new Date(createdAt), 'hh:mm:a');
2058
+ } else {
2059
+ return 'unknown time';
2060
+ }
2061
+ } catch (error) {
2062
+ console.error('Error formatting thread post time:', error);
2063
+ return 'unknown time';
2064
+ }
2065
+ })()}
1291
2066
  </Text>
1292
2067
  </Box>
1293
2068
  </HStack>
@@ -1326,7 +2101,7 @@ const ThreadConversationViewComponent = ({ channelId, postParentId, isPostParent
1326
2101
  minIndexForVisible: 0,
1327
2102
  autoscrollToTopThreshold: 100,
1328
2103
  },
1329
- scrollEventThrottle: 100,
2104
+ scrollEventThrottle: 16,
1330
2105
  keyboardDismissMode: 'on-drag',
1331
2106
  keyboardShouldPersistTaps: 'handled',
1332
2107
  }}