refactor(home): redesign home screen with sort bar and add recommendation feed
- Replace PagerView-based tabs with modern sort bar navigation - Add "推荐" (recommended) feed option with "hot" sort type - Update tab icons and labels to match new sort paradigm - Optimize PostCard, MessageBubble conditional styles perf(chat): migrate FlatList to FlashList for emoji/sticker panels - Improve virtualized rendering performance for emoji and sticker grids - Fix jump-to-latest scroll behavior for inverted FlashList perf(profile): extract user header and tab bar into separate memoized renders - Prevent unnecessary re-renders when switching tabs feat(api): add channel_id filter support for post queries - Include channel_id in cursor pagination requests - Update CursorPaginationRequest type definition style(chat-info): redesign group and private chat info screens with flat layout - Remove card borders and shadows for cleaner appearance - Adjust avatar sizes and spacing for consistency
This commit is contained in:
@@ -608,12 +608,12 @@ const GroupInfoScreen: React.FC = () => {
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* 群组基本信息卡片 */}
|
||||
{/* 群组基本信息 */}
|
||||
<View style={styles.headerCard}>
|
||||
<View style={styles.groupHeader}>
|
||||
<Avatar
|
||||
source={group.avatar}
|
||||
size={90}
|
||||
size={80}
|
||||
name={group.name}
|
||||
/>
|
||||
<View style={styles.groupInfo}>
|
||||
@@ -621,7 +621,7 @@ const GroupInfoScreen: React.FC = () => {
|
||||
{group.name}
|
||||
</Text>
|
||||
<View style={styles.groupMeta}>
|
||||
<MaterialCommunityIcons name="account-group" size={16} color={colors.text.secondary} />
|
||||
<MaterialCommunityIcons name="account-group" size={14} color={colors.text.secondary} />
|
||||
<Text variant="caption" color={colors.text.secondary}>
|
||||
{group.member_count} 人
|
||||
</Text>
|
||||
@@ -661,7 +661,7 @@ const GroupInfoScreen: React.FC = () => {
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 群公告卡片 */}
|
||||
{/* 群公告 */}
|
||||
{announcements.length > 0 && (
|
||||
<View style={styles.card}>
|
||||
<View style={styles.cardHeader}>
|
||||
@@ -684,7 +684,7 @@ const GroupInfoScreen: React.FC = () => {
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 成员预览卡片 */}
|
||||
{/* 成员预览 */}
|
||||
<View style={styles.card}>
|
||||
<TouchableOpacity style={styles.cardHeader} onPress={goToMembers} activeOpacity={0.7}>
|
||||
<View style={styles.cardIconContainer}>
|
||||
@@ -813,6 +813,7 @@ const GroupInfoScreen: React.FC = () => {
|
||||
thumbColor={isPinned ? colors.primary.main : colors.background.paper}
|
||||
/>
|
||||
</View>
|
||||
<Divider style={styles.settingDivider} />
|
||||
<View style={styles.settingItem}>
|
||||
<View style={styles.settingIconContainer}>
|
||||
<MaterialCommunityIcons name="bell-off-outline" size={20} color={colors.primary.main} />
|
||||
@@ -1126,22 +1127,20 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.background.default,
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
pageHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingHorizontal: spacing.lg,
|
||||
paddingVertical: spacing.md,
|
||||
backgroundColor: colors.background.paper,
|
||||
borderBottomWidth: 0.5,
|
||||
borderBottomColor: colors.divider + '60',
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
pageTitle: {
|
||||
fontWeight: '700',
|
||||
fontWeight: '600',
|
||||
fontSize: fontSizes.xl,
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
pageHeaderPlaceholder: {
|
||||
width: 40,
|
||||
@@ -1151,7 +1150,7 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContent: {
|
||||
padding: spacing.md,
|
||||
paddingHorizontal: spacing.lg,
|
||||
paddingBottom: spacing.xl * 2,
|
||||
},
|
||||
errorContainer: {
|
||||
@@ -1160,29 +1159,25 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
// 头部卡片样式 - 扁平化设计
|
||||
// 头部区域 - 扁平化无卡片
|
||||
headerCard: {
|
||||
backgroundColor: colors.background.paper,
|
||||
borderRadius: borderRadius.xl,
|
||||
padding: spacing.lg,
|
||||
paddingVertical: spacing.lg,
|
||||
marginBottom: spacing.md,
|
||||
borderWidth: 0.5,
|
||||
borderColor: colors.divider + '40',
|
||||
},
|
||||
groupHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: spacing.md,
|
||||
marginBottom: spacing.lg,
|
||||
},
|
||||
groupInfo: {
|
||||
marginLeft: spacing.lg,
|
||||
flex: 1,
|
||||
},
|
||||
groupName: {
|
||||
fontWeight: '800',
|
||||
fontSize: fontSizes.xl + 2,
|
||||
fontWeight: '700',
|
||||
fontSize: fontSizes['2xl'],
|
||||
marginBottom: spacing.xs,
|
||||
letterSpacing: 0.3,
|
||||
},
|
||||
groupMeta: {
|
||||
flexDirection: 'row',
|
||||
@@ -1213,8 +1208,6 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
backgroundColor: colors.background.default,
|
||||
borderRadius: borderRadius.lg,
|
||||
padding: spacing.md,
|
||||
borderWidth: 0.5,
|
||||
borderColor: colors.divider + '30',
|
||||
},
|
||||
groupDesc: {
|
||||
marginLeft: spacing.sm,
|
||||
@@ -1223,19 +1216,16 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
fontWeight: '400',
|
||||
},
|
||||
|
||||
// 通用卡片样式 - 扁平化
|
||||
// 通用卡片样式 - 扁平化:无阴影无圆角,使用分割线
|
||||
card: {
|
||||
backgroundColor: colors.background.paper,
|
||||
borderRadius: borderRadius.xl,
|
||||
padding: spacing.lg,
|
||||
marginBottom: spacing.md,
|
||||
borderWidth: 0.5,
|
||||
borderColor: colors.divider + '40',
|
||||
},
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: spacing.md,
|
||||
paddingTop: spacing.sm,
|
||||
},
|
||||
cardIconContainer: {
|
||||
width: 36,
|
||||
@@ -1247,13 +1237,12 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
marginRight: spacing.sm,
|
||||
},
|
||||
cardTitle: {
|
||||
fontWeight: '700',
|
||||
fontSize: fontSizes.md + 1,
|
||||
fontWeight: '600',
|
||||
fontSize: fontSizes.md,
|
||||
flex: 1,
|
||||
letterSpacing: 0.3,
|
||||
},
|
||||
|
||||
// 公告样式 - 更现代的卡片
|
||||
// 公告样式 - 扁平化
|
||||
announcementContent: {
|
||||
backgroundColor: colors.warning.light + '08',
|
||||
borderRadius: borderRadius.lg,
|
||||
@@ -1324,7 +1313,7 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
fontSize: fontSizes.sm,
|
||||
},
|
||||
|
||||
// 邀请按钮样式 - 更加醒目
|
||||
// 邀请按钮样式 - 扁平化
|
||||
inviteButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -1348,25 +1337,23 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
inviteButtonText: {
|
||||
fontWeight: '700',
|
||||
marginRight: spacing.xs,
|
||||
letterSpacing: 0.3,
|
||||
},
|
||||
|
||||
// 设置项样式 - 更加扁平
|
||||
// 设置项样式 - 扁平化:无圆角背景,简洁分割线
|
||||
settingsList: {
|
||||
marginTop: spacing.sm,
|
||||
marginTop: 0,
|
||||
},
|
||||
settingItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: spacing.md,
|
||||
borderRadius: borderRadius.md,
|
||||
paddingHorizontal: spacing.sm,
|
||||
marginLeft: -spacing.sm,
|
||||
marginRight: -spacing.sm,
|
||||
paddingHorizontal: 0,
|
||||
marginLeft: 0,
|
||||
marginRight: 0,
|
||||
},
|
||||
settingIconContainer: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: borderRadius.md,
|
||||
backgroundColor: colors.background.default,
|
||||
justifyContent: 'center',
|
||||
@@ -1384,18 +1371,16 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
fontWeight: '600',
|
||||
},
|
||||
settingDivider: {
|
||||
marginLeft: 56,
|
||||
marginLeft: 52,
|
||||
width: 'auto',
|
||||
marginVertical: spacing.xs,
|
||||
marginVertical: 0,
|
||||
opacity: 0.5,
|
||||
},
|
||||
|
||||
// 危险操作卡片 - 更加醒目
|
||||
// 危险操作区域 - 扁平化
|
||||
dangerCard: {
|
||||
padding: 0,
|
||||
overflow: 'hidden',
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.error.light + '30',
|
||||
marginTop: spacing.sm,
|
||||
},
|
||||
dangerButton: {
|
||||
@@ -1404,14 +1389,12 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
justifyContent: 'center',
|
||||
paddingVertical: spacing.lg,
|
||||
gap: spacing.sm,
|
||||
backgroundColor: colors.error.light + '08',
|
||||
},
|
||||
dangerButtonText: {
|
||||
fontWeight: '700',
|
||||
letterSpacing: 0.3,
|
||||
fontWeight: '600',
|
||||
},
|
||||
|
||||
// 模态框样式 - 更现代
|
||||
// 模态框样式 - 扁平化
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.45)',
|
||||
@@ -1440,20 +1423,18 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
minWidth: 60,
|
||||
},
|
||||
modalTitle: {
|
||||
fontWeight: '800',
|
||||
fontSize: fontSizes.xl + 1,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: '700',
|
||||
fontSize: fontSizes.xl,
|
||||
},
|
||||
saveButton: {
|
||||
fontWeight: '700',
|
||||
letterSpacing: 0.3,
|
||||
fontWeight: '600',
|
||||
},
|
||||
confirmButton: {
|
||||
fontWeight: '700',
|
||||
fontWeight: '600',
|
||||
textAlign: 'right',
|
||||
},
|
||||
|
||||
// 编辑表单样式 - 更现代
|
||||
// 编辑表单样式 - 扁平化
|
||||
editForm: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
@@ -1465,7 +1446,7 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
width: 90,
|
||||
height: 90,
|
||||
borderRadius: 45,
|
||||
borderWidth: 3,
|
||||
borderWidth: 2,
|
||||
borderColor: colors.primary.light + '40',
|
||||
},
|
||||
editAvatarPlaceholder: {
|
||||
@@ -1496,9 +1477,8 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
},
|
||||
editLabel: {
|
||||
marginBottom: spacing.sm,
|
||||
fontWeight: '700',
|
||||
fontSize: fontSizes.sm + 1,
|
||||
letterSpacing: 0.3,
|
||||
fontWeight: '600',
|
||||
fontSize: fontSizes.sm,
|
||||
},
|
||||
editInput: {
|
||||
backgroundColor: colors.background.default,
|
||||
@@ -1507,7 +1487,7 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
paddingVertical: spacing.md,
|
||||
fontSize: fontSizes.md,
|
||||
color: colors.text.primary,
|
||||
borderWidth: 1.5,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.divider + '80',
|
||||
fontWeight: '400',
|
||||
},
|
||||
@@ -1533,7 +1513,7 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
padding: spacing.md,
|
||||
fontSize: fontSizes.md,
|
||||
color: colors.text.primary,
|
||||
borderWidth: 1.5,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.divider + '80',
|
||||
lineHeight: 22,
|
||||
textAlignVertical: 'top',
|
||||
@@ -1553,14 +1533,14 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
alignItems: 'center',
|
||||
padding: spacing.md,
|
||||
borderRadius: borderRadius.lg,
|
||||
borderWidth: 1.5,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.divider + '80',
|
||||
backgroundColor: colors.background.default,
|
||||
},
|
||||
joinTypeItemSelected: {
|
||||
borderColor: colors.primary.main,
|
||||
backgroundColor: colors.primary.light + '12',
|
||||
borderWidth: 2,
|
||||
borderWidth: 1.5,
|
||||
},
|
||||
joinTypeIcon: {
|
||||
width: 48,
|
||||
@@ -1602,7 +1582,7 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
},
|
||||
transferItemSelected: {
|
||||
backgroundColor: colors.primary.light + '12',
|
||||
borderWidth: 1.5,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.primary.light + '60',
|
||||
},
|
||||
transferItemInfo: {
|
||||
@@ -1681,7 +1661,7 @@ function createGroupInfoStyles(colors: AppColors) {
|
||||
},
|
||||
friendItemSelected: {
|
||||
backgroundColor: colors.primary.light + '12',
|
||||
borderWidth: 1.5,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.primary.light + '60',
|
||||
},
|
||||
friendInfo: {
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useRouter, useLocalSearchParams } from 'expo-router';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { spacing, borderRadius, shadows, useAppColors, type AppColors } from '../../theme';
|
||||
import { spacing, borderRadius, shadows, fontSizes, useAppColors, type AppColors } from '../../theme';
|
||||
import { useAuthStore } from '../../stores';
|
||||
import { authService } from '@/services/auth';
|
||||
import { messageService } from '@/services/message';
|
||||
@@ -336,7 +336,7 @@ const PrivateChatInfoScreen: React.FC = () => {
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* 用户基本信息卡片 */}
|
||||
{/* 用户基本信息 */}
|
||||
<View style={styles.headerCard}>
|
||||
<TouchableOpacity
|
||||
style={styles.userHeader}
|
||||
@@ -345,7 +345,7 @@ const PrivateChatInfoScreen: React.FC = () => {
|
||||
>
|
||||
<Avatar
|
||||
source={displayUser.avatar}
|
||||
size={90}
|
||||
size={80}
|
||||
name={displayUser.nickname || ''}
|
||||
/>
|
||||
<View style={styles.userInfo}>
|
||||
@@ -421,20 +421,20 @@ function createPrivateChatInfoStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.background.default,
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
pageHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm,
|
||||
paddingHorizontal: spacing.lg,
|
||||
paddingVertical: spacing.md,
|
||||
backgroundColor: colors.background.paper,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.divider,
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
pageTitle: {
|
||||
fontWeight: '600',
|
||||
fontSize: fontSizes.xl,
|
||||
},
|
||||
pageHeaderPlaceholder: {
|
||||
width: 40,
|
||||
@@ -444,15 +444,13 @@ function createPrivateChatInfoStyles(colors: AppColors) {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingHorizontal: spacing.lg,
|
||||
paddingBottom: spacing.xl,
|
||||
},
|
||||
headerCard: {
|
||||
backgroundColor: colors.background.paper,
|
||||
marginHorizontal: spacing.md,
|
||||
marginTop: spacing.md,
|
||||
borderRadius: borderRadius.lg,
|
||||
padding: spacing.lg,
|
||||
...shadows.sm,
|
||||
paddingVertical: spacing.lg,
|
||||
marginBottom: spacing.md,
|
||||
},
|
||||
userHeader: {
|
||||
flexDirection: 'row',
|
||||
@@ -460,58 +458,61 @@ function createPrivateChatInfoStyles(colors: AppColors) {
|
||||
},
|
||||
userInfo: {
|
||||
flex: 1,
|
||||
marginLeft: spacing.md,
|
||||
marginLeft: spacing.lg,
|
||||
},
|
||||
userName: {
|
||||
fontWeight: '600',
|
||||
fontWeight: '700',
|
||||
fontSize: fontSizes['2xl'],
|
||||
marginBottom: spacing.xs,
|
||||
},
|
||||
section: {
|
||||
marginTop: spacing.lg,
|
||||
paddingHorizontal: spacing.md,
|
||||
},
|
||||
sectionTitle: {
|
||||
marginBottom: spacing.sm,
|
||||
marginLeft: spacing.sm,
|
||||
fontWeight: '500',
|
||||
fontSize: fontSizes.sm,
|
||||
color: colors.text.secondary,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: colors.background.paper,
|
||||
borderRadius: borderRadius.lg,
|
||||
...shadows.sm,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
settingItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: spacing.sm,
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.md,
|
||||
paddingHorizontal: 0,
|
||||
},
|
||||
settingIconContainer: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: borderRadius.md,
|
||||
backgroundColor: colors.primary.light + '20',
|
||||
backgroundColor: colors.background.default,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: spacing.md,
|
||||
},
|
||||
settingIconDanger: {
|
||||
backgroundColor: colors.error.light + '20',
|
||||
backgroundColor: colors.error.light + '15',
|
||||
},
|
||||
settingTitle: {
|
||||
flex: 1,
|
||||
},
|
||||
dangerText: {
|
||||
color: colors.error.main,
|
||||
fontWeight: '600',
|
||||
},
|
||||
divider: {
|
||||
marginLeft: spacing.xl + 36,
|
||||
marginLeft: 52,
|
||||
width: 'auto',
|
||||
marginVertical: spacing.xs,
|
||||
marginVertical: 0,
|
||||
},
|
||||
deleteButton: {
|
||||
marginTop: spacing.sm,
|
||||
borderRadius: borderRadius.lg,
|
||||
height: 52,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { View, TouchableOpacity, ActivityIndicator, Modal, Alert, Dimensions, StyleSheet, FlatList, ListRenderItem, Platform } from 'react-native';
|
||||
import { View, TouchableOpacity, ActivityIndicator, Modal, Alert, Dimensions, StyleSheet, Platform } from 'react-native';
|
||||
import { FlashList, ListRenderItem } from '@shopify/flash-list';
|
||||
import { Image as ExpoImage } from 'expo-image';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
@@ -72,8 +73,7 @@ export const EmojiPanel: React.FC<EmojiPanelProps> = ({
|
||||
return StyleSheet.create({
|
||||
...baseStyles,
|
||||
emojiContainer: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
flex: 1,
|
||||
padding: spacing.sm,
|
||||
},
|
||||
emojiItem: {
|
||||
@@ -160,26 +160,19 @@ export const EmojiPanel: React.FC<EmojiPanelProps> = ({
|
||||
</View>
|
||||
), [styles.emojiItem, styles.emojiText, handleEmojiPress]);
|
||||
|
||||
// 渲染 Emoji 面板(使用 FlatList 虚拟化,只渲染可见行)
|
||||
// 渲染 Emoji 面板(使用 FlashList 虚拟化,只渲染可见行)
|
||||
const renderEmojiPanel = () => (
|
||||
<FlatList
|
||||
key="emoji-flatlist"
|
||||
data={emojiRows}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderEmojiRow}
|
||||
contentContainerStyle={styles.emojiContainer}
|
||||
showsVerticalScrollIndicator={true}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
initialNumToRender={12}
|
||||
maxToRenderPerBatch={12}
|
||||
windowSize={5}
|
||||
removeClippedSubviews={true}
|
||||
getItemLayout={(_data, index) => ({
|
||||
length: 60,
|
||||
offset: 60 * index,
|
||||
index,
|
||||
})}
|
||||
/>
|
||||
<View style={styles.emojiContainer}>
|
||||
<FlashList
|
||||
key="emoji-flatlist"
|
||||
data={emojiRows}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderEmojiRow}
|
||||
showsVerticalScrollIndicator={true}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
// 添加表情(从相册选择)
|
||||
@@ -347,20 +340,18 @@ export const EmojiPanel: React.FC<EmojiPanelProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
key="sticker-flatlist"
|
||||
data={stickerListData}
|
||||
numColumns={4}
|
||||
keyExtractor={(item) => item.type === 'manage' ? 'manage-button' : item.sticker.id}
|
||||
renderItem={renderStickerListItem}
|
||||
contentContainerStyle={styles.stickerGrid}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
initialNumToRender={16}
|
||||
maxToRenderPerBatch={16}
|
||||
windowSize={7}
|
||||
removeClippedSubviews={true}
|
||||
/>
|
||||
<View style={styles.stickerGrid}>
|
||||
<FlashList
|
||||
key="sticker-flatlist"
|
||||
data={stickerListData}
|
||||
numColumns={4}
|
||||
keyExtractor={(item) => item.type === 'manage' ? 'manage-button' : item.sticker.id}
|
||||
renderItem={renderStickerListItem}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -494,19 +485,17 @@ export const EmojiPanel: React.FC<EmojiPanelProps> = ({
|
||||
<ActivityIndicator size="large" color={colors.primary.main} />
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
data={manageListData}
|
||||
numColumns={4}
|
||||
keyExtractor={(item) => item.type === 'add' ? 'manage-add-button' : item.sticker.id}
|
||||
renderItem={renderManageListItem}
|
||||
contentContainerStyle={styles.manageStickerGrid}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
initialNumToRender={16}
|
||||
maxToRenderPerBatch={16}
|
||||
windowSize={7}
|
||||
removeClippedSubviews={true}
|
||||
/>
|
||||
<View style={styles.manageStickerGrid}>
|
||||
<FlashList
|
||||
data={manageListData}
|
||||
numColumns={4}
|
||||
keyExtractor={(item) => item.type === 'add' ? 'manage-add-button' : item.sticker.id}
|
||||
renderItem={renderManageListItem}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 底部操作栏(管理模式下显示) */}
|
||||
|
||||
@@ -427,7 +427,7 @@ const MessageBubbleInner: React.FC<MessageBubbleProps> = ({
|
||||
<View style={[styles.messageContent, isMe ? styles.myMessageContentPanel : null]}>
|
||||
{/* 群聊模式:显示发送者昵称 */}
|
||||
{isGroupChat && senderInfo && (
|
||||
<Text style={[styles.senderName, isMe && styles.mySenderName]}>{senderInfo.nickname}</Text>
|
||||
<Text style={[styles.senderName, isMe ? styles.mySenderName : {}]}>{senderInfo.nickname}</Text>
|
||||
)}
|
||||
|
||||
{renderMessageContent()}
|
||||
|
||||
@@ -711,8 +711,7 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: {
|
||||
marginTop: spacing.xs,
|
||||
},
|
||||
stickerGrid: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
flex: 1,
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingTop: spacing.md,
|
||||
paddingBottom: spacing.xl,
|
||||
@@ -1259,8 +1258,7 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: {
|
||||
color: colors.chat.textSecondary,
|
||||
},
|
||||
manageStickerGrid: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
flex: 1,
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingTop: spacing.md,
|
||||
paddingBottom: spacing.xl,
|
||||
|
||||
@@ -562,8 +562,17 @@ export const useChatScreen = (props?: ChatScreenProps) => {
|
||||
const jumpToLatestMessages = useCallback(() => {
|
||||
suppressAutoFollowRef.current = false;
|
||||
isBrowsingHistoryRef.current = false;
|
||||
scrollToLatest(true, true, 'jump-latest-button');
|
||||
}, [scrollToLatest]);
|
||||
// FlashList 在 inverted 模式下 scrollToOffset({ offset: 0 }) 可能无法真正滚到最底部,
|
||||
// 因为列表内容高度变化后最小 offset 可能不是 0。先尝试滚到负值确保到底,再补偿回 0。
|
||||
isProgrammaticScrollRef.current = true;
|
||||
flatListRef.current?.scrollToOffset({ offset: -99999, animated: false });
|
||||
requestAnimationFrame(() => {
|
||||
flatListRef.current?.scrollToOffset({ offset: 0, animated: true });
|
||||
setTimeout(() => {
|
||||
isProgrammaticScrollRef.current = false;
|
||||
}, 220);
|
||||
});
|
||||
}, [flatListRef]);
|
||||
|
||||
const setBrowsingHistory = useCallback((browsing: boolean) => {
|
||||
isBrowsingHistoryRef.current = browsing;
|
||||
|
||||
Reference in New Issue
Block a user