feat(ChatScreen): 优化聊天界面 UI 和交互体验
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 2m40s
Frontend CI / ota-android (push) Successful in 13m12s
Frontend CI / build-android-apk (push) Successful in 55m45s

Made-with: Cursor
This commit is contained in:
lafay
2026-03-29 03:04:54 +08:00
parent e969e5bad4
commit 584d98307c
8 changed files with 310 additions and 134 deletions

View File

@@ -576,6 +576,7 @@ export const ChatScreen: React.FC = () => {
<MorePanel <MorePanel
onAction={handleMoreAction} onAction={handleMoreAction}
disabledActionIds={!isGroupChat && !canSendPrivateImage ? ['image', 'camera'] : []} disabledActionIds={!isGroupChat && !canSendPrivateImage ? ['image', 'camera'] : []}
isGroupChat={isGroupChat}
/> />
</View> </View>
)} )}

View File

@@ -5,7 +5,7 @@
* 使用游标分页 * 使用游标分页
*/ */
import React, { useState, useEffect, useCallback, useMemo } from 'react'; import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { import {
View, View,
StyleSheet, StyleSheet,
@@ -19,7 +19,7 @@ import {
Dimensions, Dimensions,
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useLocalSearchParams } from 'expo-router'; import { useLocalSearchParams, useRouter } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons'; import { MaterialCommunityIcons } from '@expo/vector-icons';
import { spacing, fontSizes, borderRadius, useAppColors, type AppColors } from '../../theme'; import { spacing, fontSizes, borderRadius, useAppColors, type AppColors } from '../../theme';
import { useAuthStore } from '../../stores'; import { useAuthStore } from '../../stores';
@@ -30,7 +30,7 @@ import {
IGroupMemberListPagedSource, IGroupMemberListPagedSource,
} from '../../stores/groupMemberListSources'; } from '../../stores/groupMemberListSources';
import { enrichGroupMembersWithUserProfiles } from '../../stores/groupMemberProfileResolver'; import { enrichGroupMembersWithUserProfiles } from '../../stores/groupMemberProfileResolver';
import { Avatar, Text, Button, Loading, EmptyState, Divider, ResponsiveContainer } from '../../components/common'; import { Avatar, Text, Button, Loading, EmptyState, Divider, ResponsiveContainer, AppBackButton } from '../../components/common';
import { useResponsive, useBreakpointGTE } from '../../hooks/useResponsive'; import { useResponsive, useBreakpointGTE } from '../../hooks/useResponsive';
import { useCursorPagination } from '../../hooks/useCursorPagination'; import { useCursorPagination } from '../../hooks/useCursorPagination';
import { import {
@@ -57,6 +57,7 @@ interface MemberGroup {
const GroupMembersScreen: React.FC = () => { const GroupMembersScreen: React.FC = () => {
const colors = useAppColors(); const colors = useAppColors();
const styles = useMemo(() => createGroupMembersStyles(colors), [colors]); const styles = useMemo(() => createGroupMembersStyles(colors), [colors]);
const router = useRouter();
const { groupId: groupIdParam } = useLocalSearchParams<{ groupId?: string | string[] }>(); const { groupId: groupIdParam } = useLocalSearchParams<{ groupId?: string | string[] }>();
const groupId = firstRouteParam(groupIdParam) ?? ''; const groupId = firstRouteParam(groupIdParam) ?? '';
const { currentUser } = useAuthStore(); const { currentUser } = useAuthStore();
@@ -149,11 +150,14 @@ const GroupMembersScreen: React.FC = () => {
setLoading(false); setLoading(false);
}, [refresh]); }, [refresh]);
// 初始加载 // 初始加载 - 使用 ref 避免依赖变化导致的无限循环
const initialLoadRef = React.useRef(false);
useEffect(() => { useEffect(() => {
if (!groupId) return; if (!groupId || initialLoadRef.current) return;
initialLoadRef.current = true;
refresh(); refresh();
}, [groupId, refresh]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [groupId]);
// 按角色分组 // 按角色分组
const groupMembers = useCallback((): MemberGroup[] => { const groupMembers = useCallback((): MemberGroup[] => {
@@ -640,7 +644,13 @@ const GroupMembersScreen: React.FC = () => {
}; };
return ( return (
<SafeAreaView style={styles.container} edges={['bottom']}> <SafeAreaView style={styles.container} edges={['top', 'bottom']}>
{/* Header */}
<View style={styles.pageHeader}>
<AppBackButton onPress={() => router.back()} />
<Text variant="h3" style={styles.pageTitle}></Text>
<View style={styles.pageHeaderPlaceholder} />
</View>
{loading ? <Loading /> : renderGroupedList()} {loading ? <Loading /> : renderGroupedList()}
{renderActionModal()} {renderActionModal()}
{renderNicknameModal()} {renderNicknameModal()}
@@ -654,6 +664,23 @@ function createGroupMembersStyles(colors: AppColors) {
flex: 1, flex: 1,
backgroundColor: colors.background.default, backgroundColor: colors.background.default,
}, },
// Header 样式
pageHeader: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
backgroundColor: colors.background.paper,
borderBottomWidth: 1,
borderBottomColor: colors.divider,
},
pageTitle: {
fontWeight: '600',
},
pageHeaderPlaceholder: {
width: 40,
},
section: { section: {
marginBottom: spacing.md, marginBottom: spacing.md,
}, },

View File

@@ -6,7 +6,7 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { View, TouchableOpacity, StyleSheet } from 'react-native'; import { View, TouchableOpacity, StyleSheet } from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons'; import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Avatar, Text, AppBackButton } from '../../../../components/common'; import { Avatar, Text } from '../../../../components/common';
import { useAppColors, spacing } from '../../../../theme'; import { useAppColors, spacing } from '../../../../theme';
import { useChatScreenStyles } from './styles'; import { useChatScreenStyles } from './styles';
import { useBreakpointGTE } from '../../../../hooks/useResponsive'; import { useBreakpointGTE } from '../../../../hooks/useResponsive';
@@ -73,7 +73,9 @@ export const ChatHeader: React.FC<ChatHeaderProps> = ({
return ( return (
<View style={styles.headerContainer}> <View style={styles.headerContainer}>
<View style={styles.header}> <View style={styles.header}>
<AppBackButton style={styles.backButton} onPress={onBack} /> <TouchableOpacity style={styles.backButton} onPress={onBack} activeOpacity={0.7}>
<MaterialCommunityIcons name="chevron-left" size={28} color={colors.text.primary} />
</TouchableOpacity>
<View style={styles.headerCenter}> <View style={styles.headerCenter}>
<TouchableOpacity <TouchableOpacity
@@ -84,7 +86,12 @@ export const ChatHeader: React.FC<ChatHeaderProps> = ({
{isGroupChat ? ( {isGroupChat ? (
<> <>
<View style={styles.titleLeading}> <View style={styles.titleLeading}>
<MaterialCommunityIcons name="account-group" size={20} color={colors.primary.main} /> <Avatar
source={groupInfo?.avatar || null}
size={36}
name={groupInfo?.name || '群'}
style={styles.groupAvatar}
/>
</View> </View>
<View style={styles.titleContent}> <View style={styles.titleContent}>
<Text style={styles.headerName} numberOfLines={1}>{getDisplayTitle()}</Text> <Text style={styles.headerName} numberOfLines={1}>{getDisplayTitle()}</Text>
@@ -100,7 +107,7 @@ export const ChatHeader: React.FC<ChatHeaderProps> = ({
<> <>
<Avatar <Avatar
source={otherUser?.avatar || null} source={otherUser?.avatar || null}
size={32} size={36}
name={otherUser?.nickname || ''} name={otherUser?.nickname || ''}
/> />
<View style={styles.titleContent}> <View style={styles.titleContent}>
@@ -121,15 +128,17 @@ export const ChatHeader: React.FC<ChatHeaderProps> = ({
<TouchableOpacity <TouchableOpacity
style={styles.moreButton} style={styles.moreButton}
onPress={onGroupInfoPress} onPress={onGroupInfoPress}
activeOpacity={0.7}
> >
<MaterialCommunityIcons name="dots-horizontal" size={22} color={colors.text.primary} /> <MaterialCommunityIcons name="dots-horizontal" size={24} color={colors.text.primary} />
</TouchableOpacity> </TouchableOpacity>
) : ( ) : (
<TouchableOpacity <TouchableOpacity
style={styles.moreButton} style={styles.moreButton}
onPress={onMorePress} onPress={onMorePress}
activeOpacity={0.7}
> >
<MaterialCommunityIcons name="dots-horizontal" size={22} color={colors.text.primary} /> <MaterialCommunityIcons name="dots-horizontal" size={24} color={colors.text.primary} />
</TouchableOpacity> </TouchableOpacity>
)} )}
</View> </View>

View File

@@ -18,7 +18,7 @@ import { useAppColors, spacing } from '../../../../theme';
const { height: SCREEN_HEIGHT } = Dimensions.get('window'); const { height: SCREEN_HEIGHT } = Dimensions.get('window');
// 表情尺寸配置 - 固定宽度 40px使用 flex 布局 // 表情尺寸配置 - 固定宽度 40px使用 flex 布局
const EMOJI_SIZES = { const EMOJI_SIZES = {
mobile: { size: 24, itemWidth: 40, itemHeight: 40 }, mobile: { size: 24, itemWidth: 40, itemHeight: 40 },
tablet: { size: 26, itemWidth: 40, itemHeight: 40 }, tablet: { size: 26, itemWidth: 40, itemHeight: 40 },
@@ -234,19 +234,21 @@ export const EmojiPanel: React.FC<EmojiPanelProps> = ({
); );
}; };
// 渲染管理按钮(表情面板第一位) // 渲染管理按钮(表情面板第一位)- 现代扁平化设计
const renderManageButton = () => ( const renderManageButton = () => (
<TouchableOpacity <TouchableOpacity
key="manage-button" key="manage-button"
style={[styles.stickerItem, styles.stickerManageButton]} style={[styles.stickerItem, stickerButtonStyles.manageButton]}
onPress={handleOpenManage} onPress={handleOpenManage}
activeOpacity={0.7} activeOpacity={0.7}
> >
<MaterialCommunityIcons <View style={stickerButtonStyles.iconContainer}>
name="cog-outline" <MaterialCommunityIcons
size={40} name="cog-outline"
color="#666" size={28}
/> color={colors.chat.textSecondary}
/>
</View>
</TouchableOpacity> </TouchableOpacity>
); );
@@ -515,27 +517,30 @@ export const EmojiPanel: React.FC<EmojiPanelProps> = ({
{activeTab === 'emoji' ? renderEmojiPanel() : renderStickerPanel()} {activeTab === 'emoji' ? renderEmojiPanel() : renderStickerPanel()}
</View> </View>
{/* 底部 Tab 栏 */} {/* 底部 Tab 栏 - 现代胶囊式设计 */}
<View style={styles.panelTabBar}> <View style={styles.panelTabBar}>
<TouchableOpacity <View style={styles.panelTabContainer}>
style={[styles.panelTab, activeTab === 'emoji' && styles.panelTabActive]} <TouchableOpacity
onPress={() => setActiveTab('emoji')} style={[styles.panelTab, activeTab === 'emoji' && styles.panelTabActive]}
> onPress={() => setActiveTab('emoji')}
<Text style={styles.panelTabEmoji}>😊</Text> activeOpacity={0.8}
</TouchableOpacity> >
<TouchableOpacity <Text style={[styles.panelTabEmoji, activeTab === 'emoji' ? { opacity: 1 } : {}]}>😊</Text>
style={[styles.panelTab, activeTab === 'sticker' && styles.panelTabActive]} </TouchableOpacity>
onPress={() => setActiveTab('sticker')} <TouchableOpacity
> style={[styles.panelTab, activeTab === 'sticker' && styles.panelTabActive]}
<MaterialCommunityIcons onPress={() => setActiveTab('sticker')}
name="emoticon-happy-outline" activeOpacity={0.8}
size={24} >
color={activeTab === 'sticker' ? colors.primary.main : '#666'} <MaterialCommunityIcons
/> name="emoticon-happy-outline"
</TouchableOpacity> size={22}
<View style={styles.panelTabDivider} /> color={activeTab === 'sticker' ? colors.primary.main : colors.chat.textSecondary}
<TouchableOpacity style={styles.panelCloseButton} onPress={onClose}> />
<MaterialCommunityIcons name="keyboard" size={24} color="#666" /> </TouchableOpacity>
</View>
<TouchableOpacity style={styles.panelCloseButton} onPress={onClose} activeOpacity={0.7}>
<MaterialCommunityIcons name="keyboard-outline" size={20} color={colors.chat.textSecondary} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@@ -546,3 +551,26 @@ export const EmojiPanel: React.FC<EmojiPanelProps> = ({
}; };
export default EmojiPanel; export default EmojiPanel;
// 自定义表情按钮样式
const stickerButtonStyles = StyleSheet.create({
manageButton: {
backgroundColor: '#F5F5F7',
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
},
iconContainer: {
width: 44,
height: 44,
borderRadius: 12,
backgroundColor: '#FFFFFF',
alignItems: 'center',
justifyContent: 'center',
shadowColor: '#000000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.06,
shadowRadius: 2,
elevation: 1,
},
});

View File

@@ -1,38 +1,81 @@
/** /**
* 更多功能面板组件 * 更多功能面板组件 - 现代扁平化设计
*/ */
import React from 'react'; import React from 'react';
import { View, TouchableOpacity } from 'react-native'; import { View, TouchableOpacity, StyleSheet } from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons'; import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Text } from '../../../../components/common'; import { Text } from '../../../../components/common';
import { useChatScreenStyles } from './styles'; import { useChatScreenStyles } from './styles';
import { MORE_ACTIONS } from './constants'; import { MORE_ACTIONS } from './constants';
import { MorePanelProps } from './types'; import { MorePanelProps } from './types';
// 渐变背景组件
const GradientIcon: React.FC<{
colors: [string, string];
icon: string;
size?: number;
}> = ({ colors, icon, size = 28 }) => {
return (
<View style={[gradientStyles.container, { backgroundColor: colors[0] }]}>
<View style={[gradientStyles.gradientOverlay, { backgroundColor: colors[1] }]} />
<MaterialCommunityIcons name={icon as any} size={size} color="#FFFFFF" />
</View>
);
};
const gradientStyles = StyleSheet.create({
container: {
width: 56,
height: 56,
borderRadius: 18,
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
position: 'relative',
},
gradientOverlay: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: '50%',
opacity: 0.3,
},
});
export const MorePanel: React.FC<MorePanelProps> = ({ export const MorePanel: React.FC<MorePanelProps> = ({
onAction, onAction,
disabledActionIds = [], disabledActionIds = [],
isGroupChat = false,
}) => { }) => {
const styles = useChatScreenStyles(); const styles = useChatScreenStyles();
const disabledSet = new Set(disabledActionIds); const disabledSet = new Set(disabledActionIds);
// 群聊中隐藏语音通话和视频通话
const actions = isGroupChat
? MORE_ACTIONS.filter(action => action.id !== 'voice_call' && action.id !== 'video_call')
: MORE_ACTIONS;
return ( return (
<View style={styles.panelContainer}> <View style={styles.panelContainer}>
<View style={styles.moreGrid}> <View style={styles.moreGrid}>
{MORE_ACTIONS.map((action) => { {actions.map((action) => {
const disabled = disabledSet.has(action.id); const disabled = disabledSet.has(action.id);
return ( return (
<TouchableOpacity <TouchableOpacity
key={action.id} key={action.id}
style={[styles.moreItem, disabled ? { opacity: 0.45 } : null]} style={[styles.moreItem, disabled ? { opacity: 0.4 } : null]}
onPress={() => onAction(action.id)} onPress={() => onAction(action.id)}
disabled={disabled} disabled={disabled}
> activeOpacity={0.7}
<View style={styles.moreIconContainer}> >
<MaterialCommunityIcons name={action.icon as any} size={28} color={action.color} /> <GradientIcon
</View> colors={action.gradientColors || [action.color, action.color]}
<Text style={styles.moreItemText}>{action.name}</Text> icon={action.icon}
</TouchableOpacity> />
<Text style={styles.moreItemText}>{action.name}</Text>
</TouchableOpacity>
); );
})} })}
</View> </View>

View File

@@ -141,14 +141,50 @@ export const EMOJIS = [
'🌃', '🌌', '🌉', '🌫️', '🌊', '🕳️', '💊', '💉', '🌃', '🌌', '🌉', '🌫️', '🌊', '🕳️', '💊', '💉',
]; ];
// 更多功能项 // 更多功能项 - 使用渐变色配置
export const MORE_ACTIONS: MoreAction[] = [ export const MORE_ACTIONS: MoreAction[] = [
{ id: 'voice_call', icon: 'phone', name: '语音通话', color: '#4CAF50' }, {
{ id: 'video_call', icon: 'video', name: '视频通话', color: '#2196F3' }, id: 'voice_call',
{ id: 'image', icon: 'image', name: '图片', color: '#FF6B6B' }, icon: 'phone',
{ id: 'camera', icon: 'camera', name: '拍摄', color: '#4ECDC4' }, name: '语音通话',
{ id: 'file', icon: 'file-document', name: '文件', color: '#45B7D1' }, color: '#4CAF50',
{ id: 'location', icon: 'map-marker', name: '位置', color: '#96CEB4' }, gradientColors: ['#66BB6A', '#43A047'],
},
{
id: 'video_call',
icon: 'video',
name: '视频通话',
color: '#2196F3',
gradientColors: ['#42A5F5', '#1E88E5'],
},
{
id: 'image',
icon: 'image',
name: '图片',
color: '#FF6B35',
gradientColors: ['#FF8A50', '#F4511E'],
},
{
id: 'camera',
icon: 'camera',
name: '拍摄',
color: '#26A69A',
gradientColors: ['#4DB6AC', '#00897B'],
},
{
id: 'file',
icon: 'file-document',
name: '文件',
color: '#5C6BC0',
gradientColors: ['#7986CB', '#3F51B5'],
},
{
id: 'location',
icon: 'map-marker',
name: '位置',
color: '#EC407A',
gradientColors: ['#F06292', '#D81B60'],
},
]; ];
// 消息撤回时间限制(毫秒) // 消息撤回时间限制(毫秒)

View File

@@ -30,13 +30,8 @@ export function createChatScreenStyles(colors: AppColors) {
// 头部 // 头部
headerContainer: { headerContainer: {
backgroundColor: colors.chat.card, backgroundColor: colors.chat.card,
borderBottomWidth: 1, borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: 'rgba(255, 107, 53, 0.12)', borderBottomColor: colors.chat.border,
shadowColor: colors.chat.shadow,
shadowOffset: { width: 0, height: 3 },
shadowOpacity: 0.06,
shadowRadius: 10,
elevation: 5,
}, },
header: { header: {
flexDirection: 'row', flexDirection: 'row',
@@ -45,42 +40,47 @@ export function createChatScreenStyles(colors: AppColors) {
paddingHorizontal: spacing.md, paddingHorizontal: spacing.md,
paddingVertical: spacing.sm + 2, paddingVertical: spacing.sm + 2,
backgroundColor: colors.chat.card, backgroundColor: colors.chat.card,
height: 56,
}, },
backButton: { backButton: {
width: 38, width: 36,
height: 38, height: 36,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
borderRadius: 19, borderRadius: 10,
backgroundColor: `${colors.primary.main}12`, backgroundColor: 'transparent',
borderWidth: 1,
borderColor: `${colors.primary.main}26`,
}, },
headerCenter: { headerCenter: {
alignItems: 'stretch',
flex: 1, flex: 1,
alignItems: 'center',
marginHorizontal: spacing.sm, marginHorizontal: spacing.sm,
}, },
titleRow: { titleRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
borderRadius: 14, gap: spacing.sm,
paddingHorizontal: spacing.sm, paddingHorizontal: spacing.sm,
paddingVertical: spacing.xs + 1, paddingVertical: spacing.xs,
gap: spacing.xs + 2, borderRadius: 8,
flex: 1,
}, },
titleLeading: { titleLeading: {
width: 34, width: 36,
height: 34, height: 36,
borderRadius: 17, borderRadius: 10,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
backgroundColor: `${colors.primary.main}14`, backgroundColor: colors.chat.surfaceMuted,
overflow: 'hidden',
},
groupAvatar: {
width: 36,
height: 36,
borderRadius: 10,
}, },
titleContent: { titleContent: {
flex: 1, flex: 1,
minWidth: 0, minWidth: 0,
alignItems: 'center',
}, },
subtitleRow: { subtitleRow: {
flexDirection: 'row', flexDirection: 'row',
@@ -90,30 +90,27 @@ export function createChatScreenStyles(colors: AppColors) {
minHeight: 16, minHeight: 16,
}, },
headerName: { headerName: {
fontWeight: '700', fontWeight: '600',
color: colors.text.primary, color: colors.text.primary,
fontSize: 17, fontSize: 16,
letterSpacing: -0.3, letterSpacing: -0.3,
}, },
memberCount: { memberCount: {
fontSize: 12, fontSize: 12,
color: colors.text.secondary, color: colors.text.secondary,
fontWeight: '500', fontWeight: '400',
}, },
typingHint: { typingHint: {
fontSize: 12, fontSize: 12,
color: colors.primary.dark, color: colors.primary.main,
backgroundColor: `${colors.primary.main}14`, fontWeight: '500',
borderRadius: 10,
paddingHorizontal: 6,
paddingVertical: 1,
}, },
moreButton: { moreButton: {
width: 38, width: 36,
height: 38, height: 36,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
borderRadius: 19, borderRadius: 10,
backgroundColor: 'transparent', backgroundColor: 'transparent',
}, },
headerActions: { headerActions: {
@@ -550,55 +547,62 @@ export function createChatScreenStyles(colors: AppColors) {
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: colors.chat.border, borderTopColor: colors.chat.border,
}, },
panelCloseButton: {
width: 40,
height: 40,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.chat.card,
borderRadius: 8,
shadowColor: colors.chat.shadow,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 2,
},
// 面板内容区域 // 面板内容区域
panelContent: { panelContent: {
flex: 1, flex: 1,
}, },
// Tab 栏 // Tab 栏 - 现代胶囊式设计
panelTabBar: { panelTabBar: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: spacing.sm, justifyContent: 'space-between',
paddingVertical: spacing.xs, paddingHorizontal: spacing.md,
borderTopWidth: 1, paddingVertical: spacing.sm + 2,
borderTopColor: colors.chat.border, borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: colors.chat.borderHairline,
backgroundColor: colors.chat.card, backgroundColor: colors.chat.card,
}, },
panelTabContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.chat.surfaceMuted,
borderRadius: 12,
padding: 3,
},
panelTab: { panelTab: {
width: 44, width: 42,
height: 44, height: 36,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
borderRadius: 8, borderRadius: 10,
marginRight: spacing.xs,
}, },
panelTabActive: { panelTabActive: {
backgroundColor: colors.chat.replyTint, backgroundColor: colors.chat.card,
shadowColor: colors.chat.shadow,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.08,
shadowRadius: 2,
elevation: 1,
}, },
panelTabEmoji: { panelTabEmoji: {
fontSize: 24, fontSize: 22,
}, },
panelTabDivider: { panelTabDivider: {
width: 1, width: 1,
height: 24, height: 20,
backgroundColor: colors.chat.bubbleOutgoing, backgroundColor: colors.chat.border,
marginHorizontal: spacing.sm, marginHorizontal: spacing.sm,
}, },
panelCloseButton: {
width: 36,
height: 36,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 10,
backgroundColor: colors.chat.surfaceMuted,
},
// 自定义表情面板 // 自定义表情面板
stickerLoadingContainer: { stickerLoadingContainer: {
@@ -643,35 +647,60 @@ export function createChatScreenStyles(colors: AppColors) {
height: '100%', height: '100%',
}, },
// 更多功能面板 // 更多功能面板 - 现代扁平化设计
moreGrid: { moreGrid: {
flexDirection: 'row', flexDirection: 'row',
flexWrap: 'wrap', flexWrap: 'wrap',
padding: spacing.lg, paddingHorizontal: spacing.lg + 4,
paddingTop: spacing.lg,
paddingBottom: spacing.xl,
justifyContent: 'flex-start',
}, },
moreItem: { moreItem: {
width: (SCREEN_WIDTH - spacing.lg * 2) / 4, width: (SCREEN_WIDTH - spacing.lg * 2 - 8) / 4,
alignItems: 'center', alignItems: 'center',
marginBottom: spacing.lg, marginBottom: spacing.lg + 4,
}, },
moreIconContainer: { moreIconContainer: {
width: 60, width: 56,
height: 60, height: 56,
borderRadius: 16, borderRadius: 18,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
marginBottom: spacing.sm, marginBottom: spacing.sm + 2,
backgroundColor: colors.chat.card, backgroundColor: colors.chat.card,
shadowColor: colors.chat.shadow, shadowColor: colors.chat.shadow,
shadowOffset: { width: 0, height: 2 }, shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.08, shadowOpacity: 0.06,
shadowRadius: 4, shadowRadius: 6,
elevation: 2, elevation: 2,
}, },
moreIconGradient: {
width: 56,
height: 56,
borderRadius: 18,
alignItems: 'center',
justifyContent: 'center',
marginBottom: spacing.sm + 2,
overflow: 'hidden',
},
moreIconInner: {
width: 56,
height: 56,
borderRadius: 18,
alignItems: 'center',
justifyContent: 'center',
},
moreItemText: { moreItemText: {
fontSize: 13, fontSize: 12,
color: colors.chat.textTertiary, color: colors.chat.textSecondary,
fontWeight: '500', fontWeight: '500',
letterSpacing: 0.3,
},
// MorePanel 整体背景
morePanelBackground: {
flex: 1,
backgroundColor: colors.chat.surfaceMuted,
}, },
// @成员选择浮层 - 绝对定位浮在输入框上方 // @成员选择浮层 - 绝对定位浮在输入框上方

View File

@@ -48,6 +48,7 @@ export interface MoreAction {
icon: string; icon: string;
name: string; name: string;
color: string; color: string;
gradientColors?: [string, string]; // 渐变色配置
} }
// 长按菜单项 // 长按菜单项
@@ -129,6 +130,8 @@ export interface EmojiPanelProps {
export interface MorePanelProps { export interface MorePanelProps {
onAction: (actionId: string) => void; onAction: (actionId: string) => void;
disabledActionIds?: string[]; disabledActionIds?: string[];
/** 是否为群聊(群聊中隐藏语音/视频通话) */
isGroupChat?: boolean;
} }
// @成员选择面板 Props // @成员选择面板 Props
@@ -168,7 +171,7 @@ export interface LongPressMenuProps {
// 聊天头部 Props // 聊天头部 Props
export interface ChatHeaderProps { export interface ChatHeaderProps {
isGroupChat: boolean; isGroupChat: boolean;
groupInfo: { name?: string; member_count?: number } | null; groupInfo: { name?: string; member_count?: number; avatar?: string | null } | null;
otherUser: { id?: string; nickname?: string; avatar?: string | null } | null; otherUser: { id?: string; nickname?: string; avatar?: string | null } | null;
routeGroupName?: string; routeGroupName?: string;
typingHint: string | null; typingHint: string | null;