@droppii-org/chat-mobile 0.2.11 → 0.2.13
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 +2 -3
- package/lib/module/components/ThreadCard/AvatarSection.js.map +1 -1
- package/lib/module/components/flows/inputButtons/index.js +77 -0
- package/lib/module/components/flows/inputButtons/index.js.map +1 -0
- package/lib/module/components/messages/linkMessage/index.js +35 -7
- package/lib/module/components/messages/linkMessage/index.js.map +1 -1
- package/lib/module/config/api-endpoints.js +1 -0
- package/lib/module/config/api-endpoints.js.map +1 -1
- package/lib/module/context/ChatDetailContext.js +57 -0
- package/lib/module/context/ChatDetailContext.js.map +1 -0
- package/lib/module/hooks/flows/useCurrentConversationFlow.js +9 -0
- package/lib/module/hooks/flows/useCurrentConversationFlow.js.map +1 -0
- package/lib/module/hooks/flows/useFlow.js +57 -0
- package/lib/module/hooks/flows/useFlow.js.map +1 -0
- package/lib/module/hooks/message/useSendMessage.js +4 -2
- package/lib/module/hooks/message/useSendMessage.js.map +1 -1
- package/lib/module/hooks/query-keys.js +4 -1
- package/lib/module/hooks/query-keys.js.map +1 -1
- package/lib/module/hooks/useConversationList.js +1 -1
- package/lib/module/hooks/useConversationList.js.map +1 -1
- package/lib/module/hooks/useLinkPreview/useFetchUrlMetadata.js +6 -4
- package/lib/module/hooks/useLinkPreview/useFetchUrlMetadata.js.map +1 -1
- package/lib/module/hooks/useLinkPreview/useLinkPreview.js +2 -2
- package/lib/module/hooks/useLinkPreview/useLinkPreview.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatComposer.js +41 -3
- package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +65 -60
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +45 -10
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatQuickActions.js +12 -3
- package/lib/module/screens/chat-detail/ChatQuickActions.js.map +1 -1
- package/lib/module/screens/chat-detail/conversationHeader.utils.js +1 -1
- package/lib/module/screens/chat-detail/conversationHeader.utils.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +6 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/screens/inbox/Inbox.js +1 -3
- package/lib/module/screens/inbox/Inbox.js.map +1 -1
- package/lib/module/screens/inbox/MessagesTab.js +2 -1
- package/lib/module/screens/inbox/MessagesTab.js.map +1 -1
- package/lib/module/services/endpoints.js +4 -1
- package/lib/module/services/endpoints.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +3 -1
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/module/types/chat.js.map +1 -1
- package/lib/module/types/flows.js +17 -0
- package/lib/module/types/flows.js.map +1 -0
- package/lib/module/utils/flows.js +11 -0
- package/lib/module/utils/flows.js.map +1 -0
- package/lib/module/utils/url.js +12 -1
- package/lib/module/utils/url.js.map +1 -1
- package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts +1 -1
- package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts.map +1 -1
- package/lib/typescript/src/components/flows/inputButtons/index.d.ts +9 -0
- package/lib/typescript/src/components/flows/inputButtons/index.d.ts.map +1 -0
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts +2 -1
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/config/api-endpoints.d.ts +1 -0
- package/lib/typescript/src/config/api-endpoints.d.ts.map +1 -1
- package/lib/typescript/src/context/ChatDetailContext.d.ts +5 -0
- package/lib/typescript/src/context/ChatDetailContext.d.ts.map +1 -0
- package/lib/typescript/src/hooks/flows/useCurrentConversationFlow.d.ts +3 -0
- package/lib/typescript/src/hooks/flows/useCurrentConversationFlow.d.ts.map +1 -0
- package/lib/typescript/src/hooks/flows/useFlow.d.ts +4 -0
- package/lib/typescript/src/hooks/flows/useFlow.d.ts.map +1 -0
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts +3 -1
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/query-keys.d.ts +3 -0
- package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useConversationList.d.ts +2 -2
- package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useLinkPreview/useFetchUrlMetadata.d.ts +6 -2
- package/lib/typescript/src/hooks/useLinkPreview/useFetchUrlMetadata.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useLinkPreview/useLinkPreview.d.ts +6 -1
- package/lib/typescript/src/hooks/useLinkPreview/useLinkPreview.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatComposer.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/ChatListLegend.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatQuickActions.d.ts.map +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 +2 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/types.d.ts +5 -10
- package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
- package/lib/typescript/src/screens/inbox/Inbox.d.ts +1 -2
- package/lib/typescript/src/screens/inbox/Inbox.d.ts.map +1 -1
- package/lib/typescript/src/screens/inbox/MessagesTab.d.ts +1 -2
- package/lib/typescript/src/screens/inbox/MessagesTab.d.ts.map +1 -1
- package/lib/typescript/src/services/endpoints.d.ts +3 -0
- package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
- package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
- package/lib/typescript/src/types/chat.d.ts +11 -1
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/types/flows.d.ts +82 -0
- package/lib/typescript/src/types/flows.d.ts.map +1 -0
- package/lib/typescript/src/utils/flows.d.ts +4 -0
- package/lib/typescript/src/utils/flows.d.ts.map +1 -0
- package/lib/typescript/src/utils/url.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/components/ThreadCard/AvatarSection.tsx +2 -2
- package/src/components/flows/inputButtons/index.tsx +105 -0
- package/src/components/messages/linkMessage/index.tsx +48 -8
- package/src/config/api-endpoints.ts +2 -0
- package/src/context/ChatDetailContext.tsx +85 -0
- package/src/hooks/flows/useCurrentConversationFlow.ts +11 -0
- package/src/hooks/flows/useFlow.ts +63 -0
- package/src/hooks/message/useSendMessage.ts +4 -0
- package/src/hooks/query-keys.ts +6 -0
- package/src/hooks/useConversationList.ts +15 -7
- package/src/hooks/useLinkPreview/useFetchUrlMetadata.ts +7 -3
- package/src/hooks/useLinkPreview/useLinkPreview.ts +2 -2
- package/src/index.tsx +1 -0
- package/src/screens/chat-detail/ChatComposer.tsx +41 -2
- package/src/screens/chat-detail/ChatDetail.tsx +81 -74
- package/src/screens/chat-detail/ChatListLegend.tsx +57 -3
- package/src/screens/chat-detail/ChatQuickActions.tsx +10 -3
- package/src/screens/chat-detail/conversationHeader.utils.ts +1 -4
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +7 -0
- package/src/screens/chat-detail/types.ts +9 -10
- package/src/screens/inbox/Inbox.tsx +3 -15
- package/src/screens/inbox/MessagesTab.tsx +48 -49
- package/src/services/endpoints.ts +6 -0
- package/src/translation/resources/i18n.ts +2 -0
- package/src/types/chat.ts +16 -1
- package/src/types/flows.ts +102 -0
- package/src/utils/flows.ts +18 -0
- package/src/utils/url.ts +30 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useMutation } from '@tanstack/react-query';
|
|
2
|
+
import { conversationQueryKeys } from '../query-keys';
|
|
3
|
+
import { useConversationStore } from '../../store';
|
|
4
|
+
import { apiInstance } from '../../services/apis';
|
|
5
|
+
import type { BaseResponse } from '../../types/auth';
|
|
6
|
+
import { ENDPOINTS } from '../../services/endpoints';
|
|
7
|
+
import type {
|
|
8
|
+
IConversationFlow,
|
|
9
|
+
IRunConversationFlowPayload,
|
|
10
|
+
IRunConversationFlowResponse,
|
|
11
|
+
} from '../../types/flows';
|
|
12
|
+
import { useSendMessage } from '../message/useSendMessage';
|
|
13
|
+
import { useChatDetailContext } from '../../context/ChatDetailContext';
|
|
14
|
+
|
|
15
|
+
export const useResetFlow = () => {
|
|
16
|
+
const conversationId =
|
|
17
|
+
useConversationStore((state) => state.currentConversationId) || '';
|
|
18
|
+
const { mutate: runFlow } = useRunFlow();
|
|
19
|
+
return useMutation({
|
|
20
|
+
mutationKey: conversationQueryKeys.flow(conversationId),
|
|
21
|
+
mutationFn: async () => {
|
|
22
|
+
const res = await apiInstance?.post<BaseResponse<IConversationFlow>>(
|
|
23
|
+
ENDPOINTS.chatService.resetConversationFlow(conversationId),
|
|
24
|
+
{}
|
|
25
|
+
);
|
|
26
|
+
return res?.data?.data || undefined;
|
|
27
|
+
},
|
|
28
|
+
onSuccess: (data) => {
|
|
29
|
+
if (data?.flowCurrentStep?.id) {
|
|
30
|
+
runFlow({
|
|
31
|
+
groupId: data?.flowCurrentStep?.id,
|
|
32
|
+
childId: null,
|
|
33
|
+
actionId: null,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const useRunFlow = () => {
|
|
41
|
+
const conversationId =
|
|
42
|
+
useConversationStore((state) => state.currentConversationId) || '';
|
|
43
|
+
const { sendMessage } = useSendMessage();
|
|
44
|
+
const { setConverationFlowStep } = useChatDetailContext();
|
|
45
|
+
return useMutation({
|
|
46
|
+
mutationKey: conversationQueryKeys.runFlow(conversationId),
|
|
47
|
+
mutationFn: async (payload: IRunConversationFlowPayload) => {
|
|
48
|
+
await sendMessage({
|
|
49
|
+
plainText: payload?.plainText || '',
|
|
50
|
+
flow: payload,
|
|
51
|
+
});
|
|
52
|
+
const res = await apiInstance?.post<
|
|
53
|
+
BaseResponse<IRunConversationFlowResponse>
|
|
54
|
+
>(ENDPOINTS.chatService.runConversationFlow(conversationId), payload);
|
|
55
|
+
return res?.data?.data || undefined;
|
|
56
|
+
},
|
|
57
|
+
onSuccess: (data) => {
|
|
58
|
+
if (data?.flowNextStep) {
|
|
59
|
+
setConverationFlowStep(data?.flowNextStep);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
};
|
|
@@ -13,6 +13,7 @@ import { useChatContext } from '../../context';
|
|
|
13
13
|
import { generateContentBasedOnMessageType } from '../../utils/conversation';
|
|
14
14
|
import { useMessageStore } from '../../store/message';
|
|
15
15
|
import type { IUrlMetadata } from '../../types/common';
|
|
16
|
+
import type { IRunConversationFlowPayload } from '../../types/flows';
|
|
16
17
|
|
|
17
18
|
export const useSendMessage = () => {
|
|
18
19
|
const applicationType = useChatContext().applicationType;
|
|
@@ -23,10 +24,12 @@ export const useSendMessage = () => {
|
|
|
23
24
|
plainText,
|
|
24
25
|
files,
|
|
25
26
|
urlMetadata,
|
|
27
|
+
flow,
|
|
26
28
|
}: {
|
|
27
29
|
plainText?: string;
|
|
28
30
|
files?: UploadFileItem[]; //TODO
|
|
29
31
|
urlMetadata?: IUrlMetadata;
|
|
32
|
+
flow?: IRunConversationFlowPayload;
|
|
30
33
|
}) => {
|
|
31
34
|
const { userID: recvID, groupID } =
|
|
32
35
|
useConversationStore.getState().getCurrentConversation() || {};
|
|
@@ -62,6 +65,7 @@ export const useSendMessage = () => {
|
|
|
62
65
|
const extendMessageInfo: IMessageItemEx = {
|
|
63
66
|
applicationType,
|
|
64
67
|
urlMetadata,
|
|
68
|
+
flow,
|
|
65
69
|
};
|
|
66
70
|
|
|
67
71
|
const successMessage = await OpenIMSDK.sendMessage({
|
package/src/hooks/query-keys.ts
CHANGED
|
@@ -11,6 +11,12 @@ export const conversationQueryKeys = {
|
|
|
11
11
|
details: () => [...conversationQueryKeys.all, 'detail'] as const,
|
|
12
12
|
detail: (conversationId: string) =>
|
|
13
13
|
[...conversationQueryKeys.details(), conversationId] as const,
|
|
14
|
+
flow: (conversationId: string) => ['conversationFlow', conversationId],
|
|
15
|
+
resetFlow: (conversationId: string) => [
|
|
16
|
+
'resetConversationFlow',
|
|
17
|
+
conversationId,
|
|
18
|
+
],
|
|
19
|
+
runFlow: (conversationId: string) => ['runConversationFlow', conversationId],
|
|
14
20
|
};
|
|
15
21
|
|
|
16
22
|
export type ConversationQueryKeys = typeof conversationQueryKeys;
|
|
@@ -5,10 +5,14 @@ import OpenIMSDK, {
|
|
|
5
5
|
} from '@droppii/openim-rn-client-sdk';
|
|
6
6
|
import { conversationQueryKeys } from './query-keys';
|
|
7
7
|
import { useConversationStore } from '../store';
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
DChatApplicationType,
|
|
10
|
+
DConversationItem,
|
|
11
|
+
DMessageItem,
|
|
12
|
+
} from '../types/chat';
|
|
9
13
|
|
|
10
14
|
type Params = {
|
|
11
|
-
applicationType:
|
|
15
|
+
applicationType: DChatApplicationType;
|
|
12
16
|
page: number;
|
|
13
17
|
enabled?: boolean;
|
|
14
18
|
};
|
|
@@ -23,17 +27,22 @@ function toConversationItem(raw: ConversationItem): DConversationItem {
|
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
let applicationType:
|
|
30
|
+
let applicationType: DChatApplicationType | undefined;
|
|
27
31
|
if (raw.ex) {
|
|
28
32
|
try {
|
|
29
|
-
applicationType = (
|
|
30
|
-
.applicationType
|
|
33
|
+
applicationType = (
|
|
34
|
+
JSON.parse(raw.ex) as { applicationType?: DChatApplicationType }
|
|
35
|
+
).applicationType;
|
|
31
36
|
} catch {
|
|
32
37
|
// leave undefined
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
...(raw as DConversationItem),
|
|
43
|
+
lastMessage,
|
|
44
|
+
applicationType: applicationType as DChatApplicationType,
|
|
45
|
+
};
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
export function useConversationList({
|
|
@@ -55,7 +64,6 @@ export function useConversationList({
|
|
|
55
64
|
count: 20,
|
|
56
65
|
applicationType,
|
|
57
66
|
});
|
|
58
|
-
|
|
59
67
|
const conversations = rawList.map(toConversationItem);
|
|
60
68
|
useConversationStore.getState().updateConversations(conversations);
|
|
61
69
|
return conversations;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useQuery } from '@tanstack/react-query';
|
|
2
2
|
import { commonQueryKeys } from '../query-keys';
|
|
3
3
|
import { apiInstance } from '../../services/apis';
|
|
4
4
|
import type { BaseResponse } from '../../types/auth';
|
|
@@ -11,8 +11,12 @@ export const useFetchUrlMetadata = (url?: string) =>
|
|
|
11
11
|
queryFn: async () => {
|
|
12
12
|
const path = `${ENDPOINTS.chatService.urlMetadata}?url=${url}`;
|
|
13
13
|
const res = await apiInstance?.get<BaseResponse<IUrlMetadata>>(path);
|
|
14
|
-
return res?.data?.data
|
|
14
|
+
return res?.data?.data?.title
|
|
15
|
+
? {
|
|
16
|
+
...res?.data?.data,
|
|
17
|
+
url,
|
|
18
|
+
}
|
|
19
|
+
: null;
|
|
15
20
|
},
|
|
16
21
|
enabled: !!url,
|
|
17
|
-
placeholderData: keepPreviousData,
|
|
18
22
|
});
|
|
@@ -25,7 +25,7 @@ export function useLinkPreview(value: string | undefined) {
|
|
|
25
25
|
|
|
26
26
|
const dismiss = useCallback(() => setIsDismissed(true), []);
|
|
27
27
|
|
|
28
|
-
const isVisible = !!detectedUrl && !isDismissed;
|
|
28
|
+
const isVisible = !!detectedUrl && !isDismissed && (isLoading || !!metadata);
|
|
29
29
|
|
|
30
|
-
return { isVisible, metadata, isLoading, dismiss };
|
|
30
|
+
return { isVisible, metadata: metadata || undefined, isLoading, dismiss };
|
|
31
31
|
}
|
package/src/index.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { memo, useCallback, useEffect, useMemo } from 'react';
|
|
|
2
2
|
import { Platform, StyleSheet } from 'react-native';
|
|
3
3
|
import Reanimated from 'react-native-reanimated';
|
|
4
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
|
-
import { KContainer, KColors, KImage } from '@droppii/libs';
|
|
5
|
+
import { KContainer, KColors, KImage, KButton } from '@droppii/libs';
|
|
6
6
|
|
|
7
7
|
const IS_IOS = Platform.OS === 'ios';
|
|
8
8
|
const INPUT_LINE_HEIGHT = 22.4;
|
|
@@ -25,6 +25,10 @@ import { useChatComposerState } from './hooks/useChatComposerState';
|
|
|
25
25
|
import { useChatComposerAnimation } from './hooks/useChatComposerAnimation';
|
|
26
26
|
import { useAttachmentSendHandler } from './hooks/useAttachmentSendHandler';
|
|
27
27
|
import { isVideoFromPicker } from '../../utils/imageUtils';
|
|
28
|
+
import { useChatDetailContext } from '../../context/ChatDetailContext';
|
|
29
|
+
import { trans } from '../../translation';
|
|
30
|
+
import { useRunFlow } from '../../hooks/flows/useFlow';
|
|
31
|
+
import { ReactFlowsNodeTypes } from '../../types/flows';
|
|
28
32
|
|
|
29
33
|
export const ChatComposer = memo(
|
|
30
34
|
({
|
|
@@ -43,10 +47,14 @@ export const ChatComposer = memo(
|
|
|
43
47
|
renderQuickActions,
|
|
44
48
|
renderAttachmentAction,
|
|
45
49
|
attachmentColumns,
|
|
50
|
+
isEmptyMessage = false,
|
|
46
51
|
}: ChatComposerProps) => {
|
|
47
52
|
const insets = useSafeAreaInsets();
|
|
48
53
|
const hasAttachmentActions = !!attachmentActions?.length;
|
|
49
54
|
|
|
55
|
+
const { isBotConversation, conversationFlow } = useChatDetailContext();
|
|
56
|
+
const { mutate: runFlow, isPending } = useRunFlow();
|
|
57
|
+
|
|
50
58
|
// Link preview
|
|
51
59
|
const {
|
|
52
60
|
isVisible: showLinkPreview,
|
|
@@ -137,7 +145,8 @@ export const ChatComposer = memo(
|
|
|
137
145
|
const hasAttachments = selectedImages.length > 0;
|
|
138
146
|
const hasText = (value ?? '').trim().length > 0;
|
|
139
147
|
// Only send metadata if preview is CURRENTLY visible (user didn't dismiss it)
|
|
140
|
-
const previewMetadata =
|
|
148
|
+
const previewMetadata =
|
|
149
|
+
showLinkPreview && !!urlMetadata ? urlMetadata : undefined;
|
|
141
150
|
const hasPreview = !!previewMetadata;
|
|
142
151
|
|
|
143
152
|
// Prevent sending while files are uploading
|
|
@@ -206,6 +215,15 @@ export const ChatComposer = memo(
|
|
|
206
215
|
[setIsInputWrapped]
|
|
207
216
|
);
|
|
208
217
|
|
|
218
|
+
const onStartConversationFlow = useCallback(() => {
|
|
219
|
+
runFlow({
|
|
220
|
+
groupId: conversationFlow?.flowCurrentStep?.id || '',
|
|
221
|
+
actionId: null,
|
|
222
|
+
childId: null,
|
|
223
|
+
plainText: trans('start'),
|
|
224
|
+
});
|
|
225
|
+
}, [runFlow, conversationFlow]);
|
|
226
|
+
|
|
209
227
|
// Reset wrapped state when value is cleared
|
|
210
228
|
useEffect(() => {
|
|
211
229
|
if (!value) {
|
|
@@ -233,6 +251,27 @@ export const ChatComposer = memo(
|
|
|
233
251
|
[insets.bottom, isAttachmentOpen]
|
|
234
252
|
);
|
|
235
253
|
|
|
254
|
+
if (
|
|
255
|
+
isBotConversation &&
|
|
256
|
+
conversationFlow?.flowCurrentStep?.type === ReactFlowsNodeTypes.Start &&
|
|
257
|
+
isEmptyMessage
|
|
258
|
+
) {
|
|
259
|
+
return (
|
|
260
|
+
<KContainer.View padding={'1.5rem'} paddingB={0}>
|
|
261
|
+
<KButton.Solid
|
|
262
|
+
size="md"
|
|
263
|
+
label={trans('start')}
|
|
264
|
+
stretch
|
|
265
|
+
icon={{
|
|
266
|
+
vectorName: 'chat-square-o',
|
|
267
|
+
size: 20,
|
|
268
|
+
}}
|
|
269
|
+
loading={isPending}
|
|
270
|
+
onPress={onStartConversationFlow}
|
|
271
|
+
/>
|
|
272
|
+
</KContainer.View>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
236
275
|
return (
|
|
237
276
|
<KContainer.View background={'rgba(131, 137, 157, 0.05)'}>
|
|
238
277
|
<ChatQuickActions
|
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
import { useSendMessage } from '../../hooks/message/useSendMessage';
|
|
33
33
|
import type { IUrlMetadata } from '../../types/common';
|
|
34
34
|
import { useMessageStore } from '../../store/message';
|
|
35
|
+
import { ChatDetailProvider } from '../../context/ChatDetailContext';
|
|
35
36
|
|
|
36
37
|
const ChatDetail = memo(
|
|
37
38
|
({
|
|
@@ -53,7 +54,6 @@ const ChatDetail = memo(
|
|
|
53
54
|
renderChat,
|
|
54
55
|
showQuickActions = true,
|
|
55
56
|
quickActions,
|
|
56
|
-
inputValue,
|
|
57
57
|
inputPlaceholder,
|
|
58
58
|
onChangeInput,
|
|
59
59
|
onPressAttach,
|
|
@@ -65,6 +65,7 @@ const ChatDetail = memo(
|
|
|
65
65
|
renderQuickActions,
|
|
66
66
|
renderAttachmentAction,
|
|
67
67
|
attachmentColumns,
|
|
68
|
+
onPressUrl,
|
|
68
69
|
}: ChatDetailProps) => {
|
|
69
70
|
const navigation = useNavigation();
|
|
70
71
|
const [internalInput, setInternalInput] = useState<string>('');
|
|
@@ -154,7 +155,7 @@ const ChatDetail = memo(
|
|
|
154
155
|
return getConversationSubtitle(conversation);
|
|
155
156
|
}, [conversation, getSubtitle, subtitle]);
|
|
156
157
|
|
|
157
|
-
const currentInput =
|
|
158
|
+
const currentInput = internalInput;
|
|
158
159
|
|
|
159
160
|
const handleChangeText = useCallback(
|
|
160
161
|
(text?: string) => {
|
|
@@ -222,83 +223,89 @@ const ChatDetail = memo(
|
|
|
222
223
|
const insets = useSafeAreaInsets();
|
|
223
224
|
|
|
224
225
|
return (
|
|
225
|
-
<
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
onPressSearch
|
|
237
|
-
|
|
238
|
-
onPressAddMember={
|
|
239
|
-
onPressAddMember ?? (() => console.log('[onPressAddMember]'))
|
|
240
|
-
}
|
|
241
|
-
onPressMenu={onPressMenu ?? (() => console.log('[onPressMenu]'))}
|
|
242
|
-
onPressAvatar={
|
|
243
|
-
onPressAvatar ?? (() => console.log('[onPressAvatar]'))
|
|
244
|
-
}
|
|
245
|
-
/>
|
|
246
|
-
|
|
247
|
-
<KeyboardAvoidingView
|
|
248
|
-
style={styles.keyboardAvoiding}
|
|
249
|
-
behavior="padding"
|
|
250
|
-
keyboardVerticalOffset={Platform.select({
|
|
251
|
-
android: insets.bottom,
|
|
252
|
-
default: 0,
|
|
253
|
-
})}
|
|
254
|
-
>
|
|
255
|
-
<ChatListLegend
|
|
256
|
-
messages={messages}
|
|
257
|
-
currentUserId={currentUserId}
|
|
258
|
-
renderChat={renderChat}
|
|
259
|
-
onLoadEarlier={onLoadEarlier}
|
|
260
|
-
isLoading={isLoading}
|
|
261
|
-
isLoadingEarlier={isLoadingEarlier}
|
|
262
|
-
hasMoreEarlier={hasMoreEarlier}
|
|
263
|
-
onMediaPress={(items, index) => {
|
|
264
|
-
mediaViewerRef.current?.show(items, index);
|
|
265
|
-
}}
|
|
266
|
-
/>
|
|
267
|
-
|
|
268
|
-
<ChatComposer
|
|
269
|
-
value={currentInput}
|
|
270
|
-
placeholder={inputPlaceholder}
|
|
271
|
-
onChangeText={handleChangeText}
|
|
272
|
-
onSend={handleSend}
|
|
273
|
-
onMessageSent={handleAttachmentMessageSent}
|
|
274
|
-
onPressAttach={
|
|
275
|
-
onPressAttach ?? (() => console.log('[onPressAttach]'))
|
|
226
|
+
<ChatDetailProvider>
|
|
227
|
+
<KContainer.Page flex edges={['bottom']}>
|
|
228
|
+
<ChatDetailHeader
|
|
229
|
+
title={resolvedTitle}
|
|
230
|
+
subtitle={resolvedSubtitle}
|
|
231
|
+
avatarUri={resolvedAvatarUri}
|
|
232
|
+
avatarFullName={resolvedAvatarFullName}
|
|
233
|
+
peerType={resolvedPeerType}
|
|
234
|
+
applicationType={resolvedApplicationType}
|
|
235
|
+
showAddMember={resolvedShowAddMember}
|
|
236
|
+
onBack={onBack ?? handleBackDefault}
|
|
237
|
+
onPressSearch={
|
|
238
|
+
onPressSearch ?? (() => console.log('[onPressSearch]'))
|
|
276
239
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
quickActions={quickActions ?? quickActionsDefault}
|
|
280
|
-
attachmentActions={attachmentActions ?? attachmentActionsDefault}
|
|
281
|
-
onQuickActionPress={
|
|
282
|
-
onQuickActionPress ??
|
|
283
|
-
((action: DChatQuickAction) => {
|
|
284
|
-
console.log('[onQuickActionPress]', action.id, action.meta);
|
|
285
|
-
})
|
|
240
|
+
onPressAddMember={
|
|
241
|
+
onPressAddMember ?? (() => console.log('[onPressAddMember]'))
|
|
286
242
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
((
|
|
290
|
-
console.log('[onAttachmentAction]', action.id, action.meta);
|
|
291
|
-
})
|
|
243
|
+
onPressMenu={onPressMenu ?? (() => console.log('[onPressMenu]'))}
|
|
244
|
+
onPressAvatar={
|
|
245
|
+
onPressAvatar ?? (() => console.log('[onPressAvatar]'))
|
|
292
246
|
}
|
|
293
|
-
renderQuickAction={renderQuickAction}
|
|
294
|
-
renderQuickActions={renderQuickActions}
|
|
295
|
-
renderAttachmentAction={renderAttachmentAction}
|
|
296
|
-
attachmentColumns={attachmentColumns}
|
|
297
247
|
/>
|
|
298
|
-
</KeyboardAvoidingView>
|
|
299
248
|
|
|
300
|
-
|
|
301
|
-
|
|
249
|
+
<KeyboardAvoidingView
|
|
250
|
+
style={styles.keyboardAvoiding}
|
|
251
|
+
behavior="padding"
|
|
252
|
+
keyboardVerticalOffset={Platform.select({
|
|
253
|
+
android: insets.bottom,
|
|
254
|
+
default: 0,
|
|
255
|
+
})}
|
|
256
|
+
>
|
|
257
|
+
<ChatListLegend
|
|
258
|
+
messages={messages}
|
|
259
|
+
currentUserId={currentUserId}
|
|
260
|
+
renderChat={renderChat}
|
|
261
|
+
onLoadEarlier={onLoadEarlier}
|
|
262
|
+
isLoading={isLoading}
|
|
263
|
+
isLoadingEarlier={isLoadingEarlier}
|
|
264
|
+
hasMoreEarlier={hasMoreEarlier}
|
|
265
|
+
onMediaPress={(items, index) => {
|
|
266
|
+
mediaViewerRef.current?.show(items, index);
|
|
267
|
+
}}
|
|
268
|
+
onPressUrl={onPressUrl}
|
|
269
|
+
/>
|
|
270
|
+
|
|
271
|
+
<ChatComposer
|
|
272
|
+
value={currentInput}
|
|
273
|
+
placeholder={inputPlaceholder}
|
|
274
|
+
onChangeText={handleChangeText}
|
|
275
|
+
onSend={handleSend}
|
|
276
|
+
onMessageSent={handleAttachmentMessageSent}
|
|
277
|
+
onPressAttach={
|
|
278
|
+
onPressAttach ?? (() => console.log('[onPressAttach]'))
|
|
279
|
+
}
|
|
280
|
+
onPressEmoji={
|
|
281
|
+
onPressEmoji ?? (() => console.log('[onPressEmoji]'))
|
|
282
|
+
}
|
|
283
|
+
showQuickActions={showQuickActions}
|
|
284
|
+
quickActions={quickActions ?? quickActionsDefault}
|
|
285
|
+
attachmentActions={attachmentActions ?? attachmentActionsDefault}
|
|
286
|
+
onQuickActionPress={
|
|
287
|
+
onQuickActionPress ??
|
|
288
|
+
((action: DChatQuickAction) => {
|
|
289
|
+
console.log('[onQuickActionPress]', action.id, action.meta);
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
onAttachmentAction={
|
|
293
|
+
onAttachmentAction ??
|
|
294
|
+
((action: DChatAttachmentAction) => {
|
|
295
|
+
console.log('[onAttachmentAction]', action.id, action.meta);
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
renderQuickAction={renderQuickAction}
|
|
299
|
+
renderQuickActions={renderQuickActions}
|
|
300
|
+
renderAttachmentAction={renderAttachmentAction}
|
|
301
|
+
attachmentColumns={attachmentColumns}
|
|
302
|
+
isEmptyMessage={messages?.length === 0}
|
|
303
|
+
/>
|
|
304
|
+
</KeyboardAvoidingView>
|
|
305
|
+
|
|
306
|
+
<MediaViewerModal ref={mediaViewerRef} />
|
|
307
|
+
</KContainer.Page>
|
|
308
|
+
</ChatDetailProvider>
|
|
302
309
|
);
|
|
303
310
|
}
|
|
304
311
|
);
|
|
@@ -13,6 +13,11 @@ import type { DMessageItem } from '../../types/chat';
|
|
|
13
13
|
import { LegendChatMessage } from './legend/LegendChatMessage';
|
|
14
14
|
import { LegendChatDay } from './legend/LegendChatDay';
|
|
15
15
|
import { LegendChatLoadEarlier } from './legend/LegendChatLoadEarlier';
|
|
16
|
+
import { useChatDetailContext } from '../../context/ChatDetailContext';
|
|
17
|
+
import { getFlowInputButtonsNode } from '../../utils/flows';
|
|
18
|
+
import { InputButtonFlow } from '../../components/flows/inputButtons';
|
|
19
|
+
import { useResetFlow, useRunFlow } from '../../hooks/flows/useFlow';
|
|
20
|
+
import type { IRunConversationFlowPayload } from '../../types/flows';
|
|
16
21
|
|
|
17
22
|
export const ChatListLegend = memo(
|
|
18
23
|
({
|
|
@@ -27,6 +32,7 @@ export const ChatListLegend = memo(
|
|
|
27
32
|
isLoadingNewer,
|
|
28
33
|
hasMoreNewer,
|
|
29
34
|
onMediaPress,
|
|
35
|
+
onPressUrl,
|
|
30
36
|
}: ChatListProps) => {
|
|
31
37
|
// Precompute all message data in single pass (O(n))
|
|
32
38
|
const messageDataMap = useMemo(
|
|
@@ -34,6 +40,15 @@ export const ChatListLegend = memo(
|
|
|
34
40
|
[messages]
|
|
35
41
|
);
|
|
36
42
|
|
|
43
|
+
const { isBotConversation, conversationFlow } = useChatDetailContext();
|
|
44
|
+
|
|
45
|
+
const lastMessageId = useMemo(() => {
|
|
46
|
+
const last = messages[messages.length - 1];
|
|
47
|
+
return last?.clientMsgID || last?.serverMsgID || null;
|
|
48
|
+
}, [messages]);
|
|
49
|
+
const { mutate: runFlow } = useRunFlow();
|
|
50
|
+
const { mutate: resetFlow } = useResetFlow();
|
|
51
|
+
|
|
37
52
|
const listRef = useRef<LegendListRef>(null);
|
|
38
53
|
const buttonOpacity = useSharedValue(0);
|
|
39
54
|
const isAtBottomRef = useRef(true);
|
|
@@ -70,6 +85,17 @@ export const ChatListLegend = memo(
|
|
|
70
85
|
[]
|
|
71
86
|
);
|
|
72
87
|
|
|
88
|
+
const onRunFlow = useCallback(
|
|
89
|
+
(payload: IRunConversationFlowPayload) => {
|
|
90
|
+
runFlow(payload);
|
|
91
|
+
},
|
|
92
|
+
[runFlow]
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const onResetFlow = useCallback(() => {
|
|
96
|
+
resetFlow();
|
|
97
|
+
}, [resetFlow]);
|
|
98
|
+
|
|
73
99
|
const renderItem = useCallback(
|
|
74
100
|
({ item, index }: any) => {
|
|
75
101
|
try {
|
|
@@ -77,7 +103,9 @@ export const ChatListLegend = memo(
|
|
|
77
103
|
|
|
78
104
|
const message = item as DMessageItem;
|
|
79
105
|
const isOutgoing = message.sendID === currentUserId;
|
|
80
|
-
|
|
106
|
+
const isLastIncoming =
|
|
107
|
+
!isOutgoing &&
|
|
108
|
+
(message.clientMsgID || message.serverMsgID) === lastMessageId;
|
|
81
109
|
const messageId =
|
|
82
110
|
message.clientMsgID ||
|
|
83
111
|
message.serverMsgID ||
|
|
@@ -85,11 +113,18 @@ export const ChatListLegend = memo(
|
|
|
85
113
|
|
|
86
114
|
const messageData = messageDataMap.get(messageId);
|
|
87
115
|
if (!messageData) {
|
|
88
|
-
return null;
|
|
116
|
+
return null;
|
|
89
117
|
}
|
|
90
118
|
|
|
91
119
|
const { dayStart, createdAt } = messageData;
|
|
92
120
|
|
|
121
|
+
let flowInputButtonsNode = null;
|
|
122
|
+
if (isLastIncoming && isBotConversation) {
|
|
123
|
+
flowInputButtonsNode = getFlowInputButtonsNode(
|
|
124
|
+
conversationFlow?.flowCurrentStep
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
93
128
|
return (
|
|
94
129
|
<>
|
|
95
130
|
{dayStart && <LegendChatDay createdAt={createdAt} />}
|
|
@@ -110,6 +145,14 @@ export const ChatListLegend = memo(
|
|
|
110
145
|
isOutgoing={isOutgoing}
|
|
111
146
|
createdAtTime={createdAt}
|
|
112
147
|
onMediaPress={onMediaPress}
|
|
148
|
+
onPressUrl={onPressUrl}
|
|
149
|
+
/>
|
|
150
|
+
)}
|
|
151
|
+
{!!flowInputButtonsNode && (
|
|
152
|
+
<InputButtonFlow
|
|
153
|
+
nodeData={flowInputButtonsNode}
|
|
154
|
+
onResetFlow={onResetFlow}
|
|
155
|
+
onRunFlow={onRunFlow}
|
|
113
156
|
/>
|
|
114
157
|
)}
|
|
115
158
|
</KContainer.View>
|
|
@@ -133,6 +176,12 @@ export const ChatListLegend = memo(
|
|
|
133
176
|
messageDataMap,
|
|
134
177
|
handleItemLayout,
|
|
135
178
|
onMediaPress,
|
|
179
|
+
onPressUrl,
|
|
180
|
+
isBotConversation,
|
|
181
|
+
conversationFlow,
|
|
182
|
+
onResetFlow,
|
|
183
|
+
onRunFlow,
|
|
184
|
+
lastMessageId,
|
|
136
185
|
]
|
|
137
186
|
);
|
|
138
187
|
|
|
@@ -293,7 +342,12 @@ export const ChatListLegend = memo(
|
|
|
293
342
|
renderItem={renderItem}
|
|
294
343
|
estimatedItemSize={avgItemHeightRef.current}
|
|
295
344
|
keyExtractor={(item) => String(item.clientMsgID || item.serverMsgID)}
|
|
296
|
-
recycleItems
|
|
345
|
+
recycleItems={!isBotConversation}
|
|
346
|
+
extraData={
|
|
347
|
+
!isBotConversation
|
|
348
|
+
? `${conversationFlow?.flowCurrentStep?.id ?? ''}-${lastMessageId ?? ''}`
|
|
349
|
+
: undefined
|
|
350
|
+
}
|
|
297
351
|
alignItemsAtEnd
|
|
298
352
|
maintainVisibleContentPosition={!isLoadingEarlier}
|
|
299
353
|
maintainScrollAtEnd={maintainScrollConfig}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from '@droppii/libs';
|
|
10
10
|
import { useChatActionPress } from './useChatActionPress';
|
|
11
11
|
import type { DChatQuickAction, ChatQuickActionsProps } from './types';
|
|
12
|
+
import { useResetFlow } from '../../hooks/flows/useFlow';
|
|
12
13
|
|
|
13
14
|
const QuickActionItem = memo(
|
|
14
15
|
({
|
|
@@ -69,18 +70,24 @@ export const ChatQuickActions = memo(
|
|
|
69
70
|
renderQuickActions,
|
|
70
71
|
}: ChatQuickActionsProps) => {
|
|
71
72
|
const handleActionPress = useChatActionPress(onActionPress);
|
|
73
|
+
const { mutate: resetFlow } = useResetFlow();
|
|
72
74
|
|
|
73
75
|
const renderItem = useCallback<ListRenderItem<DChatQuickAction>>(
|
|
74
76
|
({ item }) => {
|
|
75
|
-
const onPress = () =>
|
|
77
|
+
const onPress = () => {
|
|
78
|
+
handleActionPress(item);
|
|
79
|
+
if (item.id === 'restart') {
|
|
80
|
+
resetFlow?.();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
76
83
|
|
|
77
84
|
if (renderAction) {
|
|
78
85
|
return <>{renderAction(item, onPress)}</>;
|
|
79
86
|
}
|
|
80
87
|
|
|
81
|
-
return <QuickActionItem action={item} onPress={
|
|
88
|
+
return <QuickActionItem action={item} onPress={onPress} />;
|
|
82
89
|
},
|
|
83
|
-
[handleActionPress, renderAction]
|
|
90
|
+
[handleActionPress, renderAction, resetFlow]
|
|
84
91
|
);
|
|
85
92
|
|
|
86
93
|
const keyExtractor = useCallback((item: DChatQuickAction) => item.id, []);
|
|
@@ -32,10 +32,7 @@ export const getConversationSubtitle = (
|
|
|
32
32
|
return `20 thành viên • ${unreadLabel}`;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
if (
|
|
36
|
-
conversation.peerType === PeerType.Customer ||
|
|
37
|
-
conversation.applicationType === 'MALL'
|
|
38
|
-
) {
|
|
35
|
+
if (conversation.peerType === PeerType.Customer) {
|
|
39
36
|
return '338,5K Đã bán • 4,2K Theo dõi';
|
|
40
37
|
}
|
|
41
38
|
|