500 lines
14 KiB
TypeScript
500 lines
14 KiB
TypeScript
|
|
/**
|
|||
|
|
* 聊天设置页 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;
|