@droppii-org/chat-mobile 0.2.6 → 0.2.7

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 (116) hide show
  1. package/lib/module/components/ThreadCard/AvatarSection.js +4 -4
  2. package/lib/module/components/ThreadCard/AvatarSection.js.map +1 -1
  3. package/lib/module/components/ThreadCard/NamePrefixIcon.js +13 -16
  4. package/lib/module/components/ThreadCard/NamePrefixIcon.js.map +1 -1
  5. package/lib/module/components/ThreadCard/ThreadCard.js +13 -33
  6. package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
  7. package/lib/module/context/ChatContext.js +7 -6
  8. package/lib/module/context/ChatContext.js.map +1 -1
  9. package/lib/module/hooks/message/useSendMessage.js +101 -0
  10. package/lib/module/hooks/message/useSendMessage.js.map +1 -0
  11. package/lib/module/hooks/useChatMessages.js +37 -119
  12. package/lib/module/hooks/useChatMessages.js.map +1 -1
  13. package/lib/module/hooks/useConversationList.js +29 -17
  14. package/lib/module/hooks/useConversationList.js.map +1 -1
  15. package/lib/module/hooks/useLinkPreview/useLinkPreview.js +3 -2
  16. package/lib/module/hooks/useLinkPreview/useLinkPreview.js.map +1 -1
  17. package/lib/module/screens/chat-detail/ChatComposer.js +2 -2
  18. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  19. package/lib/module/screens/chat-detail/ChatDetail.js +14 -10
  20. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  21. package/lib/module/screens/chat-detail/ChatDetailHeader.js +5 -8
  22. package/lib/module/screens/chat-detail/ChatDetailHeader.js.map +1 -1
  23. package/lib/module/screens/chat-detail/ChatLinkPreview.js +1 -1
  24. package/lib/module/screens/chat-detail/ChatLinkPreview.js.map +1 -1
  25. package/lib/module/screens/chat-detail/ChatListLegend.js +0 -2
  26. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  27. package/lib/module/screens/chat-detail/conversationHeader.utils.js +7 -9
  28. package/lib/module/screens/chat-detail/conversationHeader.utils.js.map +1 -1
  29. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +10 -23
  30. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  31. package/lib/module/screens/chat-detail/legend/message-types.js +128 -6
  32. package/lib/module/screens/chat-detail/legend/message-types.js.map +1 -1
  33. package/lib/module/store/conversation.js +1 -1
  34. package/lib/module/store/conversation.js.map +1 -1
  35. package/lib/module/store/message.js +45 -0
  36. package/lib/module/store/message.js.map +1 -0
  37. package/lib/module/translation/resources/i18n.js +7 -1
  38. package/lib/module/translation/resources/i18n.js.map +1 -1
  39. package/lib/module/types/chat.js +2 -7
  40. package/lib/module/types/chat.js.map +1 -1
  41. package/lib/module/utils/conversation.js +34 -13
  42. package/lib/module/utils/conversation.js.map +1 -1
  43. package/lib/module/utils/legendListMessage.js +0 -3
  44. package/lib/module/utils/legendListMessage.js.map +1 -1
  45. package/lib/module/utils/message.js +5 -8
  46. package/lib/module/utils/message.js.map +1 -1
  47. package/lib/module/utils/url.js +3 -3
  48. package/lib/module/utils/url.js.map +1 -1
  49. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts +2 -2
  50. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts.map +1 -1
  51. package/lib/typescript/src/components/ThreadCard/NamePrefixIcon.d.ts +3 -4
  52. package/lib/typescript/src/components/ThreadCard/NamePrefixIcon.d.ts.map +1 -1
  53. package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
  54. package/lib/typescript/src/context/ChatContext.d.ts +1 -1
  55. package/lib/typescript/src/context/ChatContext.d.ts.map +1 -1
  56. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +12 -0
  57. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -0
  58. package/lib/typescript/src/hooks/useChatMessages.d.ts +0 -1
  59. package/lib/typescript/src/hooks/useChatMessages.d.ts.map +1 -1
  60. package/lib/typescript/src/hooks/useConversationList.d.ts +2 -1
  61. package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
  62. package/lib/typescript/src/hooks/useLinkPreview/useLinkPreview.d.ts.map +1 -1
  63. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts +1 -1
  64. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  65. package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts +1 -1
  66. package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts.map +1 -1
  67. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
  68. package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts +1 -1
  69. package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts.map +1 -1
  70. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +1 -3
  71. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  72. package/lib/typescript/src/screens/chat-detail/legend/message-types.d.ts +1 -0
  73. package/lib/typescript/src/screens/chat-detail/legend/message-types.d.ts.map +1 -1
  74. package/lib/typescript/src/screens/chat-detail/types.d.ts +6 -7
  75. package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
  76. package/lib/typescript/src/store/message.d.ts +3 -0
  77. package/lib/typescript/src/store/message.d.ts.map +1 -0
  78. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  79. package/lib/typescript/src/types/chat.d.ts +28 -27
  80. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  81. package/lib/typescript/src/types/common.d.ts +1 -0
  82. package/lib/typescript/src/types/common.d.ts.map +1 -1
  83. package/lib/typescript/src/utils/conversation.d.ts +3 -2
  84. package/lib/typescript/src/utils/conversation.d.ts.map +1 -1
  85. package/lib/typescript/src/utils/legendListMessage.d.ts +0 -2
  86. package/lib/typescript/src/utils/legendListMessage.d.ts.map +1 -1
  87. package/lib/typescript/src/utils/message.d.ts.map +1 -1
  88. package/lib/typescript/src/utils/url.d.ts +1 -1
  89. package/lib/typescript/src/utils/url.d.ts.map +1 -1
  90. package/package.json +3 -3
  91. package/src/components/ThreadCard/AvatarSection.tsx +5 -8
  92. package/src/components/ThreadCard/NamePrefixIcon.tsx +27 -38
  93. package/src/components/ThreadCard/ThreadCard.tsx +16 -30
  94. package/src/context/ChatContext.tsx +12 -4
  95. package/src/hooks/message/useSendMessage.ts +136 -0
  96. package/src/hooks/useChatMessages.ts +70 -158
  97. package/src/hooks/useConversationList.ts +34 -16
  98. package/src/hooks/useLinkPreview/useLinkPreview.ts +3 -2
  99. package/src/screens/chat-detail/ChatComposer.tsx +2 -2
  100. package/src/screens/chat-detail/ChatDetail.tsx +29 -22
  101. package/src/screens/chat-detail/ChatDetailHeader.tsx +4 -10
  102. package/src/screens/chat-detail/ChatLinkPreview.tsx +1 -1
  103. package/src/screens/chat-detail/ChatListLegend.tsx +1 -2
  104. package/src/screens/chat-detail/conversationHeader.utils.ts +11 -14
  105. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +15 -33
  106. package/src/screens/chat-detail/legend/message-types.tsx +167 -12
  107. package/src/screens/chat-detail/types.ts +6 -8
  108. package/src/store/conversation.ts +1 -1
  109. package/src/store/message.ts +44 -0
  110. package/src/translation/resources/i18n.ts +6 -0
  111. package/src/types/chat.ts +31 -30
  112. package/src/types/common.ts +1 -0
  113. package/src/utils/conversation.ts +44 -17
  114. package/src/utils/legendListMessage.ts +0 -5
  115. package/src/utils/message.ts +10 -12
  116. package/src/utils/url.ts +3 -3
