Files
frontend/src/screens/profile/ChatSettingsScreen.tsx

500 lines
14 KiB
TypeScript
Raw Normal View History

/**
* ChatSettingsScreen
* BBS -
*/
import React, { useMemo, useState, 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';
const { width: SCREEN_WIDTH } = Dimensions.get('window');
const CARD_MAX_WIDTH = 720;
// 主题配置
const THEMES = [
{ id: 'default', name: '默认绿', primary: '#4CAF50', secondary: '#E8F5E9', bubble: '#DCF8C6', icon: '🏠' },
{ id: 'yellow', name: '活力黄', primary: '#FFC107', secondary: '#FFF8E1', bubble: '#FFECB3', icon: '🐥' },
{ id: 'blue', name: '清新蓝', primary: '#2196F3', secondary: '#E3F2FD', bubble: '#BBDEFB', icon: '☃️' },
{ id: 'purple', name: '梦幻紫', primary: '#9C27B0', secondary: '#F3E5F5', bubble: '#E1BEE7', icon: '💎' },
{ id: 'teal', name: '自然青', primary: '#009688', secondary: '#E0F2F1', bubble: '#B2DFDB', icon: '🦁' },
] as const;
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.default,
},
scrollContent: {
paddingVertical: spacing.md,
},
section: {
marginBottom: spacing.lg,
maxWidth: CARD_MAX_WIDTH,
alignSelf: 'center',
width: '100%',
paddingHorizontal: spacing.lg,
},
sectionTitle: {
fontSize: fontSizes.sm,
fontWeight: '600',
color: colors.text.secondary,
marginBottom: spacing.sm,
marginLeft: spacing.xs,
},
card: {
backgroundColor: colors.background.paper,
borderRadius: borderRadius.lg,
padding: spacing.md,
},
// 聊天预览样式
previewContainer: {
backgroundColor: '#E8F5E9',
borderRadius: borderRadius.lg,
padding: spacing.md,
minHeight: 140,
},
previewMessage: {
backgroundColor: '#FFFFFF',
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: '#333',
},
previewTime: {
fontSize: fontSizes.xs,
color: '#999',
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: spacing.md,
borderBottomWidth: 1,
borderBottomColor: colors.divider,
},
settingItemLast: {
borderBottomWidth: 0,
},
settingItemLeft: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.md,
},
iconContainer: {
width: 36,
height: 36,
borderRadius: borderRadius.md,
backgroundColor: colors.primary.light + '20',
justifyContent: 'center',
alignItems: 'center',
},
settingTitle: {
fontSize: fontSizes.md,
color: colors.text.primary,
},
settingSubtitle: {
fontSize: fontSizes.sm,
color: colors.text.secondary,
marginTop: 2,
},
// 开关样式
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: '#fff',
},
});
}
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 [fontSize, setFontSize] = useState(16);
const [messageRadius, setMessageRadius] = useState(17);
const [selectedTheme, setSelectedTheme] = useState(0);
const [nightMode, setNightMode] = useState(false);
const currentTheme = THEMES[selectedTheme];
// 渲染聊天预览
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 }]}>MiMQy</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.card}>
<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}
>
{THEMES.map((theme, index) => (
<TouchableOpacity
key={theme.id}
style={[
styles.themeItem,
selectedTheme === index && styles.themeItemActive,
{ backgroundColor: theme.secondary },
]}
onPress={() => setSelectedTheme(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.card}>
<Slider
value={messageRadius}
min={0}
max={30}
onValueChange={setMessageRadius}
/>
</View>
</View>
);
// 渲染其他设置项
const renderOtherSettings = () => (
<View style={styles.section}>
<Text style={styles.sectionTitle}></Text>
<View style={styles.card}>
<TouchableOpacity style={styles.settingItem}>
<View style={styles.settingItemLeft}>
<View style={styles.iconContainer}>
<MaterialCommunityIcons name="image-outline" size={20} color={colors.primary.main} />
</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={20} color={colors.primary.main} />
</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>
</View>
);
// 渲染夜间模式开关
const renderNightMode = () => (
<View style={styles.section}>
<View style={styles.card}>
<View style={[styles.settingItem, styles.settingItemLast]}>
<View style={styles.settingItemLeft}>
<View style={styles.iconContainer}>
<MaterialCommunityIcons name="weather-night" size={20} color={colors.primary.main} />
</View>
<Text style={styles.settingTitle}></Text>
</View>
<TouchableOpacity
style={[styles.switch, nightMode && styles.switchActive]}
onPress={() => setNightMode(!nightMode)}
>
<View style={[styles.switchThumb, { alignSelf: nightMode ? 'flex-end' : 'flex-start' }]} />
</TouchableOpacity>
</View>
</View>
</View>
);
// 渲染浏览其他主题
const renderBrowseThemes = () => (
<View style={styles.section}>
<TouchableOpacity style={styles.card}>
<View style={[styles.settingItem, styles.settingItemLast]}>
<View style={styles.settingItemLeft}>
<View style={styles.iconContainer}>
<MaterialCommunityIcons name="apps" size={20} color={colors.primary.main} />
</View>
<Text style={styles.settingTitle}></Text>
</View>
<MaterialCommunityIcons name="chevron-right" size={20} color={colors.text.hint} />
</View>
</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;