refactor: restructure core architecture and responsive system
All checks were successful
Frontend CI / ota-android (push) Successful in 1m40s
Frontend CI / ota-ios (push) Successful in 1m39s
Frontend CI / build-and-push-web (push) Successful in 3m30s
Frontend CI / build-android-apk (push) Successful in 1h1m8s

This commit implements a major architectural refactor to improve modularity, type safety, and maintainability across the codebase.

Key changes include:
- **Responsive System Refactor**: Migrated from a monolithic `useResponsive` hook to a set of specialized, granular hooks (`useBreakpoint`, `useOrientation`, `usePlatform`, etc.) located in `src/presentation/hooks/responsive`. This reduces unnecessary re-renders and improves developer experience.
- **Core Service Restructuring**: Introduced a new directory structure for services, including dedicated folders for `datasources`, `mappers`, and domain-specific types (`message`, `post`).
- **Data Layer Improvements**:
    - Centralized JSON parsing logic in `src/database/core/jsonUtils.ts`.
    - Cleaned up repository implementations by removing redundant local utility functions.
    - Refactored `MessageMapper` to use the new centralized JSON utility.
- **Type System Cleanup**:
    - Decomposed the large `src/types/dto.ts` into a modular `src/types/dto/` directory.
    - Simplified `src/types/index.ts` and introduced backward-compatible aliases for core entities.
- **Utility Consolidation**:
    - Created a centralized `src/utils/formatTime.ts` to replace fragmented date formatting logic across various screens and components.
    - Removed deprecated responsive utility files in favor of the new hook-based system.
- **Service Logic Refinement**: Refactored `ApiClient` and `WebSocketService` to use a centralized `showVerificationModal` service, removing duplicated state management for verification prompts.
This commit is contained in:
2026-05-05 19:07:33 +08:00
parent f5f9c3a619
commit 3196972596
96 changed files with 4609 additions and 3149 deletions

View File

