@droppii-org/chat-mobile 0.2.4 → 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 (212) 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/config/feature-flags.js +38 -0
  8. package/lib/module/config/feature-flags.js.map +1 -0
  9. package/lib/module/context/ChatContext.js +7 -6
  10. package/lib/module/context/ChatContext.js.map +1 -1
  11. package/lib/module/hooks/message/useSendMessage.js +101 -0
  12. package/lib/module/hooks/message/useSendMessage.js.map +1 -0
  13. package/lib/module/hooks/query-keys.js +4 -0
  14. package/lib/module/hooks/query-keys.js.map +1 -1
  15. package/lib/module/hooks/useChatMessages.js +54 -91
  16. package/lib/module/hooks/useChatMessages.js.map +1 -1
  17. package/lib/module/hooks/useConversationList.js +29 -17
  18. package/lib/module/hooks/useConversationList.js.map +1 -1
  19. package/lib/module/hooks/useLinkPreview/useFetchUrlMetadata.js +17 -0
  20. package/lib/module/hooks/useLinkPreview/useFetchUrlMetadata.js.map +1 -0
  21. package/lib/module/hooks/useLinkPreview/useLinkPreview.js +35 -0
  22. package/lib/module/hooks/useLinkPreview/useLinkPreview.js.map +1 -0
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/screens/chat-detail/ChatComposer.js +20 -4
  25. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  26. package/lib/module/screens/chat-detail/ChatDetail.js +116 -22
  27. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  28. package/lib/module/screens/chat-detail/ChatDetailHeader.js +5 -8
  29. package/lib/module/screens/chat-detail/ChatDetailHeader.js.map +1 -1
  30. package/lib/module/screens/chat-detail/ChatLinkPreview.js +79 -0
  31. package/lib/module/screens/chat-detail/ChatLinkPreview.js.map +1 -0
  32. package/lib/module/screens/chat-detail/ChatList.js +2 -0
  33. package/lib/module/screens/chat-detail/ChatList.js.map +1 -1
  34. package/lib/module/screens/chat-detail/ChatListLegend.js +350 -0
  35. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -0
  36. package/lib/module/screens/chat-detail/ChatQuickActions.js +12 -2
  37. package/lib/module/screens/chat-detail/ChatQuickActions.js.map +1 -1
  38. package/lib/module/screens/chat-detail/conversationHeader.utils.js +29 -0
  39. package/lib/module/screens/chat-detail/conversationHeader.utils.js.map +1 -0
  40. package/lib/module/screens/chat-detail/index.js +1 -0
  41. package/lib/module/screens/chat-detail/index.js.map +1 -1
  42. package/lib/module/screens/chat-detail/legend/LegendChatDay.js +57 -0
  43. package/lib/module/screens/chat-detail/legend/LegendChatDay.js.map +1 -0
  44. package/lib/module/screens/chat-detail/legend/LegendChatLoadEarlier.js +21 -0
  45. package/lib/module/screens/chat-detail/legend/LegendChatLoadEarlier.js.map +1 -0
  46. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +34 -0
  47. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -0
  48. package/lib/module/screens/chat-detail/legend/LegendChatScrollToBottom.js +58 -0
  49. package/lib/module/screens/chat-detail/legend/LegendChatScrollToBottom.js.map +1 -0
  50. package/lib/module/screens/chat-detail/legend/message-types.js +244 -0
  51. package/lib/module/screens/chat-detail/legend/message-types.js.map +1 -0
  52. package/lib/module/screens/chat-detail/messages/ChatMessageBubble.js.map +1 -1
  53. package/lib/module/services/apis.js +1 -1
  54. package/lib/module/services/apis.js.map +1 -1
  55. package/lib/module/services/endpoints.js +8 -0
  56. package/lib/module/services/endpoints.js.map +1 -0
  57. package/lib/module/store/conversation.js +1 -1
  58. package/lib/module/store/conversation.js.map +1 -1
  59. package/lib/module/store/message.js +45 -0
  60. package/lib/module/store/message.js.map +1 -0
  61. package/lib/module/translation/resources/i18n.js +7 -1
  62. package/lib/module/translation/resources/i18n.js.map +1 -1
  63. package/lib/module/types/chat.js +2 -7
  64. package/lib/module/types/chat.js.map +1 -1
  65. package/lib/module/types/common.js +2 -0
  66. package/lib/module/types/common.js.map +1 -0
  67. package/lib/module/utils/conversation.js +34 -13
  68. package/lib/module/utils/conversation.js.map +1 -1
  69. package/lib/module/utils/legendListMessage.js +77 -0
  70. package/lib/module/utils/legendListMessage.js.map +1 -0
  71. package/lib/module/utils/message.js +5 -8
  72. package/lib/module/utils/message.js.map +1 -1
  73. package/lib/module/utils/url.js +7 -0
  74. package/lib/module/utils/url.js.map +1 -0
  75. package/lib/typescript/src/components/Avatar/Avatar.d.ts +1 -1
  76. package/lib/typescript/src/components/Avatar/Avatar.d.ts.map +1 -1
  77. package/lib/typescript/src/components/Avatar/AvatarBadge.d.ts +1 -1
  78. package/lib/typescript/src/components/Avatar/AvatarBadge.d.ts.map +1 -1
  79. package/lib/typescript/src/components/Avatar/DoubleAvatar.d.ts +1 -1
  80. package/lib/typescript/src/components/Avatar/DoubleAvatar.d.ts.map +1 -1
  81. package/lib/typescript/src/components/Avatar/SingleAvatar.d.ts +1 -1
  82. package/lib/typescript/src/components/Avatar/SingleAvatar.d.ts.map +1 -1
  83. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts +2 -2
  84. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts.map +1 -1
  85. package/lib/typescript/src/components/ThreadCard/NamePrefixIcon.d.ts +3 -4
  86. package/lib/typescript/src/components/ThreadCard/NamePrefixIcon.d.ts.map +1 -1
  87. package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts +1 -1
  88. package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
  89. package/lib/typescript/src/components/ThreadCard/UnreadBadge.d.ts +1 -1
  90. package/lib/typescript/src/components/ThreadCard/UnreadBadge.d.ts.map +1 -1
  91. package/lib/typescript/src/config/feature-flags.d.ts +12 -0
  92. package/lib/typescript/src/config/feature-flags.d.ts.map +1 -0
  93. package/lib/typescript/src/context/ChatContext.d.ts +1 -1
  94. package/lib/typescript/src/context/ChatContext.d.ts.map +1 -1
  95. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +12 -0
  96. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -0
  97. package/lib/typescript/src/hooks/query-keys.d.ts +4 -0
  98. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  99. package/lib/typescript/src/hooks/useChatMessages.d.ts +3 -1
  100. package/lib/typescript/src/hooks/useChatMessages.d.ts.map +1 -1
  101. package/lib/typescript/src/hooks/useConversationList.d.ts +2 -1
  102. package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
  103. package/lib/typescript/src/hooks/useLinkPreview/useFetchUrlMetadata.d.ts +3 -0
  104. package/lib/typescript/src/hooks/useLinkPreview/useFetchUrlMetadata.d.ts.map +1 -0
  105. package/lib/typescript/src/hooks/useLinkPreview/useLinkPreview.d.ts +7 -0
  106. package/lib/typescript/src/hooks/useLinkPreview/useLinkPreview.d.ts.map +1 -0
  107. package/lib/typescript/src/index.d.ts +1 -1
  108. package/lib/typescript/src/index.d.ts.map +1 -1
  109. package/lib/typescript/src/screens/chat-detail/ChatAttachmentPanel.d.ts +1 -1
  110. package/lib/typescript/src/screens/chat-detail/ChatAttachmentPanel.d.ts.map +1 -1
  111. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +1 -1
  112. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  113. package/lib/typescript/src/screens/chat-detail/ChatDay.d.ts +1 -1
  114. package/lib/typescript/src/screens/chat-detail/ChatDay.d.ts.map +1 -1
  115. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts +1 -1
  116. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  117. package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts +1 -1
  118. package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts.map +1 -1
  119. package/lib/typescript/src/screens/chat-detail/ChatLinkPreview.d.ts +9 -0
  120. package/lib/typescript/src/screens/chat-detail/ChatLinkPreview.d.ts.map +1 -0
  121. package/lib/typescript/src/screens/chat-detail/ChatList.d.ts +1 -1
  122. package/lib/typescript/src/screens/chat-detail/ChatList.d.ts.map +1 -1
  123. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts +3 -0
  124. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -0
  125. package/lib/typescript/src/screens/chat-detail/ChatLoadEarlier.d.ts +1 -1
  126. package/lib/typescript/src/screens/chat-detail/ChatLoadEarlier.d.ts.map +1 -1
  127. package/lib/typescript/src/screens/chat-detail/ChatQuickActions.d.ts +1 -1
  128. package/lib/typescript/src/screens/chat-detail/ChatQuickActions.d.ts.map +1 -1
  129. package/lib/typescript/src/screens/chat-detail/ChatScrollToBottom.d.ts +1 -1
  130. package/lib/typescript/src/screens/chat-detail/ChatScrollToBottom.d.ts.map +1 -1
  131. package/lib/typescript/src/screens/chat-detail/ChatTextBubble.d.ts +1 -1
  132. package/lib/typescript/src/screens/chat-detail/ChatTextBubble.d.ts.map +1 -1
  133. package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts +6 -0
  134. package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts.map +1 -0
  135. package/lib/typescript/src/screens/chat-detail/index.d.ts +2 -1
  136. package/lib/typescript/src/screens/chat-detail/index.d.ts.map +1 -1
  137. package/lib/typescript/src/screens/chat-detail/legend/LegendChatDay.d.ts +6 -0
  138. package/lib/typescript/src/screens/chat-detail/legend/LegendChatDay.d.ts.map +1 -0
  139. package/lib/typescript/src/screens/chat-detail/legend/LegendChatLoadEarlier.d.ts +6 -0
  140. package/lib/typescript/src/screens/chat-detail/legend/LegendChatLoadEarlier.d.ts.map +1 -0
  141. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +13 -0
  142. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -0
  143. package/lib/typescript/src/screens/chat-detail/legend/LegendChatScrollToBottom.d.ts +6 -0
  144. package/lib/typescript/src/screens/chat-detail/legend/LegendChatScrollToBottom.d.ts.map +1 -0
  145. package/lib/typescript/src/screens/chat-detail/legend/message-types.d.ts +13 -0
  146. package/lib/typescript/src/screens/chat-detail/legend/message-types.d.ts.map +1 -0
  147. package/lib/typescript/src/screens/chat-detail/messages/ChatMessageBubble.d.ts +1 -1
  148. package/lib/typescript/src/screens/chat-detail/messages/ChatMessageBubble.d.ts.map +1 -1
  149. package/lib/typescript/src/screens/chat-detail/types.d.ts +33 -7
  150. package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
  151. package/lib/typescript/src/screens/inbox/Inbox.d.ts +1 -1
  152. package/lib/typescript/src/screens/inbox/Inbox.d.ts.map +1 -1
  153. package/lib/typescript/src/screens/inbox/MessagesTab.d.ts +1 -1
  154. package/lib/typescript/src/screens/inbox/MessagesTab.d.ts.map +1 -1
  155. package/lib/typescript/src/services/apis.d.ts +1 -0
  156. package/lib/typescript/src/services/apis.d.ts.map +1 -1
  157. package/lib/typescript/src/services/endpoints.d.ts +6 -0
  158. package/lib/typescript/src/services/endpoints.d.ts.map +1 -0
  159. package/lib/typescript/src/store/message.d.ts +3 -0
  160. package/lib/typescript/src/store/message.d.ts.map +1 -0
  161. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  162. package/lib/typescript/src/types/chat.d.ts +28 -27
  163. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  164. package/lib/typescript/src/types/common.d.ts +7 -0
  165. package/lib/typescript/src/types/common.d.ts.map +1 -0
  166. package/lib/typescript/src/utils/conversation.d.ts +3 -2
  167. package/lib/typescript/src/utils/conversation.d.ts.map +1 -1
  168. package/lib/typescript/src/utils/legendListMessage.d.ts +23 -0
  169. package/lib/typescript/src/utils/legendListMessage.d.ts.map +1 -0
  170. package/lib/typescript/src/utils/message.d.ts.map +1 -1
  171. package/lib/typescript/src/utils/url.d.ts +2 -0
  172. package/lib/typescript/src/utils/url.d.ts.map +1 -0
  173. package/package.json +5 -3
  174. package/src/components/ThreadCard/AvatarSection.tsx +5 -8
  175. package/src/components/ThreadCard/NamePrefixIcon.tsx +27 -38
  176. package/src/components/ThreadCard/ThreadCard.tsx +16 -30
  177. package/src/config/feature-flags.ts +49 -0
  178. package/src/context/ChatContext.tsx +12 -4
  179. package/src/hooks/message/useSendMessage.ts +136 -0
  180. package/src/hooks/query-keys.ts +5 -0
  181. package/src/hooks/useChatMessages.ts +90 -118
  182. package/src/hooks/useConversationList.ts +34 -16
  183. package/src/hooks/useLinkPreview/useFetchUrlMetadata.ts +18 -0
  184. package/src/hooks/useLinkPreview/useLinkPreview.ts +31 -0
  185. package/src/index.tsx +1 -0
  186. package/src/screens/chat-detail/ChatComposer.tsx +23 -2
  187. package/src/screens/chat-detail/ChatDetail.tsx +163 -30
  188. package/src/screens/chat-detail/ChatDetailHeader.tsx +4 -10
  189. package/src/screens/chat-detail/ChatLinkPreview.tsx +86 -0
  190. package/src/screens/chat-detail/ChatList.tsx +3 -0
  191. package/src/screens/chat-detail/ChatListLegend.tsx +403 -0
  192. package/src/screens/chat-detail/ChatQuickActions.tsx +19 -2
  193. package/src/screens/chat-detail/conversationHeader.utils.ts +49 -0
  194. package/src/screens/chat-detail/index.ts +7 -0
  195. package/src/screens/chat-detail/legend/LegendChatDay.tsx +70 -0
  196. package/src/screens/chat-detail/legend/LegendChatLoadEarlier.tsx +21 -0
  197. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +48 -0
  198. package/src/screens/chat-detail/legend/LegendChatScrollToBottom.tsx +56 -0
  199. package/src/screens/chat-detail/legend/message-types.tsx +304 -0
  200. package/src/screens/chat-detail/messages/ChatMessageBubble.tsx +0 -1
  201. package/src/screens/chat-detail/types.ts +45 -7
  202. package/src/services/apis.ts +1 -1
  203. package/src/services/endpoints.ts +5 -0
  204. package/src/store/conversation.ts +1 -1
  205. package/src/store/message.ts +44 -0
  206. package/src/translation/resources/i18n.ts +6 -0
  207. package/src/types/chat.ts +31 -30
  208. package/src/types/common.ts +6 -0
  209. package/src/utils/conversation.ts +44 -17
  210. package/src/utils/legendListMessage.ts +97 -0
  211. package/src/utils/message.ts +10 -12
  212. package/src/utils/url.ts +5 -0
