@droppii-org/chat-mobile 0.2.107 → 0.2.108
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/ThreadCard.js +2 -0
- package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
- package/lib/module/components/messages/fileMessage/index.js +9 -2
- package/lib/module/components/messages/fileMessage/index.js.map +1 -1
- package/lib/module/components/messages/quotedMessage/index.js +6 -1
- package/lib/module/components/messages/quotedMessage/index.js.map +1 -1
- package/lib/module/context/TimelinePressGuardContext.js +14 -0
- package/lib/module/context/TimelinePressGuardContext.js.map +1 -0
- package/lib/module/hooks/useConversationList.js +4 -0
- package/lib/module/hooks/useConversationList.js.map +1 -1
- package/lib/module/hooks/useDismissKeyboardFirst.js +19 -0
- package/lib/module/hooks/useDismissKeyboardFirst.js.map +1 -0
- package/lib/module/hooks/useExpandableText.js +6 -1
- package/lib/module/hooks/useExpandableText.js.map +1 -1
- package/lib/module/hooks/useGuardedPress.js +19 -0
- package/lib/module/hooks/useGuardedPress.js.map +1 -0
- package/lib/module/screens/chat-detail/ChatComposer.js +89 -498
- package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +38 -20
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +113 -76
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/components/ChatComposerAccessorySlot.js +47 -0
- package/lib/module/screens/chat-detail/components/ChatComposerAccessorySlot.js.map +1 -0
- package/lib/module/screens/chat-detail/components/ChatComposerInputArea.js +149 -0
- package/lib/module/screens/chat-detail/components/ChatComposerInputArea.js.map +1 -0
- package/lib/module/screens/chat-detail/components/ChatComposerStatusBanner.js +104 -0
- package/lib/module/screens/chat-detail/components/ChatComposerStatusBanner.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js +0 -17
- package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js.map +1 -1
- package/lib/module/screens/chat-detail/hooks/useChatComposerState.js +16 -2
- package/lib/module/screens/chat-detail/hooks/useChatComposerState.js.map +1 -1
- package/lib/module/screens/chat-detail/hooks/useComposerBotMenu.js +122 -0
- package/lib/module/screens/chat-detail/hooks/useComposerBotMenu.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useComposerPanelActions.js +46 -0
- package/lib/module/screens/chat-detail/hooks/useComposerPanelActions.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useComposerPanelDismiss.js +75 -0
- package/lib/module/screens/chat-detail/hooks/useComposerPanelDismiss.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useComposerPanels.js +136 -0
- package/lib/module/screens/chat-detail/hooks/useComposerPanels.js.map +1 -0
- package/lib/module/screens/chat-detail/hooks/useComposerSend.js +74 -0
- package/lib/module/screens/chat-detail/hooks/useComposerSend.js.map +1 -0
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +2 -0
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/screens/chat-detail/types.js +5 -0
- package/lib/module/screens/chat-detail/types.js.map +1 -1
- package/lib/module/store/message.js +14 -0
- package/lib/module/store/message.js.map +1 -1
- package/lib/module/utils/composerPanelDismissGesture.js +52 -0
- package/lib/module/utils/composerPanelDismissGesture.js.map +1 -0
- package/lib/module/utils/perf-marker.js +44 -0
- package/lib/module/utils/perf-marker.js.map +1 -0
- package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/fileMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/context/TimelinePressGuardContext.d.ts +3 -0
- package/lib/typescript/src/context/TimelinePressGuardContext.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useDismissKeyboardFirst.d.ts +8 -0
- package/lib/typescript/src/hooks/useDismissKeyboardFirst.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useExpandableText.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useGuardedPress.d.ts +9 -0
- package/lib/typescript/src/hooks/useGuardedPress.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +7 -3
- package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +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 +5 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/components/ChatComposerAccessorySlot.d.ts +12 -0
- package/lib/typescript/src/screens/chat-detail/components/ChatComposerAccessorySlot.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/components/ChatComposerInputArea.d.ts +30 -0
- package/lib/typescript/src/screens/chat-detail/components/ChatComposerInputArea.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/components/ChatComposerStatusBanner.d.ts +19 -0
- package/lib/typescript/src/screens/chat-detail/components/ChatComposerStatusBanner.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts +0 -1
- package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts +7 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerState.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerBotMenu.d.ts +7 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerBotMenu.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerPanelActions.d.ts +17 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerPanelActions.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerPanelDismiss.d.ts +12 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerPanelDismiss.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerPanels.d.ts +25 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerPanels.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerSend.d.ts +16 -0
- package/lib/typescript/src/screens/chat-detail/hooks/useComposerSend.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/types.d.ts +8 -0
- package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
- package/lib/typescript/src/store/message.d.ts.map +1 -1
- package/lib/typescript/src/utils/composerPanelDismissGesture.d.ts +33 -0
- package/lib/typescript/src/utils/composerPanelDismissGesture.d.ts.map +1 -0
- package/lib/typescript/src/utils/perf-marker.d.ts +8 -0
- package/lib/typescript/src/utils/perf-marker.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/components/ThreadCard/ThreadCard.tsx +2 -0
- package/src/components/messages/fileMessage/index.tsx +17 -3
- package/src/components/messages/quotedMessage/index.tsx +5 -1
- package/src/context/TimelinePressGuardContext.tsx +15 -0
- package/src/hooks/useConversationList.ts +2 -0
- package/src/hooks/useDismissKeyboardFirst.ts +19 -0
- package/src/hooks/useExpandableText.ts +5 -1
- package/src/hooks/useGuardedPress.ts +20 -0
- package/src/screens/chat-detail/ChatComposer.tsx +105 -631
- package/src/screens/chat-detail/ChatDetail.tsx +38 -20
- package/src/screens/chat-detail/ChatListLegend.tsx +122 -71
- package/src/screens/chat-detail/components/ChatComposerAccessorySlot.tsx +61 -0
- package/src/screens/chat-detail/components/ChatComposerInputArea.tsx +198 -0
- package/src/screens/chat-detail/components/ChatComposerStatusBanner.tsx +116 -0
- package/src/screens/chat-detail/hooks/useChatComposerAnimation.ts +0 -29
- package/src/screens/chat-detail/hooks/useChatComposerState.ts +25 -2
- package/src/screens/chat-detail/hooks/useComposerBotMenu.tsx +140 -0
- package/src/screens/chat-detail/hooks/useComposerPanelActions.ts +57 -0
- package/src/screens/chat-detail/hooks/useComposerPanelDismiss.ts +111 -0
- package/src/screens/chat-detail/hooks/useComposerPanels.ts +197 -0
- package/src/screens/chat-detail/hooks/useComposerSend.ts +98 -0
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +2 -0
- package/src/screens/chat-detail/types.ts +9 -0
- package/src/store/message.ts +14 -0
- package/src/utils/composerPanelDismissGesture.ts +72 -0
- package/src/utils/perf-marker.ts +39 -0
|
@@ -1,79 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
useCallback,
|
|
4
|
-
useEffect,
|
|
5
|
-
useMemo,
|
|
6
|
-
useRef,
|
|
7
|
-
type ReactNode,
|
|
8
|
-
type RefObject,
|
|
9
|
-
} from 'react';
|
|
10
|
-
import {
|
|
11
|
-
Keyboard,
|
|
12
|
-
Platform,
|
|
13
|
-
StyleSheet,
|
|
14
|
-
type LayoutChangeEvent,
|
|
15
|
-
type View,
|
|
16
|
-
} from 'react-native';
|
|
17
|
-
import { debounce } from 'lodash';
|
|
18
|
-
import Reanimated from 'react-native-reanimated';
|
|
1
|
+
import { memo, useCallback, useMemo, type RefObject } from 'react';
|
|
2
|
+
import type { LayoutChangeEvent, View } from 'react-native';
|
|
19
3
|
import {
|
|
20
4
|
KeyboardStickyView,
|
|
21
5
|
useKeyboardState,
|
|
22
6
|
} from 'react-native-keyboard-controller';
|
|
23
7
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
const IS_IOS = Platform.OS === 'ios';
|
|
27
|
-
const INPUT_LINE_HEIGHT = 22.4;
|
|
28
|
-
const WRAPPED_HEIGHT_THRESHOLD = IS_IOS
|
|
29
|
-
? INPUT_LINE_HEIGHT + 4
|
|
30
|
-
: INPUT_LINE_HEIGHT + 12;
|
|
31
|
-
|
|
32
|
-
import { ChatQuickActions } from './ChatQuickActions';
|
|
33
|
-
import { ChatAttachmentPanel } from './ChatAttachmentPanel';
|
|
34
|
-
import { ChatLinkPreview } from './ChatLinkPreview';
|
|
35
|
-
import { ChatMessageInput } from './components/ChatMessageInput';
|
|
36
|
-
import { AttachmentPreview } from '../../components/AttachmentPreview';
|
|
37
|
-
import { StickerPicker } from '../../components/StickerPicker';
|
|
38
|
-
import type { StickerItem } from '../../types/sticker';
|
|
8
|
+
import { PeerType } from '@droppii/openim-rn-client-sdk';
|
|
39
9
|
|
|
40
|
-
import {
|
|
41
|
-
DEFAULT_QUICK_ACTIONS,
|
|
42
|
-
getAttachmentPanelHeight,
|
|
43
|
-
DEFAULT_PANNEL_ACTIONS,
|
|
44
|
-
} from './constants';
|
|
45
|
-
import { STICKER_PICKER_HEIGHT } from '../../components/StickerPicker/constants';
|
|
46
|
-
import { DChatPannelActionItemType, type DChatPannelAction } from './types';
|
|
10
|
+
import type { ComposerPanelController, DChatPannelAction } from './types';
|
|
47
11
|
import { useChatComposerState } from './hooks/useChatComposerState';
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
12
|
+
import { useComposerPanels } from './hooks/useComposerPanels';
|
|
13
|
+
import { useComposerSend } from './hooks/useComposerSend';
|
|
14
|
+
import { useComposerBotMenu } from './hooks/useComposerBotMenu';
|
|
15
|
+
import { useComposerPanelActions } from './hooks/useComposerPanelActions';
|
|
16
|
+
import { useMentionComposer } from './hooks/useMentionComposer';
|
|
51
17
|
import {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} from '
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
59
|
-
import { useAttachmentMapping } from './hooks/useAttachmentMapping';
|
|
60
|
-
import type { AttachmentType } from '../../types/attachment';
|
|
18
|
+
ChatComposerStatusBanner,
|
|
19
|
+
getComposerStatusVariant,
|
|
20
|
+
} from './components/ChatComposerStatusBanner';
|
|
21
|
+
import { ChatComposerInputArea } from './components/ChatComposerInputArea';
|
|
22
|
+
import { ChatComposerAccessorySlot } from './components/ChatComposerAccessorySlot';
|
|
23
|
+
import { useChatDetailContext } from '../../context/ChatDetailContext';
|
|
24
|
+
import { ReactFlowsNodeTypes } from '../../types/flows';
|
|
61
25
|
import { useChatCompose } from '../../hooks/useChatCompose';
|
|
62
26
|
import { useInputStateBroadcast } from '../../hooks/useInputStateBroadcast';
|
|
63
|
-
import { ChatCurrentQuotedMessage } from './ChatCurrentQuotedMessage';
|
|
64
|
-
import { useSendMessage } from '../../hooks/message/useSendMessage';
|
|
65
|
-
import { openBotMenuSheet } from './BotMenuSheet';
|
|
66
|
-
import { UIUtils } from '../../utils/UIUtils';
|
|
67
|
-
import { PeerType } from '@droppii/openim-rn-client-sdk';
|
|
68
27
|
import { useCanSendMessage } from '../../hooks/useGroupPermission';
|
|
69
28
|
import { useIsJoinedGroup } from '../../hooks/useIsJoinedGroup';
|
|
70
|
-
import { useMentionComposer } from './hooks/useMentionComposer';
|
|
71
|
-
import { MentionSuggestionList } from './components/MentionSuggestionList';
|
|
72
29
|
import { createOpenIMMentionSearchSource } from '../../services/mentionSearch';
|
|
73
|
-
import {
|
|
74
|
-
serializeMentionsForSend,
|
|
75
|
-
MAX_AT_USER_COUNT,
|
|
76
|
-
} from '../../utils/mentionSerializer';
|
|
77
30
|
|
|
78
31
|
interface ChatComposerInternalProps {
|
|
79
32
|
isEmptyMessage?: boolean;
|
|
@@ -83,6 +36,10 @@ interface ChatComposerInternalProps {
|
|
|
83
36
|
onComposerLayout?: (event: LayoutChangeEvent) => void;
|
|
84
37
|
onPressJoinGroup?: () => void;
|
|
85
38
|
isJoiningGroup?: boolean;
|
|
39
|
+
/** @internal Filled with the panel controller for tap-outside dismiss. */
|
|
40
|
+
panelControllerRef?: RefObject<ComposerPanelController | null>;
|
|
41
|
+
/** @internal Reports whether the attachment/sticker panel is open. */
|
|
42
|
+
onPanelOpenChange?: (open: boolean) => void;
|
|
86
43
|
}
|
|
87
44
|
|
|
88
45
|
export const ChatComposer = memo(
|
|
@@ -94,23 +51,16 @@ export const ChatComposer = memo(
|
|
|
94
51
|
onComposerLayout,
|
|
95
52
|
onPressJoinGroup,
|
|
96
53
|
isJoiningGroup = false,
|
|
54
|
+
panelControllerRef,
|
|
55
|
+
onPanelOpenChange,
|
|
97
56
|
}: ChatComposerInternalProps) => {
|
|
98
57
|
const insets = useSafeAreaInsets();
|
|
99
58
|
const isDisableCompose = isComposeDisabled;
|
|
100
59
|
const isKeyboardVisible = useKeyboardState((state) => state.isVisible);
|
|
101
60
|
|
|
102
|
-
const {
|
|
103
|
-
|
|
104
|
-
conversation,
|
|
105
|
-
conversationFlow,
|
|
106
|
-
conversationFlowMainMenu,
|
|
107
|
-
flowState,
|
|
108
|
-
} = useChatDetailContext();
|
|
61
|
+
const { isBotConversation, conversation, conversationFlow } =
|
|
62
|
+
useChatDetailContext();
|
|
109
63
|
const isGroupConversation = conversation?.peerType === PeerType.Group;
|
|
110
|
-
const { mutate: runFlow, isPending } = useRunFlow();
|
|
111
|
-
const { mutate: resetFlow } = useResetFlow();
|
|
112
|
-
const { mutateAsync: closeSupportSession } = useCloseSupportSession();
|
|
113
|
-
const { sendMessage } = useSendMessage();
|
|
114
64
|
const compose = useChatCompose();
|
|
115
65
|
const { notifyTyping, notifyStoppedTyping } = useInputStateBroadcast(
|
|
116
66
|
conversation?.conversationID
|
|
@@ -131,8 +81,8 @@ export const ChatComposer = memo(
|
|
|
131
81
|
setIsAttachmentOpen,
|
|
132
82
|
isInputFocused,
|
|
133
83
|
setIsInputFocused,
|
|
134
|
-
setIsInputWrapped,
|
|
135
84
|
isMultilineInput,
|
|
85
|
+
handleInputContentSizeChange,
|
|
136
86
|
inputRef,
|
|
137
87
|
} = useChatComposerState(compose.text);
|
|
138
88
|
|
|
@@ -158,176 +108,72 @@ export const ChatComposer = memo(
|
|
|
158
108
|
inputRef,
|
|
159
109
|
});
|
|
160
110
|
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
111
|
+
const isNewBotConversation =
|
|
112
|
+
isBotConversation &&
|
|
113
|
+
conversationFlow?.flowCurrentStep?.type === ReactFlowsNodeTypes.Start &&
|
|
114
|
+
isEmptyMessage;
|
|
165
115
|
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
116
|
+
const statusVariant = getComposerStatusVariant({
|
|
117
|
+
isDisableCompose,
|
|
118
|
+
isGroupConversation,
|
|
119
|
+
isNewBotConversation,
|
|
120
|
+
isNotInGroup,
|
|
121
|
+
isSendMessageAllowed,
|
|
122
|
+
});
|
|
170
123
|
|
|
171
124
|
const {
|
|
172
125
|
accessorySlotStyle,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
126
|
+
closeActivePanel,
|
|
127
|
+
yieldingCloseMode,
|
|
128
|
+
handleToggleAttachment,
|
|
129
|
+
handleToggleSticker,
|
|
130
|
+
} = useComposerPanels({
|
|
131
|
+
activePanelType,
|
|
132
|
+
setActivePanelType,
|
|
133
|
+
isAttachmentOpen,
|
|
134
|
+
setIsAttachmentOpen,
|
|
135
|
+
isMentionPanelOpen: mentionComposer.isPanelOpen,
|
|
136
|
+
closeMentionPanel: mentionComposer.closeMentionPanel,
|
|
137
|
+
isKeyboardVisible,
|
|
138
|
+
isSlotVisible:
|
|
139
|
+
!isDisableCompose && !isNewBotConversation && statusVariant === null,
|
|
140
|
+
panelControllerRef,
|
|
141
|
+
onPanelOpenChange,
|
|
179
142
|
});
|
|
180
143
|
|
|
181
|
-
|
|
182
|
-
// never renders through the attachment/sticker panel slot, but opening
|
|
183
|
-
// one must still close the other — starting a mention query while the
|
|
184
|
-
// attachment/sticker panel is open closes that panel.
|
|
185
|
-
useEffect(() => {
|
|
186
|
-
if (mentionComposer.isPanelOpen && isAttachmentOpen) {
|
|
187
|
-
const immediate = activePanelType === 'sticker';
|
|
188
|
-
closeAttachment(!immediate, immediate);
|
|
189
|
-
setActivePanelType(null);
|
|
190
|
-
}
|
|
191
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
192
|
-
}, [mentionComposer.isPanelOpen]);
|
|
193
|
-
|
|
194
|
-
const handleSend = useCallback(async () => {
|
|
195
|
-
if (!isSendMessageAllowed) {
|
|
196
|
-
UIUtils.toast.open({
|
|
197
|
-
title: sendDeniedReason,
|
|
198
|
-
theme: 'info',
|
|
199
|
-
});
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (compose.isUploading || !compose.canSend) return;
|
|
204
|
-
const mentionsToSend = mentionComposer.mentions;
|
|
205
|
-
|
|
206
|
-
// OpenIM's Go core hard-rejects an AtTextMessage tagging more than
|
|
207
|
-
// MAX_AT_USER_COUNT users, so serializeMentionsForSend caps
|
|
208
|
-
// atUserIDList to the first MAX_AT_USER_COUNT (mentionSerializer.ts's
|
|
209
|
-
// doc comment) — the message still sends, with every mention still
|
|
210
|
-
// highlighted in the text, just only the first MAX_AT_USER_COUNT
|
|
211
|
-
// actually get natively tagged/notified. This is a heads-up, not a
|
|
212
|
-
// block, so no `return` here.
|
|
213
|
-
if (mentionsToSend.length > 0) {
|
|
214
|
-
const { truncated } = serializeMentionsForSend(mentionsToSend);
|
|
215
|
-
if (truncated) {
|
|
216
|
-
UIUtils.toast.open({
|
|
217
|
-
title: trans('group:tag_limit_exceeded', {
|
|
218
|
-
max: String(MAX_AT_USER_COUNT),
|
|
219
|
-
}),
|
|
220
|
-
theme: 'info',
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Clear mention state synchronously, in the same tick as
|
|
226
|
-
// compose.send() (which clears `text`) — otherwise there's a real
|
|
227
|
-
// transient render where `value` is already '' but `mentions` still
|
|
228
|
-
// holds the just-sent entries, and the composer can visibly fail to
|
|
229
|
-
// clear (ChatMessageInput stays in children-controlled mode with a
|
|
230
|
-
// "ghost" mention anchored past the end of an empty string). See
|
|
231
|
-
// useMentionComposer.clear()'s doc comment.
|
|
232
|
-
mentionComposer.clear();
|
|
233
|
-
// Stop before awaiting the send: the peer should lose the indicator as
|
|
234
|
-
// the message goes out, not once it round-trips.
|
|
235
|
-
notifyStoppedTyping();
|
|
236
|
-
try {
|
|
237
|
-
await compose.send(mentionsToSend);
|
|
238
|
-
} catch (error) {
|
|
239
|
-
if (__DEV__) console.error('[ChatComposer] Failed to send:', error);
|
|
240
|
-
} finally {
|
|
241
|
-
// Belt-and-suspenders: force the native view to actually clear,
|
|
242
|
-
// regardless of whether the value/children mode-switch (triggered
|
|
243
|
-
// by mentions going from non-empty to empty) synced correctly on
|
|
244
|
-
// its own — the underlying nested-Text-in-TextInput technique is
|
|
245
|
-
// known to have platform quirks around this kind of transition.
|
|
246
|
-
inputRef.current?.clear();
|
|
247
|
-
}
|
|
248
|
-
// Depend on mentionComposer.mentions/.clear specifically (both
|
|
249
|
-
// stable-per-value / [] deps), not the whole mentionComposer object —
|
|
250
|
-
// same reasoning as handleToggleAttachment above.
|
|
251
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
252
|
-
}, [
|
|
144
|
+
const handleSend = useComposerSend({
|
|
253
145
|
compose,
|
|
254
146
|
isSendMessageAllowed,
|
|
255
147
|
sendDeniedReason,
|
|
256
|
-
mentionComposer.mentions,
|
|
257
|
-
mentionComposer.clear,
|
|
148
|
+
mentions: mentionComposer.mentions,
|
|
149
|
+
clearMentions: mentionComposer.clear,
|
|
258
150
|
notifyStoppedTyping,
|
|
259
151
|
inputRef,
|
|
260
|
-
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const {
|
|
155
|
+
hasBotMenuOptions,
|
|
156
|
+
handleOpenBotMenu,
|
|
157
|
+
onStartConversationFlow,
|
|
158
|
+
isRunFlowPending,
|
|
159
|
+
} = useComposerBotMenu();
|
|
160
|
+
|
|
161
|
+
const resolvedPannelActons = useComposerPanelActions({
|
|
162
|
+
pannelActions,
|
|
163
|
+
addImages: compose.addImages,
|
|
164
|
+
addFiles: compose.addFiles,
|
|
165
|
+
});
|
|
261
166
|
|
|
262
167
|
const handleInputFocus = useCallback(() => {
|
|
263
168
|
setIsInputFocused(true);
|
|
264
169
|
if (isAttachmentOpen) {
|
|
265
|
-
|
|
266
|
-
// layout conflict between the panel closing and keyboard appearing.
|
|
267
|
-
// Attachment panel is small enough that animation is fine (speedUp=true).
|
|
268
|
-
const immediate = activePanelType === 'sticker';
|
|
269
|
-
closeAttachment(!immediate, immediate);
|
|
270
|
-
setActivePanelType(null);
|
|
170
|
+
closeActivePanel(yieldingCloseMode);
|
|
271
171
|
}
|
|
272
172
|
}, [
|
|
273
|
-
|
|
173
|
+
closeActivePanel,
|
|
274
174
|
isAttachmentOpen,
|
|
275
|
-
|
|
175
|
+
yieldingCloseMode,
|
|
276
176
|
setIsInputFocused,
|
|
277
|
-
setActivePanelType,
|
|
278
|
-
]);
|
|
279
|
-
|
|
280
|
-
const handleToggleAttachment = useCallback(() => {
|
|
281
|
-
mentionComposer.closeMentionPanel();
|
|
282
|
-
if (activePanelType === 'sticker') {
|
|
283
|
-
// switch from sticker to attachment
|
|
284
|
-
setActivePanelType('attachment');
|
|
285
|
-
openWithHeight(attachmentPanelHeight);
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
setActivePanelType(isAttachmentOpen ? null : 'attachment');
|
|
289
|
-
toggleAttachmentVisibility();
|
|
290
|
-
// Deliberately depend on `mentionComposer.closeMentionPanel` (stable —
|
|
291
|
-
// [] deps in useMentionComposer), NOT the whole `mentionComposer`
|
|
292
|
-
// object, which is a fresh literal every render (useMentionComposer
|
|
293
|
-
// doesn't memoize its return value). Depending on the whole object
|
|
294
|
-
// recreated this callback — and therefore every prop built from it,
|
|
295
|
-
// e.g. ChatMessageInput's onEmojiPress — on every single render,
|
|
296
|
-
// which contributed to a real "Maximum update depth exceeded" loop
|
|
297
|
-
// found in testing. The lint rule can't prove member-access stability
|
|
298
|
-
// on its own, hence the disable.
|
|
299
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
300
|
-
}, [
|
|
301
|
-
activePanelType,
|
|
302
|
-
isAttachmentOpen,
|
|
303
|
-
attachmentPanelHeight,
|
|
304
|
-
openWithHeight,
|
|
305
|
-
toggleAttachmentVisibility,
|
|
306
|
-
setActivePanelType,
|
|
307
|
-
mentionComposer.closeMentionPanel,
|
|
308
|
-
]);
|
|
309
|
-
|
|
310
|
-
const handleToggleSticker = useCallback(() => {
|
|
311
|
-
mentionComposer.closeMentionPanel();
|
|
312
|
-
if (activePanelType === 'sticker') {
|
|
313
|
-
closeAttachment();
|
|
314
|
-
setActivePanelType(null);
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
if (isKeyboardVisible) {
|
|
318
|
-
Keyboard.dismiss();
|
|
319
|
-
}
|
|
320
|
-
setActivePanelType('sticker');
|
|
321
|
-
openWithHeight(STICKER_PICKER_HEIGHT);
|
|
322
|
-
// Same reasoning as handleToggleAttachment above — see that comment.
|
|
323
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
324
|
-
}, [
|
|
325
|
-
activePanelType,
|
|
326
|
-
isKeyboardVisible,
|
|
327
|
-
mentionComposer.closeMentionPanel,
|
|
328
|
-
closeAttachment,
|
|
329
|
-
openWithHeight,
|
|
330
|
-
setActivePanelType,
|
|
331
177
|
]);
|
|
332
178
|
|
|
333
179
|
const handleInputBlur = useCallback(() => {
|
|
@@ -346,380 +192,35 @@ export const ChatComposer = memo(
|
|
|
346
192
|
[notifyTyping, mentionComposer]
|
|
347
193
|
);
|
|
348
194
|
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (hasDefaultButton) return apiOptions;
|
|
377
|
-
|
|
378
|
-
return [
|
|
379
|
-
...apiOptions,
|
|
380
|
-
{ id: DEFAULT_BUTTON_ID, label: trans('restart') },
|
|
381
|
-
];
|
|
382
|
-
}, [conversationFlowMainMenu]);
|
|
383
|
-
|
|
384
|
-
const hasBotMenuOptions = isBotConversation && botMenuOptions.length > 0;
|
|
385
|
-
|
|
386
|
-
const runBotMenuOption = useCallback(
|
|
387
|
-
(item: InputButtonItem) => {
|
|
388
|
-
if (item.id === DEFAULT_BUTTON_ID) {
|
|
389
|
-
resetFlow();
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
runFlow({
|
|
393
|
-
actionId: item.id,
|
|
394
|
-
groupId: '',
|
|
395
|
-
childId: '',
|
|
396
|
-
plainText: item.label,
|
|
397
|
-
});
|
|
398
|
-
},
|
|
399
|
-
[runFlow, resetFlow]
|
|
400
|
-
);
|
|
401
|
-
|
|
402
|
-
const handleSelectBotMenuOption = useCallback(
|
|
403
|
-
(item: InputButtonItem) => {
|
|
404
|
-
if (flowState === DFlowState.CS) {
|
|
405
|
-
UIUtils.popup.open({
|
|
406
|
-
header: {
|
|
407
|
-
title: { text: trans('chat:end_support_session_title') },
|
|
408
|
-
alignment: 'center',
|
|
409
|
-
},
|
|
410
|
-
body: {
|
|
411
|
-
renderContent: () => (
|
|
412
|
-
<KContainer.View padding={'1rem'}>
|
|
413
|
-
<KLabel.RichText
|
|
414
|
-
typo="TextMdNormal"
|
|
415
|
-
textAlign="center"
|
|
416
|
-
color={KColors.gray.dark}
|
|
417
|
-
richTextOptions={{
|
|
418
|
-
i18nKey: trans('chat:end_support_session_desc'),
|
|
419
|
-
components: {
|
|
420
|
-
bold: (
|
|
421
|
-
<KLabel.Text
|
|
422
|
-
typo="TextMdBold"
|
|
423
|
-
color={KColors.gray.dark}
|
|
424
|
-
/>
|
|
425
|
-
),
|
|
426
|
-
},
|
|
427
|
-
}}
|
|
428
|
-
/>
|
|
429
|
-
</KContainer.View>
|
|
430
|
-
),
|
|
431
|
-
},
|
|
432
|
-
actions: {
|
|
433
|
-
buttons: [
|
|
434
|
-
{
|
|
435
|
-
label: trans('chat:end_support_session_secondary'),
|
|
436
|
-
kind: 'light',
|
|
437
|
-
onPress: () => {},
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
label: trans('chat:end_support_session_primary'),
|
|
441
|
-
kind: 'danger',
|
|
442
|
-
onPress: async () => {
|
|
443
|
-
await closeSupportSession();
|
|
444
|
-
runBotMenuOption(item);
|
|
445
|
-
},
|
|
446
|
-
},
|
|
447
|
-
],
|
|
448
|
-
type: 'horizontal-button',
|
|
449
|
-
},
|
|
450
|
-
});
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
runBotMenuOption(item);
|
|
454
|
-
},
|
|
455
|
-
[flowState, runBotMenuOption, closeSupportSession]
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
const handleOpenBotMenu = useCallback(() => {
|
|
459
|
-
if (!hasBotMenuOptions) return;
|
|
460
|
-
openBotMenuSheet({
|
|
461
|
-
options: botMenuOptions,
|
|
462
|
-
onSelect: handleSelectBotMenuOption,
|
|
463
|
-
});
|
|
464
|
-
}, [hasBotMenuOptions, botMenuOptions, handleSelectBotMenuOption]);
|
|
465
|
-
|
|
466
|
-
useEffect(() => {
|
|
467
|
-
if (!compose.text) setIsInputWrapped(false);
|
|
468
|
-
}, [compose.text, setIsInputWrapped]);
|
|
469
|
-
|
|
470
|
-
const addImagesRef = useRef(compose.addImages);
|
|
471
|
-
addImagesRef.current = compose.addImages;
|
|
472
|
-
const debouncedImagePickerRef = useRef(
|
|
473
|
-
debounce(() => addImagesRef.current?.(), 300, {
|
|
474
|
-
leading: true,
|
|
475
|
-
trailing: false,
|
|
476
|
-
})
|
|
477
|
-
);
|
|
478
|
-
|
|
479
|
-
const addFilesRef = useRef(compose.addFiles);
|
|
480
|
-
addFilesRef.current = compose.addFiles;
|
|
481
|
-
const debouncedFilePickerRef = useRef(
|
|
482
|
-
debounce(() => addFilesRef.current?.(), 300, {
|
|
483
|
-
leading: true,
|
|
484
|
-
trailing: false,
|
|
485
|
-
})
|
|
195
|
+
const content = statusVariant ? (
|
|
196
|
+
<ChatComposerStatusBanner
|
|
197
|
+
variant={statusVariant}
|
|
198
|
+
isRunFlowPending={isRunFlowPending}
|
|
199
|
+
onStartConversationFlow={onStartConversationFlow}
|
|
200
|
+
isJoiningGroup={isJoiningGroup}
|
|
201
|
+
onPressJoinGroup={onPressJoinGroup}
|
|
202
|
+
sendDeniedReason={sendDeniedReason}
|
|
203
|
+
/>
|
|
204
|
+
) : (
|
|
205
|
+
<ChatComposerInputArea
|
|
206
|
+
compose={compose}
|
|
207
|
+
mentionComposer={mentionComposer}
|
|
208
|
+
activePanelType={activePanelType}
|
|
209
|
+
isAttachmentOpen={isAttachmentOpen}
|
|
210
|
+
isInputFocused={isInputFocused}
|
|
211
|
+
isMultilineInput={isMultilineInput}
|
|
212
|
+
inputRef={inputRef}
|
|
213
|
+
onToggleAttachment={handleToggleAttachment}
|
|
214
|
+
onToggleSticker={handleToggleSticker}
|
|
215
|
+
onChangeText={handleChangeText}
|
|
216
|
+
onInputFocus={handleInputFocus}
|
|
217
|
+
onInputBlur={handleInputBlur}
|
|
218
|
+
onInputContentSizeChange={handleInputContentSizeChange}
|
|
219
|
+
onOpenBotMenu={hasBotMenuOptions ? handleOpenBotMenu : undefined}
|
|
220
|
+
onSend={handleSend}
|
|
221
|
+
/>
|
|
486
222
|
);
|
|
487
223
|
|
|
488
|
-
const resolvedPannelActons = useMemo(() => {
|
|
489
|
-
return (pannelActions || DEFAULT_PANNEL_ACTIONS)?.map((p) => {
|
|
490
|
-
let onPress;
|
|
491
|
-
switch (p.id) {
|
|
492
|
-
case DChatPannelActionItemType.Gallery:
|
|
493
|
-
onPress = debouncedImagePickerRef.current;
|
|
494
|
-
break;
|
|
495
|
-
case DChatPannelActionItemType.Documents:
|
|
496
|
-
onPress = debouncedFilePickerRef.current;
|
|
497
|
-
break;
|
|
498
|
-
default:
|
|
499
|
-
break;
|
|
500
|
-
}
|
|
501
|
-
return {
|
|
502
|
-
...p,
|
|
503
|
-
onPress: p.onPress || onPress,
|
|
504
|
-
};
|
|
505
|
-
});
|
|
506
|
-
}, [pannelActions]);
|
|
507
|
-
|
|
508
|
-
const inputRowStyle = useMemo(
|
|
509
|
-
() => ({ paddingBottom: isAttachmentOpen ? 8 : insets.bottom + 12 }),
|
|
510
|
-
[insets.bottom, isAttachmentOpen]
|
|
511
|
-
);
|
|
512
|
-
|
|
513
|
-
const handleStickerSelect = useCallback(
|
|
514
|
-
(sticker: StickerItem) => {
|
|
515
|
-
sendMessage({ sticker });
|
|
516
|
-
},
|
|
517
|
-
[sendMessage]
|
|
518
|
-
);
|
|
519
|
-
|
|
520
|
-
const attachments = useAttachmentMapping({
|
|
521
|
-
selectedImages: compose.selectedImages,
|
|
522
|
-
selectedFiles: compose.selectedFiles,
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
const onRemove = useCallback(
|
|
526
|
-
(type: AttachmentType, data: unknown) => {
|
|
527
|
-
if (type === 'file') return compose.removeFile(data as string);
|
|
528
|
-
return compose.removeImage(data as number);
|
|
529
|
-
},
|
|
530
|
-
[compose]
|
|
531
|
-
);
|
|
532
|
-
|
|
533
|
-
let content: ReactNode;
|
|
534
|
-
const isNewBotConversation =
|
|
535
|
-
isBotConversation &&
|
|
536
|
-
conversationFlow?.flowCurrentStep?.type === ReactFlowsNodeTypes.Start &&
|
|
537
|
-
isEmptyMessage;
|
|
538
|
-
|
|
539
|
-
if (isDisableCompose && !isGroupConversation) {
|
|
540
|
-
content = (
|
|
541
|
-
<KContainer.View row alignItems justifyContent>
|
|
542
|
-
<KContainer.View marginR="0.25rem">
|
|
543
|
-
<KImage.VectorIcons
|
|
544
|
-
name="info-circle-o"
|
|
545
|
-
size={16}
|
|
546
|
-
color={KColors.palette.gray.w600}
|
|
547
|
-
/>
|
|
548
|
-
</KContainer.View>
|
|
549
|
-
<KLabel.Text typo="TextXsNormal">
|
|
550
|
-
{trans('chat:compose_disabled_seller_inactive')}
|
|
551
|
-
</KLabel.Text>
|
|
552
|
-
</KContainer.View>
|
|
553
|
-
);
|
|
554
|
-
} else if (isNewBotConversation) {
|
|
555
|
-
content = (
|
|
556
|
-
<KContainer.View padding={'1.5rem'} paddingB={0}>
|
|
557
|
-
<KButton.Solid
|
|
558
|
-
size="md"
|
|
559
|
-
label={trans('start')}
|
|
560
|
-
stretch
|
|
561
|
-
icon={{ vectorName: 'chat-square-o', size: 20 }}
|
|
562
|
-
loading={isPending}
|
|
563
|
-
onPress={onStartConversationFlow}
|
|
564
|
-
/>
|
|
565
|
-
</KContainer.View>
|
|
566
|
-
);
|
|
567
|
-
} else if (isNotInGroup) {
|
|
568
|
-
content = (
|
|
569
|
-
<KContainer.View
|
|
570
|
-
paddingH="0.75rem"
|
|
571
|
-
justifyContent="center"
|
|
572
|
-
brTW={1}
|
|
573
|
-
brTC={KColors.border.light}
|
|
574
|
-
>
|
|
575
|
-
<KLabel.Text
|
|
576
|
-
typo="TextSmNormal"
|
|
577
|
-
textAlign="center"
|
|
578
|
-
paddingV={'0.75rem'}
|
|
579
|
-
>
|
|
580
|
-
{trans('group:not_in_group')}
|
|
581
|
-
</KLabel.Text>
|
|
582
|
-
<KButton.Solid
|
|
583
|
-
size="md"
|
|
584
|
-
label={trans('group:join')}
|
|
585
|
-
stretch
|
|
586
|
-
loading={isJoiningGroup}
|
|
587
|
-
onPress={onPressJoinGroup}
|
|
588
|
-
/>
|
|
589
|
-
</KContainer.View>
|
|
590
|
-
);
|
|
591
|
-
} else if (!isSendMessageAllowed) {
|
|
592
|
-
content = (
|
|
593
|
-
<KContainer.View row alignItems justifyContent paddingT={'1rem'}>
|
|
594
|
-
<KContainer.View marginR="0.25rem">
|
|
595
|
-
<KImage.VectorIcons
|
|
596
|
-
name="info-circle-o"
|
|
597
|
-
size={16}
|
|
598
|
-
color={KColors.palette.gray.w600}
|
|
599
|
-
/>
|
|
600
|
-
</KContainer.View>
|
|
601
|
-
<KLabel.Text typo="TextXsNormal">{sendDeniedReason}</KLabel.Text>
|
|
602
|
-
</KContainer.View>
|
|
603
|
-
);
|
|
604
|
-
} else {
|
|
605
|
-
content = (
|
|
606
|
-
<KContainer.View background={'#F9F9FA'}>
|
|
607
|
-
<ChatCurrentQuotedMessage />
|
|
608
|
-
<KContainer.VisibleView
|
|
609
|
-
visible={conversation?.peerType === PeerType.Bot}
|
|
610
|
-
>
|
|
611
|
-
<ChatQuickActions
|
|
612
|
-
visible
|
|
613
|
-
actions={DEFAULT_QUICK_ACTIONS}
|
|
614
|
-
onActionPress={(action) =>
|
|
615
|
-
console.log('[ChatComposer] quick action:', action.id)
|
|
616
|
-
}
|
|
617
|
-
/>
|
|
618
|
-
</KContainer.VisibleView>
|
|
619
|
-
{compose.showLinkPreview && (
|
|
620
|
-
<ChatLinkPreview
|
|
621
|
-
metadata={compose.urlMetadata}
|
|
622
|
-
isLoading={compose.isMetadataLoading}
|
|
623
|
-
onDismiss={compose.dismissPreview}
|
|
624
|
-
/>
|
|
625
|
-
)}
|
|
626
|
-
|
|
627
|
-
<AttachmentPreview
|
|
628
|
-
visible={
|
|
629
|
-
compose.isImagePreviewVisible || compose.isFilePreviewVisible
|
|
630
|
-
}
|
|
631
|
-
attachments={attachments}
|
|
632
|
-
onRemove={onRemove}
|
|
633
|
-
/>
|
|
634
|
-
|
|
635
|
-
<KContainer.View style={styles.inputRowWrapper}>
|
|
636
|
-
<KContainer.VisibleView visible={mentionComposer.isPanelOpen}>
|
|
637
|
-
<KContainer.View style={styles.mentionPanel}>
|
|
638
|
-
<MentionSuggestionList
|
|
639
|
-
suggestions={mentionComposer.suggestions}
|
|
640
|
-
isSearching={mentionComposer.isSearching}
|
|
641
|
-
isLoadingMore={mentionComposer.isLoadingMore}
|
|
642
|
-
hasMore={mentionComposer.hasMore}
|
|
643
|
-
onLoadMore={mentionComposer.loadMore}
|
|
644
|
-
onSelect={mentionComposer.selectSuggestion}
|
|
645
|
-
/>
|
|
646
|
-
</KContainer.View>
|
|
647
|
-
</KContainer.VisibleView>
|
|
648
|
-
|
|
649
|
-
<KContainer.View
|
|
650
|
-
row
|
|
651
|
-
alignItems
|
|
652
|
-
paddingH="0.75rem"
|
|
653
|
-
paddingT="0.75rem"
|
|
654
|
-
paddingB="0.75rem"
|
|
655
|
-
style={inputRowStyle}
|
|
656
|
-
>
|
|
657
|
-
<KContainer.Touchable
|
|
658
|
-
onPress={handleToggleAttachment}
|
|
659
|
-
center
|
|
660
|
-
br="round"
|
|
661
|
-
>
|
|
662
|
-
<KImage.VectorIcons
|
|
663
|
-
name={
|
|
664
|
-
activePanelType === 'attachment'
|
|
665
|
-
? 'close-circle-b'
|
|
666
|
-
: 'plus-circle-o'
|
|
667
|
-
}
|
|
668
|
-
size={24}
|
|
669
|
-
color={
|
|
670
|
-
activePanelType === 'attachment'
|
|
671
|
-
? KColors.palette.primary.w400
|
|
672
|
-
: KColors.palette.gray.w600
|
|
673
|
-
}
|
|
674
|
-
/>
|
|
675
|
-
</KContainer.Touchable>
|
|
676
|
-
|
|
677
|
-
<ChatMessageInput
|
|
678
|
-
value={compose.text}
|
|
679
|
-
placeholder="Nhập tin nhắn..."
|
|
680
|
-
onChangeText={handleChangeText}
|
|
681
|
-
onFocus={handleInputFocus}
|
|
682
|
-
onBlur={handleInputBlur}
|
|
683
|
-
onContentSizeChange={handleInputContentSizeChange}
|
|
684
|
-
onSelectionChange={mentionComposer.handleSelectionChange}
|
|
685
|
-
mentions={mentionComposer.mentions}
|
|
686
|
-
isInputFocused={isInputFocused}
|
|
687
|
-
isMultilineInput={isMultilineInput}
|
|
688
|
-
inputRef={inputRef}
|
|
689
|
-
onEmojiPress={handleToggleSticker}
|
|
690
|
-
onSuggestPress={
|
|
691
|
-
hasBotMenuOptions ? handleOpenBotMenu : undefined
|
|
692
|
-
}
|
|
693
|
-
activePanelType={activePanelType}
|
|
694
|
-
/>
|
|
695
|
-
|
|
696
|
-
<KContainer.Touchable
|
|
697
|
-
onPress={compose.isUploading ? undefined : handleSend}
|
|
698
|
-
pointerEvents={compose.isUploading ? 'none' : 'auto'}
|
|
699
|
-
width={40}
|
|
700
|
-
height={40}
|
|
701
|
-
center
|
|
702
|
-
br="round"
|
|
703
|
-
background={
|
|
704
|
-
compose.isUploading
|
|
705
|
-
? KColors.palette.primary.w100
|
|
706
|
-
: KColors.palette.primary.w400
|
|
707
|
-
}
|
|
708
|
-
marginL="0.75rem"
|
|
709
|
-
opacity={compose.isUploading ? 0.25 : 1}
|
|
710
|
-
>
|
|
711
|
-
<KImage.VectorIcons
|
|
712
|
-
name="send-b"
|
|
713
|
-
size={20}
|
|
714
|
-
color={KColors.white}
|
|
715
|
-
/>
|
|
716
|
-
</KContainer.Touchable>
|
|
717
|
-
</KContainer.View>
|
|
718
|
-
</KContainer.View>
|
|
719
|
-
</KContainer.View>
|
|
720
|
-
);
|
|
721
|
-
}
|
|
722
|
-
|
|
723
224
|
return (
|
|
724
225
|
<>
|
|
725
226
|
<KeyboardStickyView
|
|
@@ -729,43 +230,16 @@ export const ChatComposer = memo(
|
|
|
729
230
|
>
|
|
730
231
|
{content}
|
|
731
232
|
</KeyboardStickyView>
|
|
732
|
-
<
|
|
233
|
+
<ChatComposerAccessorySlot
|
|
733
234
|
visible={!isDisableCompose && !isNewBotConversation}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
{activePanelType === 'attachment' && (
|
|
740
|
-
<ChatAttachmentPanel actions={resolvedPannelActons} />
|
|
741
|
-
)}
|
|
742
|
-
{activePanelType === 'sticker' && (
|
|
743
|
-
<StickerPicker onSelectSticker={handleStickerSelect} />
|
|
744
|
-
)}
|
|
745
|
-
</Reanimated.View>
|
|
746
|
-
</KContainer.VisibleView>
|
|
235
|
+
accessorySlotStyle={accessorySlotStyle}
|
|
236
|
+
isAttachmentOpen={isAttachmentOpen}
|
|
237
|
+
activePanelType={activePanelType}
|
|
238
|
+
pannelActions={resolvedPannelActons}
|
|
239
|
+
/>
|
|
747
240
|
</>
|
|
748
241
|
);
|
|
749
242
|
}
|
|
750
243
|
);
|
|
751
244
|
|
|
752
245
|
ChatComposer.displayName = 'ChatComposer';
|
|
753
|
-
|
|
754
|
-
const styles = StyleSheet.create({
|
|
755
|
-
accessorySlot: {
|
|
756
|
-
overflow: 'hidden',
|
|
757
|
-
backgroundColor: KColors.palette.gray.w25,
|
|
758
|
-
},
|
|
759
|
-
inputRowWrapper: {
|
|
760
|
-
position: 'relative',
|
|
761
|
-
},
|
|
762
|
-
// Floats above the input row without going through the accessorySlot
|
|
763
|
-
// (attachment/sticker) render path — that slot intentionally takes over
|
|
764
|
-
// the keyboard area, which the mention picker must not do (finding #5).
|
|
765
|
-
mentionPanel: {
|
|
766
|
-
position: 'absolute',
|
|
767
|
-
left: 0,
|
|
768
|
-
right: 0,
|
|
769
|
-
bottom: '100%',
|
|
770
|
-
},
|
|
771
|
-
});
|