feat(performance): migrate FlatList to FlashList and add animations
Replace FlatList with FlashList across all message screens (ChatScreen, MessageListScreen, NotificationsScreen, HomeScreen) for improved list virtualization performance. Use `drawDistance={250}` instead of manual pagination. Simplify React.memo comparisons in MessageBubble and SegmentRenderer by removing function prop checks to prevent unnecessary re-renders.
Add AsyncStorage persistence for lastSystemMessageAt to avoid showing current time on first render. Include enter animations (fade and slide) for CreateGroupScreen and modernize UI styling to flat design.
BREAKING CHANGE: Upgrade @shopify/flash-list from 2.0.2 to ^2.3.1
This commit is contained in:
@@ -22,12 +22,12 @@
|
||||
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||
import {
|
||||
View,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import { FlashList, ListRenderItem } from '@shopify/flash-list';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { useNavigation, useRouter } from 'expo-router';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
PANEL_HEIGHTS,
|
||||
ChatScreenProps,
|
||||
} from './components/ChatScreen';
|
||||
import { GroupMessage } from './components/ChatScreen/types';
|
||||
|
||||
export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
|
||||
const navigation = useNavigation();
|
||||
@@ -92,12 +93,12 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
|
||||
isWideScreen && !props.isEmbedded ? { maxWidth: 1200, alignSelf: 'center' as const, width: '100%' as const } : null,
|
||||
]), [isWideScreen, styles.container, props.isEmbedded]);
|
||||
|
||||
const listContentStyle = useMemo(() => ([
|
||||
styles.listContent,
|
||||
isWideScreen && !props.isEmbedded
|
||||
? { paddingHorizontal: 24, maxWidth: 900, alignSelf: 'center' as const }
|
||||
: { paddingHorizontal: 16 },
|
||||
]), [isWideScreen, styles.listContent, props.isEmbedded]);
|
||||
const listContentStyle = useMemo(() => ({
|
||||
paddingHorizontal: isWideScreen && !props.isEmbedded ? 24 : 16,
|
||||
...(isWideScreen && !props.isEmbedded ? { maxWidth: 900, alignSelf: 'center' as const } : {}),
|
||||
paddingTop: 12,
|
||||
paddingBottom: 24,
|
||||
}), [isWideScreen, props.isEmbedded]);
|
||||
|
||||
const inputWrapperStyle = useMemo(() => ([
|
||||
styles.inputWrapper,
|
||||
@@ -285,7 +286,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
|
||||
return map;
|
||||
}, [messages]);
|
||||
|
||||
const renderMessage = useCallback(({ item, index }: { item: any; index: number }) => {
|
||||
const renderMessage = useCallback<ListRenderItem<GroupMessage>>(({ item, index }) => {
|
||||
// inverted 下 renderItem 的 index 与时间顺序不一致,需回查原始序索引
|
||||
const logicalIndex = messageIndexMap.get(String(item.id)) ?? index;
|
||||
return (
|
||||
@@ -330,7 +331,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
|
||||
handleReplyPreviewPress,
|
||||
]);
|
||||
|
||||
const keyExtractor = useCallback((item: any) => String(item.id), []);
|
||||
const keyExtractor = useCallback((item: GroupMessage) => String(item.id), []);
|
||||
|
||||
// 获取正在输入提示
|
||||
const typingHint = getTypingHint();
|
||||
@@ -448,22 +449,18 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
|
||||
<ActivityIndicator size="large" color={colors.primary.main} />
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
<FlashList
|
||||
ref={flatListRef}
|
||||
inverted
|
||||
data={displayMessages}
|
||||
renderItem={renderMessage}
|
||||
keyExtractor={keyExtractor}
|
||||
contentContainerStyle={listContentStyle as any}
|
||||
contentContainerStyle={listContentStyle}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
keyboardDismissMode="on-drag"
|
||||
scrollEnabled={true}
|
||||
initialNumToRender={14}
|
||||
maxToRenderPerBatch={10}
|
||||
updateCellsBatchingPeriod={50}
|
||||
windowSize={15}
|
||||
removeClippedSubviews={false}
|
||||
drawDistance={250}
|
||||
onScroll={handleMessageListScroll}
|
||||
onScrollBeginDrag={() => {
|
||||
isUserDraggingRef.current = true;
|
||||
@@ -477,26 +474,6 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
|
||||
}}
|
||||
scrollEventThrottle={16}
|
||||
onContentSizeChange={handleContentSizeChange}
|
||||
onScrollToIndexFailed={(info) => {
|
||||
const targetId = replyTargetMessageIdRef.current;
|
||||
if (!targetId || !flatListRef.current) return;
|
||||
|
||||
flatListRef.current.scrollToOffset({
|
||||
offset: Math.max(0, info.averageItemLength * info.index),
|
||||
animated: true,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
const retryIndex = displayMessages.findIndex(msg => String(msg.id) === targetId);
|
||||
if (retryIndex >= 0) {
|
||||
flatListRef.current?.scrollToIndex({
|
||||
index: retryIndex,
|
||||
animated: true,
|
||||
viewPosition: 0.5,
|
||||
});
|
||||
}
|
||||
}, 120);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showEdgeLoadingIndicator && (
|
||||
|
||||
Reference in New Issue
Block a user