@@ -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,35 +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);
36
+ const [isLoadingNewer, setIsLoadingNewer] = useState(false);
32
37
  const [error, setError] = useState<Error | null>(null);
33
38
 
34
39
  const conversationIdRef = useRef(conversationId);
35
40
  conversationIdRef.current = conversationId;
36
41
 
37
- const isLoadingEarlierRef = useRef(false);
38
- const messagesRef = useRef(messages);
39
- const hasMoreEarlierRef = useRef(hasMoreEarlier);
40
- const historyAnchorRef = useRef<HistoryPaginationAnchor | null>(null);
41
- messagesRef.current = messages;
42
- hasMoreEarlierRef.current = hasMoreEarlier;
43
-
44
- const resetState = useCallback(() => {
45
- setMessages([]);
46
- setHasMoreEarlier(false);
47
- setError(null);
48
- isLoadingEarlierRef.current = false;
49
- historyAnchorRef.current = null;
50
- }, []);
51
-
52
- const loadInitialMessages = useCallback(async () => {
53
- if (!conversationId) {
54
- return;
55
- }
42
+ const fetchInitial = useCallback(async () => {
43
+ if (!conversationId) return;
56
44
 
57
45
  setIsLoading(true);
58
46
  setError(null);
@@ -66,22 +54,9 @@ export function useChatMessages({
66
54
  ]);
67
55
 
68
56
  const initialMessages = history.messageList as DMessageItem[];
69
- const oldestAnchor = getHistoryPaginationAnchor(initialMessages);
70
57
 
71
58
  setCurrentUserId(userId);
72
- setMessages(mergeMessages([], initialMessages));
73
- historyAnchorRef.current = oldestAnchor
74
- ? {
75
- clientMsgID: oldestAnchor.clientMsgID,
76
- // lastMinSeq: resolveHistoryLastMinSeq(
77
- // history,
78
- // initialMessages.find(
79
- // (message) => message.clientMsgID === oldestAnchor.clientMsgID
80
- // )
81
- // ),
82
- }
83
- : null;
84
- setHasMoreEarlier(!history.isEnd && !!oldestAnchor);
59
+ setMessages(mergeMessages([], initialMessages), !history.isEnd);
85
60
 
86
61
  ChatMessageAPI.markConversationAsRead(conversationId).catch(
87
62
  () => undefined
@@ -91,122 +66,117 @@ export function useChatMessages({
91
66
  } finally {
92
67
  setIsLoading(false);
93
68
  }
94
- }, [conversationId, pageSize]);
69
+ }, [conversationId, pageSize, setMessages]);
95
70
 
96
71
  const onLoadEarlier = useCallback(async () => {
97
- if (
98
- !conversationId ||
99
- isLoadingEarlierRef.current ||
100
- !hasMoreEarlierRef.current ||
101
- !messagesRef.current.length
102
- ) {
103
- return;
104
- }
72
+ if (isLoadingEarlier || !hasMoreEarlier || !messages.length) return;
105
73
 
106
- isLoadingEarlierRef.current = true;
107
74
  setIsLoadingEarlier(true);
108
75
  setError(null);
109
76
 
110
- const anchor = historyAnchorRef.current;
111
- if (!anchor?.clientMsgID) {
112
- isLoadingEarlierRef.current = false;
113
- setIsLoadingEarlier(false);
114
- return;
115
- }
116
-
117
77
  try {
118
78
  const history = await ChatMessageAPI.fetchHistoryMessages(
119
79
  conversationId,
120
80
  {
121
81
  count: pageSize,
122
- startClientMsgID: anchor.clientMsgID,
82
+ startClientMsgID: messages[0]!.clientMsgID,
123
83
  }
124
84
  );
125
- const incoming = history.messageList as DMessageItem[];
126
- const hasNewMessages = hasNewHistoryMessages(
127
- messagesRef.current,
128
- incoming
129
- );
130
85
 
131
- if (hasNewMessages) {
132
- 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);
133
96
  }
134
-
135
- const nextAnchor = getHistoryPaginationAnchor(incoming);
136
- historyAnchorRef.current = nextAnchor
137
- ? {
138
- clientMsgID: nextAnchor.clientMsgID,
139
- // lastMinSeq: resolveHistoryLastMinSeq(
140
- // history,
141
- // incoming.find(
142
- // (message) => message.clientMsgID === nextAnchor.clientMsgID
143
- // )
144
- // ),
145
- }
146
- : null;
147
-
148
- setHasMoreEarlier(!history.isEnd && hasNewMessages && !!nextAnchor);
149
97
  } catch (err) {
150
98
  setError(err instanceof Error ? err : new Error(String(err)));
151
99
  } finally {
152
- isLoadingEarlierRef.current = false;
153
100
  setIsLoadingEarlier(false);
154
101
  }
155
- }, [conversationId, pageSize]);
102
+ }, [
103
+ conversationId,
104
+ hasMoreEarlier,
105
+ isLoadingEarlier,
106
+ messages,
107
+ pageSize,
108
+ setMessages,
109
+ ]);
156
110
 
