Files
frontend/src/screens/profile/ChatSettingsScreen.tsx
lafay e8651215f7
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 7m31s
Frontend CI / ota-android (push) Successful in 13m19s
Frontend CI / build-android-apk (push) Successful in 1h10m3s
feat(chat): implement dynamic theme support in chat components
- 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.
2026-04-02 17:55:56 +08:00

500 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 聊天设置页 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;