@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.
- package/lib/module/components/ThreadCard/AvatarSection.js +4 -4
- package/lib/module/components/ThreadCard/AvatarSection.js.map +1 -1
- package/lib/module/components/ThreadCard/NamePrefixIcon.js +13 -16
- package/lib/module/components/ThreadCard/NamePrefixIcon.js.map +1 -1
- package/lib/module/components/ThreadCard/ThreadCard.js +13 -33
- package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
- package/lib/module/context/ChatContext.js +7 -6
- package/lib/module/context/ChatContext.js.map +1 -1
- package/lib/module/hooks/message/useSendMessage.js +101 -0
- package/lib/module/hooks/message/useSendMessage.js.map +1 -0
- package/lib/module/hooks/useChatMessages.js +37 -119
- package/lib/module/hooks/useChatMessages.js.map +1 -1
- package/lib/module/hooks/useConversationList.js +29 -17
- package/lib/module/hooks/useConversationList.js.map +1 -1
- package/lib/module/hooks/useLinkPreview/useLinkPreview.js +3 -2
- package/lib/module/hooks/useLinkPreview/useLinkPreview.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatComposer.js +2 -2
- package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +14 -10
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetailHeader.js +5 -8
- package/lib/module/screens/chat-detail/ChatDetailHeader.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatLinkPreview.js +1 -1
- package/lib/module/screens/chat-detail/ChatLinkPreview.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +0 -2
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/conversationHeader.utils.js +7 -9
- package/lib/module/screens/chat-detail/conversationHeader.utils.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +10 -23
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/message-types.js +128 -6
- package/lib/module/screens/chat-detail/legend/message-types.js.map +1 -1
- package/lib/module/store/conversation.js +1 -1
- package/lib/module/store/conversation.js.map +1 -1
- package/lib/module/store/message.js +45 -0
- package/lib/module/store/message.js.map +1 -0
- package/lib/module/translation/resources/i18n.js +7 -1
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/module/types/chat.js +2 -7
- package/lib/module/types/chat.js.map +1 -1
- package/lib/module/utils/conversation.js +34 -13
- package/lib/module/utils/conversation.js.map +1 -1
- package/lib/module/utils/legendListMessage.js +0 -3
- package/lib/module/utils/legendListMessage.js.map +1 -1
- package/lib/module/utils/message.js +5 -8
- package/lib/module/utils/message.js.map +1 -1
- package/lib/module/utils/url.js +3 -3
- package/lib/module/utils/url.js.map +1 -1
- package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts +2 -2
- package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts.map +1 -1
- package/lib/typescript/src/components/ThreadCard/NamePrefixIcon.d.ts +3 -4
- package/lib/typescript/src/components/ThreadCard/NamePrefixIcon.d.ts.map +1 -1
- package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
- package/lib/typescript/src/context/ChatContext.d.ts +1 -1
- package/lib/typescript/src/context/ChatContext.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts +12 -0
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useChatMessages.d.ts +0 -1
- package/lib/typescript/src/hooks/useChatMessages.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useConversationList.d.ts +2 -1
- package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useLinkPreview/useLinkPreview.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +1 -3
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/legend/message-types.d.ts +1 -0
- package/lib/typescript/src/screens/chat-detail/legend/message-types.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/types.d.ts +6 -7
- package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
- package/lib/typescript/src/store/message.d.ts +3 -0
- package/lib/typescript/src/store/message.d.ts.map +1 -0
- package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
- package/lib/typescript/src/types/chat.d.ts +28 -27
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/types/common.d.ts +1 -0
- package/lib/typescript/src/types/common.d.ts.map +1 -1
- package/lib/typescript/src/utils/conversation.d.ts +3 -2
- package/lib/typescript/src/utils/conversation.d.ts.map +1 -1
- package/lib/typescript/src/utils/legendListMessage.d.ts +0 -2
- package/lib/typescript/src/utils/legendListMessage.d.ts.map +1 -1
- package/lib/typescript/src/utils/message.d.ts.map +1 -1
- package/lib/typescript/src/utils/url.d.ts +1 -1
- package/lib/typescript/src/utils/url.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/components/ThreadCard/AvatarSection.tsx +5 -8
- package/src/components/ThreadCard/NamePrefixIcon.tsx +27 -38
- package/src/components/ThreadCard/ThreadCard.tsx +16 -30
- package/src/context/ChatContext.tsx +12 -4
- package/src/hooks/message/useSendMessage.ts +136 -0
- package/src/hooks/useChatMessages.ts +70 -158
- package/src/hooks/useConversationList.ts +34 -16
- package/src/hooks/useLinkPreview/useLinkPreview.ts +3 -2
- package/src/screens/chat-detail/ChatComposer.tsx +2 -2
- package/src/screens/chat-detail/ChatDetail.tsx +29 -22
- package/src/screens/chat-detail/ChatDetailHeader.tsx +4 -10
- package/src/screens/chat-detail/ChatLinkPreview.tsx +1 -1
- package/src/screens/chat-detail/ChatListLegend.tsx +1 -2
- package/src/screens/chat-detail/conversationHeader.utils.ts +11 -14
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +15 -33
- package/src/screens/chat-detail/legend/message-types.tsx +167 -12
- package/src/screens/chat-detail/types.ts +6 -8
- package/src/store/conversation.ts +1 -1
- package/src/store/message.ts +44 -0
- package/src/translation/resources/i18n.ts +6 -0
- package/src/types/chat.ts +31 -30
- package/src/types/common.ts +1 -0
- package/src/utils/conversation.ts +44 -17
- package/src/utils/legendListMessage.ts +0 -5
- package/src/utils/message.ts +10 -12
- package/src/utils/url.ts +3 -3
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import { useQuery } from '@tanstack/react-query';
|
|
3
|
-
import OpenIMSDK
|
|
4
|
-
|
|
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 {
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
43
|
-
|
|
59
|
+
const conversations = rawList.map(toConversationItem);
|
|
60
|
+
useConversationStore.getState().updateConversations(conversations);
|
|
61
|
+
return conversations;
|
|
44
62
|
},
|
|
45
63
|
});
|
|
46
64
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { useFetchUrlMetadata } from './useFetchUrlMetadata';
|
|
3
|
-
import {
|
|
3
|
+
import { extractUrls } from '../../utils/url';
|
|
4
4
|
|
|
5
5
|
export function useLinkPreview(value: string | undefined) {
|
|
6
6
|
const [detectedUrl, setDetectedUrl] = useState<string | undefined>(undefined);
|
|
@@ -10,7 +10,8 @@ export function useLinkPreview(value: string | undefined) {
|
|
|
10
10
|
useEffect(() => {
|
|
11
11
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
12
12
|
timerRef.current = setTimeout(() => {
|
|
13
|
-
const
|
|
13
|
+
const urls = value ? extractUrls(value) : [];
|
|
14
|
+
const url = urls.length === 1 ? urls[0] : undefined;
|
|
14
15
|
setDetectedUrl(url);
|
|
15
16
|
if (url) setIsDismissed(false);
|
|
16
17
|
}, 500);
|
|
@@ -181,8 +181,8 @@ export const ChatComposer = memo(
|
|
|
181
181
|
});
|
|
182
182
|
|
|
183
183
|
const handleSend = useCallback(() => {
|
|
184
|
-
onSend?.();
|
|
185
|
-
}, [onSend]);
|
|
184
|
+
onSend?.(showLinkPreview ? urlMetadata : undefined);
|
|
185
|
+
}, [onSend, urlMetadata, showLinkPreview]);
|
|
186
186
|
|
|
187
187
|
const handleToggleAttachment = useCallback(() => {
|
|
188
188
|
if (!hasAttachmentActions) {
|
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
} from './conversationHeader.utils';
|
|
23
23
|
import { getFeatureFlag, onFlagsChange } from '../../config/feature-flags';
|
|
24
24
|
import type { ChatDetailProps } from './types';
|
|
25
|
+
import { useSendMessage } from '../../hooks/message/useSendMessage';
|
|
26
|
+
import type { IUrlMetadata } from '../../types/common';
|
|
25
27
|
|
|
26
28
|
const ChatDetail = memo(
|
|
27
29
|
({
|
|
@@ -31,8 +33,7 @@ const ChatDetail = memo(
|
|
|
31
33
|
subtitle,
|
|
32
34
|
avatarUri,
|
|
33
35
|
avatarFullName,
|
|
34
|
-
|
|
35
|
-
chatCategory,
|
|
36
|
+
peerType,
|
|
36
37
|
applicationType,
|
|
37
38
|
showAddMember,
|
|
38
39
|
getSubtitle,
|
|
@@ -62,11 +63,12 @@ const ChatDetail = memo(
|
|
|
62
63
|
const [internalInput, setInternalInput] = useState('');
|
|
63
64
|
const conversation = useConversation(conversationId);
|
|
64
65
|
|
|
66
|
+
const { sendMessage } = useSendMessage();
|
|
67
|
+
|
|
65
68
|
const {
|
|
66
69
|
messages,
|
|
67
70
|
currentUserId,
|
|
68
71
|
onLoadEarlier,
|
|
69
|
-
sendTextMessage,
|
|
70
72
|
isLoading,
|
|
71
73
|
isLoadingEarlier,
|
|
72
74
|
hasMoreEarlier,
|
|
@@ -83,8 +85,7 @@ const ChatDetail = memo(
|
|
|
83
85
|
const resolvedAvatarUri =
|
|
84
86
|
avatarUri ?? getConversationAvatarUri(conversation);
|
|
85
87
|
const resolvedAvatarFullName = avatarFullName ?? resolvedTitle;
|
|
86
|
-
const
|
|
87
|
-
const resolvedChatCategory = chatCategory ?? conversation?.chatCategory;
|
|
88
|
+
const resolvedPeerType = peerType ?? conversation?.peerType;
|
|
88
89
|
const resolvedApplicationType =
|
|
89
90
|
applicationType ?? conversation?.applicationType;
|
|
90
91
|
const resolvedShowAddMember =
|
|
@@ -151,24 +152,31 @@ const ChatDetail = memo(
|
|
|
151
152
|
|
|
152
153
|
const currentInput = inputValue ?? internalInput;
|
|
153
154
|
|
|
154
|
-
const handleSend = useCallback(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
const handleSend = useCallback(
|
|
156
|
+
async (urlMetadata?: IUrlMetadata) => {
|
|
157
|
+
const text = currentInput.trim();
|
|
158
|
+
if (!text) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
159
161
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
try {
|
|
163
|
+
await sendMessage({
|
|
164
|
+
files: [],
|
|
165
|
+
plainText: text,
|
|
166
|
+
urlMetadata,
|
|
167
|
+
});
|
|
162
168
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
169
|
+
if (onChangeInput) {
|
|
170
|
+
onChangeInput('');
|
|
171
|
+
} else {
|
|
172
|
+
setInternalInput('');
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error('[send-message]', error);
|
|
167
176
|
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}, [currentInput, onChangeInput, sendTextMessage]);
|
|
177
|
+
},
|
|
178
|
+
[currentInput, onChangeInput, sendMessage]
|
|
179
|
+
);
|
|
172
180
|
|
|
173
181
|
const insets = useSafeAreaInsets();
|
|
174
182
|
|
|
@@ -179,8 +187,7 @@ const ChatDetail = memo(
|
|
|
179
187
|
subtitle={resolvedSubtitle}
|
|
180
188
|
avatarUri={resolvedAvatarUri}
|
|
181
189
|
avatarFullName={resolvedAvatarFullName}
|
|
182
|
-
|
|
183
|
-
chatCategory={resolvedChatCategory}
|
|
190
|
+
peerType={resolvedPeerType}
|
|
184
191
|
applicationType={resolvedApplicationType}
|
|
185
192
|
showAddMember={resolvedShowAddMember}
|
|
186
193
|
onBack={onBack}
|
|
@@ -16,8 +16,7 @@ const ChatDetailHeader = memo(
|
|
|
16
16
|
subtitle,
|
|
17
17
|
avatarUri,
|
|
18
18
|
avatarFullName,
|
|
19
|
-
|
|
20
|
-
chatCategory,
|
|
19
|
+
peerType,
|
|
21
20
|
applicationType,
|
|
22
21
|
showAddMember = false,
|
|
23
22
|
onBack,
|
|
@@ -81,17 +80,13 @@ const ChatDetailHeader = memo(
|
|
|
81
80
|
<AvatarSection
|
|
82
81
|
avatar={avatarUri ?? null}
|
|
83
82
|
fullName={avatarFullName ?? title}
|
|
84
|
-
|
|
83
|
+
peerType={peerType}
|
|
85
84
|
applicationType={applicationType}
|
|
86
|
-
chatType={chatType}
|
|
87
85
|
/>
|
|
88
86
|
|
|
89
87
|
<KContainer.View flex marginL="0.5rem">
|
|
90
88
|
<KContainer.View row alignItems>
|
|
91
|
-
<NamePrefixIcon
|
|
92
|
-
chatCategory={chatCategory}
|
|
93
|
-
chatType={chatType}
|
|
94
|
-
/>
|
|
89
|
+
<NamePrefixIcon peerType={peerType} />
|
|
95
90
|
<KLabel.Text
|
|
96
91
|
typo="TextLgBold"
|
|
97
92
|
color={KColors.gray.dark}
|
|
@@ -117,9 +112,8 @@ const ChatDetailHeader = memo(
|
|
|
117
112
|
[
|
|
118
113
|
avatarFullName,
|
|
119
114
|
avatarUri,
|
|
120
|
-
|
|
115
|
+
peerType,
|
|
121
116
|
applicationType,
|
|
122
|
-
chatType,
|
|
123
117
|
onBack,
|
|
124
118
|
onPressAvatar,
|
|
125
119
|
subtitle,
|
|
@@ -47,7 +47,7 @@ export const ChatLinkPreview = memo(
|
|
|
47
47
|
</KLabel.Text>
|
|
48
48
|
</KContainer.View>
|
|
49
49
|
<KContainer.Touchable onPress={onDismiss} style={styles.right}>
|
|
50
|
-
<KImage.VectorIcons name="close" size={16} />
|
|
50
|
+
<KImage.VectorIcons name="close-o" size={16} />
|
|
51
51
|
</KContainer.Touchable>
|
|
52
52
|
</KContainer.View>
|
|
53
53
|
);
|
|
@@ -97,7 +97,7 @@ export const ChatListLegend = memo(
|
|
|
97
97
|
return null; // Shouldn't happen if logic is consistent
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
const { dayStart,
|
|
100
|
+
const { dayStart, createdAt } = messageData;
|
|
101
101
|
|
|
102
102
|
return (
|
|
103
103
|
<>
|
|
@@ -116,7 +116,6 @@ export const ChatListLegend = memo(
|
|
|
116
116
|
) : (
|
|
117
117
|
<LegendChatMessage
|
|
118
118
|
message={message}
|
|
119
|
-
messageType={messageType}
|
|
120
119
|
isOutgoing={isOutgoing}
|
|
121
120
|
createdAtTime={createdAt}
|
|
122
121
|
/>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type
|
|
1
|
+
import { PeerType } from '@droppii/openim-rn-client-sdk';
|
|
2
|
+
import { type DConversationItem } from '../../types/chat';
|
|
3
3
|
|
|
4
4
|
export const getConversationTitle = (
|
|
5
5
|
conversation?: DConversationItem
|
|
@@ -8,18 +8,13 @@ export const getConversationTitle = (
|
|
|
8
8
|
return 'Tin nhắn';
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
return
|
|
12
|
-
conversation.peer?.fullName ??
|
|
13
|
-
conversation.peer?.username ??
|
|
14
|
-
conversation.showName ??
|
|
15
|
-
'Tin nhắn'
|
|
16
|
-
);
|
|
11
|
+
return conversation.showName ?? 'Tin nhắn';
|
|
17
12
|
};
|
|
18
13
|
|
|
19
14
|
export const getConversationAvatarUri = (
|
|
20
15
|
conversation?: DConversationItem
|
|
21
16
|
): string | null => {
|
|
22
|
-
return conversation?.
|
|
17
|
+
return conversation?.faceURL ?? null;
|
|
23
18
|
};
|
|
24
19
|
|
|
25
20
|
export const getConversationSubtitle = (
|
|
@@ -29,24 +24,26 @@ export const getConversationSubtitle = (
|
|
|
29
24
|
return undefined;
|
|
30
25
|
}
|
|
31
26
|
|
|
32
|
-
if (conversation
|
|
27
|
+
if (conversation.peerType === PeerType.Group) {
|
|
33
28
|
const unreadLabel =
|
|
34
|
-
(conversation
|
|
29
|
+
(conversation.unreadCount ?? 0) > 0
|
|
35
30
|
? `${conversation.unreadCount} tin nhắn mới`
|
|
36
31
|
: 'Không có tin mới';
|
|
37
32
|
return `20 thành viên • ${unreadLabel}`;
|
|
38
33
|
}
|
|
39
34
|
|
|
40
|
-
if (
|
|
35
|
+
if (
|
|
36
|
+
conversation.peerType === PeerType.Customer ||
|
|
37
|
+
conversation.applicationType === 'MALL'
|
|
38
|
+
) {
|
|
41
39
|
return '338,5K Đã bán • 4,2K Theo dõi';
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
// const lastMessageText = getLastMessageText(conversation.lastMessage);
|
|
45
42
|
return 'Nhấn để xem thông tin';
|
|
46
43
|
};
|
|
47
44
|
|
|
48
45
|
export const shouldShowAddMember = (
|
|
49
46
|
conversation?: DConversationItem
|
|
50
47
|
): boolean => {
|
|
51
|
-
return conversation?.
|
|
48
|
+
return conversation?.peerType === PeerType.Group;
|
|
52
49
|
};
|
|
@@ -1,32 +1,29 @@
|
|
|
1
|
-
import { memo
|
|
1
|
+
import { memo } from 'react';
|
|
2
|
+
import type { ComponentType } from 'react';
|
|
3
|
+
import { MessageType } from '@droppii/openim-rn-client-sdk';
|
|
2
4
|
import type { DMessageItem } from '../../../types/chat';
|
|
3
|
-
import type { DChatMessageType } from '../../../types/message';
|
|
4
5
|
import {
|
|
5
6
|
LegendTextMessage,
|
|
6
7
|
LegendImageMessage,
|
|
7
8
|
LegendVideoMessage,
|
|
8
9
|
LegendFileMessage,
|
|
10
|
+
LegendLinkMessage,
|
|
9
11
|
} from './message-types';
|
|
10
|
-
import { DChatMessageType as MessageTypeEnum } from '../../../types/message';
|
|
11
12
|
|
|
12
13
|
interface LegendChatMessageProps {
|
|
13
14
|
message: DMessageItem;
|
|
14
|
-
messageType: DChatMessageType;
|
|
15
15
|
isOutgoing: boolean;
|
|
16
16
|
createdAtTime: number;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
[
|
|
24
|
-
[
|
|
25
|
-
[
|
|
26
|
-
[
|
|
27
|
-
[MessageTypeEnum.Link]: LegendTextMessage, // Link renders as text for now
|
|
28
|
-
[MessageTypeEnum.Order]: LegendTextMessage, // Order renders as text for now
|
|
29
|
-
[MessageTypeEnum.Unsupported]: LegendTextMessage, // Unsupported renders as text for now
|
|
19
|
+
const messageComponentMap: Partial<Record<MessageType, ComponentType<any>>> = {
|
|
20
|
+
[MessageType.TextMessage]: LegendTextMessage,
|
|
21
|
+
[MessageType.AtTextMessage]: LegendTextMessage,
|
|
22
|
+
[MessageType.QuoteMessage]: LegendTextMessage,
|
|
23
|
+
[MessageType.UrlTextMessage]: LegendLinkMessage,
|
|
24
|
+
[MessageType.PictureMessage]: LegendImageMessage,
|
|
25
|
+
[MessageType.VideoMessage]: LegendVideoMessage,
|
|
26
|
+
[MessageType.FileMessage]: LegendFileMessage,
|
|
30
27
|
};
|
|
31
28
|
|
|
32
29
|
/**
|
|
@@ -34,24 +31,9 @@ const messageComponentMap: Partial<
|
|
|
34
31
|
* based on message type. Extensible for new message types.
|
|
35
32
|
*/
|
|
36
33
|
export const LegendChatMessage = memo(
|
|
37
|
-
({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
isOutgoing,
|
|
41
|
-
createdAtTime,
|
|
42
|
-
}: LegendChatMessageProps) => {
|
|
43
|
-
const MessageComponent = messageComponentMap[messageType];
|
|
44
|
-
|
|
45
|
-
// Fallback to text message if type not found
|
|
46
|
-
if (!MessageComponent) {
|
|
47
|
-
return (
|
|
48
|
-
<LegendTextMessage
|
|
49
|
-
message={message}
|
|
50
|
-
isOutgoing={isOutgoing}
|
|
51
|
-
createdAtTime={createdAtTime}
|
|
52
|
-
/>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
34
|
+
({ message, isOutgoing, createdAtTime }: LegendChatMessageProps) => {
|
|
35
|
+
const MessageComponent =
|
|
36
|
+
messageComponentMap[message?.contentType] ?? LegendTextMessage;
|
|
55
37
|
|
|
56
38
|
return (
|
|
57
39
|
<MessageComponent
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { memo } from 'react';
|
|
2
|
-
import { StyleSheet } from 'react-native';
|
|
3
|
-
import {
|
|
4
|
-
|
|
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';
|
|
5
12
|
import { getMessageText } from '../../../utils/legendListMessage';
|
|
6
13
|
import { CHAT_BUBBLE_COLORS } from '../constants';
|
|
7
14
|
|
|
@@ -13,10 +20,7 @@ interface BaseLegendMessageProps {
|
|
|
13
20
|
|
|
14
21
|
const formatMessageTime = (createdAt: number) => {
|
|
15
22
|
const date = new Date(createdAt);
|
|
16
|
-
if (Number.isNaN(date.getTime()))
|
|
17
|
-
return '';
|
|
18
|
-
}
|
|
19
|
-
|
|
23
|
+
if (Number.isNaN(date.getTime())) return '';
|
|
20
24
|
return date.toLocaleTimeString('vi-VN', {
|
|
21
25
|
hour: '2-digit',
|
|
22
26
|
minute: '2-digit',
|
|
@@ -51,7 +55,7 @@ export const LegendTextMessage = memo(
|
|
|
51
55
|
<KLabel.Text
|
|
52
56
|
typo="TextXsNormal"
|
|
53
57
|
color={CHAT_BUBBLE_COLORS.timestamp}
|
|
54
|
-
marginL=
|
|
58
|
+
marginL="0.25rem"
|
|
55
59
|
>
|
|
56
60
|
{timeLabel}
|
|
57
61
|
</KLabel.Text>
|
|
@@ -60,7 +64,6 @@ export const LegendTextMessage = memo(
|
|
|
60
64
|
);
|
|
61
65
|
}
|
|
62
66
|
);
|
|
63
|
-
|
|
64
67
|
LegendTextMessage.displayName = 'LegendTextMessage';
|
|
65
68
|
|
|
66
69
|
// Image Message Component (placeholder)
|
|
@@ -82,7 +85,6 @@ export const LegendImageMessage = memo(
|
|
|
82
85
|
);
|
|
83
86
|
}
|
|
84
87
|
);
|
|
85
|
-
|
|
86
88
|
LegendImageMessage.displayName = 'LegendImageMessage';
|
|
87
89
|
|
|
88
90
|
// Video Message Component (placeholder)
|
|
@@ -104,7 +106,6 @@ export const LegendVideoMessage = memo(
|
|
|
104
106
|
);
|
|
105
107
|
}
|
|
106
108
|
);
|
|
107
|
-
|
|
108
109
|
LegendVideoMessage.displayName = 'LegendVideoMessage';
|
|
109
110
|
|
|
110
111
|
// File Message Component
|
|
@@ -128,9 +129,140 @@ export const LegendFileMessage = memo(
|
|
|
128
129
|
);
|
|
129
130
|
}
|
|
130
131
|
);
|
|
131
|
-
|
|
132
132
|
LegendFileMessage.displayName = 'LegendFileMessage';
|
|
133
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
|
+
|
|
134
266
|
const styles = StyleSheet.create({
|
|
135
267
|
wrapper: {
|
|
136
268
|
maxWidth: '80%',
|
|
@@ -146,4 +278,27 @@ const styles = StyleSheet.create({
|
|
|
146
278
|
bubbleSent: {
|
|
147
279
|
backgroundColor: CHAT_BUBBLE_COLORS.sent,
|
|
148
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
|
+
},
|
|
149
304
|
});
|
|
@@ -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
|
-
|
|
26
|
-
chatCategory?: DChatCategory;
|
|
26
|
+
peerType?: PeerType;
|
|
27
27
|
applicationType?: DConversationItem['applicationType'];
|
|
28
28
|
showAddMember?: boolean;
|
|
29
29
|
onBack?: () => void;
|
|
@@ -60,7 +60,7 @@ export interface ChatComposerProps {
|
|
|
60
60
|
value?: string;
|
|
61
61
|
placeholder?: string;
|
|
62
62
|
onChangeText?: (text: string) => void;
|
|
63
|
-
onSend?: () => void;
|
|
63
|
+
onSend?: (urlMetadata?: IUrlMetadata) => void;
|
|
64
64
|
onPressAttach?: () => void;
|
|
65
65
|
onPressEmoji?: () => void;
|
|
66
66
|
showQuickActions?: boolean;
|
|
@@ -93,8 +93,7 @@ export interface ChatDetailProps extends Omit<
|
|
|
93
93
|
| 'subtitle'
|
|
94
94
|
| 'avatarUri'
|
|
95
95
|
| 'avatarFullName'
|
|
96
|
-
| '
|
|
97
|
-
| 'chatCategory'
|
|
96
|
+
| 'peerType'
|
|
98
97
|
| 'applicationType'
|
|
99
98
|
| 'showAddMember'
|
|
100
99
|
> {
|
|
@@ -104,8 +103,7 @@ export interface ChatDetailProps extends Omit<
|
|
|
104
103
|
subtitle?: string;
|
|
105
104
|
avatarUri?: string | null;
|
|
106
105
|
avatarFullName?: string;
|
|
107
|
-
|
|
108
|
-
chatCategory?: DChatCategory;
|
|
106
|
+
peerType?: PeerType;
|
|
109
107
|
applicationType?: DConversationItem['applicationType'];
|
|
110
108
|
showAddMember?: boolean;
|
|
111
109
|
getSubtitle?: (conversation: DConversationItem) => string | undefined;
|