/** * 学习资料主页:学科分类网格 */ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { View, StyleSheet, ScrollView, TouchableOpacity, StatusBar, RefreshControl, } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { spacing, fontSizes, borderRadius, shadows, useAppColors, useResolvedColorScheme, type AppColors, } from '../../theme'; import * as hrefs from '../../navigation/hrefs'; import { Text, ResponsiveContainer, Loading } from '../../components/common'; import { useResponsive, useResponsiveSpacing, useBreakpointGTE } from '../../hooks/useResponsive'; import type { MaterialSubject } from '../../types/material'; import { materialRepository } from '../../data/repositories/MaterialRepository'; export const MaterialsScreen: React.FC = () => { const colors = useAppColors(); const resolvedScheme = useResolvedColorScheme(); const statusBarStyle = resolvedScheme === 'dark' ? 'light-content' : 'dark-content'; const styles = useMemo(() => createMaterialsStyles(colors), [colors]); const router = useRouter(); const insets = useSafeAreaInsets(); const { isMobile } = useResponsive(); const isWideScreen = useBreakpointGTE('lg'); const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 }); const [subjects, setSubjects] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isRefreshing, setIsRefreshing] = useState(false); const [error, setError] = useState(null); const scrollBottomInset = isMobile ? 88 + insets.bottom + spacing.md : spacing['3xl']; const loadSubjects = useCallback(async () => { try { setError(null); const data = await materialRepository.getSubjects(); setSubjects(data); } catch (err) { setError('加载学科列表失败'); console.error('Failed to load subjects:', err); } finally { setIsLoading(false); setIsRefreshing(false); } }, []); useEffect(() => { loadSubjects(); }, [loadSubjects]); const handleRefresh = useCallback(() => { setIsRefreshing(true); loadSubjects(); }, [loadSubjects]); const onOpenSubject = useCallback( (subjectId: string) => { router.push(hrefs.hrefMaterialSubject(subjectId)); }, [router] ); const cardStyle = useMemo( () => ({ backgroundColor: colors.background.paper, borderRadius: 16, overflow: 'hidden' as const, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.06, shadowRadius: 8, elevation: 2, }), [colors] ); const renderSubjectCard = (subject: MaterialSubject) => ( onOpenSubject(subject.id)} activeOpacity={0.85} > {subject.name} {subject.description || `${subject.material_count} 份资料`} {subject.material_count} ); const renderContent = () => { if (isLoading) { return ( ); } if (error) { return ( {error} 重试 ); } return ( <> 按学科分类浏览学习资料 {subjects.map(renderSubjectCard)} ); }; return ( {/* Header */} router.back()} style={styles.backButton}> 学习资料 {/* Content */} {isWideScreen ? ( } > {renderContent()} ) : ( } > {renderContent()} )} ); }; export default MaterialsScreen; function createMaterialsStyles(colors: AppColors) { const headerBg = colors.background.default; return StyleSheet.create({ container: { flex: 1, backgroundColor: colors.background.default, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: spacing.md, paddingVertical: spacing.md, backgroundColor: headerBg, ...shadows.sm, }, headerWide: { paddingHorizontal: spacing.lg, paddingVertical: spacing.lg, }, headerLeft: { width: 44, alignItems: 'flex-start', }, backButton: { padding: spacing.xs, }, headerCenter: { flexDirection: 'row', alignItems: 'center', }, headerTitle: { fontSize: 19, fontWeight: '700', color: colors.text.primary, }, headerTitleWide: { fontSize: 22, }, headerRight: { width: 44, alignItems: 'flex-end', }, searchButton: { padding: spacing.xs, }, scroll: { flex: 1, backgroundColor: headerBg, }, scrollContent: { paddingTop: spacing.md, flexGrow: 1, backgroundColor: headerBg, }, pageSubtitle: { marginBottom: spacing.lg, lineHeight: fontSizes.sm * 1.45, }, subjectsGrid: { gap: spacing.md, }, subjectCard: { flexDirection: 'row', alignItems: 'center', paddingVertical: spacing.lg, paddingHorizontal: spacing.lg, }, subjectIconCircle: { width: 56, height: 56, borderRadius: borderRadius.full, alignItems: 'center', justifyContent: 'center', }, subjectContent: { flex: 1, marginLeft: spacing.md, marginRight: spacing.sm, }, subjectName: { fontWeight: '700', fontSize: fontSizes.md + 1, color: colors.text.primary, }, subjectDesc: { marginTop: 4, }, subjectMeta: { flexDirection: 'row', alignItems: 'center', gap: spacing.xs, }, centerContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingVertical: spacing['3xl'], }, errorText: { marginTop: spacing.md, textAlign: 'center', }, retryButton: { marginTop: spacing.md, paddingVertical: spacing.sm, paddingHorizontal: spacing.lg, }, }); }