@droppii-org/chat-mobile 0.2.106 → 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/ExpandableMessageText.js +112 -0
- package/lib/module/components/messages/ExpandableMessageText.js.map +1 -0
- package/lib/module/components/messages/PreviewScrollableText.js +80 -0
- package/lib/module/components/messages/PreviewScrollableText.js.map +1 -0
- 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/linkMessage/index.js +21 -12
- package/lib/module/components/messages/linkMessage/index.js.map +1 -1
- package/lib/module/components/messages/quotedMessage/index.js +18 -3
- package/lib/module/components/messages/quotedMessage/index.js.map +1 -1
- package/lib/module/components/messages/textMessage/index.js +7 -1
- package/lib/module/components/messages/textMessage/index.js.map +1 -1
- package/lib/module/components/messages/types.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 +125 -0
- package/lib/module/hooks/useExpandableText.js.map +1 -0
- 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 +49 -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/PinnedMessagesSheet.js +7 -0
- package/lib/module/screens/chat-detail/PinnedMessagesSheet.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 +12 -3
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +11 -2
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js +79 -6
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js.map +1 -1
- package/lib/module/screens/chat-detail/messageToolbox/useClonePreview.js +30 -0
- package/lib/module/screens/chat-detail/messageToolbox/useClonePreview.js.map +1 -0
- 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/expandedMessage.js +28 -0
- package/lib/module/store/expandedMessage.js.map +1 -0
- 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/expandableText.js +41 -0
- package/lib/module/utils/expandableText.js.map +1 -0
- package/lib/module/utils/inlineTruncation.js +51 -0
- package/lib/module/utils/inlineTruncation.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/module/utils/truncateTextChildren.js +38 -0
- package/lib/module/utils/truncateTextChildren.js.map +1 -0
- package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/ExpandableMessageText.d.ts +25 -0
- package/lib/typescript/src/components/messages/ExpandableMessageText.d.ts.map +1 -0
- package/lib/typescript/src/components/messages/PreviewScrollableText.d.ts +29 -0
- package/lib/typescript/src/components/messages/PreviewScrollableText.d.ts.map +1 -0
- package/lib/typescript/src/components/messages/fileMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts +1 -1
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/textMessage/index.d.ts +1 -1
- package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/types.d.ts +18 -0
- package/lib/typescript/src/components/messages/types.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 +13 -0
- package/lib/typescript/src/hooks/useExpandableText.d.ts.map +1 -0
- 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/PinnedMessagesSheet.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 +11 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts +14 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/useClonePreview.d.ts +18 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/useClonePreview.d.ts.map +1 -0
- 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/expandedMessage.d.ts +15 -0
- package/lib/typescript/src/store/expandedMessage.d.ts.map +1 -0
- 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/expandableText.d.ts +18 -0
- package/lib/typescript/src/utils/expandableText.d.ts.map +1 -0
- package/lib/typescript/src/utils/inlineTruncation.d.ts +24 -0
- package/lib/typescript/src/utils/inlineTruncation.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/lib/typescript/src/utils/truncateTextChildren.d.ts +3 -0
- package/lib/typescript/src/utils/truncateTextChildren.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/components/ThreadCard/ThreadCard.tsx +2 -0
- package/src/components/messages/ExpandableMessageText.tsx +145 -0
- package/src/components/messages/PreviewScrollableText.tsx +98 -0
- package/src/components/messages/fileMessage/index.tsx +17 -3
- package/src/components/messages/linkMessage/index.tsx +30 -21
- package/src/components/messages/quotedMessage/index.tsx +19 -3
- package/src/components/messages/textMessage/index.tsx +11 -2
- package/src/components/messages/types.ts +18 -0
- 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 +143 -0
- package/src/hooks/useGuardedPress.ts +20 -0
- package/src/screens/chat-detail/ChatComposer.tsx +105 -631
- package/src/screens/chat-detail/ChatDetail.tsx +49 -20
- package/src/screens/chat-detail/ChatListLegend.tsx +122 -71
- package/src/screens/chat-detail/PinnedMessagesSheet.tsx +8 -0
- 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 +24 -2
- package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +20 -1
- package/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.tsx +111 -5
- package/src/screens/chat-detail/messageToolbox/useClonePreview.ts +34 -0
- package/src/screens/chat-detail/types.ts +9 -0
- package/src/store/expandedMessage.ts +42 -0
- package/src/store/message.ts +14 -0
- package/src/utils/composerPanelDismissGesture.ts +72 -0
- package/src/utils/expandableText.ts +51 -0
- package/src/utils/inlineTruncation.ts +65 -0
- package/src/utils/perf-marker.ts +39 -0
- package/src/utils/truncateTextChildren.ts +46 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { useCallback, useMemo, type RefObject } from 'react';
|
|
2
|
+
import { StyleSheet } from 'react-native';
|
|
3
|
+
import { KContainer, KColors, KImage } from '@droppii/libs';
|
|
4
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
|
+
import { PeerType } from '@droppii/openim-rn-client-sdk';
|
|
6
|
+
|
|
7
|
+
import { ChatQuickActions } from '../ChatQuickActions';
|
|
8
|
+
import { ChatLinkPreview } from '../ChatLinkPreview';
|
|
9
|
+
import { ChatCurrentQuotedMessage } from '../ChatCurrentQuotedMessage';
|
|
10
|
+
import { ChatMessageInput } from './ChatMessageInput';
|
|
11
|
+
import { MentionSuggestionList } from './MentionSuggestionList';
|
|
12
|
+
import { AttachmentPreview } from '../../../components/AttachmentPreview';
|
|
13
|
+
import { DEFAULT_QUICK_ACTIONS } from '../constants';
|
|
14
|
+
import { useChatDetailContext } from '../../../context/ChatDetailContext';
|
|
15
|
+
import { useAttachmentMapping } from '../hooks/useAttachmentMapping';
|
|
16
|
+
import type { ActivePanelType } from '../hooks/useChatComposerState';
|
|
17
|
+
import type { useMentionComposer } from '../hooks/useMentionComposer';
|
|
18
|
+
import type { useChatCompose } from '../../../hooks/useChatCompose';
|
|
19
|
+
import type { AttachmentType } from '../../../types/attachment';
|
|
20
|
+
|
|
21
|
+
interface ChatComposerInputAreaProps {
|
|
22
|
+
compose: ReturnType<typeof useChatCompose>;
|
|
23
|
+
mentionComposer: ReturnType<typeof useMentionComposer>;
|
|
24
|
+
activePanelType: ActivePanelType;
|
|
25
|
+
isAttachmentOpen: boolean;
|
|
26
|
+
isInputFocused: boolean;
|
|
27
|
+
isMultilineInput: boolean;
|
|
28
|
+
inputRef: RefObject<any>;
|
|
29
|
+
onToggleAttachment: () => void;
|
|
30
|
+
onToggleSticker: () => void;
|
|
31
|
+
onChangeText: (text?: string) => void;
|
|
32
|
+
onInputFocus: () => void;
|
|
33
|
+
onInputBlur: () => void;
|
|
34
|
+
onInputContentSizeChange: (event: {
|
|
35
|
+
nativeEvent: { contentSize: { height: number } };
|
|
36
|
+
}) => void;
|
|
37
|
+
onOpenBotMenu?: () => void;
|
|
38
|
+
onSend: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The composer's normal state: quoted message, previews, mention picker and
|
|
42
|
+
// the input row (attachment toggle / input / send).
|
|
43
|
+
export const ChatComposerInputArea = ({
|
|
44
|
+
compose,
|
|
45
|
+
mentionComposer,
|
|
46
|
+
activePanelType,
|
|
47
|
+
isAttachmentOpen,
|
|
48
|
+
isInputFocused,
|
|
49
|
+
isMultilineInput,
|
|
50
|
+
inputRef,
|
|
51
|
+
onToggleAttachment,
|
|
52
|
+
onToggleSticker,
|
|
53
|
+
onChangeText,
|
|
54
|
+
onInputFocus,
|
|
55
|
+
onInputBlur,
|
|
56
|
+
onInputContentSizeChange,
|
|
57
|
+
onOpenBotMenu,
|
|
58
|
+
onSend,
|
|
59
|
+
}: ChatComposerInputAreaProps) => {
|
|
60
|
+
const insets = useSafeAreaInsets();
|
|
61
|
+
const { conversation } = useChatDetailContext();
|
|
62
|
+
|
|
63
|
+
const inputRowStyle = useMemo(
|
|
64
|
+
() => ({ paddingBottom: isAttachmentOpen ? 8 : insets.bottom + 12 }),
|
|
65
|
+
[insets.bottom, isAttachmentOpen]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const attachments = useAttachmentMapping({
|
|
69
|
+
selectedImages: compose.selectedImages,
|
|
70
|
+
selectedFiles: compose.selectedFiles,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const onRemove = useCallback(
|
|
74
|
+
(type: AttachmentType, data: unknown) => {
|
|
75
|
+
if (type === 'file') return compose.removeFile(data as string);
|
|
76
|
+
return compose.removeImage(data as number);
|
|
77
|
+
},
|
|
78
|
+
[compose]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<KContainer.View background={'#F9F9FA'}>
|
|
83
|
+
<ChatCurrentQuotedMessage />
|
|
84
|
+
<KContainer.VisibleView visible={conversation?.peerType === PeerType.Bot}>
|
|
85
|
+
<ChatQuickActions
|
|
86
|
+
visible
|
|
87
|
+
actions={DEFAULT_QUICK_ACTIONS}
|
|
88
|
+
onActionPress={(action) =>
|
|
89
|
+
console.log('[ChatComposer] quick action:', action.id)
|
|
90
|
+
}
|
|
91
|
+
/>
|
|
92
|
+
</KContainer.VisibleView>
|
|
93
|
+
{compose.showLinkPreview && (
|
|
94
|
+
<ChatLinkPreview
|
|
95
|
+
metadata={compose.urlMetadata}
|
|
96
|
+
isLoading={compose.isMetadataLoading}
|
|
97
|
+
onDismiss={compose.dismissPreview}
|
|
98
|
+
/>
|
|
99
|
+
)}
|
|
100
|
+
|
|
101
|
+
<AttachmentPreview
|
|
102
|
+
visible={compose.isImagePreviewVisible || compose.isFilePreviewVisible}
|
|
103
|
+
attachments={attachments}
|
|
104
|
+
onRemove={onRemove}
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
<KContainer.View style={styles.inputRowWrapper}>
|
|
108
|
+
<KContainer.VisibleView visible={mentionComposer.isPanelOpen}>
|
|
109
|
+
<KContainer.View style={styles.mentionPanel}>
|
|
110
|
+
<MentionSuggestionList
|
|
111
|
+
suggestions={mentionComposer.suggestions}
|
|
112
|
+
isSearching={mentionComposer.isSearching}
|
|
113
|
+
isLoadingMore={mentionComposer.isLoadingMore}
|
|
114
|
+
hasMore={mentionComposer.hasMore}
|
|
115
|
+
onLoadMore={mentionComposer.loadMore}
|
|
116
|
+
onSelect={mentionComposer.selectSuggestion}
|
|
117
|
+
/>
|
|
118
|
+
</KContainer.View>
|
|
119
|
+
</KContainer.VisibleView>
|
|
120
|
+
|
|
121
|
+
<KContainer.View
|
|
122
|
+
row
|
|
123
|
+
alignItems
|
|
124
|
+
paddingH="0.75rem"
|
|
125
|
+
paddingT="0.75rem"
|
|
126
|
+
paddingB="0.75rem"
|
|
127
|
+
style={inputRowStyle}
|
|
128
|
+
>
|
|
129
|
+
<KContainer.Touchable onPress={onToggleAttachment} center br="round">
|
|
130
|
+
<KImage.VectorIcons
|
|
131
|
+
name={
|
|
132
|
+
activePanelType === 'attachment'
|
|
133
|
+
? 'close-circle-b'
|
|
134
|
+
: 'plus-circle-o'
|
|
135
|
+
}
|
|
136
|
+
size={24}
|
|
137
|
+
color={
|
|
138
|
+
activePanelType === 'attachment'
|
|
139
|
+
? KColors.palette.primary.w400
|
|
140
|
+
: KColors.palette.gray.w600
|
|
141
|
+
}
|
|
142
|
+
/>
|
|
143
|
+
</KContainer.Touchable>
|
|
144
|
+
|
|
145
|
+
<ChatMessageInput
|
|
146
|
+
value={compose.text}
|
|
147
|
+
placeholder="Nhập tin nhắn..."
|
|
148
|
+
onChangeText={onChangeText}
|
|
149
|
+
onFocus={onInputFocus}
|
|
150
|
+
onBlur={onInputBlur}
|
|
151
|
+
onContentSizeChange={onInputContentSizeChange}
|
|
152
|
+
onSelectionChange={mentionComposer.handleSelectionChange}
|
|
153
|
+
mentions={mentionComposer.mentions}
|
|
154
|
+
isInputFocused={isInputFocused}
|
|
155
|
+
isMultilineInput={isMultilineInput}
|
|
156
|
+
inputRef={inputRef}
|
|
157
|
+
onEmojiPress={onToggleSticker}
|
|
158
|
+
onSuggestPress={onOpenBotMenu}
|
|
159
|
+
activePanelType={activePanelType}
|
|
160
|
+
/>
|
|
161
|
+
|
|
162
|
+
<KContainer.Touchable
|
|
163
|
+
onPress={compose.isUploading ? undefined : onSend}
|
|
164
|
+
pointerEvents={compose.isUploading ? 'none' : 'auto'}
|
|
165
|
+
width={40}
|
|
166
|
+
height={40}
|
|
167
|
+
center
|
|
168
|
+
br="round"
|
|
169
|
+
background={
|
|
170
|
+
compose.isUploading
|
|
171
|
+
? KColors.palette.primary.w100
|
|
172
|
+
: KColors.palette.primary.w400
|
|
173
|
+
}
|
|
174
|
+
marginL="0.75rem"
|
|
175
|
+
opacity={compose.isUploading ? 0.25 : 1}
|
|
176
|
+
>
|
|
177
|
+
<KImage.VectorIcons name="send-b" size={20} color={KColors.white} />
|
|
178
|
+
</KContainer.Touchable>
|
|
179
|
+
</KContainer.View>
|
|
180
|
+
</KContainer.View>
|
|
181
|
+
</KContainer.View>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const styles = StyleSheet.create({
|
|
186
|
+
inputRowWrapper: {
|
|
187
|
+
position: 'relative',
|
|
188
|
+
},
|
|
189
|
+
// Floats above the input row without going through the accessorySlot
|
|
190
|
+
// (attachment/sticker) render path — that slot intentionally takes over
|
|
191
|
+
// the keyboard area, which the mention picker must not do (finding #5).
|
|
192
|
+
mentionPanel: {
|
|
193
|
+
position: 'absolute',
|
|
194
|
+
left: 0,
|
|
195
|
+
right: 0,
|
|
196
|
+
bottom: '100%',
|
|
197
|
+
},
|
|
198
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { KContainer, KColors, KImage, KButton, KLabel } from '@droppii/libs';
|
|
2
|
+
|
|
3
|
+
import { trans } from '../../../translation';
|
|
4
|
+
|
|
5
|
+
export type ComposerStatusVariant =
|
|
6
|
+
| 'sellerInactive'
|
|
7
|
+
| 'botStart'
|
|
8
|
+
| 'notInGroup'
|
|
9
|
+
| 'sendDenied';
|
|
10
|
+
|
|
11
|
+
// Priority order matters — mirrors the composer's original if/else chain.
|
|
12
|
+
export const getComposerStatusVariant = ({
|
|
13
|
+
isDisableCompose,
|
|
14
|
+
isGroupConversation,
|
|
15
|
+
isNewBotConversation,
|
|
16
|
+
isNotInGroup,
|
|
17
|
+
isSendMessageAllowed,
|
|
18
|
+
}: {
|
|
19
|
+
isDisableCompose: boolean;
|
|
20
|
+
isGroupConversation: boolean;
|
|
21
|
+
isNewBotConversation: boolean;
|
|
22
|
+
isNotInGroup: boolean;
|
|
23
|
+
isSendMessageAllowed: boolean;
|
|
24
|
+
}): ComposerStatusVariant | null => {
|
|
25
|
+
if (isDisableCompose && !isGroupConversation) return 'sellerInactive';
|
|
26
|
+
if (isNewBotConversation) return 'botStart';
|
|
27
|
+
if (isNotInGroup) return 'notInGroup';
|
|
28
|
+
if (!isSendMessageAllowed) return 'sendDenied';
|
|
29
|
+
return null;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
interface ChatComposerStatusBannerProps {
|
|
33
|
+
variant: ComposerStatusVariant;
|
|
34
|
+
isRunFlowPending?: boolean;
|
|
35
|
+
onStartConversationFlow?: () => void;
|
|
36
|
+
isJoiningGroup?: boolean;
|
|
37
|
+
onPressJoinGroup?: () => void;
|
|
38
|
+
sendDeniedReason?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const InfoLine = ({ text }: { text?: string }) => (
|
|
42
|
+
<>
|
|
43
|
+
<KContainer.View marginR="0.25rem">
|
|
44
|
+
<KImage.VectorIcons
|
|
45
|
+
name="info-circle-o"
|
|
46
|
+
size={16}
|
|
47
|
+
color={KColors.palette.gray.w600}
|
|
48
|
+
/>
|
|
49
|
+
</KContainer.View>
|
|
50
|
+
<KLabel.Text typo="TextXsNormal">{text}</KLabel.Text>
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// The composer's non-input states — rendered in place of the input row.
|
|
55
|
+
export const ChatComposerStatusBanner = ({
|
|
56
|
+
variant,
|
|
57
|
+
isRunFlowPending,
|
|
58
|
+
onStartConversationFlow,
|
|
59
|
+
isJoiningGroup = false,
|
|
60
|
+
onPressJoinGroup,
|
|
61
|
+
sendDeniedReason,
|
|
62
|
+
}: ChatComposerStatusBannerProps) => {
|
|
63
|
+
switch (variant) {
|
|
64
|
+
case 'sellerInactive':
|
|
65
|
+
return (
|
|
66
|
+
<KContainer.View row alignItems justifyContent>
|
|
67
|
+
<InfoLine text={trans('chat:compose_disabled_seller_inactive')} />
|
|
68
|
+
</KContainer.View>
|
|
69
|
+
);
|
|
70
|
+
case 'botStart':
|
|
71
|
+
return (
|
|
72
|
+
<KContainer.View padding={'1.5rem'} paddingB={0}>
|
|
73
|
+
<KButton.Solid
|
|
74
|
+
size="md"
|
|
75
|
+
label={trans('start')}
|
|
76
|
+
stretch
|
|
77
|
+
icon={{ vectorName: 'chat-square-o', size: 20 }}
|
|
78
|
+
loading={isRunFlowPending}
|
|
79
|
+
onPress={onStartConversationFlow}
|
|
80
|
+
/>
|
|
81
|
+
</KContainer.View>
|
|
82
|
+
);
|
|
83
|
+
case 'notInGroup':
|
|
84
|
+
return (
|
|
85
|
+
<KContainer.View
|
|
86
|
+
paddingH="0.75rem"
|
|
87
|
+
justifyContent="center"
|
|
88
|
+
brTW={1}
|
|
89
|
+
brTC={KColors.border.light}
|
|
90
|
+
>
|
|
91
|
+
<KLabel.Text
|
|
92
|
+
typo="TextSmNormal"
|
|
93
|
+
textAlign="center"
|
|
94
|
+
paddingV={'0.75rem'}
|
|
95
|
+
>
|
|
96
|
+
{trans('group:not_in_group')}
|
|
97
|
+
</KLabel.Text>
|
|
98
|
+
<KButton.Solid
|
|
99
|
+
size="md"
|
|
100
|
+
label={trans('group:join')}
|
|
101
|
+
stretch
|
|
102
|
+
loading={isJoiningGroup}
|
|
103
|
+
onPress={onPressJoinGroup}
|
|
104
|
+
/>
|
|
105
|
+
</KContainer.View>
|
|
106
|
+
);
|
|
107
|
+
case 'sendDenied':
|
|
108
|
+
return (
|
|
109
|
+
<KContainer.View row alignItems justifyContent paddingT={'1rem'}>
|
|
110
|
+
<InfoLine text={sendDeniedReason} />
|
|
111
|
+
</KContainer.View>
|
|
112
|
+
);
|
|
113
|
+
default:
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { useCallback, useEffect } from 'react';
|
|
2
|
-
import { Keyboard } from 'react-native';
|
|
3
2
|
import {
|
|
4
3
|
useAnimatedStyle,
|
|
5
4
|
useSharedValue,
|
|
6
5
|
withTiming,
|
|
7
6
|
} from 'react-native-reanimated';
|
|
8
|
-
import { useKeyboardState } from 'react-native-keyboard-controller';
|
|
9
7
|
|
|
10
8
|
const ACCESSORY_ANIMATION_MS = 250;
|
|
11
9
|
|
|
@@ -17,7 +15,6 @@ interface UseChatComposerAnimationProps {
|
|
|
17
15
|
interface UseChatComposerAnimationReturn {
|
|
18
16
|
attachmentHeightSV: any;
|
|
19
17
|
accessorySlotStyle: any;
|
|
20
|
-
toggleAttachmentVisibility: () => void;
|
|
21
18
|
openAttachment: () => void;
|
|
22
19
|
openWithHeight: (height: number) => void;
|
|
23
20
|
closeAttachment: (speedUp?: boolean, immediate?: boolean) => void;
|
|
@@ -30,8 +27,6 @@ export const useChatComposerAnimation = ({
|
|
|
30
27
|
const panelHeightSV = useSharedValue(panelHeight);
|
|
31
28
|
const attachmentHeightSV = useSharedValue(0);
|
|
32
29
|
|
|
33
|
-
const isKeyboardVisible = useKeyboardState((state) => state.isVisible);
|
|
34
|
-
|
|
35
30
|
useEffect(() => {
|
|
36
31
|
panelHeightSV.value = panelHeight;
|
|
37
32
|
}, [panelHeight, panelHeightSV]);
|
|
@@ -71,33 +66,9 @@ export const useChatComposerAnimation = ({
|
|
|
71
66
|
[attachmentHeightSV, onAttachmentOpenChange]
|
|
72
67
|
);
|
|
73
68
|
|
|
74
|
-
const toggleAttachmentVisibility = useCallback(() => {
|
|
75
|
-
// Use animated value as source of truth, not stale state
|
|
76
|
-
const isOpen = attachmentHeightSV.value > 0;
|
|
77
|
-
|
|
78
|
-
if (isOpen) {
|
|
79
|
-
closeAttachment();
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (isKeyboardVisible) {
|
|
84
|
-
Keyboard.dismiss();
|
|
85
|
-
onAttachmentOpenChange(true);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
openAttachment();
|
|
89
|
-
}, [
|
|
90
|
-
closeAttachment,
|
|
91
|
-
isKeyboardVisible,
|
|
92
|
-
onAttachmentOpenChange,
|
|
93
|
-
openAttachment,
|
|
94
|
-
attachmentHeightSV,
|
|
95
|
-
]);
|
|
96
|
-
|
|
97
69
|
return {
|
|
98
70
|
attachmentHeightSV,
|
|
99
71
|
accessorySlotStyle,
|
|
100
|
-
toggleAttachmentVisibility,
|
|
101
72
|
openAttachment,
|
|
102
73
|
openWithHeight,
|
|
103
74
|
closeAttachment,
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import { useState, useMemo, useRef } from 'react';
|
|
1
|
+
import { useState, useMemo, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const IS_IOS = Platform.OS === 'ios';
|
|
5
|
+
const INPUT_LINE_HEIGHT = 22.4;
|
|
6
|
+
const WRAPPED_HEIGHT_THRESHOLD = IS_IOS
|
|
7
|
+
? INPUT_LINE_HEIGHT + 4
|
|
8
|
+
: INPUT_LINE_HEIGHT + 12;
|
|
2
9
|
|
|
3
10
|
// 'mention' is used only for mutual-exclusion (closing attachment/sticker
|
|
4
11
|
// when the mention picker opens) — the mention picker itself renders as its
|
|
5
|
-
// own absolutely-positioned sibling in
|
|
12
|
+
// own absolutely-positioned sibling in ChatComposerInputArea, not through this
|
|
6
13
|
// state's render slot, since it must float above the input while the
|
|
7
14
|
// keyboard stays open (attachment/sticker intentionally take over the
|
|
8
15
|
// keyboard area, which the mention picker must not do).
|
|
@@ -18,6 +25,9 @@ interface UseChatComposerStateReturn {
|
|
|
18
25
|
isInputWrapped: boolean;
|
|
19
26
|
setIsInputWrapped: (wrapped: boolean) => void;
|
|
20
27
|
isMultilineInput: boolean;
|
|
28
|
+
handleInputContentSizeChange: (event: {
|
|
29
|
+
nativeEvent: { contentSize: { height: number } };
|
|
30
|
+
}) => void;
|
|
21
31
|
inputRef: React.RefObject<any>;
|
|
22
32
|
}
|
|
23
33
|
|
|
@@ -38,6 +48,18 @@ export const useChatComposerState = (
|
|
|
38
48
|
return value.includes('\n') || isInputWrapped;
|
|
39
49
|
}, [isInputWrapped, value]);
|
|
40
50
|
|
|
51
|
+
const handleInputContentSizeChange = useCallback(
|
|
52
|
+
(event: { nativeEvent: { contentSize: { height: number } } }) => {
|
|
53
|
+
const { height } = event.nativeEvent.contentSize;
|
|
54
|
+
setIsInputWrapped(height > WRAPPED_HEIGHT_THRESHOLD);
|
|
55
|
+
},
|
|
56
|
+
[]
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (!value) setIsInputWrapped(false);
|
|
61
|
+
}, [value]);
|
|
62
|
+
|
|
41
63
|
return {
|
|
42
64
|
activePanelType,
|
|
43
65
|
setActivePanelType,
|
|
@@ -48,6 +70,7 @@ export const useChatComposerState = (
|
|
|
48
70
|
isInputWrapped,
|
|
49
71
|
setIsInputWrapped,
|
|
50
72
|
isMultilineInput,
|
|
73
|
+
handleInputContentSizeChange,
|
|
51
74
|
inputRef,
|
|
52
75
|
};
|
|
53
76
|
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
import { KContainer, KColors, KLabel } from '@droppii/libs';
|
|
3
|
+
|
|
4
|
+
import { trans } from '../../../translation';
|
|
5
|
+
import { UIUtils } from '../../../utils/UIUtils';
|
|
6
|
+
import {
|
|
7
|
+
useCloseSupportSession,
|
|
8
|
+
useResetFlow,
|
|
9
|
+
useRunFlow,
|
|
10
|
+
} from '../../../hooks/flows/useFlow';
|
|
11
|
+
import type { InputButtonItem } from '../../../types/flows';
|
|
12
|
+
import { DEFAULT_BUTTON_ID, isContextButton } from '../../../utils/flows';
|
|
13
|
+
import { DFlowState } from '../../../types/chat';
|
|
14
|
+
import { useChatDetailContext } from '../../../context/ChatDetailContext';
|
|
15
|
+
import { openBotMenuSheet } from '../BotMenuSheet';
|
|
16
|
+
|
|
17
|
+
export const useComposerBotMenu = () => {
|
|
18
|
+
const {
|
|
19
|
+
isBotConversation,
|
|
20
|
+
conversationFlow,
|
|
21
|
+
conversationFlowMainMenu,
|
|
22
|
+
flowState,
|
|
23
|
+
} = useChatDetailContext();
|
|
24
|
+
const { mutate: runFlow, isPending } = useRunFlow();
|
|
25
|
+
const { mutate: resetFlow } = useResetFlow();
|
|
26
|
+
const { mutateAsync: closeSupportSession } = useCloseSupportSession();
|
|
27
|
+
|
|
28
|
+
const onStartConversationFlow = useCallback(() => {
|
|
29
|
+
runFlow({
|
|
30
|
+
groupId: conversationFlow?.flowCurrentStep?.id || '',
|
|
31
|
+
actionId: null,
|
|
32
|
+
childId: null,
|
|
33
|
+
plainText: trans('start'),
|
|
34
|
+
});
|
|
35
|
+
}, [runFlow, conversationFlow]);
|
|
36
|
+
|
|
37
|
+
const botMenuOptions = useMemo(() => {
|
|
38
|
+
const apiOptions =
|
|
39
|
+
conversationFlowMainMenu?.filter(
|
|
40
|
+
(item) => !isContextButton(item.label)
|
|
41
|
+
) || [];
|
|
42
|
+
|
|
43
|
+
const hasDefaultButton = apiOptions.some(
|
|
44
|
+
(item) => item.id === DEFAULT_BUTTON_ID
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (hasDefaultButton) return apiOptions;
|
|
48
|
+
|
|
49
|
+
return [...apiOptions, { id: DEFAULT_BUTTON_ID, label: trans('restart') }];
|
|
50
|
+
}, [conversationFlowMainMenu]);
|
|
51
|
+
|
|
52
|
+
const hasBotMenuOptions = isBotConversation && botMenuOptions.length > 0;
|
|
53
|
+
|
|
54
|
+
const runBotMenuOption = useCallback(
|
|
55
|
+
(item: InputButtonItem) => {
|
|
56
|
+
if (item.id === DEFAULT_BUTTON_ID) {
|
|
57
|
+
resetFlow();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
runFlow({
|
|
61
|
+
actionId: item.id,
|
|
62
|
+
groupId: '',
|
|
63
|
+
childId: '',
|
|
64
|
+
plainText: item.label,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
[runFlow, resetFlow]
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const handleSelectBotMenuOption = useCallback(
|
|
71
|
+
(item: InputButtonItem) => {
|
|
72
|
+
if (flowState === DFlowState.CS) {
|
|
73
|
+
UIUtils.popup.open({
|
|
74
|
+
header: {
|
|
75
|
+
title: { text: trans('chat:end_support_session_title') },
|
|
76
|
+
alignment: 'center',
|
|
77
|
+
},
|
|
78
|
+
body: {
|
|
79
|
+
renderContent: () => (
|
|
80
|
+
<KContainer.View padding={'1rem'}>
|
|
81
|
+
<KLabel.RichText
|
|
82
|
+
typo="TextMdNormal"
|
|
83
|
+
textAlign="center"
|
|
84
|
+
color={KColors.gray.dark}
|
|
85
|
+
richTextOptions={{
|
|
86
|
+
i18nKey: trans('chat:end_support_session_desc'),
|
|
87
|
+
components: {
|
|
88
|
+
bold: (
|
|
89
|
+
<KLabel.Text
|
|
90
|
+
typo="TextMdBold"
|
|
91
|
+
color={KColors.gray.dark}
|
|
92
|
+
/>
|
|
93
|
+
),
|
|
94
|
+
},
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
</KContainer.View>
|
|
98
|
+
),
|
|
99
|
+
},
|
|
100
|
+
actions: {
|
|
101
|
+
buttons: [
|
|
102
|
+
{
|
|
103
|
+
label: trans('chat:end_support_session_secondary'),
|
|
104
|
+
kind: 'light',
|
|
105
|
+
onPress: () => {},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
label: trans('chat:end_support_session_primary'),
|
|
109
|
+
kind: 'danger',
|
|
110
|
+
onPress: async () => {
|
|
111
|
+
await closeSupportSession();
|
|
112
|
+
runBotMenuOption(item);
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
type: 'horizontal-button',
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
runBotMenuOption(item);
|
|
122
|
+
},
|
|
123
|
+
[flowState, runBotMenuOption, closeSupportSession]
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const handleOpenBotMenu = useCallback(() => {
|
|
127
|
+
if (!hasBotMenuOptions) return;
|
|
128
|
+
openBotMenuSheet({
|
|
129
|
+
options: botMenuOptions,
|
|
130
|
+
onSelect: handleSelectBotMenuOption,
|
|
131
|
+
});
|
|
132
|
+
}, [hasBotMenuOptions, botMenuOptions, handleSelectBotMenuOption]);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
hasBotMenuOptions,
|
|
136
|
+
handleOpenBotMenu,
|
|
137
|
+
onStartConversationFlow,
|
|
138
|
+
isRunFlowPending: isPending,
|
|
139
|
+
};
|
|
140
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useMemo, useRef } from 'react';
|
|
2
|
+
import { debounce } from 'lodash';
|
|
3
|
+
|
|
4
|
+
import { DEFAULT_PANNEL_ACTIONS } from '../constants';
|
|
5
|
+
import { DChatPannelActionItemType, type DChatPannelAction } from '../types';
|
|
6
|
+
|
|
7
|
+
interface UseComposerPanelActionsProps {
|
|
8
|
+
pannelActions?: DChatPannelAction[];
|
|
9
|
+
addImages?: () => void;
|
|
10
|
+
addFiles?: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Resolves the attachment panel's actions, wiring Gallery/Documents to
|
|
14
|
+
// debounced pickers unless the consumer supplied its own onPress.
|
|
15
|
+
export const useComposerPanelActions = ({
|
|
16
|
+
pannelActions,
|
|
17
|
+
addImages,
|
|
18
|
+
addFiles,
|
|
19
|
+
}: UseComposerPanelActionsProps) => {
|
|
20
|
+
const addImagesRef = useRef(addImages);
|
|
21
|
+
addImagesRef.current = addImages;
|
|
22
|
+
const debouncedImagePickerRef = useRef(
|
|
23
|
+
debounce(() => addImagesRef.current?.(), 300, {
|
|
24
|
+
leading: true,
|
|
25
|
+
trailing: false,
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const addFilesRef = useRef(addFiles);
|
|
30
|
+
addFilesRef.current = addFiles;
|
|
31
|
+
const debouncedFilePickerRef = useRef(
|
|
32
|
+
debounce(() => addFilesRef.current?.(), 300, {
|
|
33
|
+
leading: true,
|
|
34
|
+
trailing: false,
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return useMemo(() => {
|
|
39
|
+
return (pannelActions || DEFAULT_PANNEL_ACTIONS)?.map((p) => {
|
|
40
|
+
let onPress;
|
|
41
|
+
switch (p.id) {
|
|
42
|
+
case DChatPannelActionItemType.Gallery:
|
|
43
|
+
onPress = debouncedImagePickerRef.current;
|
|
44
|
+
break;
|
|
45
|
+
case DChatPannelActionItemType.Documents:
|
|
46
|
+
onPress = debouncedFilePickerRef.current;
|
|
47
|
+
break;
|
|
48
|
+
default:
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
...p,
|
|
53
|
+
onPress: p.onPress || onPress,
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}, [pannelActions]);
|
|
57
|
+
};
|