@@ -19,7 +19,7 @@ import {
} from '../../theme';
import * as hrefs from '../../navigation/hrefs';
import { Text } from '../../components/common';
import { useResponsive, useResponsiveSpacing, useBreakpointGTE } from '../../hooks/useResponsive';
import { useResponsive, useResponsiveSpacing, useBreakpointGTE } from '../../hooks';
type AppItem = {
id: string;

View File

@@ -32,7 +32,7 @@ import { channelService, postService } from '../../services';
import { PostCard, SearchBar, ShareSheet } from '../../components/business';
import type { PostCardAction } from '../../components/business/PostCard';
import { Loading, EmptyState, Text, ImageGallery, ImageGridItem } from '../../components/common';
import { useResponsive, useResponsiveSpacing } from '../../hooks/useResponsive';
import { useResponsive, useResponsiveSpacing } from '../../hooks';
import { useDifferentialPosts } from '../../hooks/useDifferentialPosts';
import { postSyncService } from '@/services/post';
import { SearchScreen } from './SearchScreen';

View File

@@ -13,7 +13,7 @@ import type {
TradeType,
} from '../../types/trade';
import { useRouter } from 'expo-router';
import { useResponsive, useResponsiveSpacing } from '../../hooks/useResponsive';
import { useResponsive, useResponsiveSpacing } from '../../hooks';
type TradeFilterType = 'all' | 'sell' | 'buy';

View File

@@ -43,8 +43,9 @@ import { postSyncService } from '@/services/post';
import { useCursorPagination } from '../../hooks/useCursorPagination';
import { CommentItem, VoteCard, ReportDialog, ShareSheet, PostContentRenderer, PostMentionInput } from '../../components/business';
import { Avatar, Button, Loading, EmptyState, Text, ImageGallery, ImageGrid, ImageGridItem, AdaptiveLayout, AppBackButton } from '../../components/common';
import { useResponsive, useResponsiveValue, useResponsiveSpacing } from '../../hooks/useResponsive';
import { useResponsive, useResponsiveValue, useResponsiveSpacing } from '../../hooks';
import { handleError } from '@/services/core';
import { formatDateTime as formatDateTimeUtil, formatRelativeTime as formatRelativeTimeUtil } from '@/utils/formatTime';
import * as hrefs from '../../navigation/hrefs';
const EMOJIS = [
@@ -458,37 +459,9 @@ export const PostDetailScreen: React.FC = () => {
});
}, [comments.length, loadMoreComments, postId]);
const formatDateTime = (dateString?: string | null): string => {
if (!dateString) return '';
const date = new Date(dateString);
if (Number.isNaN(date.getTime())) return '';
const pad = (num: number) => String(num).padStart(2, '0');
return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
};
const formatDateTime = (dateString?: string | null): string => formatDateTimeUtil(dateString);
const formatRelativeTime = (dateString?: string | null): string => {
if (!dateString) return '';
const date = new Date(dateString);
if (Number.isNaN(date.getTime())) return '';
const now = Date.now();
const diffMs = now - date.getTime();
if (diffMs < 0) return formatDateTime(dateString);
const minuteMs = 60 * 1000;
const hourMs = 60 * minuteMs;
const dayMs = 24 * hourMs;
if (diffMs < minuteMs) return '刚刚';
if (diffMs < hourMs) return `${Math.floor(diffMs / minuteMs)}分钟前`;
if (diffMs < dayMs) return `${Math.floor(diffMs / hourMs)}小时前`;
if (diffMs < 2 * dayMs) {
const pad = (num: number) => String(num).padStart(2, '0');
return `昨天 ${pad(date.getHours())}:${pad(date.getMinutes())}`;
}
return formatDateTime(dateString);
};
const formatRelativeTime = (dateString?: string | null): string => formatRelativeTimeUtil(dateString);
const getPostEditedDisplayAt = (
createdAt?: string | null,

View File

@@ -25,7 +25,7 @@ import { PostCard, TabBar, SearchBar } from '../../components/business';
import type { PostCardAction } from '../../components/business/PostCard';
import { Avatar, EmptyState, Text, ResponsiveGrid, Loading } from '../../components/common';
import * as hrefs from '../../navigation/hrefs';
import { useResponsive, useResponsiveSpacing, useResponsiveValue } from '../../hooks/useResponsive';
import { useResponsive, useResponsiveSpacing, useResponsiveValue } from '../../hooks';
import { useCursorPagination } from '../../hooks/useCursorPagination';
const TABS = ['帖子', '用户'];

View File

@@ -26,8 +26,9 @@ import {
type AppColors,
} from '../../theme';
import { Text, ResponsiveContainer, Loading } from '../../components/common';
import { useResponsive, useResponsiveSpacing, useBreakpointGTE } from '../../hooks/useResponsive';
import { useResponsive, useResponsiveSpacing, useBreakpointGTE } from '../../hooks';
import type { MaterialFile } from '../../types/material';
import { formatDate as formatDateUtil } from '../../utils/formatTime';
import { materialRepository } from '../../data/repositories/MaterialRepository';
import {
MATERIAL_FILE_TYPE_ICONS,
@@ -106,14 +107,7 @@ export const MaterialDetailScreen: React.FC = () => {
);
}, [material]);
const formatDate = useCallback((dateStr: string) => {
const date = new Date(dateStr);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
}, []);
const formatDate = useCallback((dateStr: string) => formatDateUtil(dateStr), []);
// 扁平化卡片样式:更柔和的阴影,更大的圆角
const cardStyle = useMemo(

View File

@@ -26,7 +26,7 @@ import {
} from '../../theme';
import * as hrefs from '../../navigation/hrefs';
import { Text, Loading } from '../../components/common';
import { useResponsive, useResponsiveSpacing, useBreakpointGTE } from '../../hooks/useResponsive';
import { useResponsive, useResponsiveSpacing, useBreakpointGTE } from '../../hooks';
// 学科卡片最大宽度设置
const SUBJECT_CARD_MAX_WIDTH = 720;

View File

@@ -27,7 +27,7 @@ import {
} from '../../theme';
import * as hrefs from '../../navigation/hrefs';
import { Text, Loading } from '../../components/common';
import { useResponsive, useResponsiveSpacing, useBreakpointGTE } from '../../hooks/useResponsive';
import { useResponsive, useResponsiveSpacing, useBreakpointGTE } from '../../hooks';
// 资料卡片最大宽度
const MATERIAL_CARD_MAX_WIDTH = 720;

View File

@@ -37,7 +37,7 @@ import { Text, ImageGallery, ImageGridItem } from '../../components/common';
import { useAppColors, useResolvedColorScheme } from '../../theme';
import * as hrefs from '../../navigation/hrefs';
import { messageManager, callStore } from '../../stores';
import { useBreakpointGTE } from '../../hooks/useResponsive';
import { useBreakpointGTE } from '../../hooks';
import {
useChatScreen,
useChatScreenStyles,

View File

@@ -38,7 +38,7 @@ import { groupService } from '@/services/message';
import { uploadService } from '@/services/upload';
import { messageService } from '@/services/message';
import { Avatar, Text, Button, Loading, Divider } from '../../components/common';
import { useResponsive, useBreakpointGTE } from '../../hooks/useResponsive';
import { useResponsive, useBreakpointGTE } from '../../hooks';
import {
GroupResponse,
GroupMemberResponse,

View File

@@ -34,7 +34,7 @@ import {
} from '../../stores/group';
import { enrichGroupMembersWithUserProfiles } from '../../stores/group';
import { Avatar, Text, Button, Loading, EmptyState, Divider, ResponsiveContainer, AppBackButton } from '../../components/common';
import { useResponsive, useBreakpointGTE } from '../../hooks/useResponsive';
import { useResponsive, useBreakpointGTE } from '../../hooks';
import { useCursorPagination } from '../../hooks/useCursorPagination';
import {
GroupMemberResponse,

View File

@@ -38,6 +38,7 @@ import {
type AppColors,
} from '../../theme';
import { ConversationResponse, UserDTO, MessageResponse, extractTextFromSegments } from '../../types/dto';
import { formatTime as formatTimeUtil } from '../../utils/formatTime';
import { authService } from '../../services';
import { blurActiveElement } from '../../infrastructure/platform';
import { useUserStore, useAuthStore } from '../../stores';
@@ -52,7 +53,7 @@ import {
useMessageStore,
} from '../../stores';
import { Avatar, Text, EmptyState, ResponsiveContainer, AppBackButton } from '../../components/common';
import { useResponsive, useBreakpointGTE } from '../../hooks/useResponsive';
import { useResponsive, useBreakpointGTE } from '../../hooks';
import * as hrefs from '../../navigation/hrefs';
// 导入 ChatScreen 组件用于桌面端双栏布局
import { ChatScreen } from './ChatScreen';
@@ -245,32 +246,7 @@ export const MessageListScreen: React.FC = () => {
// 格式化时间(稳定引用,配合会话行 memo
const formatTime = useCallback((dateString: string): string => {
if (!dateString) return '';
try {
const date = new Date(dateString);
if (Number.isNaN(date.getTime())) return '';
const now = new Date();
const diffInHours = (now.getTime() - date.getTime()) / (1000 * 60 * 60);
if (diffInHours < 24 && date.getDate() === now.getDate()) {
return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
}
const yesterday = new Date(now);
yesterday.setDate(yesterday.getDate() - 1);
if (date.getDate() === yesterday.getDate()) {
return '昨天';
}
if (diffInHours < 24 * 7) {
const days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
return days[date.getDay()];
}
return `${date.getMonth() + 1}/${date.getDate()}`;
} catch {
return '';
}
return formatTimeUtil(dateString);
}, []);
// 跳转到聊天页

View File

@@ -10,7 +10,7 @@ import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Avatar, Text } from '../../../../components/common';
import { useAppColors, spacing } from '../../../../theme';
import { useChatScreenStyles, useChatDynamicStyles } from './styles';
import { useBreakpointGTE } from '../../../../hooks/useResponsive';
import { useBreakpointGTE } from '../../../../hooks';
import { ChatHeaderProps } from './types';
export const ChatHeader: React.FC<ChatHeaderProps> = ({

View File

@@ -10,7 +10,7 @@ import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Text } from '../../../../components/common';
import { useAppColors } from '../../../../theme';
import { useChatScreenStyles, useChatDynamicStyles } from './styles';
import { useBreakpointGTE } from '../../../../hooks/useResponsive';
import { useBreakpointGTE } from '../../../../hooks';
import { ChatInputProps, GroupMessage, SenderInfo } from './types';
import { extractTextFromSegments } from '../../../../types/dto';

View File

@@ -12,7 +12,7 @@ import { MaterialCommunityIcons } from '@expo/vector-icons';
import * as ImagePicker from 'expo-image-picker';
import { Text } from '../../../../components/common';
import { useChatScreenStyles } from './styles';
import { useResponsive, useBreakpointGTE } from '../../../../hooks/useResponsive';
import { useResponsive, useBreakpointGTE } from '../../../../hooks';
import { EMOJIS } from './constants';
import { CustomSticker, getCustomStickers, batchDeleteStickers, addStickerFromUrl } from '@/services/message';
import { useAppColors, spacing } from '../../../../theme';

View File

@@ -19,7 +19,7 @@ import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Avatar, Text } from '../../../../components/common';
import { useAppColors, spacing, fontSizes, shadows, type AppColors } from '../../../../theme';
import { GroupMemberResponse, GroupResponse, GroupAnnouncementResponse } from '../../../../types/dto';
import { useBreakpointGTE } from '../../../../hooks/useResponsive';
import { useBreakpointGTE } from '../../../../hooks';
import { groupManager } from '../../../../stores/group';
import { groupService } from '@/services/message';

View File

@@ -19,7 +19,7 @@ import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Avatar, Text } from '../../../../components/common';
import { useAppColors, spacing, fontSizes, shadows, borderRadius, type AppColors } from '../../../../theme';
import { GroupMemberResponse, GroupResponse, GroupAnnouncementResponse } from '../../../../types/dto';
import { useBreakpointGTE } from '../../../../hooks/useResponsive';
import { useBreakpointGTE } from '../../../../hooks';
import { groupManager } from '../../../../stores/group';
import { groupService } from '@/services/message';

View File

@@ -15,7 +15,7 @@ import {
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Avatar, Text, ImageGridItem } from '../../../../components/common';
import { useChatScreenStyles } from './styles';
import { useResponsive, useBreakpointGTE } from '../../../../hooks/useResponsive';
import { useResponsive, useBreakpointGTE } from '../../../../hooks';
import { MessageBubbleProps, SenderInfo, MenuPosition, GroupMessage } from './types';
import { MessageSegmentsRenderer } from './SegmentRenderer';
import { MessageSegment, ImageSegmentData, extractTextFromSegments } from '../../../../types/dto';

View File

@@ -18,8 +18,7 @@ import {
Alert,
} from 'react-native';
import { useLocalSearchParams, router } from 'expo-router';
import { formatDistanceToNow } from 'date-fns';
import { zhCN } from 'date-fns/locale';
import { formatChatTime } from '@/utils/formatTime';
import * as ImagePicker from 'expo-image-picker';
import { GroupMemberResponse, MessageSegment, TextSegmentData, ImageSegmentData, AtSegmentData, ReplySegmentData, MessageStatus } from '../../../../types/dto';
import { messageService } from '@/services/message';
@@ -707,24 +706,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
// 格式化时间
const formatTime = useCallback((dateString: string): string => {
if (!dateString) return '';
try {
const date = new Date(dateString);
if (Number.isNaN(date.getTime())) return '';
const now = new Date();
const diffInHours = (now.getTime() - date.getTime()) / (1000 * 60 * 60);
if (diffInHours < 24 && date.getDate() === now.getDate()) {
return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
}
return formatDistanceToNow(date, {
addSuffix: true,
locale: zhCN,
});
} catch {
return '';
}
return formatChatTime(dateString);
}, []);
// 检查是否需要显示时间分隔

View File

@@ -33,7 +33,7 @@ import {
useAppColors,
type AppColors,
} from '../../theme';
import { useResponsive } from '../../hooks/useResponsive';
import { useResponsive } from '../../hooks';
import {
Course,
COURSE_COLORS,

View File

@@ -11,7 +11,7 @@ import { useIsAuthenticated, useCurrentUser } from '../../stores/auth';
import type { TradeItemDetailDTO } from '../../types/trade';
import { TRADE_CONDITION_MAP } from '../../types/trade';
import { hrefChat, hrefEditTrade } from '../../navigation/hrefs';
import { useResponsive, useResponsiveValue, useResponsiveSpacing } from '../../hooks/useResponsive';
import { useResponsive, useResponsiveValue, useResponsiveSpacing } from '../../hooks';
function createStyles(colors: AppColors) {
return StyleSheet.create({