feat(profile): add chat settings and language options to profile settings
- Introduced hrefProfileChatSettings() for navigation to chat settings. - Updated SettingsScreen to include new settings items for chat settings, language selection, data storage, terms, and privacy policy. - Enhanced user experience by providing alerts for language and data storage options. This update improves the profile settings interface by adding more customization options for users.
This commit is contained in:
499
src/screens/profile/ChatSettingsScreen.tsx
Normal file
499
src/screens/profile/ChatSettingsScreen.tsx
Normal file
@@ -0,0 +1,499 @@
|
||||
/**
|
||||
* 聊天设置页 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;
|
||||
Reference in New Issue
Block a user