@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
@@ -0,0 +1,304 @@
1
+ import { memo } from 'react';
2
+ import { Linking, StyleSheet, Text } from 'react-native';
3
+ import {
4
+ KContainer,
5
+ KImage,
6
+ KLabel,
7
+ KColors,
8
+ KSpacingValue,
9
+ } from '@droppii/libs';
10
+ import type { DMessageItem, IMessageItemEx } from '../../../types/chat';
11
+ import type { IUrlMetadata } from '../../../types/common';
12
+ import { getMessageText } from '../../../utils/legendListMessage';
13
+ import { CHAT_BUBBLE_COLORS } from '../constants';
14
+
15
+ interface BaseLegendMessageProps {
16
+ message: DMessageItem;
17
+ isOutgoing: boolean;
18
+ createdAtTime: number;
19
+ }
20
+
21
+ const formatMessageTime = (createdAt: number) => {
22
+ const date = new Date(createdAt);
23
+ if (Number.isNaN(date.getTime())) return '';
24
+ return date.toLocaleTimeString('vi-VN', {
25
+ hour: '2-digit',
26
+ minute: '2-digit',
27
+ hour12: false,
28
+ });
29
+ };
30
+
31
+ // Text Message Component
32
+ export const LegendTextMessage = memo(
33
+ ({ message, isOutgoing, createdAtTime }: BaseLegendMessageProps) => {
34
+ const messageText = getMessageText(message);
35
+ const timeLabel = formatMessageTime(createdAtTime);
36
+
37
+ if (!messageText?.trim()) {
38
+ return null;
39
+ }
40
+
41
+ return (
42
+ <KContainer.View style={styles.wrapper}>
43
+ <KContainer.View
44
+ style={[
45
+ styles.bubble,
46
+ isOutgoing ? styles.bubbleSent : styles.bubbleReceived,
47
+ ]}
48
+ >
49
+ <KLabel.Text typo="TextMdNormal" color={CHAT_BUBBLE_COLORS.text}>
50
+ {messageText}
51
+ </KLabel.Text>
52
+ </KContainer.View>
53
+
54
+ {!isOutgoing && timeLabel ? (
55
+ <KLabel.Text
56
+ typo="TextXsNormal"
57
+ color={CHAT_BUBBLE_COLORS.timestamp}
58
+ marginL="0.25rem"
59
+ >
60
+ {timeLabel}
61
+ </KLabel.Text>
62
+ ) : null}
63
+ </KContainer.View>
64
+ );
65
+ }
66
+ );
67
+ LegendTextMessage.displayName = 'LegendTextMessage';
68
+
69
+ // Image Message Component (placeholder)
70
+ export const LegendImageMessage = memo(
71
+ ({ isOutgoing }: BaseLegendMessageProps) => {
72
+ return (
73
+ <KContainer.View style={styles.wrapper}>
74
+ <KContainer.View
75
+ style={[
76
+ styles.bubble,
77
+ isOutgoing ? styles.bubbleSent : styles.bubbleReceived,
78
+ ]}
79
+ >
80
+ <KLabel.Text typo="TextSmNormal" color={CHAT_BUBBLE_COLORS.text}>
81
+ [Image]
82
+ </KLabel.Text>
83
+ </KContainer.View>
84
+ </KContainer.View>
85
+ );
86
+ }
87
+ );
88
+ LegendImageMessage.displayName = 'LegendImageMessage';
89
+
90
+ // Video Message Component (placeholder)
91
+ export const LegendVideoMessage = memo(
92
+ ({ isOutgoing }: BaseLegendMessageProps) => {
93
+ return (
94
+ <KContainer.View style={styles.wrapper}>
95
+ <KContainer.View
96
+ style={[
97
+ styles.bubble,
98
+ isOutgoing ? styles.bubbleSent : styles.bubbleReceived,
99
+ ]}
100
+ >
101
+ <KLabel.Text typo="TextSmNormal" color={CHAT_BUBBLE_COLORS.text}>
102
+ [Video]
103
+ </KLabel.Text>
104
+ </KContainer.View>
105
+ </KContainer.View>
106
+ );
107
+ }
108
+ );
109
+ LegendVideoMessage.displayName = 'LegendVideoMessage';
110
+
111
+ // File Message Component
112
+ export const LegendFileMessage = memo(
113
+ ({ message, isOutgoing }: BaseLegendMessageProps) => {
114
+ const fileName = message.fileElem?.fileName || message.content || '[File]';
115
+
116
+ return (
117
+ <KContainer.View style={styles.wrapper}>
118
+ <KContainer.View
119
+ style={[
120
+ styles.bubble,
121
+ isOutgoing ? styles.bubbleSent : styles.bubbleReceived,
122
+ ]}
123
+ >
124
+ <KLabel.Text typo="TextSmNormal" color={CHAT_BUBBLE_COLORS.text}>
125
+ 📎 {fileName}
126
+ </KLabel.Text>
127
+ </KContainer.View>
128
+ </KContainer.View>
129
+ );
130
+ }
131
+ );
132
+ LegendFileMessage.displayName = 'LegendFileMessage';
133
+
134
+ // Link Message Component
135
+ const parseUrlMetadata = (ex?: string): IUrlMetadata | undefined => {
136
+ if (!ex) return undefined;
137
+ try {
138
+ return (JSON.parse(ex) as IMessageItemEx).urlMetadata;
139
+ } catch {
140
+ return undefined;
141
+ }
142
+ };
143
+
144
+ const renderTextWithLinks = (content: string, urls: string[]) => {
145
+ if (!urls.length) {
146
+ return (
147
+ <KLabel.Text typo="TextMdNormal" color={CHAT_BUBBLE_COLORS.text}>
148
+ {content}
149
+ </KLabel.Text>
150
+ );
151
+ }
152
+
153
+ const parts: { text: string; isUrl: boolean }[] = [];
154
+ let remaining = content;
155
+
156
+ for (const url of urls) {
157
+ const idx = remaining.indexOf(url);
158
+ if (idx === -1) continue;
159
+ if (idx > 0) parts.push({ text: remaining.slice(0, idx), isUrl: false });
160
+ parts.push({ text: url, isUrl: true });
161
+ remaining = remaining.slice(idx + url.length);
162
+ }
163
+
164
+ if (remaining) parts.push({ text: remaining, isUrl: false });
165
+
166
+ return (
167
+ <KLabel.Text typo="TextMdNormal" color={CHAT_BUBBLE_COLORS.text}>
168
+ {parts.map((part, i) =>
169
+ part.isUrl ? (
170
+ <Text
171
+ key={i}
172
+ style={styles.urlText}
173
+ onPress={() => Linking.openURL(part.text)}
174
+ >
175
+ {part.text}
176
+ </Text>
177
+ ) : (
178
+ <Text key={i}>{part.text}</Text>
179
+ )
180
+ )}
181
+ </KLabel.Text>
182
+ );
183
+ };
184
+
185
+ export const LegendLinkMessage = memo(
186
+ ({ message, isOutgoing, createdAtTime }: BaseLegendMessageProps) => {
187
+ const content = message?.urlTextElem?.content;
188
+ const urls = message?.urlTextElem?.urls ?? [];
189
+ const metadata = parseUrlMetadata(message.ex);
190
+ const timeLabel = formatMessageTime(createdAtTime);
191
+
192
+ return (
193
+ <KContainer.View style={styles.wrapper}>
194
+ <KContainer.View
195
+ style={[
196
+ styles.bubble,
197
+ isOutgoing ? styles.bubbleSent : styles.bubbleReceived,
198
+ ]}
199
+ >
200
+ {!!content?.trim() && (
201
+ <KContainer.View style={styles.textRow}>
202
+ {renderTextWithLinks(content, urls)}
203
+ </KContainer.View>
204
+ )}
205
+
206
+ {metadata && (
207
+ <KContainer.Touchable
208
+ style={styles.card}
209
+ onPress={() => metadata.url && Linking.openURL(metadata.url)}
210
+ >
211
+ {!!metadata.image && (
212
+ <KImage.Base
213
+ uri={metadata.image}
214
+ style={styles.cardThumb}
215
+ resizeMode="cover"
216
+ />
217
+ )}
218
+ <KContainer.View style={styles.cardBody}>
219
+ {!!metadata.title && (
220
+ <KLabel.Text
221
+ typo="TextNmMedium"
222
+ color={KColors.palette.gray.w900}
223
+ numberOfLines={1}
224
+ >
225
+ {metadata.title}
226
+ </KLabel.Text>
227
+ )}
228
+ {!!metadata.url && (
229
+ <KLabel.Text
230
+ typo="TextXsMedium"
231
+ color={KColors.palette.primary.w400}
232
+ numberOfLines={1}
233
+ >
234
+ {metadata.url}
235
+ </KLabel.Text>
236
+ )}
237
+ {!!metadata.description && (
238
+ <KLabel.Text
239
+ typo="TextXsNormal"
240
+ color={KColors.gray.normal}
241
+ numberOfLines={2}
242
+ >
243
+ {metadata.description}
244
+ </KLabel.Text>
245
+ )}
246
+ </KContainer.View>
247
+ </KContainer.Touchable>
248
+ )}
249
+ </KContainer.View>
250
+
251
+ {!isOutgoing && timeLabel ? (
252
+ <KLabel.Text
253
+ typo="TextXsNormal"
254
+ color={CHAT_BUBBLE_COLORS.timestamp}
255
+ marginL="0.25rem"
256
+ >
257
+ {timeLabel}
258
+ </KLabel.Text>
259
+ ) : null}
260
+ </KContainer.View>
261
+ );
262
+ }
263
+ );
264
+ LegendLinkMessage.displayName = 'LegendLinkMessage';
265
+
266
+ const styles = StyleSheet.create({
267
+ wrapper: {
268
+ maxWidth: '80%',
269
+ },
270
+ bubble: {
271
+ paddingHorizontal: KSpacingValue['0.75rem'],
272
+ paddingVertical: KSpacingValue['0.5rem'],
273
+ borderRadius: KSpacingValue['1.25rem'],
274
+ },
275
+ bubbleReceived: {
276
+ backgroundColor: CHAT_BUBBLE_COLORS.received,
277
+ },
278
+ bubbleSent: {
279
+ backgroundColor: CHAT_BUBBLE_COLORS.sent,
280
+ },
281
+ textRow: {
282
+ paddingHorizontal: KSpacingValue['0.25rem'],
283
+ },
284
+ urlText: {
285
+ color: KColors.palette.primary.w400,
286
+ fontWeight: '500',
287
+ },
288
+ card: {
289
+ borderRadius: KSpacingValue['0.75rem'],
290
+ borderWidth: 1,
291
+ borderColor: 'rgba(57,62,64,0.1)',
292
+ backgroundColor: KColors.white,
293
+ overflow: 'hidden',
294
+ },
295
+ cardThumb: {
296
+ width: '100%',
297
+ aspectRatio: 16 / 9,
298
+ },
299
+ cardBody: {
300
+ paddingHorizontal: KSpacingValue['0.75rem'],
301
+ paddingVertical: KSpacingValue['0.5rem'],
302
+ gap: 2,
303
+ },
304
+ });
@@ -12,7 +12,6 @@ const getMessageRenderers = (): Partial<
12
12
  export const ChatMessageBubble = memo(
13
13
  ({ message, position }: ChatMessageBubbleProps) => {
14
14
  const Renderer = getMessageRenderers()[message.messageType];
15
-
16
15
  if (!Renderer) {
17
16
  return null;
18
17
  }
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { DChatType, DChatCategory } from '../../types/chat';
3
2
  import type { DConversationItem, DMessageItem } from '../../types/chat';
3
+ import type { IUrlMetadata } from '../../types/common';
4
+ import type { PeerType } from '@droppii/openim-rn-client-sdk';
4
5
 
5
6
  export type DChatActionIconProvider = 'MaterialCommunityIcons' | 'DroppiiNew';
6
7
 
@@ -22,8 +23,7 @@ export interface ChatDetailHeaderProps {
22
23
  subtitle?: string;
23
24
  avatarUri?: string | null;
24
25
  avatarFullName?: string;
25
- chatType?: DChatType;
26
- chatCategory?: DChatCategory;
26
+ peerType?: PeerType;
27
27
  applicationType?: DConversationItem['applicationType'];
28
28
  showAddMember?: boolean;
29
29
  onBack?: () => void;
@@ -33,10 +33,17 @@ export interface ChatDetailHeaderProps {
33
33
  onPressAvatar?: () => void;
34
34
  }
35
35
 
36
+ export interface ChatQuickActionsRenderParams {
37
+ actions: DChatQuickAction[];
38
+ onActionPress: (action: DChatQuickAction) => void;
39
+ }
40
+
36
41
  export interface ChatQuickActionsProps {
42
+ visible?: boolean;
37
43
  actions?: DChatQuickAction[];
38
44
  onActionPress?: (action: DChatQuickAction) => void;
39
45
  renderAction?: (action: DChatQuickAction, onPress: () => void) => ReactNode;
46
+ renderQuickActions?: (params: ChatQuickActionsRenderParams) => ReactNode;
40
47
  }
41
48
 
42
49
  export interface ChatAttachmentPanelProps {
@@ -53,14 +60,16 @@ export interface ChatComposerProps {
53
60
  value?: string;
54
61
  placeholder?: string;
55
62
  onChangeText?: (text: string) => void;
56
- onSend?: () => void;
63
+ onSend?: (urlMetadata?: IUrlMetadata) => void;
57
64
  onPressAttach?: () => void;
58
65
  onPressEmoji?: () => void;
66
+ showQuickActions?: boolean;
59
67
  quickActions?: DChatQuickAction[];
60
68
  attachmentActions?: DChatAttachmentAction[];
61
69
  onQuickActionPress?: (action: DChatQuickAction) => void;
62
70
  onAttachmentAction?: (action: DChatAttachmentAction) => void;
63
71
  renderQuickAction?: ChatQuickActionsProps['renderAction'];
72
+ renderQuickActions?: ChatQuickActionsProps['renderQuickActions'];
64
73
  renderAttachmentAction?: ChatAttachmentPanelProps['renderAction'];
65
74
  attachmentColumns?: number;
66
75
  }
@@ -73,17 +82,45 @@ export interface ChatListProps {
73
82
  isLoading?: boolean;
74
83
  isLoadingEarlier?: boolean;
75
84
  hasMoreEarlier?: boolean;
85
+ onLoadNewer?: () => void;
86
+ isLoadingNewer?: boolean;
87
+ hasMoreNewer?: boolean;
76
88
  }
77
89
 
78
- export interface ChatDetailProps extends ChatDetailHeaderProps {
79
- messages?: DMessageItem[];
80
- currentUserId?: string;
90
+ export interface ChatDetailProps extends Omit<
91
+ ChatDetailHeaderProps,
92
+ | 'title'
93
+ | 'subtitle'
94
+ | 'avatarUri'
95
+ | 'avatarFullName'
96
+ | 'peerType'
97
+ | 'applicationType'
98
+ | 'showAddMember'
99
+ > {
100
+ conversationId: string;
101
+ enabled?: boolean;
102
+ title?: string;
103
+ subtitle?: string;
104
+ avatarUri?: string | null;
105
+ avatarFullName?: string;
106
+ peerType?: PeerType;
107
+ applicationType?: DConversationItem['applicationType'];
108
+ showAddMember?: boolean;
109
+ getSubtitle?: (conversation: DConversationItem) => string | undefined;
81
110
  renderChat?: (item: DMessageItem) => ReactNode;
111
+ showQuickActions?: boolean;
112
+ showAttachmentActions?: boolean;
82
113
  onLoadEarlier?: () => void;
83
114
  isLoading?: boolean;
84
115
  isLoadingEarlier?: boolean;
85
116
  hasMoreEarlier?: boolean;
117
+ onLoadNewer?: () => void;
118
+ isLoadingNewer?: boolean;
119
+ hasMoreNewer?: boolean;
86
120
  quickActions?: DChatQuickAction[];
121
+ getQuickActions?: (
122
+ conversation: DConversationItem
123
+ ) => DChatQuickAction[] | undefined;
87
124
  attachmentActions?: DChatAttachmentAction[];
88
125
  inputValue?: string;
89
126
  inputPlaceholder?: string;
@@ -94,6 +131,7 @@ export interface ChatDetailProps extends ChatDetailHeaderProps {
94
131
  onQuickActionPress?: (action: DChatQuickAction) => void;
95
132
  onAttachmentAction?: (action: DChatAttachmentAction) => void;
96
133
  renderQuickAction?: ChatQuickActionsProps['renderAction'];
134
+ renderQuickActions?: ChatQuickActionsProps['renderQuickActions'];
97
135
  renderAttachmentAction?: ChatAttachmentPanelProps['renderAction'];
98
136
  attachmentColumns?: number;
99
137
  }
@@ -8,7 +8,7 @@ import type { BaseResponse, GetOpenIMTokenResponse } from '../types/auth';
8
8
  import { Platform } from 'react-native';
9
9
  import { useConversationStore } from '../store';
10
10
 
11
- let apiInstance: AxiosInstance | null = null;
11
+ export let apiInstance: AxiosInstance | null = null;
12
12
 
13
13
  export namespace ChatAPI {
14
14
  export function initApiInstance(api: AxiosInstance): void {
@@ -0,0 +1,5 @@
1
+ export const ENDPOINTS = {
2
+ chatService: {
3
+ urlMetadata: '/chat-service/v1/utils/url-metadata',
4
+ },
5
+ };
@@ -48,7 +48,7 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
48
48
  sortConversation(
49
49
  conversations.reduce(
50
50
  (pre, curr) => {
51
- pre[curr.conversationId] = curr;
51
+ pre[curr.conversationID] = curr;
52
52
  return pre;
53
53
  },
54
54
  { ...get().map }
@@ -0,0 +1,44 @@
1
+ import { create } from 'zustand';
2
+ import type { MessageItem } from '@droppii/openim-rn-client-sdk';
3
+ import type { DMessageItem, MessageStore } from '../types/chat';
4
+ import { mergeMessages } from '../utils/message';
5
+
6
+ export const useMessageStore = create<MessageStore>()((set, get) => ({
7
+ messages: [],
8
+ hasMoreEarlier: false,
9
+ hasMoreNewer: false,
10
+
11
+ setMessages: (messages, hasMore) => {
12
+ set({ messages, hasMoreEarlier: hasMore });
13
+ },
14
+
15
+ pushNewMessage: (message: MessageItem) => {
16
+ set((state) => ({
17
+ messages: mergeMessages(state.messages, [message as DMessageItem]),
18
+ }));
19
+ },
20
+
21
+ updateOneMessage: (message: MessageItem) => {
22
+ const tmpList = [...get().messages];
23
+ const idx = tmpList.findIndex(
24
+ (msg) => msg.clientMsgID === message.clientMsgID
25
+ );
26
+ if (idx < 0) return;
27
+ tmpList[idx] = { ...tmpList[idx]!, ...message } as DMessageItem;
28
+ set({ messages: tmpList });
29
+ },
30
+
31
+ deleteOneMessage: (clientMsgID: string) => {
32
+ set((state) => ({
33
+ messages: state.messages.filter((msg) => msg.clientMsgID !== clientMsgID),
34
+ }));
35
+ },
36
+
37
+ reset: () => {
38
+ set({
39
+ messages: [],
40
+ hasMoreEarlier: false,
41
+ hasMoreNewer: false,
42
+ });
43
+ },
44
+ }));
@@ -1,6 +1,12 @@
1
1
  const i18nKey: Record<string, string> = {
2
2
  thread_card_bot_crm_name: 'Droppii Hỗ Trợ',
3
3
  thread_card_fallback_name: 'Người dùng',
4
+ msg_type_image: '[Hình ảnh]',
5
+ msg_type_voice: '[Tin nhắn thoại]',
6
+ msg_type_video: '[Video]',
7
+ msg_type_file: '[File đính kèm]',
8
+ msg_type_link: '[Liên kết]',
9
+ msg_type_revoke: 'Đã thu hồi tin nhắn',
4
10
  };
5
11
 
6
12
  export default {
package/src/types/chat.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  type MessageItem,
3
- type SessionType,
4
- type MessageReceiveOptType,
5
- type GroupAtType,
3
+ type ConversationItem,
4
+ PeerType,
6
5
  } from '@droppii/openim-rn-client-sdk';
7
6
  import type { PropsWithChildren } from 'react';
7
+ import type { IUrlMetadata } from './common';
8
8
 
9
9
  export const EventProvider = {
10
10
  ga: 'ga',
@@ -20,16 +20,17 @@ type LogGA = (
20
20
 
21
21
  export interface ChatContextType {
22
22
  logGA?: LogGA;
23
+ applicationType: DChatApplicationType;
23
24
  }
24
25
 
25
26
  export type ChatProviderProps = PropsWithChildren<{
26
- enabled?: boolean;
27
27
  logGA?: LogGA;
28
+ applicationType: DChatApplicationType;
28
29
  }>;
29
30
 
30
31
  export enum DChatApplicationType {
31
- BIZ = 'BIZ',
32
- MALL = 'MALL',
32
+ DROPPII = 'DROPPII',
33
+ OBEFE = 'OBEFE',
33
34
  }
34
35
 
35
36
  export enum DChatCategory {
@@ -40,11 +41,6 @@ export enum DChatCategory {
40
41
  BIZ_BOT_CRM = 'BIZ_BOT_CRM',
41
42
  }
42
43
 
43
- export enum DChatType {
44
- SINGLE = 'SINGLE',
45
- GROUP = 'GROUP',
46
- }
47
-
48
44
  export enum DMemberRole {
49
45
  AGENT = 'AGENT',
50
46
  MEMBER = 'MEMBER',
@@ -65,11 +61,11 @@ export interface DConversationPeer {
65
61
  group: null; // to be defined when group is implemented
66
62
  }
67
63
 
68
- export interface DConversationItem {
64
+ export interface DConversationItem extends ConversationItem {
69
65
  // Droppii API fields
70
66
  conversationId: string;
71
67
  chatCategory: DChatCategory;
72
- chatType: DChatType;
68
+ chatType: PeerType;
73
69
  applicationType: string;
74
70
  memberRole: DMemberRole;
75
71
  pinnedAt: string | null;
@@ -77,23 +73,6 @@ export interface DConversationItem {
77
73
  peer: DConversationPeerUser;
78
74
  // Merged from OpenIM (available after getConversationListSplit merge)
79
75
  lastMessage?: DMessageItem;
80
- unreadCount?: number;
81
- conversationType?: SessionType;
82
- userID?: string;
83
- groupID?: string;
84
- showName?: string;
85
- faceURL?: string;
86
- recvMsgOpt?: MessageReceiveOptType;
87
- groupAtType?: GroupAtType;
88
- latestMsgSendTime?: number;
89
- draftText?: string;
90
- draftTextTime?: number;
91
- isPinned?: boolean;
92
- isNotInGroup?: boolean;
93
- isPrivateChat?: boolean;
94
- isMsgDestruct?: boolean;
95
- attachedInfo?: string;
96
- ex?: string;
97
76
  }
98
77
 
99
78
  export interface DConversationQueryParams {
@@ -134,3 +113,25 @@ export interface DMessagePushInfoEx {
134
113
  title: string;
135
114
  desc: string;
136
115
  }
116
+
117
+ export interface IMessageItemEx {
118
+ applicationType: DChatApplicationType;
119
+ sessionId?: string;
120
+ messageInfo?: {
121
+ type: 'MESSAGE_INFO';
122
+ data: 'rich_text';
123
+ content: string;
124
+ };
125
+ urlMetadata?: IUrlMetadata;
126
+ }
127
+
128
+ export interface MessageStore {
129
+ messages: DMessageItem[];
130
+ hasMoreEarlier: boolean;
131
+ hasMoreNewer: boolean;
132
+ setMessages: (messages: DMessageItem[], hasMore: boolean) => void;
133
+ pushNewMessage: (message: MessageItem) => void;
134
+ updateOneMessage: (message: MessageItem) => void;
135
+ deleteOneMessage: (clientMsgID: string) => void;
136
+ reset: () => void;
137
+ }
@@ -0,0 +1,6 @@
1
+ export interface IUrlMetadata {
2
+ title?: string;
3
+ description?: string;
4
+ image?: string;
5
+ url?: string;
6
+ }