diff --git a/src/screens/message/components/ChatScreen/EmojiPanel.tsx b/src/screens/message/components/ChatScreen/EmojiPanel.tsx index dbad4ad..cf8ccac 100644 --- a/src/screens/message/components/ChatScreen/EmojiPanel.tsx +++ b/src/screens/message/components/ChatScreen/EmojiPanel.tsx @@ -52,6 +52,7 @@ export const EmojiPanel: React.FC = ({ // 响应式布局 const isWideScreen = useBreakpointGTE('lg'); const isTablet = useBreakpointGTE('md'); + const { width: screenWidth } = useResponsive(); // 获取表情尺寸 const emojiSize = useMemo(() => { @@ -60,12 +61,14 @@ export const EmojiPanel: React.FC = ({ return EMOJI_SIZES.mobile; }, [isWideScreen, isTablet]); - // 表情网格列数 + // 表情网格列数 - 根据实际屏幕宽度动态计算 const emojiColumns = useMemo(() => { - if (isWideScreen) return 10; - if (isTablet) return 9; - return 8; - }, [isWideScreen, isTablet]); + // 根据屏幕宽度计算列数,每个表情项约44px宽(包含间距) + const itemWidth = emojiSize.itemHeight; + const calculatedColumns = Math.floor(screenWidth / itemWidth); + // 限制最小和最大列数 + return Math.max(8, Math.min(calculatedColumns, 20)); + }, [screenWidth, emojiSize.itemHeight]); // 合并样式 const styles = useMemo(() => { @@ -104,36 +107,31 @@ export const EmojiPanel: React.FC = ({ } }, [activeTab, loadStickers]); - const renderEmojiItem: ListRenderItem = useCallback(({ item }) => ( + // 渲染单个emoji + const renderEmojiButton = (emoji: string, index: number) => ( onInsertEmoji(item)} + key={`${emoji}-${index}`} + style={[styles.emojiItem, { width: 40, margin: 4 }]} + onPress={() => onInsertEmoji(emoji)} activeOpacity={0.7} > - {item} + {emoji} - ), [styles.emojiItem, styles.emojiText, onInsertEmoji]); + ); - // 渲染 Emoji 面板(使用 FlatList 虚拟化,降低首次打开卡顿) + // 渲染 Emoji 面板 - 使用ScrollView包裹实现滚动 const renderEmojiPanel = () => ( `${item}-${index}`} - renderItem={renderEmojiItem} - contentContainerStyle={styles.emojiGrid} - showsVerticalScrollIndicator={false} + renderItem={({ item, index }) => renderEmojiButton(item, index)} + contentContainerStyle={[styles.emojiGrid, { flexDirection: 'row', flexWrap: 'wrap', padding: spacing.sm }]} + showsVerticalScrollIndicator={true} keyboardShouldPersistTaps="handled" - initialNumToRender={emojiColumns * 4} - maxToRenderPerBatch={emojiColumns * 4} - windowSize={7} + initialNumToRender={100} + maxToRenderPerBatch={50} + windowSize={10} removeClippedSubviews={true} - getItemLayout={(_, index) => ({ - length: emojiSize.itemHeight, - offset: emojiSize.itemHeight * Math.floor(index / emojiColumns), - index, - })} /> ); diff --git a/src/screens/message/components/EmbeddedChat.tsx b/src/screens/message/components/EmbeddedChat.tsx index 9aa2fe3..1879dd5 100644 --- a/src/screens/message/components/EmbeddedChat.tsx +++ b/src/screens/message/components/EmbeddedChat.tsx @@ -29,6 +29,9 @@ import { useMarkAsRead } from '../../../stores/messageManagerHooks'; import { messageService } from '../../../services'; import { GroupInfoPanel } from './ChatScreen/GroupInfoPanel'; import { LongPressMenu, MenuPosition, GroupMessage } from './ChatScreen'; +import { EmojiPanel } from './ChatScreen/EmojiPanel'; +import { MORE_ACTIONS } from './ChatScreen/constants'; +import * as ImagePicker from 'expo-image-picker'; const { width: screenWidth } = Dimensions.get('window'); const PANEL_WIDTH = 360; @@ -69,6 +72,12 @@ export const EmbeddedChat: React.FC = ({ conversation, onBack // 回复状态 const [replyingToMessage, setReplyingToMessage] = useState(null); + // 表情面板状态 + const [showEmojiPanel, setShowEmojiPanel] = useState(false); + + // 更多功能面板状态 + const [showMorePanel, setShowMorePanel] = useState(false); + // 群信息面板动画 const slideAnim = useRef(new Animated.Value(PANEL_WIDTH)).current; const fadeAnim = useRef(new Animated.Value(0)).current; @@ -265,6 +274,94 @@ export const EmbeddedChat: React.FC = ({ conversation, onBack console.error('[EmbeddedChat] 删除消息失败:', error); } }; + + // 处理插入表情 + const handleInsertEmoji = (emoji: string) => { + setInputText(prev => prev + emoji); + }; + + // 处理插入自定义表情 + const handleInsertSticker = async (stickerUrl: string) => { + try { + setSending(true); + const segments: MessageSegment[] = [{ + type: 'image', + data: { + url: stickerUrl, + thumbnail_url: stickerUrl, + width: 200, + height: 200 + } + }]; + await messageManager.sendMessage(String(conversation.id), segments); + setShowEmojiPanel(false); + } catch (error) { + console.error('[EmbeddedChat] 发送表情失败:', error); + } finally { + setSending(false); + } + }; + + // 处理选择图片 + const handlePickImage = async () => { + try { + const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync(); + if (!permissionResult.granted) { + return; + } + + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ImagePicker.MediaTypeOptions.Images, + quality: 0.8, + allowsMultipleSelection: true, + selectionLimit: 9, + }); + + if (!result.canceled && result.assets.length > 0) { + setSending(true); + + for (const asset of result.assets) { + const segments: MessageSegment[] = [{ + type: 'image', + data: { + url: asset.uri, + thumbnail_url: asset.uri, + width: asset.width || 800, + height: asset.height || 600 + } + }]; + await messageManager.sendMessage(String(conversation.id), segments); + } + + setShowMorePanel(false); + } + } catch (error) { + console.error('[EmbeddedChat] 选择图片失败:', error); + } finally { + setSending(false); + } + }; + + // 处理更多功能项点击 + const handleMoreAction = (actionId: string) => { + switch (actionId) { + case 'image': + handlePickImage(); + break; + case 'camera': + // TODO: 实现相机拍摄 + console.log('Camera'); + break; + case 'file': + // TODO: 实现文件选择 + console.log('File'); + break; + case 'location': + // TODO: 实现位置分享 + console.log('Location'); + break; + } + }; // 格式化时间 const formatTime = (dateString: string) => { @@ -520,10 +617,56 @@ export const EmbeddedChat: React.FC = ({ conversation, onBack behavior={Platform.OS === 'ios' ? 'padding' : undefined} keyboardVerticalOffset={Platform.OS === 'ios' ? 90 : 0} > + {/* 表情面板 */} + {showEmojiPanel && ( + + setShowEmojiPanel(false)} + /> + + )} + + {/* 更多功能面板 */} + {showMorePanel && ( + + + 更多功能 + setShowMorePanel(false)}> + + + + + {MORE_ACTIONS.map(action => ( + handleMoreAction(action.id)} + > + + + + {action.name} + + ))} + + + )} - - + { + setShowEmojiPanel(false); + setShowMorePanel(!showMorePanel); + }} + > + @@ -544,8 +687,8 @@ export const EmbeddedChat: React.FC = ({ conversation, onBack /> - - + { setShowMorePanel(false); setShowEmojiPanel(!showEmojiPanel); }}> +