refactor(database): migrate to new modular database layer and unify data access
- Remove legacy database.ts, LocalDataSource.ts, and MessageRepository.ts - Create new src/database/ module with messageRepository, userCacheRepository, conversationRepository, and groupCacheRepository - Update all consumers to import from @/database instead of services/database - Add web platform blur handling for modal components to fix focus issues - Flatten SystemMessageItem and NotificationsScreen styles for consistent design - Add draggable slider in ChatSettingsScreen and dynamic font size support - Introduce 9 new chat color themes - Add profile screens for about, terms, and privacy policy with navigation routes - Add policy links to login and registration screens - Fix post share URL format from /posts/ to /post/
This commit is contained in:
@@ -3,17 +3,16 @@
|
||||
* 胡萝卜BBS - 聊天个性化设置
|
||||
*/
|
||||
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import React, { useMemo, useCallback, useRef } from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
ScrollView,
|
||||
Dimensions,
|
||||
PanResponder,
|
||||
} 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,
|
||||
@@ -28,7 +27,6 @@ import {
|
||||
useChatSettingsActions,
|
||||
CHAT_THEMES,
|
||||
} from '../../stores/chatSettingsStore';
|
||||
import { useCurrentUser } from '../../stores';
|
||||
|
||||
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
||||
const CARD_MAX_WIDTH = 720;
|
||||
@@ -43,7 +41,7 @@ interface SliderProps {
|
||||
showValue?: boolean;
|
||||
}
|
||||
|
||||
// 自定义滑块组件
|
||||
// 自定义滑块组件(支持拖动)
|
||||
const Slider: React.FC<SliderProps> = ({
|
||||
value,
|
||||
min,
|
||||
@@ -55,34 +53,51 @@ const Slider: React.FC<SliderProps> = ({
|
||||
}) => {
|
||||
const colors = useAppColors();
|
||||
const styles = useMemo(() => createSliderStyles(colors), [colors]);
|
||||
const trackWidthRef = useRef(0);
|
||||
|
||||
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 percentage = isNaN(value) ? 0 : ((value - min) / (max - min)) * 100;
|
||||
|
||||
const updateValue = useCallback((locationX: number) => {
|
||||
const width = trackWidthRef.current;
|
||||
if (width <= 0) return;
|
||||
|
||||
const newPercentage = Math.max(0, Math.min(100, (locationX / width) * 100));
|
||||
const newValue = Math.round(min + (newPercentage / 100) * (max - min));
|
||||
onValueChange(newValue);
|
||||
}, [min, max, onValueChange]);
|
||||
|
||||
const panResponder = useMemo(() => PanResponder.create({
|
||||
onStartShouldSetPanResponder: () => true,
|
||||
onMoveShouldSetPanResponder: () => true,
|
||||
onPanResponderGrant: (evt) => {
|
||||
updateValue(evt.nativeEvent.locationX);
|
||||
},
|
||||
onPanResponderMove: (evt) => {
|
||||
updateValue(evt.nativeEvent.locationX);
|
||||
},
|
||||
}), [updateValue]);
|
||||
|
||||
const handleLayout = useCallback((event: any) => {
|
||||
trackWidthRef.current = event.nativeEvent.layout.width;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.labelRow}>
|
||||
{leftLabel && <Text style={styles.label}>{leftLabel}</Text>}
|
||||
{showValue && <Text style={styles.value}>{value}</Text>}
|
||||
{showValue && <Text style={styles.value}>{isNaN(value) ? 0 : value}</Text>}
|
||||
{rightLabel && <Text style={styles.label}>{rightLabel}</Text>}
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
<View
|
||||
style={styles.trackContainer}
|
||||
onPress={handlePress}
|
||||
activeOpacity={1}
|
||||
onLayout={handleLayout}
|
||||
{...panResponder.panHandlers}
|
||||
>
|
||||
<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>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -168,29 +183,32 @@ function createStyles(colors: AppColors) {
|
||||
minHeight: 140,
|
||||
},
|
||||
previewMessage: {
|
||||
backgroundColor: colors.background.paper,
|
||||
borderRadius: borderRadius.md,
|
||||
backgroundColor: '#FFFFFF',
|
||||
padding: spacing.sm,
|
||||
marginBottom: spacing.sm,
|
||||
maxWidth: '80%',
|
||||
alignSelf: 'flex-start',
|
||||
minWidth: 44,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.06,
|
||||
shadowRadius: 2,
|
||||
elevation: 1,
|
||||
},
|
||||
previewReply: {
|
||||
borderRadius: borderRadius.md,
|
||||
padding: spacing.sm,
|
||||
maxWidth: '80%',
|
||||
alignSelf: 'flex-end',
|
||||
minWidth: 44,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.06,
|
||||
shadowRadius: 2,
|
||||
elevation: 1,
|
||||
},
|
||||
previewText: {
|
||||
fontSize: fontSizes.sm,
|
||||
color: colors.text.primary,
|
||||
},
|
||||
previewTime: {
|
||||
fontSize: fontSizes.xs,
|
||||
color: colors.text.hint,
|
||||
marginTop: 2,
|
||||
alignSelf: 'flex-end',
|
||||
},
|
||||
// 主题选择样式
|
||||
themeList: {
|
||||
flexDirection: 'row',
|
||||
@@ -240,106 +258,56 @@ function createStyles(colors: AppColors) {
|
||||
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 { setFontSize, setMessageRadius, setTheme } = 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 }]}>
|
||||
<View style={[
|
||||
styles.previewMessage,
|
||||
{
|
||||
borderRadius: messageRadius,
|
||||
borderTopLeftRadius: Math.max(4, messageRadius * 0.3),
|
||||
}
|
||||
]}>
|
||||
<Text style={[styles.previewText, { fontSize }]}>
|
||||
早上好!👋
|
||||
</Text>
|
||||
<Text style={[styles.previewText, { fontSize: fontSize - 2 }]}>
|
||||
<Text style={[styles.previewText, { fontSize }]}>
|
||||
你知道现在几点吗?
|
||||
</Text>
|
||||
<Text style={styles.previewTime}>AM12:42</Text>
|
||||
</View>
|
||||
<View style={[styles.previewReply, { borderRadius: messageRadius, backgroundColor: currentTheme.bubble }]}>
|
||||
<View style={[
|
||||
styles.previewReply,
|
||||
{
|
||||
borderRadius: messageRadius,
|
||||
borderBottomRightRadius: Math.max(4, messageRadius * 0.3),
|
||||
backgroundColor: currentTheme.bubble
|
||||
}
|
||||
]}>
|
||||
<Text style={[styles.previewText, { fontSize }]}>
|
||||
现在是威海的早晨😎
|
||||
</Text>
|
||||
<Text style={styles.previewTime}>AM12:57</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -410,87 +378,18 @@ export const ChatSettingsScreen: React.FC = () => {
|
||||
</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 }
|
||||
{ paddingHorizontal: responsivePadding, paddingBottom: 80 }
|
||||
]}
|
||||
>
|
||||
{renderFontSizeSlider()}
|
||||
{renderChatPreview()}
|
||||
{renderThemeColors()}
|
||||
{renderRadiusSlider()}
|
||||
{renderOtherSettings()}
|
||||
{renderNightMode()}
|
||||
{renderBrowseThemes()}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user