/** * 表情面板组件 * 支持 Emoji 和自定义表情 Tab 切换 * 支持响应式布局(平板/桌面端显示更大的表情) */ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { View, TouchableOpacity, ActivityIndicator, Modal, Alert, Dimensions, StyleSheet, FlatList, ListRenderItem } from 'react-native'; import { Image as ExpoImage } from 'expo-image'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import * as ImagePicker from 'expo-image-picker'; import { Text } from '../../../../components/common'; import { chatScreenStyles as baseStyles } from './styles'; import { useResponsive, useBreakpointGTE } from '../../../../hooks/useResponsive'; import { EMOJIS } from './constants'; import { CustomSticker, getCustomStickers, batchDeleteStickers, addStickerFromUrl } from '../../../../services/stickerService'; import { colors, spacing } from '../../../../theme'; const { height: SCREEN_HEIGHT } = Dimensions.get('window'); // 表情尺寸配置 const EMOJI_SIZES = { mobile: { size: 28, itemHeight: 52 }, tablet: { size: 32, itemHeight: 60 }, desktop: { size: 36, itemHeight: 68 }, }; // Tab 类型 type TabType = 'emoji' | 'sticker'; type StickerListItem = { type: 'manage' } | { type: 'sticker'; sticker: CustomSticker }; type ManageListItem = { type: 'add' } | { type: 'sticker'; sticker: CustomSticker }; interface EmojiPanelProps { onInsertEmoji: (emoji: string) => void; onInsertSticker: (stickerUrl: string) => void; onClose: () => void; } export const EmojiPanel: React.FC = ({ onInsertEmoji, onInsertSticker, onClose, }) => { const [activeTab, setActiveTab] = useState('emoji'); const [stickers, setStickers] = useState([]); const [loadingStickers, setLoadingStickers] = useState(false); const [showManageModal, setShowManageModal] = useState(false); const [selectedStickers, setSelectedStickers] = useState>(new Set()); const [manageMode, setManageMode] = useState(false); const [addingSticker, setAddingSticker] = useState(false); // 响应式布局 const isWideScreen = useBreakpointGTE('lg'); const isTablet = useBreakpointGTE('md'); // 获取表情尺寸 const emojiSize = useMemo(() => { if (isWideScreen) return EMOJI_SIZES.desktop; if (isTablet) return EMOJI_SIZES.tablet; return EMOJI_SIZES.mobile; }, [isWideScreen, isTablet]); // 表情网格列数 const emojiColumns = useMemo(() => { if (isWideScreen) return 10; if (isTablet) return 9; return 8; }, [isWideScreen, isTablet]); // 合并样式 const styles = useMemo(() => { return StyleSheet.create({ ...baseStyles, emojiItem: { ...baseStyles.emojiItem, width: `${100 / emojiColumns}%`, height: emojiSize.itemHeight, }, emojiText: { ...baseStyles.emojiText, fontSize: emojiSize.size, lineHeight: emojiSize.size + 6, }, }); }, [emojiSize, emojiColumns]); // 加载自定义表情 const loadStickers = useCallback(async () => { setLoadingStickers(true); try { const data = await getCustomStickers(); setStickers(data); } catch (error) { console.error('加载自定义表情失败:', error); } finally { setLoadingStickers(false); } }, []); // 切换到自定义表情 Tab 时加载数据 useEffect(() => { if (activeTab === 'sticker') { loadStickers(); } }, [activeTab, loadStickers]); const renderEmojiItem: ListRenderItem = useCallback(({ item }) => ( onInsertEmoji(item)} activeOpacity={0.7} > {item} ), [styles.emojiItem, styles.emojiText, onInsertEmoji]); // 渲染 Emoji 面板(使用 FlatList 虚拟化,降低首次打开卡顿) const renderEmojiPanel = () => ( `${item}-${index}`} renderItem={renderEmojiItem} contentContainerStyle={styles.emojiGrid} showsVerticalScrollIndicator={false} keyboardShouldPersistTaps="handled" initialNumToRender={emojiColumns * 4} maxToRenderPerBatch={emojiColumns * 4} windowSize={7} removeClippedSubviews={true} getItemLayout={(_, index) => ({ length: emojiSize.itemHeight, offset: emojiSize.itemHeight * Math.floor(index / emojiColumns), index, })} /> ); // 添加表情(从相册选择) const handleAddSticker = async () => { try { const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (!permissionResult.granted) { Alert.alert('提示', '需要相册权限才能添加表情'); return; } const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, quality: 0.8, allowsEditing: false, }); if (!result.canceled && result.assets[0]) { setAddingSticker(true); const asset = result.assets[0]; try { const sticker = await addStickerFromUrl( asset.uri, asset.width, asset.height ); if (sticker) { await loadStickers(); Alert.alert('成功', '表情添加成功'); } else { Alert.alert('提示', '该表情已存在'); } } catch (error) { console.error('添加表情失败:', error); Alert.alert('错误', '添加表情失败,请重试'); } finally { setAddingSticker(false); } } } catch (error) { console.error('选择图片失败:', error); Alert.alert('错误', '选择图片失败'); } }; // 打开管理界面 const handleOpenManage = () => { setShowManageModal(true); setManageMode(false); setSelectedStickers(new Set()); }; // 关闭管理界面 const handleCloseManage = () => { setShowManageModal(false); setManageMode(false); setSelectedStickers(new Set()); }; // 切换选择表情 const toggleStickerSelection = (stickerId: string) => { const newSelected = new Set(selectedStickers); if (newSelected.has(stickerId)) { newSelected.delete(stickerId); } else { newSelected.add(stickerId); } setSelectedStickers(newSelected); }; // 删除选中的表情 const handleDeleteSelected = async () => { if (selectedStickers.size === 0) { Alert.alert('提示', '请先选择要删除的表情'); return; } Alert.alert( '确认删除', `确定要删除选中的 ${selectedStickers.size} 个表情吗?`, [ { text: '取消', style: 'cancel' }, { text: '删除', style: 'destructive', onPress: async () => { const stickerIds = Array.from(selectedStickers); const result = await batchDeleteStickers(stickerIds); // 重新加载表情列表 await loadStickers(); setSelectedStickers(new Set()); setManageMode(false); if (result.failed > 0) { Alert.alert('完成', `成功删除 ${result.success} 个表情,失败 ${result.failed} 个`); } else { Alert.alert('完成', `成功删除 ${result.success} 个表情`); } }, }, ] ); }; // 渲染管理按钮(表情面板第一位) const renderManageButton = () => ( ); const stickerListData = useMemo(() => { return [{ type: 'manage' }, ...stickers.map(sticker => ({ type: 'sticker', sticker } as const))]; }, [stickers]); const manageListData = useMemo(() => { const base = stickers.map(sticker => ({ type: 'sticker', sticker } as const)); return manageMode ? base : ([{ type: 'add' } as const, ...base]); }, [stickers, manageMode]); const renderStickerListItem: ListRenderItem = useCallback(({ item }) => { if (item.type === 'manage') { return renderManageButton(); } return ( onInsertSticker(item.sticker.url)} activeOpacity={0.7} > ); }, [styles.stickerItem, styles.stickerImage, onInsertSticker]); // 渲染自定义表情面板 const renderStickerPanel = () => { if (loadingStickers) { return ( ); } return ( 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} /> ); }; // 渲染管理界面中的添加按钮(第一位) const renderManageAddButton = () => ( {addingSticker ? ( ) : ( 添加 )} ); // 渲染管理界面中的表情项 const renderManageStickerItem = (sticker: CustomSticker) => { const isSelected = selectedStickers.has(sticker.id); return ( { if (manageMode) { toggleStickerSelection(sticker.id); } }} onLongPress={() => { if (!manageMode) { setManageMode(true); toggleStickerSelection(sticker.id); } }} activeOpacity={0.7} > {manageMode && ( {isSelected && ( )} )} ); }; const renderManageListItem: ListRenderItem = useCallback(({ item }) => { if (item.type === 'add') { return renderManageAddButton(); } return renderManageStickerItem(item.sticker); }, [addingSticker, manageMode, selectedStickers, styles.stickerItem, styles.stickerImage]); // 渲染管理模态框(2/3 高度) const renderManageModal = () => ( {/* 拖动指示器 */} {/* 头部 */} 表情管理 {manageMode ? ( { setManageMode(false); setSelectedStickers(new Set()); }} style={styles.manageHeaderButton} > 完成 ) : ( setManageMode(true)} style={styles.manageHeaderButton} > 编辑 )} {/* 分隔线 */} {/* 表情网格 */} {loadingStickers ? ( ) : ( 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} /> )} {/* 底部操作栏(管理模式下显示) */} {manageMode && ( { if (selectedStickers.size === stickers.length) { setSelectedStickers(new Set()); } else { setSelectedStickers(new Set(stickers.map(s => s.id))); } }} > {selectedStickers.size === stickers.length ? '取消全选' : '全选'} 删除{selectedStickers.size > 0 && ` (${selectedStickers.size})`} )} {/* 使用提示 */} {!manageMode && stickers.length > 0 && ( 长按表情可删除 )} {/* 空状态 */} {!loadingStickers && stickers.length === 0 && !manageMode && ( 暂无表情 点击上方的 + 添加您喜欢的表情 )} ); return ( {/* 内容区域 */} {activeTab === 'emoji' ? renderEmojiPanel() : renderStickerPanel()} {/* 底部 Tab 栏 */} setActiveTab('emoji')} > 😊 setActiveTab('sticker')} > {/* 表情管理模态框 */} {renderManageModal()} ); }; export default EmojiPanel;