- Enhanced ChatHeader and ChatInput components to support dynamic theme colors, improving visual consistency across different themes. - Updated styles in ChatScreen to utilize new dynamic styles for better responsiveness and user experience. - Refactored styles in various profile screens to adopt a unified background color scheme and improved spacing for a cleaner layout. This update significantly enhances the chat interface and profile settings by allowing for dynamic theming and improved visual elements.
500 lines
14 KiB
TypeScript
500 lines
14 KiB
TypeScript
/**
|
||
* 聊天设置页 ChatSettingsScreen
|
||
* 胡萝卜BBS - 聊天个性化设置
|
||
*/
|
||
|
||
import React, { useMemo, useCallback } from 'react';
|
||
import {
|
||
View,
|
||
StyleSheet,
|
||
TouchableOpacity,
|
||
ScrollView,
|
||
Dimensions,
|
||
} from 'react-native';
|
||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||
import { useRouter } from 'expo-router';
|
||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||
import {
|
||
useAppColors,
|
||
spacing,
|
||
fontSizes,
|
||
borderRadius,
|
||
type AppColors,
|
||
} from '../../theme';
|
||
import { Text } from '../../components/common';
|
||
import { useResponsiveSpacing } from '../../hooks';
|
||
import {
|
||
useChatSettingsStore,
|
||
useChatSettingsActions,
|
||
CHAT_THEMES,
|
||
} from '../../stores/chatSettingsStore';
|
||
import { useCurrentUser } from '../../stores';
|
||
|
||
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
||
const CARD_MAX_WIDTH = 720;
|
||
|
||
interface SliderProps {
|
||
value: number;
|
||
min: number;
|
||
max: number;
|
||
onValueChange: (value: number) => void;
|
||
leftLabel?: string;
|
||
rightLabel?: string;
|
||
showValue?: boolean;
|
||
}
|
||
|
||
// 自定义滑块组件
|
||
const Slider: React.FC<SliderProps> = ({
|
||
value,
|
||
min,
|
||
max,
|
||
onValueChange,
|
||
leftLabel,
|
||
rightLabel,
|
||
showValue = true,
|
||
}) => {
|
||
const colors = useAppColors();
|
||
const styles = useMemo(() => createSliderStyles(colors), [colors]);
|
||
|
||
const percentage = ((value - min) / (max - min)) * 100;
|
||
|
||
const handlePress = useCallback((event: any) => {
|
||
const { locationX } = event.nativeEvent;
|
||
const sliderWidth = SCREEN_WIDTH - 64; // 考虑边距
|
||
const newPercentage = Math.max(0, Math.min(100, (locationX / sliderWidth) * 100));
|
||
const newValue = Math.round(min + (newPercentage / 100) * (max - min));
|
||
onValueChange(newValue);
|
||
}, [min, max, onValueChange]);
|
||
|
||
return (
|
||
<View style={styles.container}>
|
||
<View style={styles.labelRow}>
|
||
{leftLabel && <Text style={styles.label}>{leftLabel}</Text>}
|
||
{showValue && <Text style={styles.value}>{value}</Text>}
|
||
{rightLabel && <Text style={styles.label}>{rightLabel}</Text>}
|
||
</View>
|
||
<TouchableOpacity
|
||
style={styles.trackContainer}
|
||
onPress={handlePress}
|
||
activeOpacity={1}
|
||
>
|
||
<View style={styles.track}>
|
||
<View style={[styles.fill, { width: `${percentage}%`, backgroundColor: colors.primary.main }]} />
|
||
</View>
|
||
<View style={[styles.thumb, { left: `${percentage}%`, backgroundColor: colors.primary.main }]} />
|
||
</TouchableOpacity>
|
||
</View>
|
||
);
|
||
};
|
||
|
||
function createSliderStyles(colors: AppColors) {
|
||
return StyleSheet.create({
|
||
container: {
|
||
marginVertical: spacing.sm,
|
||
},
|
||
labelRow: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
marginBottom: spacing.xs,
|
||
},
|
||
label: {
|
||
fontSize: fontSizes.sm,
|
||
color: colors.text.secondary,
|
||
},
|
||
value: {
|
||
fontSize: fontSizes.md,
|
||
fontWeight: '600',
|
||
color: colors.primary.main,
|
||
},
|
||
trackContainer: {
|
||
height: 24,
|
||
justifyContent: 'center',
|
||
},
|
||
track: {
|
||
height: 4,
|
||
backgroundColor: colors.divider,
|
||
borderRadius: 2,
|
||
},
|
||
fill: {
|
||
height: '100%',
|
||
borderRadius: 2,
|
||
},
|
||
thumb: {
|
||
position: 'absolute',
|
||
width: 20,
|
||
height: 20,
|
||
borderRadius: 10,
|
||
marginLeft: -10,
|
||
shadowColor: '#000',
|
||
shadowOffset: { width: 0, height: 2 },
|
||
shadowOpacity: 0.2,
|
||
shadowRadius: 2,
|
||
elevation: 3,
|
||
},
|
||
});
|
||
}
|
||
|
||
function createStyles(colors: AppColors) {
|
||
return StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
backgroundColor: colors.background.paper,
|
||
},
|
||
scrollContent: {
|
||
paddingVertical: spacing.lg,
|
||
},
|
||
section: {
|
||
marginBottom: spacing['2xl'],
|
||
maxWidth: CARD_MAX_WIDTH,
|
||
alignSelf: 'center',
|
||
width: '100%',
|
||
paddingHorizontal: spacing['2xl'],
|
||
},
|
||
sectionTitle: {
|
||
fontSize: fontSizes.sm,
|
||
fontWeight: '600',
|
||
color: colors.text.secondary,
|
||
marginBottom: spacing.sm,
|
||
marginTop: spacing.sm,
|
||
textTransform: 'uppercase',
|
||
letterSpacing: 0.5,
|
||
},
|
||
// 聊天预览样式
|
||
previewContainer: {
|
||
backgroundColor: colors.primary.light + '20',
|
||
borderRadius: 14,
|
||
padding: spacing.md,
|
||
minHeight: 140,
|
||
},
|
||
previewMessage: {
|
||
backgroundColor: colors.background.paper,
|
||
borderRadius: borderRadius.md,
|
||
padding: spacing.sm,
|
||
marginBottom: spacing.sm,
|
||
maxWidth: '80%',
|
||
alignSelf: 'flex-start',
|
||
},
|
||
previewReply: {
|
||
borderRadius: borderRadius.md,
|
||
padding: spacing.sm,
|
||
maxWidth: '80%',
|
||
alignSelf: 'flex-end',
|
||
},
|
||
previewText: {
|
||
fontSize: fontSizes.sm,
|
||
color: colors.text.primary,
|
||
},
|
||
previewTime: {
|
||
fontSize: fontSizes.xs,
|
||
color: colors.text.hint,
|
||
marginTop: 2,
|
||
alignSelf: 'flex-end',
|
||
},
|
||
// 主题选择样式
|
||
themeList: {
|
||
flexDirection: 'row',
|
||
gap: spacing.sm,
|
||
},
|
||
themeItem: {
|
||
width: 70,
|
||
height: 90,
|
||
borderRadius: borderRadius.md,
|
||
borderWidth: 2,
|
||
borderColor: 'transparent',
|
||
overflow: 'hidden',
|
||
padding: spacing.xs,
|
||
},
|
||
themeItemActive: {
|
||
borderColor: colors.primary.main,
|
||
},
|
||
themePreview: {
|
||
flex: 1,
|
||
justifyContent: 'center',
|
||
gap: 4,
|
||
},
|
||
themeBubble1: {
|
||
height: 12,
|
||
borderRadius: 6,
|
||
width: '70%',
|
||
},
|
||
themeBubble2: {
|
||
height: 12,
|
||
borderRadius: 6,
|
||
width: '50%',
|
||
alignSelf: 'flex-end',
|
||
},
|
||
themeIcon: {
|
||
position: 'absolute',
|
||
bottom: 4,
|
||
right: 4,
|
||
width: 18,
|
||
height: 18,
|
||
borderRadius: 9,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
},
|
||
themeName: {
|
||
fontSize: fontSizes.xs,
|
||
textAlign: 'center',
|
||
marginTop: 4,
|
||
color: colors.text.secondary,
|
||
},
|
||
// 设置项样式 - 扁平化
|
||
settingItem: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
paddingVertical: 16,
|
||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||
borderBottomColor: colors.divider,
|
||
},
|
||
settingItemLast: {
|
||
borderBottomWidth: 0,
|
||
},
|
||
settingItemLeft: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
flex: 1,
|
||
},
|
||
iconContainer: {
|
||
width: 24,
|
||
height: 24,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
marginRight: spacing.lg,
|
||
},
|
||
settingTitle: {
|
||
fontSize: fontSizes.md,
|
||
color: colors.text.primary,
|
||
},
|
||
settingSubtitle: {
|
||
fontSize: fontSizes.sm,
|
||
color: colors.text.secondary,
|
||
marginTop: 2,
|
||
},
|
||
// 滑块容器 - 扁平化
|
||
sliderContainer: {
|
||
paddingVertical: 8,
|
||
},
|
||
// 开关样式
|
||
switch: {
|
||
width: 50,
|
||
height: 28,
|
||
borderRadius: 14,
|
||
backgroundColor: colors.divider,
|
||
padding: 2,
|
||
},
|
||
switchActive: {
|
||
backgroundColor: colors.primary.main,
|
||
},
|
||
switchThumb: {
|
||
width: 24,
|
||
height: 24,
|
||
borderRadius: 12,
|
||
backgroundColor: colors.background.paper,
|
||
},
|
||
});
|
||
}
|
||
|
||
export const ChatSettingsScreen: React.FC = () => {
|
||
const router = useRouter();
|
||
const colors = useAppColors();
|
||
const styles = useMemo(() => createStyles(colors), [colors]);
|
||
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
|
||
|
||
// 获取当前用户信息
|
||
const currentUser = useCurrentUser();
|
||
|
||
// 从 store 获取状态
|
||
const fontSize = useChatSettingsStore((s) => s.fontSize);
|
||
const messageRadius = useChatSettingsStore((s) => s.messageRadius);
|
||
const themeIndex = useChatSettingsStore((s) => s.themeIndex);
|
||
const nightMode = useChatSettingsStore((s) => s.nightMode);
|
||
|
||
// 获取操作方法
|
||
const { setFontSize, setMessageRadius, setTheme, toggleNightMode } = useChatSettingsActions();
|
||
|
||
const currentTheme = CHAT_THEMES[themeIndex];
|
||
|
||
// 用户显示名称
|
||
const userName = currentUser?.nickname || currentUser?.username || '我';
|
||
|
||
// 渲染聊天预览
|
||
const renderChatPreview = () => (
|
||
<View style={styles.section}>
|
||
<Text style={styles.sectionTitle}>预览</Text>
|
||
<View style={[styles.previewContainer, { backgroundColor: currentTheme.secondary }]}>
|
||
<View style={[styles.previewMessage, { borderRadius: messageRadius }]}>
|
||
<Text style={[styles.previewText, { fontSize }]}>{userName}</Text>
|
||
<Text style={[styles.previewText, { fontSize: fontSize - 2 }]}>
|
||
早上好!👋
|
||
</Text>
|
||
<Text style={[styles.previewText, { fontSize: fontSize - 2 }]}>
|
||
你知道现在几点吗?
|
||
</Text>
|
||
<Text style={styles.previewTime}>AM12:42</Text>
|
||
</View>
|
||
<View style={[styles.previewReply, { borderRadius: messageRadius, backgroundColor: currentTheme.bubble }]}>
|
||
<Text style={[styles.previewText, { fontSize }]}>
|
||
现在是威海的早晨😎
|
||
</Text>
|
||
<Text style={styles.previewTime}>AM12:57</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
);
|
||
|
||
// 渲染字号滑块
|
||
const renderFontSizeSlider = () => (
|
||
<View style={styles.section}>
|
||
<Text style={styles.sectionTitle}>消息字号</Text>
|
||
<View style={styles.sliderContainer}>
|
||
<Slider
|
||
value={fontSize}
|
||
min={12}
|
||
max={24}
|
||
onValueChange={setFontSize}
|
||
leftLabel="A"
|
||
rightLabel="A"
|
||
/>
|
||
</View>
|
||
</View>
|
||
);
|
||
|
||
// 渲染主题颜色选择
|
||
const renderThemeColors = () => (
|
||
<View style={styles.section}>
|
||
<Text style={styles.sectionTitle}>主题颜色</Text>
|
||
<ScrollView
|
||
horizontal
|
||
showsHorizontalScrollIndicator={false}
|
||
contentContainerStyle={styles.themeList}
|
||
>
|
||
{CHAT_THEMES.map((theme, index) => (
|
||
<TouchableOpacity
|
||
key={theme.id}
|
||
style={[
|
||
styles.themeItem,
|
||
themeIndex === index && styles.themeItemActive,
|
||
{ backgroundColor: theme.secondary },
|
||
]}
|
||
onPress={() => setTheme(index)}
|
||
activeOpacity={0.8}
|
||
>
|
||
<View style={styles.themePreview}>
|
||
<View style={[styles.themeBubble1, { backgroundColor: '#FFFFFF' }]} />
|
||
<View style={[styles.themeBubble2, { backgroundColor: theme.bubble }]} />
|
||
</View>
|
||
<View style={[styles.themeIcon, { backgroundColor: theme.primary }]}>
|
||
<Text style={{ fontSize: 10 }}>{theme.icon}</Text>
|
||
</View>
|
||
</TouchableOpacity>
|
||
))}
|
||
</ScrollView>
|
||
</View>
|
||
);
|
||
|
||
// 渲染圆角滑块
|
||
const renderRadiusSlider = () => (
|
||
<View style={styles.section}>
|
||
<Text style={styles.sectionTitle}>消息圆角</Text>
|
||
<View style={styles.sliderContainer}>
|
||
<Slider
|
||
value={messageRadius}
|
||
min={0}
|
||
max={30}
|
||
onValueChange={setMessageRadius}
|
||
/>
|
||
</View>
|
||
</View>
|
||
);
|
||
|
||
// 渲染其他设置项
|
||
const renderOtherSettings = () => (
|
||
<View style={styles.section}>
|
||
<Text style={styles.sectionTitle}>其他</Text>
|
||
<TouchableOpacity style={styles.settingItem}>
|
||
<View style={styles.settingItemLeft}>
|
||
<View style={styles.iconContainer}>
|
||
<MaterialCommunityIcons name="image-outline" size={22} color={colors.text.secondary} />
|
||
</View>
|
||
<View>
|
||
<Text style={styles.settingTitle}>更改聊天壁纸</Text>
|
||
</View>
|
||
</View>
|
||
<MaterialCommunityIcons name="chevron-right" size={20} color={colors.text.hint} />
|
||
</TouchableOpacity>
|
||
|
||
<TouchableOpacity style={[styles.settingItem, styles.settingItemLast]}>
|
||
<View style={styles.settingItemLeft}>
|
||
<View style={styles.iconContainer}>
|
||
<MaterialCommunityIcons name="palette-outline" size={22} color={colors.text.secondary} />
|
||
</View>
|
||
<View>
|
||
<Text style={styles.settingTitle}>更改名称颜色</Text>
|
||
<Text style={styles.settingSubtitle}>MiMQy</Text>
|
||
</View>
|
||
</View>
|
||
<MaterialCommunityIcons name="chevron-right" size={20} color={colors.text.hint} />
|
||
</TouchableOpacity>
|
||
</View>
|
||
);
|
||
|
||
// 渲染夜间模式开关
|
||
const renderNightMode = () => (
|
||
<View style={styles.section}>
|
||
<View style={[styles.settingItem, styles.settingItemLast]}>
|
||
<View style={styles.settingItemLeft}>
|
||
<View style={styles.iconContainer}>
|
||
<MaterialCommunityIcons name="weather-night" size={22} color={colors.text.secondary} />
|
||
</View>
|
||
<Text style={styles.settingTitle}>切换到夜间模式</Text>
|
||
</View>
|
||
<TouchableOpacity
|
||
style={[styles.switch, nightMode && styles.switchActive]}
|
||
onPress={() => toggleNightMode()}
|
||
>
|
||
<View style={[styles.switchThumb, { alignSelf: nightMode ? 'flex-end' : 'flex-start' }]} />
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
);
|
||
|
||
// 渲染浏览其他主题
|
||
const renderBrowseThemes = () => (
|
||
<View style={styles.section}>
|
||
<TouchableOpacity style={[styles.settingItem, styles.settingItemLast]}>
|
||
<View style={styles.settingItemLeft}>
|
||
<View style={styles.iconContainer}>
|
||
<MaterialCommunityIcons name="apps" size={22} color={colors.text.secondary} />
|
||
</View>
|
||
<Text style={styles.settingTitle}>浏览其他主题</Text>
|
||
</View>
|
||
<MaterialCommunityIcons name="chevron-right" size={20} color={colors.text.hint} />
|
||
</TouchableOpacity>
|
||
</View>
|
||
);
|
||
|
||
return (
|
||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
||
<ScrollView
|
||
contentContainerStyle={[
|
||
styles.scrollContent,
|
||
{ paddingHorizontal: responsivePadding }
|
||
]}
|
||
>
|
||
{renderFontSizeSlider()}
|
||
{renderChatPreview()}
|
||
{renderThemeColors()}
|
||
{renderRadiusSlider()}
|
||
{renderOtherSettings()}
|
||
{renderNightMode()}
|
||
{renderBrowseThemes()}
|
||
</ScrollView>
|
||
</SafeAreaView>
|
||
);
|
||
};
|
||
|
||
export default ChatSettingsScreen;
|