@droppii-org/chat-mobile 0.2.12 → 0.2.14

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 (115) hide show
  1. package/lib/module/components/ThreadCard/AvatarSection.js +2 -3
  2. package/lib/module/components/ThreadCard/AvatarSection.js.map +1 -1
  3. package/lib/module/components/flows/inputButtons/index.js +77 -0
  4. package/lib/module/components/flows/inputButtons/index.js.map +1 -0
  5. package/lib/module/components/messages/linkMessage/index.js +1 -1
  6. package/lib/module/components/messages/linkMessage/index.js.map +1 -1
  7. package/lib/module/config/api-endpoints.js +1 -0
  8. package/lib/module/config/api-endpoints.js.map +1 -1
  9. package/lib/module/context/ChatDetailContext.js +57 -0
  10. package/lib/module/context/ChatDetailContext.js.map +1 -0
  11. package/lib/module/hooks/flows/useCurrentConversationFlow.js +9 -0
  12. package/lib/module/hooks/flows/useCurrentConversationFlow.js.map +1 -0
  13. package/lib/module/hooks/flows/useFlow.js +57 -0
  14. package/lib/module/hooks/flows/useFlow.js.map +1 -0
  15. package/lib/module/hooks/message/useSendMessage.js +4 -2
  16. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  17. package/lib/module/hooks/query-keys.js +4 -1
  18. package/lib/module/hooks/query-keys.js.map +1 -1
  19. package/lib/module/hooks/useConversationList.js +1 -1
  20. package/lib/module/hooks/useConversationList.js.map +1 -1
  21. package/lib/module/hooks/useLinkPreview/useFetchUrlMetadata.js +2 -3
  22. package/lib/module/hooks/useLinkPreview/useFetchUrlMetadata.js.map +1 -1
  23. package/lib/module/screens/chat-detail/ChatComposer.js +40 -2
  24. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  25. package/lib/module/screens/chat-detail/ChatDetail.js +66 -58
  26. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  27. package/lib/module/screens/chat-detail/ChatListLegend.js +41 -8
  28. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  29. package/lib/module/screens/chat-detail/ChatQuickActions.js +12 -3
  30. package/lib/module/screens/chat-detail/ChatQuickActions.js.map +1 -1
  31. package/lib/module/screens/chat-detail/conversationHeader.utils.js +1 -1
  32. package/lib/module/screens/chat-detail/conversationHeader.utils.js.map +1 -1
  33. package/lib/module/screens/inbox/Inbox.js +1 -3
  34. package/lib/module/screens/inbox/Inbox.js.map +1 -1
  35. package/lib/module/screens/inbox/MessagesTab.js +2 -1
  36. package/lib/module/screens/inbox/MessagesTab.js.map +1 -1
  37. package/lib/module/services/endpoints.js +4 -1
  38. package/lib/module/services/endpoints.js.map +1 -1
  39. package/lib/module/translation/resources/i18n.js +3 -1
  40. package/lib/module/translation/resources/i18n.js.map +1 -1
  41. package/lib/module/types/chat.js.map +1 -1
  42. package/lib/module/types/flows.js +17 -0
  43. package/lib/module/types/flows.js.map +1 -0
  44. package/lib/module/utils/flows.js +11 -0
  45. package/lib/module/utils/flows.js.map +1 -0
  46. package/lib/module/utils/url.js +6 -0
  47. package/lib/module/utils/url.js.map +1 -1
  48. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts +1 -1
  49. package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts.map +1 -1
  50. package/lib/typescript/src/components/flows/inputButtons/index.d.ts +9 -0
  51. package/lib/typescript/src/components/flows/inputButtons/index.d.ts.map +1 -0
  52. package/lib/typescript/src/config/api-endpoints.d.ts +1 -0
  53. package/lib/typescript/src/config/api-endpoints.d.ts.map +1 -1
  54. package/lib/typescript/src/context/ChatDetailContext.d.ts +5 -0
  55. package/lib/typescript/src/context/ChatDetailContext.d.ts.map +1 -0
  56. package/lib/typescript/src/hooks/flows/useCurrentConversationFlow.d.ts +3 -0
  57. package/lib/typescript/src/hooks/flows/useCurrentConversationFlow.d.ts.map +1 -0
  58. package/lib/typescript/src/hooks/flows/useFlow.d.ts +4 -0
  59. package/lib/typescript/src/hooks/flows/useFlow.d.ts.map +1 -0
  60. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +3 -1
  61. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  62. package/lib/typescript/src/hooks/query-keys.d.ts +3 -0
  63. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  64. package/lib/typescript/src/hooks/useConversationList.d.ts +2 -2
  65. package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
  66. package/lib/typescript/src/hooks/useLinkPreview/useFetchUrlMetadata.d.ts.map +1 -1
  67. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +1 -1
  68. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  69. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  70. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
  71. package/lib/typescript/src/screens/chat-detail/ChatQuickActions.d.ts.map +1 -1
  72. package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts.map +1 -1
  73. package/lib/typescript/src/screens/chat-detail/types.d.ts +1 -0
  74. package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
  75. package/lib/typescript/src/screens/inbox/Inbox.d.ts +1 -2
  76. package/lib/typescript/src/screens/inbox/Inbox.d.ts.map +1 -1
  77. package/lib/typescript/src/screens/inbox/MessagesTab.d.ts +1 -2
  78. package/lib/typescript/src/screens/inbox/MessagesTab.d.ts.map +1 -1
  79. package/lib/typescript/src/services/endpoints.d.ts +3 -0
  80. package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
  81. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  82. package/lib/typescript/src/types/chat.d.ts +11 -1
  83. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  84. package/lib/typescript/src/types/flows.d.ts +82 -0
  85. package/lib/typescript/src/types/flows.d.ts.map +1 -0
  86. package/lib/typescript/src/utils/flows.d.ts +4 -0
  87. package/lib/typescript/src/utils/flows.d.ts.map +1 -0
  88. package/lib/typescript/src/utils/url.d.ts +1 -0
  89. package/lib/typescript/src/utils/url.d.ts.map +1 -1
  90. package/package.json +2 -2
  91. package/src/components/ThreadCard/AvatarSection.tsx +2 -2
  92. package/src/components/flows/inputButtons/index.tsx +105 -0
  93. package/src/components/messages/linkMessage/index.tsx +1 -1
  94. package/src/config/api-endpoints.ts +2 -0
  95. package/src/context/ChatDetailContext.tsx +85 -0
  96. package/src/hooks/flows/useCurrentConversationFlow.ts +11 -0
  97. package/src/hooks/flows/useFlow.ts +63 -0
  98. package/src/hooks/message/useSendMessage.ts +4 -0
  99. package/src/hooks/query-keys.ts +6 -0
  100. package/src/hooks/useConversationList.ts +15 -7
  101. package/src/hooks/useLinkPreview/useFetchUrlMetadata.ts +1 -2
  102. package/src/screens/chat-detail/ChatComposer.tsx +39 -1
  103. package/src/screens/chat-detail/ChatDetail.tsx +87 -73
  104. package/src/screens/chat-detail/ChatListLegend.tsx +54 -3
  105. package/src/screens/chat-detail/ChatQuickActions.tsx +10 -3
  106. package/src/screens/chat-detail/conversationHeader.utils.ts +1 -4
  107. package/src/screens/chat-detail/types.ts +1 -0
  108. package/src/screens/inbox/Inbox.tsx +3 -15
  109. package/src/screens/inbox/MessagesTab.tsx +48 -49
  110. package/src/services/endpoints.ts +6 -0
  111. package/src/translation/resources/i18n.ts +2 -0
  112. package/src/types/chat.ts +16 -1
  113. package/src/types/flows.ts +102 -0
  114. package/src/utils/flows.ts +18 -0
  115. package/src/utils/url.ts +7 -0
