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:
@@ -155,6 +155,13 @@ const QRCodeScannerWeb: React.FC<QRCodeScannerProps> = ({ visible, onClose }) =>
|
||||
const themeColors = useAppColors();
|
||||
const styles = useMemo(() => createQrScannerStyles(themeColors), [themeColors]);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible && Platform.OS === 'web') {
|
||||
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
|
||||
active?.blur?.();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<Modal visible={visible} animationType="slide" onRequestClose={onClose}>
|
||||
<View style={styles.container}>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 提供友好的用户界面和流畅的交互体验
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import React, { useState, useCallback, useMemo, useEffect } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
View,
|
||||
@@ -62,6 +62,13 @@ const ReportDialog: React.FC<ReportDialogProps> = ({
|
||||
|
||||
const targetConfig = TARGET_CONFIG[targetType];
|
||||
|
||||
useEffect(() => {
|
||||
if (visible && Platform.OS === 'web') {
|
||||
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
|
||||
active?.blur?.();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
// 判断补充说明是否必填(选择"其他原因"时必填)
|
||||
const isDescriptionRequired = selectedReason === 'other';
|
||||
const descriptionPlaceholder = isDescriptionRequired
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* SystemMessageItem 系统消息项组件 - 美化版
|
||||
* SystemMessageItem 系统消息项组件 - 扁平化风格
|
||||
* 根据系统消息类型显示不同图标和内容
|
||||
* 采用卡片式设计,符合胡萝卜BBS整体风格
|
||||
* 与登录、注册、设置页面风格保持一致
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
@@ -9,7 +9,7 @@ import { View, TouchableOpacity, StyleSheet } from 'react-native';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { zhCN } from 'date-fns/locale';
|
||||
import { spacing, borderRadius, shadows, fontSizes, useAppColors, type AppColors } from '../../theme';
|
||||
import { spacing, borderRadius, fontSizes, useAppColors, type AppColors } from '../../theme';
|
||||
import { SystemMessageResponse, SystemMessageType } from '../../types/dto';
|
||||
import Text from '../common/Text';
|
||||
import Avatar from '../common/Avatar';
|
||||
@@ -124,17 +124,13 @@ function createSystemMessageStyles(colors: AppColors) {
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
padding: spacing.md,
|
||||
backgroundColor: colors.background.paper,
|
||||
borderRadius: borderRadius.lg,
|
||||
marginHorizontal: spacing.md,
|
||||
marginBottom: spacing.sm,
|
||||
...shadows.sm,
|
||||
padding: 16,
|
||||
backgroundColor: '#fff',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.divider || '#E5E5EA',
|
||||
},
|
||||
unreadContainer: {
|
||||
backgroundColor: colors.primary.light + '08',
|
||||
borderLeftWidth: 3,
|
||||
borderLeftColor: colors.primary.main,
|
||||
backgroundColor: '#FFF8F6',
|
||||
},
|
||||
unreadIndicator: {
|
||||
position: 'absolute',
|
||||
@@ -143,19 +139,19 @@ function createSystemMessageStyles(colors: AppColors) {
|
||||
marginTop: -4,
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: borderRadius.full,
|
||||
backgroundColor: colors.primary.main,
|
||||
borderRadius: 4,
|
||||
backgroundColor: '#FF6B35',
|
||||
},
|
||||
iconContainer: {
|
||||
marginRight: spacing.md,
|
||||
marginRight: 14,
|
||||
},
|
||||
avatarWrapper: {
|
||||
position: 'relative',
|
||||
},
|
||||
iconWrapper: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: borderRadius.lg,
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 12,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
@@ -163,13 +159,13 @@ function createSystemMessageStyles(colors: AppColors) {
|
||||
position: 'absolute',
|
||||
bottom: -2,
|
||||
right: -2,
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: borderRadius.full,
|
||||
width: 18,
|
||||
height: 18,
|
||||
borderRadius: 9,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 2,
|
||||
borderColor: colors.background.paper,
|
||||
borderColor: '#fff',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
@@ -180,17 +176,17 @@ function createSystemMessageStyles(colors: AppColors) {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: spacing.xs,
|
||||
marginBottom: 4,
|
||||
},
|
||||
titleLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
marginRight: spacing.sm,
|
||||
marginRight: 8,
|
||||
},
|
||||
title: {
|
||||
fontWeight: '500',
|
||||
fontSize: fontSizes.md,
|
||||
fontSize: 15,
|
||||
color: colors.text.primary,
|
||||
},
|
||||
unreadTitle: {
|
||||
@@ -200,10 +196,10 @@ function createSystemMessageStyles(colors: AppColors) {
|
||||
statusBadge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: spacing.xs,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 2,
|
||||
borderRadius: borderRadius.sm,
|
||||
marginLeft: spacing.xs,
|
||||
borderRadius: 4,
|
||||
marginLeft: 6,
|
||||
},
|
||||
statusText: {
|
||||
fontSize: 10,
|
||||
@@ -211,55 +207,59 @@ function createSystemMessageStyles(colors: AppColors) {
|
||||
marginLeft: 2,
|
||||
},
|
||||
time: {
|
||||
fontSize: fontSizes.sm,
|
||||
fontSize: 13,
|
||||
flexShrink: 0,
|
||||
color: colors.text.hint,
|
||||
},
|
||||
messageContent: {
|
||||
lineHeight: 20,
|
||||
fontSize: fontSizes.sm,
|
||||
fontSize: 14,
|
||||
color: colors.text.secondary,
|
||||
},
|
||||
extraInfo: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: spacing.xs,
|
||||
backgroundColor: colors.primary.light + '12',
|
||||
paddingHorizontal: spacing.sm,
|
||||
paddingVertical: spacing.xs,
|
||||
borderRadius: borderRadius.md,
|
||||
marginTop: 8,
|
||||
backgroundColor: '#F5F5F7',
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 8,
|
||||
alignSelf: 'flex-start',
|
||||
},
|
||||
extraText: {
|
||||
marginLeft: spacing.xs,
|
||||
marginLeft: 6,
|
||||
flex: 1,
|
||||
fontWeight: '500',
|
||||
fontSize: 13,
|
||||
},
|
||||
actionsRow: {
|
||||
flexDirection: 'row',
|
||||
marginTop: spacing.sm,
|
||||
gap: spacing.sm,
|
||||
marginTop: 12,
|
||||
gap: 10,
|
||||
},
|
||||
actionBtn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: spacing.xs,
|
||||
paddingHorizontal: spacing.md,
|
||||
borderRadius: borderRadius.md,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1,
|
||||
gap: spacing.xs,
|
||||
gap: 4,
|
||||
},
|
||||
rejectBtn: {
|
||||
borderColor: `${colors.error.main}55`,
|
||||
backgroundColor: `${colors.error.main}18`,
|
||||
borderColor: colors.error.main + '40',
|
||||
backgroundColor: colors.error.light + '20',
|
||||
},
|
||||
approveBtn: {
|
||||
borderColor: `${colors.success.main}55`,
|
||||
backgroundColor: `${colors.success.main}18`,
|
||||
borderColor: colors.success.main + '40',
|
||||
backgroundColor: colors.success.light + '20',
|
||||
},
|
||||
actionBtnText: {
|
||||
fontWeight: '500',
|
||||
fontSize: 13,
|
||||
},
|
||||
arrowContainer: {
|
||||
marginLeft: spacing.sm,
|
||||
marginLeft: 8,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: 20,
|
||||
@@ -318,6 +318,13 @@ const getSystemMessageTitle = (message: SystemMessageResponse): string => {
|
||||
}
|
||||
};
|
||||
|
||||
// 胡萝卜橙主题色
|
||||
const THEME_COLORS = {
|
||||
primary: '#FF6B35',
|
||||
primaryLight: '#FF8C5A',
|
||||
primaryDark: '#E55A2B',
|
||||
};
|
||||
|
||||
const SystemMessageItem: React.FC<SystemMessageItemProps> = ({
|
||||
message,
|
||||
onPress,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Modal,
|
||||
StatusBar,
|
||||
Dimensions,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { callStore } from '../../stores/callStore';
|
||||
@@ -25,6 +26,10 @@ const IncomingCallModal: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (incomingCall) {
|
||||
if (Platform.OS === 'web') {
|
||||
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
|
||||
active?.blur?.();
|
||||
}
|
||||
const pulse = Animated.loop(
|
||||
Animated.sequence([
|
||||
Animated.timing(pulseAnim, {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
Modal,
|
||||
Pressable,
|
||||
Dimensions,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import {
|
||||
useResponsive,
|
||||
@@ -159,6 +160,10 @@ export function AdaptiveLayout({
|
||||
// 抽屉动画
|
||||
useEffect(() => {
|
||||
if (isDrawerOpen) {
|
||||
if (Platform.OS === 'web') {
|
||||
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
|
||||
active?.blur?.();
|
||||
}
|
||||
Animated.parallel([
|
||||
Animated.timing(slideAnim, {
|
||||
toValue: 1,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Modal, Pressable, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { Modal, Pressable, StyleSheet, Text, TouchableOpacity, View, Platform } from 'react-native';
|
||||
import type { AlertButton } from 'react-native';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
@@ -93,6 +93,12 @@ function createDialogHostStyles(colors: AppColors) {
|
||||
});
|
||||
}
|
||||
|
||||
const blurActiveElementOnWeb = () => {
|
||||
if (Platform.OS !== 'web') return;
|
||||
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
|
||||
active?.blur?.();
|
||||
};
|
||||
|
||||
const AppDialogHost: React.FC = () => {
|
||||
const [dialog, setDialog] = useState<DialogPayload | null>(null);
|
||||
const colors = useAppColors();
|
||||
@@ -105,6 +111,12 @@ const AppDialogHost: React.FC = () => {
|
||||
return unbind;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (dialog) {
|
||||
blurActiveElementOnWeb();
|
||||
}
|
||||
}, [dialog]);
|
||||
|
||||
const actions = useMemo<AlertButton[]>(() => {
|
||||
if (!dialog?.actions?.length) return [{ text: '确定' }];
|
||||
return dialog.actions;
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
Text,
|
||||
StatusBar,
|
||||
Alert,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { Image as ExpoImage } from 'expo-image';
|
||||
import {
|
||||
@@ -126,6 +127,10 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
||||
// 打开/关闭时重置状态
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
if (Platform.OS === 'web') {
|
||||
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
|
||||
active?.blur?.();
|
||||
}
|
||||
setCurrentIndex(initialIndex);
|
||||
setShowControls(true);
|
||||
setError(false);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* 全屏模态播放视频,基于 expo-video
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
View,
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
TouchableOpacity,
|
||||
Text,
|
||||
StatusBar,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { VideoView, useVideoPlayer } from 'expo-video';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
@@ -79,6 +80,13 @@ export const VideoPlayerModal: React.FC<VideoPlayerModalProps> = ({
|
||||
url,
|
||||
onClose,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
if (visible && Platform.OS === 'web') {
|
||||
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
|
||||
active?.blur?.();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
|
||||
Reference in New Issue
Block a user