@@ -1,8 +1,8 @@
1
1
  import { memo } from 'react';
2
2
  import { Image, StyleSheet } from 'react-native';
3
3
  import { KContainer, KImage, KColors } from '@droppii/libs';
4
- import { DChatCategory, DChatType } from '../../types/chat';
5
4
  import Images from '../../assets/images';
5
+ import { PeerType } from '@droppii/openim-rn-client-sdk';
6
6
 
7
7
  const styles = StyleSheet.create({
8
8
  icon: {
@@ -12,46 +12,35 @@ const styles = StyleSheet.create({
12
12
  });
13
13
 
14
14
  interface NamePrefixIconProps {
15
- chatCategory?: DChatCategory;
16
- chatType?: DChatType;
15
+ peerType?: PeerType | string;
17
16
  }
18
17
 
19
- export const NamePrefixIcon = memo(
20
- ({ chatCategory, chatType }: NamePrefixIconProps) => {
21
- if (chatCategory === DChatCategory.BIZ_BOT_CRM) {
22
- return (
23
- <KContainer.View marginR="0.25rem">
24
- <Image source={Images.ICON_DROPPII} style={styles.icon} />
25
- </KContainer.View>
26
- );
27
- }
28
- if (chatCategory === DChatCategory.BIZ_BOT_PDP) {
29
- return (
30
- <KContainer.View marginR="0.25rem">
31
- <KImage.VectorIcons
32
- name="robot-outline"
33
- provider="MaterialCommunityIcons"
34
- size={14}
35
- color={KColors.palette.primary.w400}
36
- />
37
- </KContainer.View>
38
- );
39
- }
40
-
41
- if (chatType === DChatType.GROUP) {
42
- return (
43
- <KContainer.View marginR="0.25rem">
44
- <KImage.VectorIcons
45
- name="user-two-b"
46
- size={14}
47
- color={KColors.black}
48
- />
49
- </KContainer.View>
50
- );
51
- }
18
+ export const NamePrefixIcon = memo(({ peerType }: NamePrefixIconProps) => {
19
+ if (peerType === PeerType.Bot) {
20
+ return (
21
+ <KContainer.View marginR="0.25rem">
22
+ <Image source={Images.ICON_DROPPII} style={styles.icon} />
23
+ </KContainer.View>
24
+ );
25
+ }
52
26
 
53
- return null;
27
+ if (peerType === PeerType.Group) {
28
+ return (
29
+ <KContainer.View marginR="0.25rem">
30
+ <KImage.VectorIcons name="user-two-b" size={14} color={KColors.black} />
31
+ </KContainer.View>
32
+ );
54
33
  }
55
- );
34
+
35
+ // if (peerType === DChatPeerType.CUSTOMER) {
36
+ // return (
37
+ // <KContainer.View marginR="0.25rem">
38
+ // <Image source={Images.ICON_DROPPII} style={styles.icon} />
39
+ // </KContainer.View>
40
+ // );
41
+ // }
42
+
43
+ return null;
44
+ });
56
45
 
57
46
  NamePrefixIcon.displayName = 'NamePrefixIcon';
@@ -1,12 +1,10 @@
1
1
  import { memo, useCallback } from 'react';
2
- import { StyleSheet, type GestureResponderEvent } from 'react-native';
2
+ import { type GestureResponderEvent } from 'react-native';
3
3
  import { KContainer, KImage, KLabel, KColors } from '@droppii/libs';
4
- import { DChatCategory } from '../../types/chat';
5
4
  import { AvatarSection } from './AvatarSection';
6
5
  import { NamePrefixIcon } from './NamePrefixIcon';
7
6
  import { UnreadBadge } from './UnreadBadge';
8
7
  import { formatTimestamp, getLastMessageText } from './thread-card.utils';
9
- import Images from '../../assets/images';
10
8
  import { useConversation, useUserStore } from '../../store';
11
9
 
12
10
  interface ThreadCardProps {
@@ -16,19 +14,20 @@ interface ThreadCardProps {
16
14
 
17
15
  const ThreadCard = memo(({ item, onPress }: ThreadCardProps) => {
18
16
  const {
19
- peer,
20
- chatCategory,
17
+ peerType,
18
+ showName,
19
+ faceURL,
21
20
  lastMessage,
22
21
  unreadCount = 0,
23
- isMuted,
24
- pinnedAt,
22
+ recvMsgOpt,
23
+ isPinned,
25
24
  applicationType,
26
- chatType,
27
25
  latestMsgSendTime,
28
26
  } = useConversation(item) ?? {};
29
27
 
30
- const fullName = peer?.fullName ?? peer?.username ?? '';
28
+ const fullName = showName ?? '';
31
29
  const hasUnread = unreadCount > 0;
30
+ const isMuted = (recvMsgOpt ?? 0) !== 0;
32
31
  const timestamp = formatTimestamp(
33
32
  latestMsgSendTime ?? lastMessage?.sendTime ?? null
34
33
  );
@@ -52,30 +51,25 @@ const ThreadCard = memo(({ item, onPress }: ThreadCardProps) => {
52
51
  activeOpacity={0.7}
53
52
  >
54
53
  <AvatarSection
55
- avatar={peer?.avatar ?? null}
54
+ avatar={faceURL ?? null}
56
55
  fullName={fullName}
57
- chatCategory={chatCategory}
56
+ peerType={peerType}
58
57
  applicationType={applicationType}
59
- chatType={chatType}
60
58
  />
61
59
 
62
60
  {/* Chat-Content */}
63
61
  <KContainer.View flex marginL="0.5rem" paddingB={2}>
64
62
  {/* Row-Title */}
65
63
  <KContainer.View row alignItems="center">
66
- <NamePrefixIcon chatCategory={chatCategory} chatType={chatType} />
67
- <KContainer.VisibleView
68
- visible={
69
- // chatCategory === DChatCategory.BIZ_BOT_CRM ||
70
- chatCategory === DChatCategory.BIZ_BOT_PDP
71
- }
72
- >
64
+ <NamePrefixIcon peerType={peerType} />
65
+ {/* For verified PDP (configuration details to be defined later) */}
66
+ {/* <KContainer.VisibleView visible={peerType === DChatPeerType.BOT}>
73
67
  <KImage.Base
74
68
  uri={Images.ICON_BOT}
75
69
  size={14}
76
70
  style={styles.botIcon}
77
71
  />
78
- </KContainer.VisibleView>
72
+ </KContainer.VisibleView> */}
79
73
  <KLabel.Text
80
74
  typo={hasUnread ? 'TextMdBold' : 'TextMdNormal'}
81
75
  numberOfLines={1}
@@ -84,7 +78,7 @@ const ThreadCard = memo(({ item, onPress }: ThreadCardProps) => {
84
78
  >
85
79
  {fullName}
86
80
  </KLabel.Text>
87
- <KContainer.VisibleView visible={!!isMuted}>
81
+ <KContainer.VisibleView visible={isMuted}>
88
82
  <KContainer.View marginL="0.25rem">
89
83
  <KImage.VectorIcons
90
84
  name="bell-off"
@@ -115,7 +109,7 @@ const ThreadCard = memo(({ item, onPress }: ThreadCardProps) => {
115
109
  >
116
110
  {lastMessageText}
117
111
  </KLabel.Text>
118
- <KContainer.VisibleView visible={Boolean(pinnedAt)}>
112
+ <KContainer.VisibleView visible={Boolean(isPinned)}>
119
113
  <KContainer.View marginL="0.5rem">
120
114
  <KImage.VectorIcons
121
115
  name="pin-outline"
@@ -135,11 +129,3 @@ const ThreadCard = memo(({ item, onPress }: ThreadCardProps) => {
135
129
  ThreadCard.displayName = 'ThreadCard';
136
130
 
137
131
  export default ThreadCard;
138
-
139
- const styles = StyleSheet.create({
140
- botIcon: {
141
- width: 16,
142
- height: 16,
143
- marginRight: 4,
144
- },
145
- });
@@ -13,11 +13,19 @@ export const useChatContext = (): ChatContextType => {
13
13
  return context;
14
14
  };
15
15
 
16
- const ChatProviderInner = ({ logGA, children }: ChatProviderProps) => {
17
- const value = useMemo(() => ({ logGA }), [logGA]);
16
+ const ChatProviderInner = ({
17
+ logGA,
18
+ children,
19
+ applicationType,
20
+ }: ChatProviderProps) => {
21
+ const value = useMemo(
22
+ () => ({ logGA, applicationType }),
23
+ [logGA, applicationType]
24
+ );
18
25
 
19
26
  return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
20
27
  };
21
28
 
22
- export const ChatProvider = ({ enabled, ...props }: ChatProviderProps) =>
23
- enabled ? <ChatProviderInner {...props} /> : props.children;
29
+ export const ChatProvider = ({ ...props }: ChatProviderProps) => (
30
+ <ChatProviderInner {...props} />
31
+ );
@@ -0,0 +1,136 @@
1
+ import { useCallback } from 'react';
2
+ import type { UploadFileItem } from 'react-native-fs';
3
+ import { useConversationStore } from '../../store';
4
+ import type {
5
+ MessageItem,
6
+ MessageType,
7
+ OfflinePush,
8
+ } from '@droppii/openim-rn-client-sdk';
9
+ import { extractUrls } from '../../utils/url';
10
+ import OpenIMSDK from '@droppii/openim-rn-client-sdk';
11
+ import type { IMessageItemEx } from '../../types/chat';
12
+ import { useChatContext } from '../../context';
13
+ import { generateContentBasedOnMessageType } from '../../utils/conversation';
14
+ import { useMessageStore } from '../../store/message';
15
+ import type { IUrlMetadata } from '../../types/common';
16
+
17
+ export const useSendMessage = () => {
18
+ const applicationType = useChatContext().applicationType;
19
+ const pushNewMessage = useMessageStore((state) => state.pushNewMessage);
20
+
21
+ const sendMessage = useCallback(
22
+ async ({
23
+ plainText,
24
+ files,
25
+ urlMetadata,
26
+ }: {
27
+ plainText?: string;
28
+ files?: UploadFileItem[]; //TODO
29
+ urlMetadata?: IUrlMetadata;
30
+ }) => {
31
+ const { userID: recvID, groupID } =
32
+ useConversationStore.getState().getCurrentConversation() || {};
33
+ if (!recvID && !groupID) return;
34
+
35
+ const messageList: MessageItem[] = [];
36
+
37
+ for (const file of files || []) {
38
+ if (!file) continue;
39
+ }
40
+
41
+ if (!!plainText && plainText.trim() !== '') {
42
+ const urls = extractUrls(plainText);
43
+ const isUrlMessage = urls.length > 0;
44
+ let message: MessageItem | null = null;
45
+ if (isUrlMessage) {
46
+ message = await createUrlTextMessage(plainText, urls);
47
+ } else {
48
+ message = await createTextMessage(plainText);
49
+ }
50
+ if (!message) return;
51
+ messageList.push(message);
52
+ }
53
+
54
+ for (const message of messageList) {
55
+ const extendMessageInfo: IMessageItemEx = {
56
+ applicationType,
57
+ urlMetadata,
58
+ };
59
+
60
+ const successMessage = await OpenIMSDK.sendMessage({
61
+ groupID: groupID || '',
62
+ recvID: recvID || '',
63
+ message: {
64
+ ...message,
65
+ ex: JSON.stringify(extendMessageInfo) || '{}',
66
+ },
67
+ offlinePushInfo: generateOfflinePushInfo(
68
+ message.contentType,
69
+ plainText || ''
70
+ ),
71
+ });
72
+
73
+ pushNewMessage(successMessage);
74
+ }
75
+ },
76
+ [applicationType, pushNewMessage]
77
+ );
78
+
79
+ return {
80
+ sendMessage,
81
+ };
82
+ };
83
+
84
+ const generateOfflinePushInfo = (
85
+ contentType: MessageType,
86
+ plainText: string
87
+ ) => {
88
+ const conversationData = useConversationStore
89
+ ?.getState?.()
90
+ ?.getCurrentConversation();
91
+ const title = conversationData?.showName || 'Droppii';
92
+ const desc = generateContentBasedOnMessageType(contentType, plainText);
93
+ return {
94
+ title,
95
+ desc,
96
+ ex: JSON.stringify({
97
+ icon: conversationData?.faceURL || '',
98
+ conversationId: conversationData?.conversationId || '',
99
+ title,
100
+ desc,
101
+ }),
102
+ iOSPushSound: 'default',
103
+ iOSBadgeCount: true,
104
+ } as OfflinePush;
105
+ };
106
+
107
+ export const createTextMessage = async (text: string) => {
108
+ let textMessage = await OpenIMSDK.createTextMessage(
109
+ text,
110
+ new Date().getTime().toString()
111
+ )
112
+ .then((data) => {
113
+ return data;
114
+ })
115
+ .catch(({ errCode, errMsg }) => {
116
+ console.error('createTextMessage', errCode, errMsg);
117
+ return null;
118
+ });
119
+ return textMessage;
120
+ };
121
+
122
+ export const createUrlTextMessage = async (text: string, urls: string[]) => {
123
+ let urlTextMessage = await OpenIMSDK.createUrlTextMessage(
124
+ text,
125
+ urls,
126
+ new Date().getTime().toString()
127
+ )
128
+ .then((data) => {
129
+ return data;
130
+ })
131
+ .catch(({ errCode, errMsg }) => {
132
+ console.error('createUrlTextMessage', errCode, errMsg);
133
+ return null;
134
+ });
135
+ return urlTextMessage;
136
+ };
@@ -7,11 +7,10 @@ import { ChatMessageAPI } from '../services/message';
7
7
  import type { DMessageItem } from '../types/chat';
8
8
  import {
9
9
  belongsToConversation,
10
- getHistoryPaginationAnchor,
11
10
  hasNewHistoryMessages,
12
11
  mergeMessages,
13
- type HistoryPaginationAnchor,
14
12
  } from '../utils/message';
13
+ import { useMessageStore } from '../store/message';
15
14
 
16
15
  type UseChatMessagesOptions = {
17
16
  conversationId: string;
@@ -24,44 +23,24 @@ export function useChatMessages({
24
23
  enabled = true,
25
24
  pageSize = 20,
26
25
  }: UseChatMessagesOptions) {
27
- const [messages, setMessages] = useState<DMessageItem[]>([]);
26
+ const messages = useMessageStore((s) => s.messages);
27
+ const hasMoreEarlier = useMessageStore((s) => s.hasMoreEarlier);
28
+ const hasMoreNewer = useMessageStore((s) => s.hasMoreNewer);
29
+ const setMessages = useMessageStore((s) => s.setMessages);
30
+ const pushNewMessage = useMessageStore((s) => s.pushNewMessage);
31
+ const reset = useMessageStore((s) => s.reset);
32
+
28
33
  const [currentUserId, setCurrentUserId] = useState<string>();
29
34
  const [isLoading, setIsLoading] = useState(false);
30
35
  const [isLoadingEarlier, setIsLoadingEarlier] = useState(false);
31
- const [hasMoreEarlier, setHasMoreEarlier] = useState(false);
32
36
  const [isLoadingNewer, setIsLoadingNewer] = useState(false);
33
- const [hasMoreNewer, setHasMoreNewer] = useState(false);
34
37
  const [error, setError] = useState<Error | null>(null);
35
38
 
36
39
  const conversationIdRef = useRef(conversationId);
37
40
  conversationIdRef.current = conversationId;
38
41
 
39
- const isLoadingEarlierRef = useRef(false);
40
- const isLoadingNewerRef = useRef(false);
41
- const messagesRef = useRef(messages);
42
- const hasMoreEarlierRef = useRef(hasMoreEarlier);
43
- const hasMoreNewerRef = useRef(hasMoreNewer);
44
- const historyAnchorRef = useRef<HistoryPaginationAnchor | null>(null);
45
- const newestAnchorRef = useRef<HistoryPaginationAnchor | null>(null);
46
- messagesRef.current = messages;
47
- hasMoreEarlierRef.current = hasMoreEarlier;
48
- hasMoreNewerRef.current = hasMoreNewer;
49
-
50
- const resetState = useCallback(() => {
51
- setMessages([]);
52
- setHasMoreEarlier(false);
53
- setHasMoreNewer(false);
54
- setError(null);
55
- isLoadingEarlierRef.current = false;
56
- isLoadingNewerRef.current = false;
57
- historyAnchorRef.current = null;
58
- newestAnchorRef.current = null;
59
- }, []);
60
-
61
- const loadInitialMessages = useCallback(async () => {
62
- if (!conversationId) {
63
- return;
64
- }
42
+ const fetchInitial = useCallback(async () => {
43
+ if (!conversationId) return;
65
44
 
66
45
  setIsLoading(true);
67
46
  setError(null);
@@ -75,22 +54,9 @@ export function useChatMessages({
75
54
  ]);
76
55
 
77
56
  const initialMessages = history.messageList as DMessageItem[];
78
- const oldestAnchor = getHistoryPaginationAnchor(initialMessages);
79
57
 
80
58
  setCurrentUserId(userId);
81
- setMessages(mergeMessages([], initialMessages));
82
- historyAnchorRef.current = oldestAnchor
83
- ? {
84
- clientMsgID: oldestAnchor.clientMsgID,
85
- // lastMinSeq: resolveHistoryLastMinSeq(
86
- // history,
87
- // initialMessages.find(
88
- // (message) => message.clientMsgID === oldestAnchor.clientMsgID
89
- // )
90
- // ),
91
- }
92
- : null;
93
- setHasMoreEarlier(!history.isEnd && !!oldestAnchor);
59
+ setMessages(mergeMessages([], initialMessages), !history.isEnd);
94
60
 
95
61
  ChatMessageAPI.markConversationAsRead(conversationId).catch(
96
62
  () => undefined
@@ -100,170 +66,117 @@ export function useChatMessages({
100
66
  } finally {
101
67
  setIsLoading(false);
102
68
  }
103
- }, [conversationId, pageSize]);
69
+ }, [conversationId, pageSize, setMessages]);
104
70
 
105
71
  const onLoadEarlier = useCallback(async () => {
106
- if (
107
- !conversationId ||
108
- isLoadingEarlierRef.current ||
109
- !hasMoreEarlierRef.current ||
110
- !messagesRef.current.length
111
- ) {
112
- return;
113
- }
72
+ if (isLoadingEarlier || !hasMoreEarlier || !messages.length) return;
114
73
 
115
- isLoadingEarlierRef.current = true;
116
74
  setIsLoadingEarlier(true);
117
75
  setError(null);
118
76
 
119
- const anchor = historyAnchorRef.current;
120
- if (!anchor?.clientMsgID) {
121
- isLoadingEarlierRef.current = false;
122
- setIsLoadingEarlier(false);
123
- return;
124
- }
125
-
126
77
  try {
127
78
  const history = await ChatMessageAPI.fetchHistoryMessages(
128
79
  conversationId,
129
80
  {
130
81
  count: pageSize,
131
- startClientMsgID: anchor.clientMsgID,
82
+ startClientMsgID: messages[0]!.clientMsgID,
132
83
  }
133
84
  );
134
- const incoming = history.messageList as DMessageItem[];
135
- const hasNewMessages = hasNewHistoryMessages(
136
- messagesRef.current,
137
- incoming
138
- );
139
85
 
140
- if (hasNewMessages) {
141
- setMessages((current) => mergeMessages(current, incoming));
86
+ const incoming = history.messageList as DMessageItem[];
87
+ const hasNew = hasNewHistoryMessages(messages, incoming);
88
+
89
+ if (hasNew) {
90
+ setMessages(
91
+ mergeMessages(messages, incoming),
92
+ !history.isEnd && hasNew
93
+ );
94
+ } else {
95
+ setMessages(messages, false);
142
96
  }
143
-
144
- const nextAnchor = getHistoryPaginationAnchor(incoming);
145
- historyAnchorRef.current = nextAnchor
146
- ? {
147
- clientMsgID: nextAnchor.clientMsgID,
148
- // lastMinSeq: resolveHistoryLastMinSeq(
149
- // history,
150
- // incoming.find(
151
- // (message) => message.clientMsgID === nextAnchor.clientMsgID
152
- // )
153
- // ),
154
- }
155
- : null;
156
-
157
- setHasMoreEarlier(!history.isEnd && hasNewMessages && !!nextAnchor);
158
97
  } catch (err) {
159
98
  setError(err instanceof Error ? err : new Error(String(err)));
160
99
  } finally {
161
- isLoadingEarlierRef.current = false;
162
100
  setIsLoadingEarlier(false);
163
101
  }
164
- }, [conversationId, pageSize]);
102
+ }, [
103
+ conversationId,
104
+ hasMoreEarlier,
105
+ isLoadingEarlier,
106
+ messages,
107
+ pageSize,
108
+ setMessages,
109
+ ]);
165
110
 
166
111
  const onLoadNewer = useCallback(async () => {
167
- if (
168
- !conversationId ||
169
- isLoadingNewerRef.current ||
170
- !hasMoreNewerRef.current ||
171
- !messagesRef.current.length
172
- ) {
173
- return;
174
- }
112
+ if (isLoadingNewer || !hasMoreNewer || !messages.length) return;
175
113
 
176
- isLoadingNewerRef.current = true;
177
114
  setIsLoadingNewer(true);
178
115
  setError(null);
179
116
 
180
- const anchor = newestAnchorRef.current;
181
- if (!anchor?.clientMsgID && messagesRef.current.length === 0) {
182
- isLoadingNewerRef.current = false;
183
- setIsLoadingNewer(false);
184
- return;
185
- }
186
-
187
117
  try {
188
- // Get the newest message ID to load from
189
- const newestMessage = messagesRef.current[messagesRef.current.length - 1];
190
118
  const history = await ChatMessageAPI.fetchHistoryMessages(
191
119
  conversationId,
192
120
  {
193
121
  count: pageSize,
194
- startClientMsgID: newestMessage?.clientMsgID,
122
+ startClientMsgID: messages[messages.length - 1]!.clientMsgID,
195
123
  }
196
124
  );
197
125
 
198
126
  const incoming = history.messageList as DMessageItem[];
199
- const hasNewMessages = incoming.length > 0;
127
+ const hasNew = incoming.length > 0;
200
128
 
201
- if (hasNewMessages) {
202
- setMessages((current) => mergeMessages(current, incoming));
129
+ if (hasNew) {
130
+ setMessages(
131
+ mergeMessages(messages, incoming),
132
+ !history.isEnd && hasNew
133
+ );
203
134
  }
204
-
205
- setHasMoreNewer(!history.isEnd && hasNewMessages);
206
135
  } catch (err) {
207
136
  setError(err instanceof Error ? err : new Error(String(err)));
208
137
  } finally {
209
- isLoadingNewerRef.current = false;
210
138
  setIsLoadingNewer(false);
211
139
  }
212
- }, [conversationId, pageSize]);
213
-
214
- const sendTextMessage = useCallback(async (text: string) => {
215
- const trimmed = text.trim();
216
- if (!trimmed) {
217
- return;
218
- }
219
-
220
- const sentMessage = await ChatMessageAPI.sendTextMessage({
221
- text: trimmed,
222
- });
223
-
224
- setMessages((current) => mergeMessages(current, [sentMessage]));
225
- }, []);
226
-
227
- const appendIncomingMessages = useCallback((incoming: MessageItem[]) => {
228
- const relevant = incoming.filter((message) =>
229
- belongsToConversation(message, conversationIdRef.current)
230
- );
231
-
232
- if (!relevant.length) {
233
- return;
234
- }
140
+ }, [
141
+ conversationId,
142
+ hasMoreNewer,
143
+ isLoadingNewer,
144
+ messages,
145
+ pageSize,
146
+ setMessages,
147
+ ]);
148
+
149
+ const appendIncomingMessages = useCallback(
150
+ (incoming: MessageItem[]) => {
151
+ const relevant = incoming.filter((message) =>
152
+ belongsToConversation(message, conversationIdRef.current)
153
+ );
235
154
 
236
- setMessages((current) =>
237
- mergeMessages(current, relevant as DMessageItem[])
238
- );
155
+ if (!relevant.length) return;
239
156
 
240
- ChatMessageAPI.markConversationAsRead(conversationIdRef.current).catch(
241
- () => undefined
242
- );
243
- }, []);
157
+ relevant.forEach((msg) => pushNewMessage(msg));
158
+ ChatMessageAPI.markConversationAsRead(conversationIdRef.current).catch(
159
+ () => undefined
160
+ );
161
+ },
162
+ [pushNewMessage]
163
+ );
244
164
 
245
165
  useEffect(() => {
246
- resetState();
166
+ reset();
247
167
 
248
- if (!enabled || !conversationId) {
249
- return;
250
- }
168
+ if (!enabled || !conversationId) return;
251
169
 
252
- loadInitialMessages();
253
- }, [conversationId, enabled, loadInitialMessages, resetState]);
170
+ fetchInitial();
171
+ }, [conversationId, enabled, fetchInitial, reset]);
254
172
 
255
173
  useEffect(() => {
256
- if (!enabled || !conversationId) {
257
- return;
258
- }
174
+ if (!enabled || !conversationId) return;
259
175
 
260
- const handleNewMessages = (incoming: MessageItem[]) => {
176
+ const handleNewMessages = (incoming: MessageItem[]) =>
261
177
  appendIncomingMessages(incoming);
262
- };
263
-
264
- const handleNewMessage = (incoming: MessageItem) => {
178
+ const handleNewMessage = (incoming: MessageItem) =>
265
179
  appendIncomingMessages([incoming]);
266
- };
267
180
 
268
181
  OpenIMSDK.on(OpenIMEvent.OnRecvNewMessages, handleNewMessages);
269
182
  OpenIMSDK.on(OpenIMEvent.OnRecvNewMessage, handleNewMessage);
@@ -285,7 +198,6 @@ export function useChatMessages({
285
198
  error,
286
199
  onLoadEarlier,
287
200
  onLoadNewer,
288
- sendTextMessage,
289
- refresh: loadInitialMessages,
201
+ refresh: fetchInitial,
290
202
  };
291
203
  }