@@ -0,0 +1,105 @@
1
+ import {
2
+ KColors,
3
+ KContainer,
4
+ KImage,
5
+ KLabel,
6
+ KRadiusValue,
7
+ KSpacingValue,
8
+ } from '@droppii/libs';
9
+ import { memo, useCallback } from 'react';
10
+ import { StyleSheet } from 'react-native';
11
+ import { messageStyles } from '../../messages/styles';
12
+ import type {
13
+ GroupChild,
14
+ InputButtonItem,
15
+ IRunConversationFlowPayload,
16
+ } from '../../../types/flows';
17
+ import { DEFAULT_BUTTON_ID } from '../../../utils/flows';
18
+ import { trans } from '../../../translation';
19
+
20
+ interface IInputButtonFlow {
21
+ nodeData: GroupChild<'input-buttons'> | null;
22
+ onResetFlow: () => void;
23
+ onRunFlow: (payload: IRunConversationFlowPayload) => void;
24
+ }
25
+
26
+ export const InputButtonFlow = memo((props: IInputButtonFlow) => {
27
+ const { nodeData, onResetFlow, onRunFlow } = props || {};
28
+
29
+ const onPressItem = useCallback(
30
+ (item: InputButtonItem) => {
31
+ if (item.id === DEFAULT_BUTTON_ID) {
32
+ onResetFlow?.();
33
+ } else {
34
+ onRunFlow?.({
35
+ actionId: item.id,
36
+ groupId: nodeData?.parentId || '',
37
+ childId: nodeData?.id || '',
38
+ plainText: item?.label || '',
39
+ });
40
+ }
41
+ },
42
+ [onRunFlow, onResetFlow, nodeData]
43
+ );
44
+
45
+ const renderButtonItem = useCallback(
46
+ (item: InputButtonItem) => {
47
+ return (
48
+ <KContainer.Touchable
49
+ key={item.id}
50
+ style={styles.buttonItem}
51
+ onPress={() => onPressItem(item)}
52
+ >
53
+ <KLabel.Text typo="TextMdMedium">
54
+ {item?.id === DEFAULT_BUTTON_ID
55
+ ? trans('restart')
56
+ : item?.label || ''}
57
+ </KLabel.Text>
58
+ <KImage.VectorIcons
59
+ name="angle-right-o"
60
+ size={20}
61
+ color={KColors.gray.light}
62
+ />
63
+ </KContainer.Touchable>
64
+ );
65
+ },
66
+ [onPressItem]
67
+ );
68
+
69
+ if (!nodeData) return null;
70
+
71
+ return (
72
+ <KContainer.View style={messageStyles.wrapper}>
73
+ <KContainer.View style={styles.bubble}>
74
+ <KContainer.View style={styles.buttonsContainer}>
75
+ {nodeData?.data?.buttons?.map((item) => renderButtonItem(item))}
76
+ </KContainer.View>
77
+ </KContainer.View>
78
+ </KContainer.View>
79
+ );
80
+ });
81
+
82
+ InputButtonFlow.displayName = 'InputButtonFlow';
83
+
84
+ const styles = StyleSheet.create({
85
+ bubble: {
86
+ padding: KSpacingValue['0.5rem'],
87
+ borderRadius: KSpacingValue['1.25rem'],
88
+ ...messageStyles.bubbleReceived,
89
+ marginVertical: KSpacingValue['0.5rem'],
90
+ },
91
+ buttonsContainer: {
92
+ backgroundColor: KColors.white,
93
+ borderRadius: KRadiusValue['3x'],
94
+ paddingHorizontal: KSpacingValue['0.75rem'],
95
+ },
96
+ buttonItem: {
97
+ flexDirection: 'row',
98
+ justifyContent: 'space-between',
99
+ alignItems: 'center',
100
+ gap: KSpacingValue['1rem'],
101
+ borderBottomWidth: 1,
102
+ borderBottomColor: KColors.border.light,
103
+ paddingVertical: KSpacingValue['0.5rem'],
104
+ },
105
+ });
@@ -98,7 +98,7 @@ export const LinkMessage = memo(
98
98
  >
99
99
  {!!content?.trim() && (
100
100
  <KContainer.View style={styles.textRow}>
101
- {renderTextWithLinks(content, urls)}
101
+ {renderTextWithLinks(content, urls, onPressUrl)}
102
102
  </KContainer.View>
103
103
  )}
104
104
 
@@ -12,12 +12,14 @@
12
12
  export interface ApiEndpoints {
13
13
  // Chat service (relative paths - used with axios baseURL)
14
14
  urlMetadata: string;
15
+ conversationFlow: string;
15
16
  // Image upload service (relative path - full URL constructed in imageUpload service)
16
17
  imageUpload: string;
17
18
  }
18
19
 
19
20
  const DEFAULT_ENDPOINTS: ApiEndpoints = {
20
21
  urlMetadata: '/chat-service/v1/utils/url-metadata',
22
+ conversationFlow: '/chat-service/v1/app/conversations',
21
23
  imageUpload: '/uploader-service/v1/uploader/permanently',
22
24
  };
23
25
 
@@ -0,0 +1,85 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ } from 'react';
9
+ import type {
10
+ ChatDetailContextType,
11
+ ChatDetailProviderProps,
12
+ } from '../types/chat';
13
+ import { fechtCurrentConversationFlow } from '../hooks/flows/useCurrentConversationFlow';
14
+ import { useConversationStore } from '../store';
15
+ import { PeerType } from '@droppii/openim-rn-client-sdk';
16
+ import type { IConversationFlow, IConversationFlowStep } from '../types/flows';
17
+
18
+ export const ChatDetailContext = createContext<ChatDetailContextType>({
19
+ isBotConversation: false,
20
+ conversationFlow: undefined,
21
+ refetchConversationFlow: () => {},
22
+ setConverationFlowStep: () => {},
23
+ });
24
+
25
+ export const useChatDetailContext = (): ChatDetailContextType => {
26
+ const context = useContext(ChatDetailContext);
27
+ if (!context) {
28
+ throw new Error(
29
+ 'useChatDetailContext must be used within ChatDetailProvider'
30
+ );
31
+ }
32
+ return context;
33
+ };
34
+
35
+ export const ChatDetailProvider = ({ children }: ChatDetailProviderProps) => {
36
+ const conversationId = useConversationStore(
37
+ (state) => state.currentConversationId || ''
38
+ );
39
+ const conversation = useConversationStore(
40
+ (state) => state.map[conversationId]
41
+ );
42
+ const [conversationFlow, setConverationFlow] = useState<
43
+ IConversationFlow | undefined
44
+ >(undefined);
45
+
46
+ const isBotConversation = useMemo(() => {
47
+ return conversation?.peerType === PeerType.Bot;
48
+ }, [conversation]);
49
+
50
+ const setConverationFlowStep = useCallback((step: IConversationFlowStep) => {
51
+ setConverationFlow(
52
+ (prev) =>
53
+ ({
54
+ ...prev,
55
+ flowCurrentStep: step,
56
+ }) as IConversationFlow
57
+ );
58
+ }, []);
59
+
60
+ const getConversationFlow = useCallback(async () => {
61
+ const res = await fechtCurrentConversationFlow(conversationId);
62
+ if (res) {
63
+ setConverationFlow(res);
64
+ }
65
+ }, [conversationId]);
66
+
67
+ useEffect(() => {
68
+ if (!!conversationId && !!isBotConversation) {
69
+ getConversationFlow();
70
+ }
71
+ }, [conversationId, isBotConversation, getConversationFlow]);
72
+
73
+ return (
74
+ <ChatDetailContext.Provider
75
+ value={{
76
+ conversationFlow,
77
+ refetchConversationFlow: () => {},
78
+ setConverationFlowStep,
79
+ isBotConversation,
80
+ }}
81
+ >
82
+ {children}
83
+ </ChatDetailContext.Provider>
84
+ );
85
+ };
@@ -0,0 +1,11 @@
1
+ import { ENDPOINTS } from '../../services/endpoints';
2
+ import type { BaseResponse } from '../../types/auth';
3
+ import { apiInstance } from '../../services/apis';
4
+ import type { IConversationFlow } from '../../types/flows';
5
+
6
+ export const fechtCurrentConversationFlow = async (conversationId: string) => {
7
+ const res = await apiInstance?.get<BaseResponse<IConversationFlow>>(
8
+ ENDPOINTS.chatService.conversationFlow(conversationId)
9
+ );
10
+ return res?.data?.data || undefined;
11
+ };
@@ -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({
@@ -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 { DConversationItem, DMessageItem } from '../types/chat';
8
+ import type {
9
+ DChatApplicationType,
10
+ DConversationItem,
11
+ DMessageItem,
12
+ } from '../types/chat';
9
13
 
10
14
  type Params = {
11
- applicationType: string;
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: string | undefined;
30
+ let applicationType: DChatApplicationType | undefined;
27
31
  if (raw.ex) {
28
32
  try {
29
- applicationType = (JSON.parse(raw.ex) as { applicationType?: string })
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 { ...(raw as DConversationItem), lastMessage, applicationType };
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 { keepPreviousData, useQuery } from '@tanstack/react-query';
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';
@@ -19,5 +19,4 @@ export const useFetchUrlMetadata = (url?: string) =>
19
19
  : null;
20
20
  },
21
21
  enabled: !!url,
22
- placeholderData: keepPreviousData,
23
22
  });
@@ -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,
@@ -207,6 +215,15 @@ export const ChatComposer = memo(
207
215
  [setIsInputWrapped]
208
216
  );
209
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
+
210
227
  // Reset wrapped state when value is cleared
211
228
  useEffect(() => {
212
229
  if (!value) {
@@ -234,6 +251,27 @@ export const ChatComposer = memo(
234
251
  [insets.bottom, isAttachmentOpen]
235
252
  );
236
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
+ }
237
275
  return (
238
276
  <KContainer.View background={'rgba(131, 137, 157, 0.05)'}>
239
277
  <ChatQuickActions
@@ -32,6 +32,8 @@ 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';
36
+ import { normalizeUrl } from '../../utils/url';
35
37
 
36
38
  const ChatDetail = memo(
37
39
  ({
@@ -221,85 +223,97 @@ const ChatDetail = memo(
221
223
 
222
224
  const insets = useSafeAreaInsets();
223
225
 
224
- return (
225
- <KContainer.Page flex edges={['bottom']}>
226
- <ChatDetailHeader
227
- title={resolvedTitle}
228
- subtitle={resolvedSubtitle}
229
- avatarUri={resolvedAvatarUri}
230
- avatarFullName={resolvedAvatarFullName}
231
- peerType={resolvedPeerType}
232
- applicationType={resolvedApplicationType}
233
- showAddMember={resolvedShowAddMember}
234
- onBack={onBack ?? handleBackDefault}
235
- onPressSearch={
236
- onPressSearch ?? (() => console.log('[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
- onPressUrl={onPressUrl}
267
- />
226
+ const handlePressUrl = useCallback(
227
+ (url: string) => {
228
+ onPressUrl?.(normalizeUrl(url));
229
+ },
230
+ [onPressUrl]
231
+ );
268
232
 
269
- <ChatComposer
270
- value={currentInput}
271
- placeholder={inputPlaceholder}
272
- onChangeText={handleChangeText}
273
- onSend={handleSend}
274
- onMessageSent={handleAttachmentMessageSent}
275
- onPressAttach={
276
- onPressAttach ?? (() => console.log('[onPressAttach]'))
233
+ return (
234
+ <ChatDetailProvider>
235
+ <KContainer.Page flex edges={['bottom']}>
236
+ <ChatDetailHeader
237
+ title={resolvedTitle}
238
+ subtitle={resolvedSubtitle}
239
+ avatarUri={resolvedAvatarUri}
240
+ avatarFullName={resolvedAvatarFullName}
241
+ peerType={resolvedPeerType}
242
+ applicationType={resolvedApplicationType}
243
+ showAddMember={resolvedShowAddMember}
244
+ onBack={onBack ?? handleBackDefault}
245
+ onPressSearch={
246
+ onPressSearch ?? (() => console.log('[onPressSearch]'))
277
247
  }
278
- onPressEmoji={onPressEmoji ?? (() => console.log('[onPressEmoji]'))}
279
- showQuickActions={showQuickActions}
280
- quickActions={quickActions ?? quickActionsDefault}
281
- attachmentActions={attachmentActions ?? attachmentActionsDefault}
282
- onQuickActionPress={
283
- onQuickActionPress ??
284
- ((action: DChatQuickAction) => {
285
- console.log('[onQuickActionPress]', action.id, action.meta);
286
- })
248
+ onPressAddMember={
249
+ onPressAddMember ?? (() => console.log('[onPressAddMember]'))
287
250
  }
288
- onAttachmentAction={
289
- onAttachmentAction ??
290
- ((action: DChatAttachmentAction) => {
291
- console.log('[onAttachmentAction]', action.id, action.meta);
292
- })
251
+ onPressMenu={onPressMenu ?? (() => console.log('[onPressMenu]'))}
252
+ onPressAvatar={
253
+ onPressAvatar ?? (() => console.log('[onPressAvatar]'))
293
254
  }
294
- renderQuickAction={renderQuickAction}
295
- renderQuickActions={renderQuickActions}
296
- renderAttachmentAction={renderAttachmentAction}
297
- attachmentColumns={attachmentColumns}
298
255
  />
299
- </KeyboardAvoidingView>
300
256
 
301
- <MediaViewerModal ref={mediaViewerRef} />
302
- </KContainer.Page>
257
+ <KeyboardAvoidingView
258
+ style={styles.keyboardAvoiding}
259
+ behavior="padding"
260
+ keyboardVerticalOffset={Platform.select({
261
+ android: insets.bottom,
262
+ default: 0,
263
+ })}
264
+ >
265
+ <ChatListLegend
266
+ messages={messages}
267
+ currentUserId={currentUserId}
268
+ renderChat={renderChat}
269
+ onLoadEarlier={onLoadEarlier}
270
+ isLoading={isLoading}
271
+ isLoadingEarlier={isLoadingEarlier}
272
+ hasMoreEarlier={hasMoreEarlier}
273
+ onMediaPress={(items, index) => {
274
+ mediaViewerRef.current?.show(items, index);
275
+ }}
276
+ onPressUrl={handlePressUrl}
277
+ />
278
+
279
+ <ChatComposer
280
+ value={currentInput}
281
+ placeholder={inputPlaceholder}
282
+ onChangeText={handleChangeText}
283
+ onSend={handleSend}
284
+ onMessageSent={handleAttachmentMessageSent}
285
+ onPressAttach={
286
+ onPressAttach ?? (() => console.log('[onPressAttach]'))
287
+ }
288
+ onPressEmoji={
289
+ onPressEmoji ?? (() => console.log('[onPressEmoji]'))
290
+ }
291
+ showQuickActions={showQuickActions}
292
+ quickActions={quickActions ?? quickActionsDefault}
293
+ attachmentActions={attachmentActions ?? attachmentActionsDefault}
294
+ onQuickActionPress={
295
+ onQuickActionPress ??
296
+ ((action: DChatQuickAction) => {
297
+ console.log('[onQuickActionPress]', action.id, action.meta);
298
+ })
299
+ }
300
+ onAttachmentAction={
301
+ onAttachmentAction ??
302
+ ((action: DChatAttachmentAction) => {
303
+ console.log('[onAttachmentAction]', action.id, action.meta);
304
+ })
305
+ }
306
+ renderQuickAction={renderQuickAction}
307
+ renderQuickActions={renderQuickActions}
308
+ renderAttachmentAction={renderAttachmentAction}
309
+ attachmentColumns={attachmentColumns}
310
+ isEmptyMessage={messages?.length === 0}
311
+ />
312
+ </KeyboardAvoidingView>
313
+
314
+ <MediaViewerModal ref={mediaViewerRef} />
315
+ </KContainer.Page>
316
+ </ChatDetailProvider>
303
317
  );
304
318
  }
305
319
  );