refactor(database): migrate to new modular database layer and unify data access
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 2m44s
Frontend CI / ota-android (push) Successful in 12m51s
Frontend CI / build-android-apk (push) Successful in 1h1m26s

- 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:
lafay
2026-04-04 08:01:45 +08:00
parent 189b977fac
commit 82c2970a85
76 changed files with 3382 additions and 2000 deletions

View File

@@ -5,7 +5,7 @@
*/
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { View, TouchableOpacity, ActivityIndicator, Modal, Alert, Dimensions, StyleSheet, FlatList, ListRenderItem, ScrollView } from 'react-native';
import { View, TouchableOpacity, ActivityIndicator, Modal, Alert, Dimensions, StyleSheet, FlatList, ListRenderItem, ScrollView, Platform } from 'react-native';
import { Image as ExpoImage } from 'expo-image';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import * as ImagePicker from 'expo-image-picker';
@@ -18,6 +18,12 @@ import { useAppColors, spacing } from '../../../../theme';
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
const blurActiveElementOnWeb = () => {
if (Platform.OS !== 'web') return;
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
active?.blur?.();
};
// 表情尺寸配置 - 固定宽度 40px使用 flex 布局
const EMOJI_SIZES = {
mobile: { size: 24, itemWidth: 40, itemHeight: 40 },
@@ -176,6 +182,7 @@ export const EmojiPanel: React.FC<EmojiPanelProps> = ({
// 打开管理界面
const handleOpenManage = () => {
blurActiveElementOnWeb();
setShowManageModal(true);
setManageMode(false);
setSelectedStickers(new Set());

View File

@@ -33,6 +33,7 @@ import {
} from '../../../../types/dto';
import { spacing, useAppColors, type AppColors } from '../../../../theme';
import { SenderInfo } from './types';
import { useChatSettingsStore } from '../../../../stores/chatSettingsStore';
const IMAGE_MAX_WIDTH = 200;
const IMAGE_MAX_HEIGHT = 260;
@@ -617,7 +618,8 @@ export const ReplyPreviewSegment: React.FC<ReplyPreviewSegmentProps> = ({
getSenderInfo,
}) => {
const themeColors = useAppColors();
const styles = useMemo(() => createSegmentStyles(themeColors), [themeColors]);
const fontSize = useChatSettingsStore((s) => s.fontSize);
const styles = useMemo(() => createSegmentStyles(themeColors, fontSize), [themeColors, fontSize]);
if (!replyMessage) {
// 如果没有引用消息详情只显示引用ID
@@ -729,7 +731,8 @@ export const MessageSegmentsRenderer: React.FC<{
getSenderInfo,
}) => {
const themeColors = useAppColors();
const segStyles = useMemo(() => createSegmentStyles(themeColors), [themeColors]);
const fontSize = useChatSettingsStore((s) => s.fontSize);
const segStyles = useMemo(() => createSegmentStyles(themeColors, fontSize), [themeColors, fontSize]);
const replySegment = segments.find(s => s.type === 'reply');
const otherSegments = segments.filter(s => s.type !== 'reply');
@@ -815,7 +818,7 @@ const formatDuration = (seconds: number): string => {
return mins > 0 ? `${mins}:${secs.toString().padStart(2, '0')}` : `0:${secs.toString().padStart(2, '0')}`;
};
function createSegmentStyles(colors: AppColors) {
function createSegmentStyles(colors: AppColors, fontSize: number = 16) {
return StyleSheet.create({
// 容器
segmentsContainer: {
@@ -847,10 +850,10 @@ function createSegmentStyles(colors: AppColors) {
marginTop: 2,
},
// 文本 - 适配暗色模式
// 文本 - 适配暗色模式,支持动态字号
textContent: {
fontSize: 17.5,
lineHeight: 25,
fontSize,
lineHeight: Math.round(fontSize * 1.43),
letterSpacing: 0.1,
fontWeight: '400',
},
@@ -861,10 +864,10 @@ function createSegmentStyles(colors: AppColors) {
color: colors.chat.textPrimary,
},
// @提及 - 微信/QQ 风格:更精致的高亮
// @提及 - 微信/QQ 风格:更精致的高亮,支持动态字号
atText: {
fontSize: 16,
lineHeight: 22,
fontSize,
lineHeight: Math.round(fontSize * 1.4),
fontWeight: '500',
},
atTextMe: {
@@ -904,7 +907,7 @@ function createSegmentStyles(colors: AppColors) {
height: 28,
},
faceText: {
fontSize: 16,
fontSize,
color: '#666',
},
@@ -930,7 +933,7 @@ function createSegmentStyles(colors: AppColors) {
},
voiceDuration: {
marginLeft: spacing.sm,
fontSize: 15,
fontSize: fontSize - 1,
fontWeight: '500',
},
@@ -1113,14 +1116,14 @@ function createSegmentStyles(colors: AppColors) {
minWidth: 0,
},
replySender: {
fontSize: 13,
fontSize: fontSize - 3,
fontWeight: '600',
color: colors.chat.link,
marginRight: 6,
flexShrink: 0,
},
replyText: {
fontSize: 13,
fontSize: fontSize - 3,
color: colors.chat.textTertiary,
flex: 1,
flexShrink: 1,

View File

@@ -42,10 +42,7 @@ import {
PendingChatAttachment,
} from './types';
import { TIME_SEPARATOR_INTERVAL, MEMBERS_PAGE_SIZE, MAX_PENDING_CHAT_IMAGES } from './constants';
import {
deleteMessage as deleteMessageFromDb,
clearConversationMessages,
} from '../../../../services/database';
import { messageRepository } from '@/database';
export const useChatScreen = () => {
const getSendErrorMessage = useCallback((error: unknown, fallback: string) => {
@@ -1209,7 +1206,7 @@ export const useChatScreen = () => {
const updatedMessages = messages.filter(m => m.id !== messageId);
// 注意:这里我们依赖 MessageManager 的数据,所以需要通过其他方式刷新
// 暂时只删除本地数据库
await deleteMessageFromDb(messageId);
await messageRepository.delete(messageId);
} catch (error) {
console.error('删除消息失败:', error);
Alert.alert('删除失败', '无法删除消息');
@@ -1224,7 +1221,7 @@ export const useChatScreen = () => {
setLastSeq(0);
setFirstSeq(0);
setHasMoreHistory(true);
await clearConversationMessages(conversationId);
await messageRepository.clearConversation(conversationId);
// 刷新消息列表
await refreshMessages();
} catch (error) {