@droppii-org/chat-mobile 0.2.38 → 0.2.40
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 -1
- package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
- package/lib/module/components/messages/quotedMessage/index.js +7 -3
- package/lib/module/components/messages/quotedMessage/index.js.map +1 -1
- package/lib/module/core/useChatListener.js +15 -5
- package/lib/module/core/useChatListener.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatCurrentQuotedMessage.js +4 -2
- package/lib/module/screens/chat-detail/ChatCurrentQuotedMessage.js.map +1 -1
- package/lib/module/screens/chat-detail/PinnedMessageBar.js +8 -3
- package/lib/module/screens/chat-detail/PinnedMessageBar.js.map +1 -1
- package/lib/module/screens/chat-detail/PinnedMessagesSheet.js +80 -110
- package/lib/module/screens/chat-detail/PinnedMessagesSheet.js.map +1 -1
- package/lib/module/screens/chat-detail/PinnedMessagesSheetBackup.js +179 -0
- package/lib/module/screens/chat-detail/PinnedMessagesSheetBackup.js.map +1 -0
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +18 -6
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +3 -2
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/module/utils/UIUtils.js +4 -2
- package/lib/module/utils/UIUtils.js.map +1 -1
- package/lib/module/utils/messageUtils.js +29 -5
- package/lib/module/utils/messageUtils.js.map +1 -1
- package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatCurrentQuotedMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/PinnedMessageBar.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/PinnedMessageBar.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/PinnedMessagesSheet.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/PinnedMessagesSheet.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/PinnedMessagesSheetBackup.d.ts +9 -0
- package/lib/typescript/src/screens/chat-detail/PinnedMessagesSheetBackup.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +2 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
- package/lib/typescript/src/types/chat.d.ts +1 -0
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/utils/UIUtils.d.ts.map +1 -1
- package/lib/typescript/src/utils/messageUtils.d.ts +7 -0
- package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/ThreadCard/ThreadCard.tsx +5 -1
- package/src/components/messages/quotedMessage/index.tsx +7 -3
- package/src/core/useChatListener.ts +29 -6
- package/src/screens/chat-detail/ChatCurrentQuotedMessage.tsx +5 -2
- package/src/screens/chat-detail/PinnedMessageBar.tsx +19 -5
- package/src/screens/chat-detail/PinnedMessagesSheet.tsx +118 -153
- package/src/screens/chat-detail/PinnedMessagesSheetBackup.tsx +253 -0
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +26 -13
- package/src/translation/resources/i18n.ts +4 -2
- package/src/types/chat.ts +1 -1
- package/src/utils/UIUtils.ts +3 -1
- package/src/utils/messageUtils.ts +39 -6
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
memo,
|
|
4
|
+
useCallback,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import type { ReactNode } from 'react';
|
|
10
|
+
import { Modal, Pressable, StyleSheet, type View } from 'react-native';
|
|
11
|
+
import type { MessageItem } from '@droppii/openim-rn-client-sdk';
|
|
12
|
+
import { KContainer, KLabel, KColors } from '@droppii/libs';
|
|
13
|
+
import { UIUtils } from '../../utils/UIUtils';
|
|
14
|
+
import { trans } from '../../translation';
|
|
15
|
+
import { usePinnedMessageStore } from '../../store/pinnedMessage';
|
|
16
|
+
import { LegendChatMessage } from './legend/LegendChatMessage';
|
|
17
|
+
import { formatMessageTime, messageStyles } from './legend/messageTypes';
|
|
18
|
+
import { MessageToolboxWrapper } from './messageToolbox/MessageToolboxWrapper';
|
|
19
|
+
import { MessageToolboxMenu } from './messageToolbox/MessageToolboxMenu';
|
|
20
|
+
import type { MeasuredRect } from './messageToolbox/types';
|
|
21
|
+
|
|
22
|
+
interface PinnedMessageToolboxRef {
|
|
23
|
+
show: (message: MessageItem, rect: MeasuredRect) => void;
|
|
24
|
+
hide: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface PinnedMessageToolboxOverlayProps {
|
|
28
|
+
currentUserId?: string;
|
|
29
|
+
unpinMessage: (message: MessageItem) => void | Promise<void>;
|
|
30
|
+
customMessageItemFactory?: (item: MessageItem) => ReactNode;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Mirrors MessageToolboxOverlay (used for the main chat list's long-press
|
|
34
|
+
// menu) but restricted to the 2 actions relevant to an already-pinned
|
|
35
|
+
// message: unpin and jump to the original message.
|
|
36
|
+
const PinnedMessageToolboxOverlay = memo(
|
|
37
|
+
forwardRef<PinnedMessageToolboxRef, PinnedMessageToolboxOverlayProps>(
|
|
38
|
+
({ currentUserId, unpinMessage, customMessageItemFactory }, ref) => {
|
|
39
|
+
const [visible, setVisible] = useState(false);
|
|
40
|
+
const [message, setMessage] = useState<MessageItem | undefined>(
|
|
41
|
+
undefined
|
|
42
|
+
);
|
|
43
|
+
const [rect, setRect] = useState<MeasuredRect | null>(null);
|
|
44
|
+
|
|
45
|
+
const hide = useCallback(() => {
|
|
46
|
+
setVisible(false);
|
|
47
|
+
setMessage(undefined);
|
|
48
|
+
setRect(null);
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const show = useCallback((msg: MessageItem, msgRect: MeasuredRect) => {
|
|
52
|
+
setMessage(msg);
|
|
53
|
+
setRect(msgRect);
|
|
54
|
+
setVisible(true);
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
useImperativeHandle(ref, () => ({ show, hide }), [show, hide]);
|
|
58
|
+
|
|
59
|
+
const handleUnpinMessage = useCallback(() => {
|
|
60
|
+
if (!message) return;
|
|
61
|
+
hide();
|
|
62
|
+
unpinMessage(message);
|
|
63
|
+
}, [message, hide, unpinMessage]);
|
|
64
|
+
|
|
65
|
+
const handleViewOriginalMessage = useCallback(() => {
|
|
66
|
+
if (!message) return;
|
|
67
|
+
hide();
|
|
68
|
+
// TODO: implement navigating to / highlighting the original message
|
|
69
|
+
}, [message, hide]);
|
|
70
|
+
|
|
71
|
+
if (!visible || !message || !rect) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const isOutgoing = message.sendID === currentUserId;
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<Modal
|
|
79
|
+
visible={visible}
|
|
80
|
+
transparent
|
|
81
|
+
statusBarTranslucent
|
|
82
|
+
animationType="fade"
|
|
83
|
+
onRequestClose={hide}
|
|
84
|
+
>
|
|
85
|
+
<Pressable style={styles.backdrop} onPress={hide} />
|
|
86
|
+
|
|
87
|
+
<MessageToolboxWrapper
|
|
88
|
+
rect={rect}
|
|
89
|
+
isOutgoing={isOutgoing}
|
|
90
|
+
message={
|
|
91
|
+
<LegendChatMessage
|
|
92
|
+
message={message}
|
|
93
|
+
isOutgoing={isOutgoing}
|
|
94
|
+
createdAtTime={message.sendTime || message.createTime}
|
|
95
|
+
customMessageItemFactory={customMessageItemFactory}
|
|
96
|
+
/>
|
|
97
|
+
}
|
|
98
|
+
toolbox={
|
|
99
|
+
<MessageToolboxMenu
|
|
100
|
+
showUnpinMessage
|
|
101
|
+
showViewOriginalMessage
|
|
102
|
+
onUnpinMessage={handleUnpinMessage}
|
|
103
|
+
onViewOriginalMessage={handleViewOriginalMessage}
|
|
104
|
+
/>
|
|
105
|
+
}
|
|
106
|
+
/>
|
|
107
|
+
</Modal>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
PinnedMessageToolboxOverlay.displayName = 'PinnedMessageToolboxOverlay';
|
|
114
|
+
|
|
115
|
+
const styles = StyleSheet.create({
|
|
116
|
+
backdrop: {
|
|
117
|
+
position: 'absolute',
|
|
118
|
+
top: 0,
|
|
119
|
+
left: 0,
|
|
120
|
+
right: 0,
|
|
121
|
+
bottom: 0,
|
|
122
|
+
backgroundColor: 'rgba(0,0,0,0.75)',
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
interface PinnedMessagesSheetContentProps {
|
|
127
|
+
currentUserId?: string;
|
|
128
|
+
unpinMessage: (message: MessageItem) => void | Promise<void>;
|
|
129
|
+
customMessageItemFactory?: (item: MessageItem) => ReactNode;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const PinnedMessagesSheetContent = memo(
|
|
133
|
+
({
|
|
134
|
+
currentUserId,
|
|
135
|
+
unpinMessage,
|
|
136
|
+
customMessageItemFactory,
|
|
137
|
+
}: PinnedMessagesSheetContentProps) => {
|
|
138
|
+
const messages = usePinnedMessageStore((state) => state.messages);
|
|
139
|
+
const toolboxRef = useRef<PinnedMessageToolboxRef>(null);
|
|
140
|
+
const rowRefsMap = useRef<Map<string, View | null>>(new Map());
|
|
141
|
+
|
|
142
|
+
const handleUnpinMessage = useCallback(
|
|
143
|
+
async (message: MessageItem) => {
|
|
144
|
+
await unpinMessage(message);
|
|
145
|
+
if (usePinnedMessageStore.getState().messages.length === 0) {
|
|
146
|
+
UIUtils.bottomSheet.dismiss();
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
[unpinMessage]
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const handleLongPress = useCallback(
|
|
153
|
+
(messageId: string, message: MessageItem) => {
|
|
154
|
+
const node = rowRefsMap.current.get(messageId);
|
|
155
|
+
node?.measureInWindow((x, y, width, height) => {
|
|
156
|
+
toolboxRef.current?.show(message, { x, y, width, height });
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
[]
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<KContainer.View paddingV="0.5rem">
|
|
164
|
+
<KLabel.Text
|
|
165
|
+
typo="TextNmNormal"
|
|
166
|
+
color={KColors.gray.normal}
|
|
167
|
+
paddingH="0.75rem"
|
|
168
|
+
marginB="0.75rem"
|
|
169
|
+
>
|
|
170
|
+
{trans('chat:pinned_messages_hint')}
|
|
171
|
+
</KLabel.Text>
|
|
172
|
+
|
|
173
|
+
{messages.map((message) => {
|
|
174
|
+
const isOutgoing = message.sendID === currentUserId;
|
|
175
|
+
const messageId = message.clientMsgID || message.serverMsgID;
|
|
176
|
+
const createdAtTime = message.sendTime || message.createTime;
|
|
177
|
+
const timeLabel = formatMessageTime(createdAtTime);
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<KContainer.View
|
|
181
|
+
key={messageId}
|
|
182
|
+
ref={(node: View | null) => {
|
|
183
|
+
if (node) rowRefsMap.current.set(messageId, node);
|
|
184
|
+
else rowRefsMap.current.delete(messageId);
|
|
185
|
+
}}
|
|
186
|
+
style={
|
|
187
|
+
isOutgoing
|
|
188
|
+
? messageStyles.messageRowRight
|
|
189
|
+
: messageStyles.messageRowLeft
|
|
190
|
+
}
|
|
191
|
+
>
|
|
192
|
+
<LegendChatMessage
|
|
193
|
+
message={message}
|
|
194
|
+
isOutgoing={isOutgoing}
|
|
195
|
+
createdAtTime={createdAtTime}
|
|
196
|
+
onLongPress={() => handleLongPress(messageId, message)}
|
|
197
|
+
customMessageItemFactory={customMessageItemFactory}
|
|
198
|
+
/>
|
|
199
|
+
{!!timeLabel && (
|
|
200
|
+
<KLabel.Text
|
|
201
|
+
typo="TextXsNormal"
|
|
202
|
+
color={KColors.gray.light}
|
|
203
|
+
marginT="0.25rem"
|
|
204
|
+
>
|
|
205
|
+
{timeLabel}
|
|
206
|
+
</KLabel.Text>
|
|
207
|
+
)}
|
|
208
|
+
</KContainer.View>
|
|
209
|
+
);
|
|
210
|
+
})}
|
|
211
|
+
|
|
212
|
+
<PinnedMessageToolboxOverlay
|
|
213
|
+
ref={toolboxRef}
|
|
214
|
+
currentUserId={currentUserId}
|
|
215
|
+
unpinMessage={handleUnpinMessage}
|
|
216
|
+
customMessageItemFactory={customMessageItemFactory}
|
|
217
|
+
/>
|
|
218
|
+
</KContainer.View>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
PinnedMessagesSheetContent.displayName = 'PinnedMessagesSheetContent';
|
|
224
|
+
|
|
225
|
+
export interface OpenPinnedMessagesSheetParams {
|
|
226
|
+
currentUserId?: string;
|
|
227
|
+
unpinMessage: (message: MessageItem) => void | Promise<void>;
|
|
228
|
+
customMessageItemFactory?: (item: MessageItem) => ReactNode;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export const openPinnedMessagesSheet = ({
|
|
232
|
+
currentUserId,
|
|
233
|
+
unpinMessage,
|
|
234
|
+
customMessageItemFactory,
|
|
235
|
+
}: OpenPinnedMessagesSheetParams) => {
|
|
236
|
+
UIUtils.bottomSheet.open({
|
|
237
|
+
header: {
|
|
238
|
+
title: { text: trans('chat:pinned_messages_title') },
|
|
239
|
+
alignment: 'left',
|
|
240
|
+
showCloseButton: true,
|
|
241
|
+
},
|
|
242
|
+
touchOutsideToDismiss: true,
|
|
243
|
+
body: {
|
|
244
|
+
renderContent: () => (
|
|
245
|
+
<PinnedMessagesSheetContent
|
|
246
|
+
currentUserId={currentUserId}
|
|
247
|
+
unpinMessage={unpinMessage}
|
|
248
|
+
customMessageItemFactory={customMessageItemFactory}
|
|
249
|
+
/>
|
|
250
|
+
),
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
};
|
|
@@ -31,6 +31,7 @@ interface LegendChatMessageProps {
|
|
|
31
31
|
onMediaPress?: (items: MediaItem[], index?: number) => void;
|
|
32
32
|
onPressUrl?: (url: string) => void;
|
|
33
33
|
onRetry?: (message: MessageItem) => void;
|
|
34
|
+
onPress?: () => void;
|
|
34
35
|
onLongPress?: () => void;
|
|
35
36
|
customMessageItemFactory?: (m: MessageItem) => ReactNode;
|
|
36
37
|
}
|
|
@@ -66,6 +67,7 @@ export const LegendChatMessage = memo(
|
|
|
66
67
|
onMediaPress,
|
|
67
68
|
onPressUrl,
|
|
68
69
|
onRetry,
|
|
70
|
+
onPress,
|
|
69
71
|
onLongPress,
|
|
70
72
|
customMessageItemFactory,
|
|
71
73
|
}: LegendChatMessageProps) => {
|
|
@@ -91,7 +93,7 @@ export const LegendChatMessage = memo(
|
|
|
91
93
|
onRetry={onRetry}
|
|
92
94
|
/>
|
|
93
95
|
<KContainer.View style={styles.content}>
|
|
94
|
-
<LongPressWrapper onLongPress={onLongPress}>
|
|
96
|
+
<LongPressWrapper onPress={onPress} onLongPress={onLongPress}>
|
|
95
97
|
<MessageComponent
|
|
96
98
|
message={message}
|
|
97
99
|
isOutgoing={isOutgoing}
|
|
@@ -139,29 +141,40 @@ const styles = StyleSheet.create({
|
|
|
139
141
|
elevation: 3,
|
|
140
142
|
borderWidth: 1,
|
|
141
143
|
borderColor: KColors.white,
|
|
144
|
+
zIndex: Number.MAX_SAFE_INTEGER,
|
|
142
145
|
},
|
|
143
146
|
});
|
|
144
147
|
|
|
145
148
|
const LongPressWrapper = ({
|
|
146
149
|
children,
|
|
150
|
+
onPress,
|
|
147
151
|
onLongPress,
|
|
148
|
-
}: PropsWithChildren<{ onLongPress?: () => void }>) => {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
onLongPress
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
152
|
+
}: PropsWithChildren<{ onPress?: () => void; onLongPress?: () => void }>) => {
|
|
153
|
+
const gesture = useMemo(() => {
|
|
154
|
+
const gestures = [
|
|
155
|
+
onLongPress &&
|
|
156
|
+
Gesture.LongPress().onStart(() => {
|
|
157
|
+
runOnJS(onLongPress)();
|
|
158
|
+
}),
|
|
159
|
+
onPress &&
|
|
160
|
+
Gesture.Tap().onStart(() => {
|
|
161
|
+
runOnJS(onPress)();
|
|
162
|
+
}),
|
|
163
|
+
].filter(Boolean) as ReturnType<typeof Gesture.Tap>[];
|
|
164
|
+
|
|
165
|
+
if (!gestures.length) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return gestures.length > 1 ? Gesture.Race(...gestures) : gestures[0];
|
|
170
|
+
}, [onPress, onLongPress]);
|
|
158
171
|
|
|
159
|
-
if (!
|
|
172
|
+
if (!gesture) {
|
|
160
173
|
return children;
|
|
161
174
|
}
|
|
162
175
|
|
|
163
176
|
return (
|
|
164
|
-
<GestureDetector gesture={
|
|
177
|
+
<GestureDetector gesture={gesture}>
|
|
165
178
|
<KContainer.View>{children}</KContainer.View>
|
|
166
179
|
</GestureDetector>
|
|
167
180
|
);
|
|
@@ -43,15 +43,17 @@ const i18nKey: Record<string, string> = {
|
|
|
43
43
|
'chat:pinned_image_subtitle': 'Hình ảnh từ {{sender}}',
|
|
44
44
|
'chat:pinned_video_subtitle': 'Video từ {{sender}}',
|
|
45
45
|
'chat:pinned_file_subtitle': 'Tài liệu từ {{sender}}',
|
|
46
|
-
'chat:pinned_link_subtitle': '
|
|
46
|
+
'chat:pinned_link_subtitle': 'Từ {{sender}}',
|
|
47
47
|
'chat:pin_limit_title': 'Không thể ghim thêm tin nhắn',
|
|
48
48
|
'chat:pin_limit_desc':
|
|
49
49
|
'Chỉ được ghim tối đa 5 tin nhắn. Vui lòng bỏ ghim một tin nhắn hiện có trước khi ghim tin nhắn mới.',
|
|
50
50
|
'chat:pin_limit_primary': 'Đã hiểu',
|
|
51
51
|
'chat:pinned_messages_title': 'Tin nhắn đã ghim',
|
|
52
52
|
'chat:pinned_messages_hint':
|
|
53
|
-
'Nhấn
|
|
53
|
+
'Nhấn vào tin nhắn để xem tin nhắn gốc, nhấn vào biểu tượng để bỏ ghim',
|
|
54
54
|
'chat:view_original_message': 'Xem tin nhắn gốc',
|
|
55
|
+
'chat:scroll_to_message_todo':
|
|
56
|
+
'Tính năng di chuyển đến tin nhắn gốc sẽ sớm được cập nhật',
|
|
55
57
|
'msg_type_image': '[Hình ảnh]',
|
|
56
58
|
'msg_type_voice': '[Tin nhắn thoại]',
|
|
57
59
|
'msg_type_video': '[Video]',
|
package/src/types/chat.ts
CHANGED
|
@@ -28,7 +28,7 @@ export interface ChatUIAdapter {
|
|
|
28
28
|
toast?: { open: (params: any) => void };
|
|
29
29
|
alert?: { open: (params: any) => void };
|
|
30
30
|
popup?: { open: (params: any) => void };
|
|
31
|
-
bottomSheet?: { open: (params: any) => void };
|
|
31
|
+
bottomSheet?: { open: (params: any) => void; dismiss: () => void };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export interface ChatContextType {
|
package/src/utils/UIUtils.ts
CHANGED
|
@@ -10,7 +10,7 @@ const defaultAdapter: Required<ChatUIAdapter> = {
|
|
|
10
10
|
toast: { open: openWithAlert },
|
|
11
11
|
alert: { open: openWithAlert },
|
|
12
12
|
popup: { open: openWithAlert },
|
|
13
|
-
bottomSheet: { open: openWithAlert },
|
|
13
|
+
bottomSheet: { open: openWithAlert, dismiss: () => {} },
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
let _adapter: ChatUIAdapter = {};
|
|
@@ -32,5 +32,7 @@ export const UIUtils: Required<ChatUIAdapter> = {
|
|
|
32
32
|
bottomSheet: {
|
|
33
33
|
open: (params) =>
|
|
34
34
|
(_adapter.bottomSheet ?? defaultAdapter.bottomSheet).open(params),
|
|
35
|
+
dismiss: () =>
|
|
36
|
+
(_adapter.bottomSheet ?? defaultAdapter.bottomSheet).dismiss(),
|
|
35
37
|
},
|
|
36
38
|
};
|
|
@@ -12,6 +12,7 @@ import { formatDuration } from './dateTimeUtils';
|
|
|
12
12
|
import { getFileIcon, getMimeType } from './imageUtils';
|
|
13
13
|
import { trans } from '../translation';
|
|
14
14
|
import type { IUrlMetadata } from '../types/common';
|
|
15
|
+
import type { TextProps } from 'react-native';
|
|
15
16
|
|
|
16
17
|
export const isMessageRevoked = (message: MessageItem): boolean =>
|
|
17
18
|
message.contentType === MessageType.RevokeMessage;
|
|
@@ -46,14 +47,13 @@ export const getMessagePreview = (message?: MessageItem): string => {
|
|
|
46
47
|
return '[Video]';
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
case MessageType.FileMessage:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const fileName = fileData.fileName || fileData.name || 'File';
|
|
50
|
+
case MessageType.FileMessage: {
|
|
51
|
+
const fileName = message?.fileElem?.fileName;
|
|
52
|
+
if (fileName) {
|
|
53
53
|
return `[File] ${fileName}`;
|
|
54
|
-
} catch {
|
|
55
|
-
return '[File]';
|
|
56
54
|
}
|
|
55
|
+
return '[File]';
|
|
56
|
+
}
|
|
57
57
|
|
|
58
58
|
case MessageType.MergeMessage:
|
|
59
59
|
// Show image count for merged messages containing images
|
|
@@ -97,6 +97,27 @@ export const getMessagePreview = (message?: MessageItem): string => {
|
|
|
97
97
|
}
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
+
export const getMessagePreviewTextProps = (
|
|
101
|
+
message?: MessageItem
|
|
102
|
+
): TextProps => {
|
|
103
|
+
if (!message) return {};
|
|
104
|
+
switch (message.contentType) {
|
|
105
|
+
case MessageType.FileMessage:
|
|
106
|
+
return {
|
|
107
|
+
ellipsizeMode: 'middle',
|
|
108
|
+
};
|
|
109
|
+
default:
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const getMessagePreviewParts = (
|
|
115
|
+
message?: MessageItem
|
|
116
|
+
): { text: string; textProps: TextProps } => ({
|
|
117
|
+
text: getMessagePreview(message),
|
|
118
|
+
textProps: getMessagePreviewTextProps(message),
|
|
119
|
+
});
|
|
120
|
+
|
|
100
121
|
export type PinnedMessageMediaType = 'image' | 'video' | 'file' | 'text';
|
|
101
122
|
|
|
102
123
|
export interface PinnedMessagePreview {
|
|
@@ -233,3 +254,15 @@ export const parseUrlMetadata = (ex?: string): IUrlMetadata | undefined => {
|
|
|
233
254
|
return undefined;
|
|
234
255
|
}
|
|
235
256
|
};
|
|
257
|
+
|
|
258
|
+
export const truncateMiddle = (name: string, maxLength: number): string => {
|
|
259
|
+
const dotIndex = name.lastIndexOf('.');
|
|
260
|
+
const baseName = dotIndex !== -1 ? name.slice(0, dotIndex) : name;
|
|
261
|
+
const ext = dotIndex !== -1 ? name.slice(dotIndex) : '';
|
|
262
|
+
|
|
263
|
+
if (baseName.length <= maxLength) return name;
|
|
264
|
+
|
|
265
|
+
const headLen = Math.ceil((maxLength - 3) / 2);
|
|
266
|
+
const tailLen = Math.floor((maxLength - 3) / 2);
|
|
267
|
+
return `${baseName.slice(0, headLen)}...${tailLen > 0 ? baseName.slice(-tailLen) : ''}${ext}`;
|
|
268
|
+
};
|