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

@@ -12,7 +12,6 @@
import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
import {
View,
FlatList,
StyleSheet,
TouchableOpacity,
Pressable,
@@ -24,6 +23,7 @@ import {
BackHandler,
Platform,
} from 'react-native';
import { FlashList, ListRenderItem } from '@shopify/flash-list';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { useRouter } from 'expo-router';
import { useIsFocused } from '@react-navigation/native';
@@ -115,6 +115,7 @@ export const MessageListScreen: React.FC = () => {
// 使用 MessageManager 获取未读数和系统通知数
const { totalUnreadCount, systemUnreadCount } = useUnreadCount();
const lastSystemMessageAt = useMessageStore(state => state.lastSystemMessageAt);
const systemMessageTime = lastSystemMessageAt ?? '';
const { markAllAsRead, isMarking } = useMarkAsRead(null);
// 【新架构】使用MessageManager的hook创建会话
@@ -162,14 +163,14 @@ export const MessageListScreen: React.FC = () => {
seq: 0,
segments: [{ type: 'text', data: { text: '系统通知' } }],
status: 'normal',
created_at: lastSystemMessageAt || new Date().toISOString(),
created_at: systemMessageTime,
},
last_message_at: lastSystemMessageAt || new Date().toISOString(),
last_message_at: systemMessageTime,
unread_count: systemUnreadCount,
participants: [],
created_at: lastSystemMessageAt || new Date().toISOString(),
updated_at: lastSystemMessageAt || new Date().toISOString(),
}), [systemUnreadCount, lastSystemMessageAt]);
created_at: systemMessageTime,
updated_at: systemMessageTime,
}), [systemUnreadCount, systemMessageTime]);
// 动画值
const [scaleAnims] = useState(() =>
@@ -453,7 +454,6 @@ export const MessageListScreen: React.FC = () => {
// 创建系统通知会话项
const createSystemMessageItem = (): ConversationResponse => {
const noticeContent = systemUnreadCount > 0 ? `您有 ${systemUnreadCount} 条未读通知` : '暂无新通知';
const lastTime = lastSystemMessageAt || new Date().toISOString();
return {
id: SYSTEM_MESSAGE_CHANNEL_ID,
type: 'private',
@@ -465,13 +465,13 @@ export const MessageListScreen: React.FC = () => {
seq: 0,
segments: [{ type: 'text', data: { text: noticeContent } }],
status: 'normal',
created_at: lastTime,
created_at: systemMessageTime,
},
last_message_at: lastTime,
last_message_at: systemMessageTime,
unread_count: systemUnreadCount,
participants: [],
created_at: lastTime,
updated_at: lastTime,
created_at: systemMessageTime,
updated_at: systemMessageTime,
};
};
@@ -500,8 +500,8 @@ export const MessageListScreen: React.FC = () => {
const totalUnread = totalUnreadCount + systemUnreadCount;
// 渲染会话项(行级 memo + 按 id 解析最新会话,避免 Tab 切换时整表无效重绘)
const renderConversation = useCallback(
({ item, index }: { item: ConversationResponse; index: number }) => {
const renderConversation = useCallback<ListRenderItem<ConversationResponse>>(
({ item, index }) => {
const isSelected = selectedConversation?.id === item.id;
return (
<ConversationListRow
@@ -715,7 +715,7 @@ export const MessageListScreen: React.FC = () => {
</View>
{/* 搜索结果 */}
<FlatList
<FlashList
data={searchResults}
renderItem={renderSearchResult}
keyExtractor={(item, index) => {
@@ -724,14 +724,16 @@ export const MessageListScreen: React.FC = () => {
}
return `user-${item.user?.id || index}`;
}}
contentContainerStyle={[
styles.searchResultsContent,
{ paddingBottom: listBottomInset },
]}
contentContainerStyle={{
paddingHorizontal: spacing.md,
paddingTop: spacing.sm,
paddingBottom: listBottomInset,
}}
showsVerticalScrollIndicator={false}
scrollEnabled={true}
keyboardShouldPersistTaps="handled"
keyboardDismissMode="on-drag"
drawDistance={250}
ListEmptyComponent={renderSearchEmpty}
/>
</View>
@@ -779,15 +781,15 @@ export const MessageListScreen: React.FC = () => {
<ActivityIndicator size="large" color={colors.primary.main} />
</View>
) : (
<FlatList
<FlashList
data={listData}
renderItem={renderConversation}
keyExtractor={(item) => String(item.id)}
contentContainerStyle={[
styles.listContent,
...(isWideScreen ? [styles.listContentWide] : []),
{ paddingBottom: listBottomInset },
]}
contentContainerStyle={{
backgroundColor: colors.background.default,
paddingHorizontal: spacing.xs,
paddingBottom: listBottomInset,
}}
showsVerticalScrollIndicator={false}
ListEmptyComponent={renderEmpty}
onEndReached={onEndReached}
@@ -795,6 +797,7 @@ export const MessageListScreen: React.FC = () => {
scrollEnabled={true}
keyboardShouldPersistTaps="handled"
keyboardDismissMode="on-drag"
drawDistance={250}
refreshControl={
<RefreshControl
refreshing={refreshing}