@droppii-org/chat-mobile 0.2.54 → 0.2.56
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/messages/imageMessage/index.js +31 -14
- package/lib/module/components/messages/imageMessage/index.js.map +1 -1
- package/lib/module/components/messages/stickerMessage/index.js +1 -1
- package/lib/module/components/messages/stickerMessage/index.js.map +1 -1
- package/lib/module/screens/ChatInfo/ChatInfo.js +2 -14
- package/lib/module/screens/ChatInfo/ChatInfo.js.map +1 -1
- package/lib/module/screens/ChatInfo/ChatInfoProfile.js +14 -19
- package/lib/module/screens/ChatInfo/ChatInfoProfile.js.map +1 -1
- package/lib/module/screens/MediaView/index.js +7 -3
- package/lib/module/screens/MediaView/index.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +5 -3
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +91 -36
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/typescript/src/components/messages/imageMessage/index.d.ts +12 -0
- package/lib/typescript/src/components/messages/imageMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/stickerMessage/index.d.ts +1 -0
- package/lib/typescript/src/components/messages/stickerMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts +1 -1
- package/lib/typescript/src/screens/ChatInfo/ChatInfo.d.ts.map +1 -1
- package/lib/typescript/src/screens/ChatInfo/ChatInfoProfile.d.ts +1 -2
- package/lib/typescript/src/screens/ChatInfo/ChatInfoProfile.d.ts.map +1 -1
- package/lib/typescript/src/screens/ChatInfo/types.d.ts +1 -4
- package/lib/typescript/src/screens/ChatInfo/types.d.ts.map +1 -1
- package/lib/typescript/src/screens/MediaView/index.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/types.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/messages/imageMessage/index.tsx +48 -19
- package/src/components/messages/stickerMessage/index.tsx +1 -1
- package/src/screens/ChatInfo/ChatInfo.tsx +2 -16
- package/src/screens/ChatInfo/ChatInfoProfile.tsx +37 -39
- package/src/screens/ChatInfo/types.ts +1 -4
- package/src/screens/MediaView/index.tsx +5 -1
- package/src/screens/chat-detail/ChatDetail.tsx +6 -3
- package/src/screens/chat-detail/ChatListLegend.tsx +125 -51
- package/src/screens/chat-detail/types.ts +1 -1
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
type ConversationItem,
|
|
7
7
|
} from '@droppii/openim-rn-client-sdk';
|
|
8
8
|
import { Avatar } from '../../components/Avatar';
|
|
9
|
-
import { useGetUsersInfo } from '../../hooks/users/useGetUsersInfo';
|
|
10
9
|
import { useMuteConversation } from '../../hooks/useMuteConversation';
|
|
11
10
|
import { trans } from '../../translation';
|
|
12
11
|
import { StyleSheet } from 'react-native';
|
|
@@ -15,51 +14,44 @@ import { NamePrefixIcon } from '../../components/ThreadCard';
|
|
|
15
14
|
interface QuickActionProps {
|
|
16
15
|
iconName: string;
|
|
17
16
|
label: string;
|
|
17
|
+
background: string;
|
|
18
|
+
iconColor: string;
|
|
18
19
|
onPress?: () => void;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
const QuickAction = memo(
|
|
22
|
-
|
|
23
|
-
<KContainer.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
22
|
+
const QuickAction = memo(
|
|
23
|
+
({ iconName, label, background, iconColor, onPress }: QuickActionProps) => (
|
|
24
|
+
<KContainer.View alignItems paddingH="0.75rem" paddingV="0.5rem">
|
|
25
|
+
<KContainer.Touchable
|
|
26
|
+
height={40}
|
|
27
|
+
width={40}
|
|
28
|
+
br={'round'}
|
|
29
|
+
background={background}
|
|
30
|
+
center
|
|
31
|
+
onPress={onPress}
|
|
32
|
+
>
|
|
33
|
+
<KImage.VectorIcons name={iconName} size={18} color={iconColor} />
|
|
34
|
+
</KContainer.Touchable>
|
|
35
|
+
<KLabel.Text
|
|
36
|
+
typo="TextSmMedium"
|
|
37
|
+
color={KColors.gray.dark}
|
|
38
|
+
marginT="0.25rem"
|
|
39
|
+
textAlign="center"
|
|
40
|
+
>
|
|
41
|
+
{label}
|
|
42
|
+
</KLabel.Text>
|
|
43
|
+
</KContainer.View>
|
|
44
|
+
)
|
|
45
|
+
);
|
|
43
46
|
|
|
44
47
|
interface ChatInfoProfileProps {
|
|
45
48
|
conversation?: ConversationItem;
|
|
46
|
-
targetUserId?: string;
|
|
47
49
|
onPressSearch?: () => void;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
const ChatInfoProfile = memo(
|
|
51
|
-
({ conversation,
|
|
52
|
-
const {
|
|
53
|
-
targetUserId ? [targetUserId] : []
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
const { faceURL, showName } = useMemo(() => {
|
|
57
|
-
const userInfo = usersInfo?.[0];
|
|
58
|
-
return {
|
|
59
|
-
faceURL: conversation?.faceURL || userInfo?.faceURL,
|
|
60
|
-
showName: conversation?.showName || userInfo?.nickname,
|
|
61
|
-
};
|
|
62
|
-
}, [conversation, usersInfo]);
|
|
53
|
+
({ conversation, onPressSearch }: ChatInfoProfileProps) => {
|
|
54
|
+
const { faceURL, showName = '' } = conversation ?? {};
|
|
63
55
|
|
|
64
56
|
const isMuted =
|
|
65
57
|
conversation != null &&
|
|
@@ -98,7 +90,7 @@ const ChatInfoProfile = memo(
|
|
|
98
90
|
<Avatar
|
|
99
91
|
source={faceURL}
|
|
100
92
|
size="4xlg"
|
|
101
|
-
fullName={showName
|
|
93
|
+
fullName={showName}
|
|
102
94
|
badge={badge}
|
|
103
95
|
/>
|
|
104
96
|
<KContainer.View row alignItems marginT="0.5rem">
|
|
@@ -108,7 +100,7 @@ const ChatInfoProfile = memo(
|
|
|
108
100
|
color={KColors.black}
|
|
109
101
|
numberOfLines={1}
|
|
110
102
|
>
|
|
111
|
-
{showName
|
|
103
|
+
{showName}
|
|
112
104
|
</KLabel.Text>
|
|
113
105
|
</KContainer.View>
|
|
114
106
|
</KContainer.View>
|
|
@@ -124,12 +116,18 @@ const ChatInfoProfile = memo(
|
|
|
124
116
|
iconName="search-o"
|
|
125
117
|
label={trans('chat:info_search_action')}
|
|
126
118
|
onPress={onPressSearch}
|
|
119
|
+
background={KColors.palette.gray.w25}
|
|
120
|
+
iconColor={KColors.gray.dark}
|
|
127
121
|
/>
|
|
128
122
|
<QuickAction
|
|
129
|
-
iconName={isMuted ? 'bell-
|
|
123
|
+
iconName={isMuted ? 'bell-b' : 'bell-off-o'}
|
|
130
124
|
label={
|
|
131
125
|
isMuted ? trans('chat:unmute_action') : trans('chat:mute_action')
|
|
132
126
|
}
|
|
127
|
+
background={
|
|
128
|
+
isMuted ? KColors.primary.normal : KColors.palette.gray.w25
|
|
129
|
+
}
|
|
130
|
+
iconColor={isMuted ? KColors.white : KColors.gray.dark}
|
|
133
131
|
onPress={handleNotificationPress}
|
|
134
132
|
/>
|
|
135
133
|
</KContainer.View>
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type { DThreadResourceContentType } from '../../types/chat';
|
|
2
2
|
|
|
3
3
|
export interface ChatInfoProps {
|
|
4
|
-
|
|
5
|
-
targetUserId?: string;
|
|
6
|
-
targetGroupId?: string;
|
|
7
|
-
targetBotId?: string;
|
|
4
|
+
conversationId?: string;
|
|
8
5
|
onBack?: () => void;
|
|
9
6
|
onPressSearch?: () => void;
|
|
10
7
|
onPressMedia?: (tab: DThreadResourceContentType) => void;
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
StyleSheet,
|
|
8
8
|
type ViewToken,
|
|
9
9
|
StatusBar,
|
|
10
|
+
useWindowDimensions,
|
|
10
11
|
} from 'react-native';
|
|
11
12
|
import {
|
|
12
13
|
LegendList,
|
|
@@ -65,6 +66,8 @@ const MediaViewerScreen = (props: MediaViewerParams) => {
|
|
|
65
66
|
const [isScrolling, setIsScrolling] = useState(false);
|
|
66
67
|
const [isDownloading, setIsDownloading] = useState(false);
|
|
67
68
|
|
|
69
|
+
const { height } = useWindowDimensions();
|
|
70
|
+
|
|
68
71
|
const dismiss = useCallback(() => {
|
|
69
72
|
onDismiss?.();
|
|
70
73
|
}, [onDismiss]);
|
|
@@ -153,6 +156,7 @@ const MediaViewerScreen = (props: MediaViewerParams) => {
|
|
|
153
156
|
url={item?.url || ''}
|
|
154
157
|
isActive={itemIndex === index && !isScrolling}
|
|
155
158
|
showCustomControls={false}
|
|
159
|
+
height={height}
|
|
156
160
|
/>
|
|
157
161
|
</KContainer.VisibleView>
|
|
158
162
|
<KContainer.VisibleView visible={!item.isVideo}>
|
|
@@ -167,7 +171,7 @@ const MediaViewerScreen = (props: MediaViewerParams) => {
|
|
|
167
171
|
</KContainer.VisibleView>
|
|
168
172
|
</KContainer.Touchable>
|
|
169
173
|
),
|
|
170
|
-
[index, isScrolling]
|
|
174
|
+
[index, isScrolling, height]
|
|
171
175
|
);
|
|
172
176
|
|
|
173
177
|
const renderFooter = useMemo(() => {
|
|
@@ -46,7 +46,6 @@ const ChatDetail = memo(
|
|
|
46
46
|
onPressSearch,
|
|
47
47
|
onPressAddMember,
|
|
48
48
|
onPressMenu,
|
|
49
|
-
onPressAvatar,
|
|
50
49
|
onPressUrl,
|
|
51
50
|
customMessageItemFactory,
|
|
52
51
|
pannelActions,
|
|
@@ -149,6 +148,10 @@ const ChatDetail = memo(
|
|
|
149
148
|
});
|
|
150
149
|
}, [currentUser, unpinMessage, customMessageItemFactory]);
|
|
151
150
|
|
|
151
|
+
const handlePressMenu = useCallback(() => {
|
|
152
|
+
onPressMenu?.(conversationId);
|
|
153
|
+
}, [conversationId, onPressMenu]);
|
|
154
|
+
|
|
152
155
|
return (
|
|
153
156
|
<KContainer.Page flex edges={['bottom']}>
|
|
154
157
|
<ChatDetailHeader
|
|
@@ -158,8 +161,8 @@ const ChatDetail = memo(
|
|
|
158
161
|
onBack={onBack ?? handleBackDefault}
|
|
159
162
|
onPressSearch={onPressSearch}
|
|
160
163
|
onPressAddMember={onPressAddMember}
|
|
161
|
-
onPressMenu={
|
|
162
|
-
onPressAvatar={
|
|
164
|
+
onPressMenu={handlePressMenu}
|
|
165
|
+
onPressAvatar={handlePressMenu}
|
|
163
166
|
/>
|
|
164
167
|
|
|
165
168
|
<PinnedMessageBar
|
|
@@ -7,7 +7,12 @@ import {
|
|
|
7
7
|
useState,
|
|
8
8
|
type RefObject,
|
|
9
9
|
} from 'react';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
DeviceEventEmitter,
|
|
12
|
+
StyleSheet,
|
|
13
|
+
useWindowDimensions,
|
|
14
|
+
type View,
|
|
15
|
+
} from 'react-native';
|
|
11
16
|
import Animated, {
|
|
12
17
|
useSharedValue,
|
|
13
18
|
useAnimatedStyle,
|
|
@@ -32,9 +37,20 @@ import { ChatEvent, type ScrollToMessagePayload } from '../../types/events';
|
|
|
32
37
|
import { useMessageStore } from '../../store/message';
|
|
33
38
|
import { useChatContext } from '../../context';
|
|
34
39
|
import { messageStyles } from './legend/messageTypes';
|
|
40
|
+
import { MessageType } from '@droppii/openim-rn-client-sdk';
|
|
35
41
|
import type { MessageItem } from '@droppii/openim-rn-client-sdk';
|
|
36
42
|
import { useUserStore } from '../../store';
|
|
37
43
|
import { ChatMessageAPI } from '../../services';
|
|
44
|
+
import { resolveImageDisplayDims } from '../../components/messages/imageMessage';
|
|
45
|
+
import { STICKER_SIZE } from '../../components/messages/stickerMessage';
|
|
46
|
+
import ChatImageDimens, {
|
|
47
|
+
CHAT_IMAGE_VIEW_SOURCE,
|
|
48
|
+
} from '../../utils/chatImageDimens';
|
|
49
|
+
|
|
50
|
+
// Row wrapper (`messageRowLeft`/`messageRowRight` in `components/messages/styles.ts`)
|
|
51
|
+
// adds only a bottom margin — no vertical padding — so a fixed-size item's true
|
|
52
|
+
// rendered height is `content height + this margin`.
|
|
53
|
+
const ROW_MARGIN_BOTTOM = KSpacingValue['0.25rem'];
|
|
38
54
|
|
|
39
55
|
interface ChatListLegendInternalProps extends ChatListProps {
|
|
40
56
|
listRef: RefObject<LegendListRef | null>;
|
|
@@ -66,6 +82,7 @@ export const ChatListLegend = memo(
|
|
|
66
82
|
keyboardOffset,
|
|
67
83
|
}: ChatListLegendInternalProps) => {
|
|
68
84
|
const currentUserId = useUserStore((s) => s.user?.userID);
|
|
85
|
+
const windowWidth = useWindowDimensions().width;
|
|
69
86
|
// Precompute all message data in single pass (O(n))
|
|
70
87
|
const messageDataMap = useMemo(
|
|
71
88
|
() => precomputeLegendListData(messages),
|
|
@@ -85,39 +102,12 @@ export const ChatListLegend = memo(
|
|
|
85
102
|
const buttonOpacity = useSharedValue(0);
|
|
86
103
|
const isAtBottomRef = useRef(true);
|
|
87
104
|
const scrollDebounceRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
|
88
|
-
const itemHeightsRef = useRef<Map<string, number>>(new Map());
|
|
89
|
-
const avgItemHeightRef = useRef(120);
|
|
90
|
-
const heightDebounceRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
|
91
105
|
const rowRefsMap = useRef<Map<string, View | null>>(new Map());
|
|
92
106
|
const [showBadge, setShowBadge] = useState(false);
|
|
93
107
|
|
|
94
108
|
const { applicationType } = useChatContext();
|
|
95
109
|
const searchMessages = useMessageStore((s) => s.searchMessages);
|
|
96
110
|
|
|
97
|
-
const handleItemLayout = useCallback(
|
|
98
|
-
(messageId: string, height: number) => {
|
|
99
|
-
itemHeightsRef.current.set(messageId, height);
|
|
100
|
-
|
|
101
|
-
// Debounce average height calculation (batch updates)
|
|
102
|
-
if (heightDebounceRef.current) {
|
|
103
|
-
clearTimeout(heightDebounceRef.current);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
heightDebounceRef.current = setTimeout(() => {
|
|
107
|
-
if (itemHeightsRef.current.size > 0) {
|
|
108
|
-
const total = Array.from(itemHeightsRef.current.values()).reduce(
|
|
109
|
-
(a, b) => a + b,
|
|
110
|
-
0
|
|
111
|
-
);
|
|
112
|
-
avgItemHeightRef.current = Math.round(
|
|
113
|
-
total / itemHeightsRef.current.size
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
}, 100); // Batch updates every 100ms during load
|
|
117
|
-
},
|
|
118
|
-
[]
|
|
119
|
-
);
|
|
120
|
-
|
|
121
111
|
const handleMessageLongPress = useCallback(
|
|
122
112
|
(messageId: string, message: MessageItem) => {
|
|
123
113
|
const node = rowRefsMap.current.get(messageId);
|
|
@@ -181,9 +171,6 @@ export const ChatListLegend = memo(
|
|
|
181
171
|
? messageStyles.messageRowRight
|
|
182
172
|
: messageStyles.messageRowLeft
|
|
183
173
|
}
|
|
184
|
-
onLayout={(e) =>
|
|
185
|
-
handleItemLayout(messageId, e.nativeEvent.layout.height)
|
|
186
|
-
}
|
|
187
174
|
>
|
|
188
175
|
<LegendChatMessage
|
|
189
176
|
message={message}
|
|
@@ -234,30 +221,118 @@ export const ChatListLegend = memo(
|
|
|
234
221
|
onResetFlow,
|
|
235
222
|
onRunFlow,
|
|
236
223
|
conversationFlow?.flowCurrentStep,
|
|
237
|
-
handleItemLayout,
|
|
238
224
|
retryMessage,
|
|
239
225
|
]
|
|
240
226
|
);
|
|
241
227
|
|
|
242
|
-
const maintainScrollConfig = useMemo(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
);
|
|
228
|
+
// const maintainScrollConfig = useMemo(
|
|
229
|
+
// () => ({
|
|
230
|
+
// animated: false,
|
|
231
|
+
// on: {
|
|
232
|
+
// dataChange: !moreOldLoading,
|
|
233
|
+
// layout: !moreOldLoading,
|
|
234
|
+
// // Must be disabled while prepending older history; otherwise re-measuring
|
|
235
|
+
// // the freshly prepended items snaps the list back to the bottom.
|
|
236
|
+
// itemLayout: !moreOldLoading,
|
|
237
|
+
// },
|
|
238
|
+
// }),
|
|
239
|
+
// [moreOldLoading]
|
|
240
|
+
// );
|
|
255
241
|
|
|
256
242
|
// Stable config so the native maintainVisibleContentPosition prop isn't toggled
|
|
257
243
|
// mid-prepend (toggling forces an extra layout recompute). `data` anchoring keeps
|
|
258
244
|
// the viewport pinned to the item being read when older messages are added on top.
|
|
259
245
|
const maintainVisiblePositionConfig = useMemo(() => ({ data: true }), []);
|
|
260
246
|
|
|
247
|
+
// Buckets averaging (`averageSizes[itemType]`, internal to LegendList) per
|
|
248
|
+
// content type, so a batch of images landing early doesn't pollute the
|
|
249
|
+
// estimate used for still-unmeasured text rows and vice versa. Items
|
|
250
|
+
// rendered via `customMessageItemFactory` (host apps can override ANY
|
|
251
|
+
// content type with an arbitrary custom node — see `LegendChatMessage`'s
|
|
252
|
+
// `customNode` short-circuit, checked before its own contentType switch)
|
|
253
|
+
// get their own bucket per content type, so a custom node's real measured
|
|
254
|
+
// height never pollutes the native component's average for that same type.
|
|
255
|
+
const getItemType = useCallback(
|
|
256
|
+
(item: MessageItem) => {
|
|
257
|
+
const isCustom = !!customMessageItemFactory?.(item);
|
|
258
|
+
return `${item?.contentType ?? 'default'}${isCustom ? '-custom' : ''}`;
|
|
259
|
+
},
|
|
260
|
+
[customMessageItemFactory]
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
// Exact size for content types that are knowable ahead of render — see
|
|
264
|
+
// `plans/260728-1400-chatlist-legend-fixed-item-size/plan.md`. Returns
|
|
265
|
+
// `undefined` (defer to dynamic measurement) for anything whose size can't
|
|
266
|
+
// be predicted, AND for any item carrying extra chrome this function
|
|
267
|
+
// doesn't account for (day-divider above it, trailing flow-input-buttons
|
|
268
|
+
// row) — `getFixedItemSize` describes the ENTIRE item LegendList renders,
|
|
269
|
+
// and once it resolves a number that item is never measured again, so
|
|
270
|
+
// guessing wrong here is a permanent regression, not just an approximation.
|
|
271
|
+
const getFixedItemSize = useCallback(
|
|
272
|
+
(item: MessageItem, index: number): number | undefined => {
|
|
273
|
+
const message = item;
|
|
274
|
+
const messageId =
|
|
275
|
+
message.clientMsgID ||
|
|
276
|
+
message.serverMsgID ||
|
|
277
|
+
`temp-${message.sendTime || message.createTime}-${index}`;
|
|
278
|
+
|
|
279
|
+
if (messageDataMap.get(messageId)?.dayStart) return undefined;
|
|
280
|
+
|
|
281
|
+
// Resolve the content-type candidate FIRST — for every type other
|
|
282
|
+
// than these three the result is `undefined` regardless of anything
|
|
283
|
+
// else, so bail before paying for a `customMessageItemFactory` call
|
|
284
|
+
// (text/quote/link/file/merge/revoke are the overwhelming majority of
|
|
285
|
+
// messages in a real chat — skip the extra call for all of them).
|
|
286
|
+
let candidateSize: number;
|
|
287
|
+
switch (message.contentType) {
|
|
288
|
+
case MessageType.PictureMessage:
|
|
289
|
+
candidateSize =
|
|
290
|
+
resolveImageDisplayDims(message, windowWidth).height +
|
|
291
|
+
ROW_MARGIN_BOTTOM;
|
|
292
|
+
break;
|
|
293
|
+
case MessageType.VideoMessage:
|
|
294
|
+
candidateSize =
|
|
295
|
+
ChatImageDimens.getDimensions(
|
|
296
|
+
CHAT_IMAGE_VIEW_SOURCE.MessageListThumbnail
|
|
297
|
+
).height + ROW_MARGIN_BOTTOM;
|
|
298
|
+
break;
|
|
299
|
+
case MessageType.StickerMessage:
|
|
300
|
+
candidateSize = STICKER_SIZE + ROW_MARGIN_BOTTOM;
|
|
301
|
+
break;
|
|
302
|
+
default:
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// `customMessageItemFactory` can override rendering for ANY content
|
|
307
|
+
// type with an arbitrary node (`LegendChatMessage` checks it first,
|
|
308
|
+
// before its own contentType switch) — its real height is unknowable
|
|
309
|
+
// here, so defer to dynamic measurement when it intercepts. Only
|
|
310
|
+
// checked once we know this item would otherwise be fixed-sizeable.
|
|
311
|
+
if (customMessageItemFactory?.(message)) return undefined;
|
|
312
|
+
|
|
313
|
+
const isOutgoing = message.sendID === currentUserId;
|
|
314
|
+
const isLastIncoming = !isOutgoing && messageId === lastMessageId;
|
|
315
|
+
if (
|
|
316
|
+
isLastIncoming &&
|
|
317
|
+
isBotConversation &&
|
|
318
|
+
getFlowInputButtonsNode(conversationFlow?.flowCurrentStep)
|
|
319
|
+
) {
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return candidateSize;
|
|
324
|
+
},
|
|
325
|
+
[
|
|
326
|
+
messageDataMap,
|
|
327
|
+
customMessageItemFactory,
|
|
328
|
+
currentUserId,
|
|
329
|
+
lastMessageId,
|
|
330
|
+
isBotConversation,
|
|
331
|
+
conversationFlow?.flowCurrentStep,
|
|
332
|
+
windowWidth,
|
|
333
|
+
]
|
|
334
|
+
);
|
|
335
|
+
|
|
261
336
|
const handleStartReached = useCallback(() => {
|
|
262
337
|
if (__DEV__) {
|
|
263
338
|
console.log('[LegendList] onStartReached triggered', {
|
|
@@ -320,15 +395,12 @@ export const ChatListLegend = memo(
|
|
|
320
395
|
pointerEvents: buttonOpacity.value === 0 ? 'none' : 'auto',
|
|
321
396
|
}));
|
|
322
397
|
|
|
323
|
-
// Cleanup debounce
|
|
398
|
+
// Cleanup debounce timeout on unmount
|
|
324
399
|
useEffect(() => {
|
|
325
400
|
return () => {
|
|
326
401
|
if (scrollDebounceRef.current) {
|
|
327
402
|
clearTimeout(scrollDebounceRef.current);
|
|
328
403
|
}
|
|
329
|
-
if (heightDebounceRef.current) {
|
|
330
|
-
clearTimeout(heightDebounceRef.current);
|
|
331
|
-
}
|
|
332
404
|
};
|
|
333
405
|
}, []);
|
|
334
406
|
|
|
@@ -402,13 +474,15 @@ export const ChatListLegend = memo(
|
|
|
402
474
|
ref={listRef}
|
|
403
475
|
data={messages}
|
|
404
476
|
renderItem={renderItem}
|
|
405
|
-
estimatedItemSize={
|
|
477
|
+
estimatedItemSize={100}
|
|
478
|
+
getItemType={getItemType}
|
|
479
|
+
getFixedItemSize={getFixedItemSize}
|
|
406
480
|
keyExtractor={(item) => String(item.clientMsgID || item.serverMsgID)}
|
|
407
481
|
recycleItems={false}
|
|
408
482
|
extraData={`${conversationFlow?.flowCurrentStep?.id ?? ''}-${lastMessageId ?? ''}`}
|
|
409
483
|
alignItemsAtEnd
|
|
410
484
|
maintainVisibleContentPosition={maintainVisiblePositionConfig}
|
|
411
|
-
maintainScrollAtEnd={
|
|
485
|
+
maintainScrollAtEnd={true}
|
|
412
486
|
freeze={freeze}
|
|
413
487
|
keyboardOffset={keyboardOffset}
|
|
414
488
|
scrollEventThrottle={16}
|
|
@@ -42,7 +42,7 @@ export interface ChatDetailHeaderProps {
|
|
|
42
42
|
onBack?: () => void;
|
|
43
43
|
onPressSearch?: () => void;
|
|
44
44
|
onPressAddMember?: () => void;
|
|
45
|
-
onPressMenu?: () => void;
|
|
45
|
+
onPressMenu?: (conversationId?: string) => void;
|
|
46
46
|
onPressAvatar?: () => void;
|
|
47
47
|
conversationId: string;
|
|
48
48
|
targetUserId?: string;
|