feat(ChatScreen): 优化聊天界面 UI 和交互体验
Made-with: Cursor
This commit is contained in:
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 消息撤回时间限制(毫秒)
|
// 消息撤回时间限制(毫秒)
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|
||||||
// @成员选择浮层 - 绝对定位浮在输入框上方
|
// @成员选择浮层 - 绝对定位浮在输入框上方
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user