157
- const sendTextMessage = useCallback(async (text: string) => {
158
- const trimmed = text.trim();
159
- if (!trimmed) {
160
- return;
161
- }
111
+ const onLoadNewer = useCallback(async () => {
112
+ if (isLoadingNewer || !hasMoreNewer || !messages.length) return;
162
113
 
163
- const sentMessage = await ChatMessageAPI.sendTextMessage({
164
- text: trimmed,
165
- });
114
+ setIsLoadingNewer(true);
115
+ setError(null);
166
116
 
167
- setMessages((current) => mergeMessages(current, [sentMessage]));
168
- }, []);
117
+ try {
118
+ const history = await ChatMessageAPI.fetchHistoryMessages(
119
+ conversationId,
120
+ {
121
+ count: pageSize,
122
+ startClientMsgID: messages[messages.length - 1]!.clientMsgID,
123
+ }
124
+ );
169
125
 
170
- const appendIncomingMessages = useCallback((incoming: MessageItem[]) => {
171
- const relevant = incoming.filter((message) =>
172
- belongsToConversation(message, conversationIdRef.current)
173
- );
126
+ const incoming = history.messageList as DMessageItem[];
127
+ const hasNew = incoming.length > 0;
174
128
 
175
- if (!relevant.length) {
176
- return;
129
+ if (hasNew) {
130
+ setMessages(
131
+ mergeMessages(messages, incoming),
132
+ !history.isEnd && hasNew
133
+ );
134
+ }
135
+ } catch (err) {
136
+ setError(err instanceof Error ? err : new Error(String(err)));
137
+ } finally {
138
+ setIsLoadingNewer(false);
177
139
  }
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
+ );
178
154
 
179
- setMessages((current) =>
180
- mergeMessages(current, relevant as DMessageItem[])
181
- );
155
+ if (!relevant.length) return;
182
156
 
183
- ChatMessageAPI.markConversationAsRead(conversationIdRef.current).catch(
184
- () => undefined
185
- );
186
- }, []);
157
+ relevant.forEach((msg) => pushNewMessage(msg));
158
+ ChatMessageAPI.markConversationAsRead(conversationIdRef.current).catch(
159
+ () => undefined
160
+ );
161
+ },
162
+ [pushNewMessage]
163
+ );
187
164
 
