feat(performance): migrate FlatList to FlashList and add animations
Some checks failed
Frontend CI / build-and-push-web (push) Failing after 3m9s
Frontend CI / ota-android (push) Successful in 10m37s
Frontend CI / build-android-apk (push) Has been cancelled

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:
lafay
2026-04-25 15:09:00 +08:00
parent 19054d64b3
commit a6a4198ac5
14 changed files with 746 additions and 680 deletions

View File

@@ -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 && (