188
165
  useEffect(() => {
189
- resetState();
166
+ reset();
190
167
 
191
- if (!enabled || !conversationId) {
192
- return;
193
- }
168
+ if (!enabled || !conversationId) return;
194
169
 
195
- loadInitialMessages();
196
- }, [conversationId, enabled, loadInitialMessages, resetState]);
170
+ fetchInitial();
171
+ }, [conversationId, enabled, fetchInitial, reset]);
197
172
 
198
173
  useEffect(() => {
199
- if (!enabled || !conversationId) {
200
- return;
201
- }
174
+ if (!enabled || !conversationId) return;
202
175
 
203
- const handleNewMessages = (incoming: MessageItem[]) => {
176
+ const handleNewMessages = (incoming: MessageItem[]) =>
204
177
  appendIncomingMessages(incoming);
205
- };
206
-
207
- const handleNewMessage = (incoming: MessageItem) => {
178
+ const handleNewMessage = (incoming: MessageItem) =>
208
179
  appendIncomingMessages([incoming]);
209
- };
210
180
 
211
181
  OpenIMSDK.on(OpenIMEvent.OnRecvNewMessages, handleNewMessages);
212
182
  OpenIMSDK.on(OpenIMEvent.OnRecvNewMessage, handleNewMessage);
@@ -223,9 +193,11 @@ export function useChatMessages({
223
193
  isLoading,
224
194
  isLoadingEarlier,
225
195
  hasMoreEarlier,
196
+ isLoadingNewer,
197
+ hasMoreNewer,
226
198
  error,
227
199
  onLoadEarlier,
228
- sendTextMessage,
229
- refresh: loadInitialMessages,
200
+ onLoadNewer,
201
+ refresh: fetchInitial,
230
202
  };
231
203
  }
@@ -1,10 +1,11 @@
1
1
  import { useEffect } from 'react';
2
2
  import { useQuery } from '@tanstack/react-query';
3
- import OpenIMSDK from '@droppii/openim-rn-client-sdk';
4
- import { ChatAPI } from '../services';
3
+ import OpenIMSDK, {
4
+ type ConversationItem,
5
+ } from '@droppii/openim-rn-client-sdk';
5
6
  import { conversationQueryKeys } from './query-keys';
6
7
  import { useConversationStore } from '../store';
7
- import { mergeOpenIMIntoConversation } from '../utils/conversation';
8
+ import type { DConversationItem, DMessageItem } from '../types/chat';
8
9
 
9
10
  type Params = {
10
11
  applicationType: string;
@@ -12,6 +13,29 @@ type Params = {
12
13
  enabled?: boolean;
13
14
  };
14
15
 
16
+ function toConversationItem(raw: ConversationItem): DConversationItem {
17
+ let lastMessage: DMessageItem | undefined;
18
+ if (raw.latestMsg) {
19
+ try {
20
+ lastMessage = JSON.parse(raw.latestMsg) as DMessageItem;
21
+ } catch {
22
+ // leave undefined
23
+ }
24
+ }
25
+
26
+ let applicationType: string | undefined;
27
+ if (raw.ex) {
28
+ try {
29
+ applicationType = (JSON.parse(raw.ex) as { applicationType?: string })
30
+ .applicationType;
31
+ } catch {
32
+ // leave undefined
33
+ }
34
+ }
35
+
36
+ return { ...(raw as DConversationItem), lastMessage, applicationType };
37
+ }
38
+
15
39
  export function useConversationList({
16
40
  applicationType,
17
41
  page = 1,
@@ -26,21 +50,15 @@ export function useConversationList({
26
50
  enabled: enabled !== false,
27
51
  queryFn: async () => {
28
52
  const offset = (page - 1) * 20;
29
- const [{ data: droppiiList }, openimList] = await Promise.all([
30
- ChatAPI.queryConversations({ applicationType, page, pageSize: 20 }),
31
- OpenIMSDK.getConversationListSplit({ offset, count: 20 }),
32
- ]);
33
- const openimMap = new Map(openimList.map((c) => [c.conversationID, c]));
34
-
35
- const merged = droppiiList.map((conv) => {
36
- const openimConv = openimMap.get(conv.conversationId);
37
- return openimConv
38
- ? mergeOpenIMIntoConversation(conv, openimConv)
39
- : conv;
53
+ const rawList = await OpenIMSDK.getConversationListSplitApp({
54
+ offset,
55
+ count: 20,
56
+ applicationType,
40
57
  });
41
58
 
42
- useConversationStore.getState().updateConversations(merged);
43
- return merged;
59
+ const conversations = rawList.map(toConversationItem);
60
+ useConversationStore.getState().updateConversations(conversations);
61
+ return conversations;
44
62
  },
45
63
  });
46
64
 
@@ -0,0 +1,18 @@
1
+ import { keepPreviousData, useQuery } from '@tanstack/react-query';
2
+ import { commonQueryKeys } from '../query-keys';
3
+ import { apiInstance } from '../../services/apis';
4
+ import type { BaseResponse } from '../../types/auth';
5
+ import type { IUrlMetadata } from '../../types/common';
6
+ import { ENDPOINTS } from '../../services/endpoints';
7
+
8
+ export const useFetchUrlMetadata = (url?: string) =>
9
+ useQuery({
10
+ queryKey: commonQueryKeys.urlMetadata(url || ''),
11
+ queryFn: async () => {
12
+ const path = `${ENDPOINTS.chatService.urlMetadata}?url=${url}`;
13
+ const res = await apiInstance?.get<BaseResponse<IUrlMetadata>>(path);
14
+ return res?.data?.data;
15
+ },
16
+ enabled: !!url,
17
+ placeholderData: keepPreviousData,
18
+ });
@@ -0,0 +1,31 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { useFetchUrlMetadata } from './useFetchUrlMetadata';
3
+ import { extractUrls } from '../../utils/url';
4
+
5
+ export function useLinkPreview(value: string | undefined) {
6
+ const [detectedUrl, setDetectedUrl] = useState<string | undefined>(undefined);
7
+ const [isDismissed, setIsDismissed] = useState(false);
8
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
9
+
10
+ useEffect(() => {
11
+ if (timerRef.current) clearTimeout(timerRef.current);
12
+ timerRef.current = setTimeout(() => {
13
+ const urls = value ? extractUrls(value) : [];
14
+ const url = urls.length === 1 ? urls[0] : undefined;
15
+ setDetectedUrl(url);
16
+ if (url) setIsDismissed(false);
17
+ }, 500);
18
+ return () => {
19
+ if (timerRef.current) clearTimeout(timerRef.current);
20
+ };
21
+ }, [value]);
22
+
23
+ const { data: metadata, isFetching: isLoading } =
24
+ useFetchUrlMetadata(detectedUrl);
25
+
26
+ const dismiss = useCallback(() => setIsDismissed(true), []);
27
+
28
+ const isVisible = !!detectedUrl && !isDismissed;
29
+
30
+ return { isVisible, metadata, isLoading, dismiss };
31
+ }
package/src/index.tsx CHANGED
@@ -28,6 +28,7 @@ export type {
28
28
  ChatListProps,
29
29
  ChatComposerProps,
30
30
  ChatQuickActionsProps,
31
+ ChatQuickActionsRenderParams,
31
32
  ChatAttachmentPanelProps,
32
33
  } from './screens/chat-detail';
33
34
  export type { DGiftedChatMessage } from './utils/giftedChatMessage';
@@ -22,8 +22,10 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
22
22
  import { KContainer, KImage, KColors, KSpacingValue } from '@droppii/libs';
23
23
  import { ChatQuickActions } from './ChatQuickActions';
24
24
  import { ChatAttachmentPanel } from './ChatAttachmentPanel';
25
+ import { ChatLinkPreview } from './ChatLinkPreview';
25
26
  import { getAttachmentPanelHeight } from './constants';
26
27
  import type { ChatComposerProps } from './types';
28
+ import { useLinkPreview } from '../../hooks/useLinkPreview/useLinkPreview';
27
29
 
28
30
  const ACCESSORY_ANIMATION_MS = 250;
29
31
  const ACCESSORY_SETTLE_MS = 150;
@@ -44,11 +46,13 @@ export const ChatComposer = memo(
44
46
  onSend,
45
47
  onPressAttach,
46
48
  onPressEmoji,
49
+ showQuickActions = true,
47
50
  quickActions,
48
51
  attachmentActions,
49
52
  onQuickActionPress,
50
53
  onAttachmentAction,
51
54
  renderQuickAction,
55
+ renderQuickActions,
52
56
  renderAttachmentAction,
53
57
  attachmentColumns,
54
58
  }: ChatComposerProps) => {
@@ -60,6 +64,13 @@ export const ChatComposer = memo(
60
64
  const [isInputWrapped, setIsInputWrapped] = useState(false);
61
65
  const insets = useSafeAreaInsets();
62
66
 
67
+ const {
68
+ isVisible: showLinkPreview,
69
+ metadata: urlMetadata,
70
+ isLoading: isMetadataLoading,
71
+ dismiss: handleDismissPreview,
72
+ } = useLinkPreview(value);
73
+
63
74
  const isKeyboardVisible = useKeyboardState((state) => state.isVisible);
64
75
  const keyboardHeight = useKeyboardState((state) => state.height);
65
76
  const { progress: keyboardProgress } = useReanimatedKeyboardAnimation();
@@ -170,8 +181,8 @@ export const ChatComposer = memo(
170
181
  });
171
182
 
172
183
  const handleSend = useCallback(() => {
173
- onSend?.();
174
- }, [onSend]);
184
+ onSend?.(showLinkPreview ? urlMetadata : undefined);
185
+ }, [onSend, urlMetadata, showLinkPreview]);
175
186
 
176
187
  const handleToggleAttachment = useCallback(() => {
177
188
  if (!hasAttachmentActions) {
@@ -268,11 +279,21 @@ export const ChatComposer = memo(
268
279
  return (
269
280
  <KContainer.View background={'rgba(131, 137, 157, 0.05)'}>
270
281
  <ChatQuickActions
282
+ visible={showQuickActions}
271
283
  actions={quickActions}
272
284
  onActionPress={onQuickActionPress}
273
285
  renderAction={renderQuickAction}
286
+ renderQuickActions={renderQuickActions}
274
287
  />
275
288
 
289
+ {showLinkPreview ? (
290
+ <ChatLinkPreview
291
+ metadata={urlMetadata}
292
+ isLoading={isMetadataLoading}
293
+ onDismiss={handleDismissPreview}
294
+ />
295
+ ) : null}
296
+
276
297
  <KContainer.View
277
298
  row
278
299
  alignItems