/** * 应用中心:聚合站内轻应用入口 */ import React, { useCallback, useMemo, useState } from 'react'; import { View, StyleSheet, ScrollView, TouchableOpacity } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { StatusBar } from 'expo-status-bar'; import { useRouter } from 'expo-router'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import { colors, spacing, fontSizes, borderRadius, shadows } from '../../theme'; import * as hrefs from '../../navigation/hrefs'; import { Text, ResponsiveContainer } from '../../components/common'; import { useResponsive } from '../../hooks/useResponsive'; type AppItem = { id: string; title: string; subtitle: string; icon: React.ComponentProps['name']; gradient: readonly [string, string, ...string[]]; href: string; }; const APP_ENTRIES: AppItem[] = [ { id: 'schedule', title: '课表', subtitle: '周视图 · 教务同步', icon: 'calendar-week', gradient: [colors.primary.main, colors.primary.light], href: hrefs.hrefSchedule(), }, ]; export const AppsScreen: React.FC = () => { const router = useRouter(); const insets = useSafeAreaInsets(); const { isMobile, isTablet, width } = useResponsive(); const columns = useMemo(() => { if (width >= 900) return 4; if (isTablet || width >= 600) return 3; return 2; }, [isTablet, width]); const gap = spacing.md; const [gridWidth, setGridWidth] = useState(0); const cardWidth = gridWidth > 0 ? (gridWidth - gap * (columns - 1)) / columns : undefined; const scrollBottomInset = isMobile ? 88 + insets.bottom + spacing.md : spacing['3xl']; const onOpenApp = useCallback( (href: string) => { router.push(href); }, [router] ); return ( 应用 学习与生活常用工具,持续扩充中 全部应用 setGridWidth(e.nativeEvent.layout.width)} > {APP_ENTRIES.map(item => ( onOpenApp(item.href)} activeOpacity={0.88} > {item.title} {item.subtitle} 打开 ))} 更多应用敬请期待 ); }; export default AppsScreen; const styles = StyleSheet.create({ safe: { flex: 1, backgroundColor: colors.background.default, }, scroll: { flex: 1, }, scrollContent: { paddingTop: spacing.md, paddingHorizontal: spacing.lg, }, hero: { borderRadius: borderRadius['2xl'], paddingVertical: spacing['2xl'], paddingHorizontal: spacing.xl, marginBottom: spacing['2xl'], overflow: 'hidden', }, heroIconWrap: { marginBottom: spacing.md, }, heroIconGradient: { width: 52, height: 52, borderRadius: borderRadius.xl, alignItems: 'center', justifyContent: 'center', ...shadows.md, }, heroTitle: { fontSize: fontSizes['3xl'], fontWeight: '800', color: colors.text.primary, letterSpacing: -0.5, }, heroSubtitle: { marginTop: spacing.xs, fontSize: fontSizes.sm, color: colors.text.secondary, lineHeight: 20, maxWidth: 320, }, sectionLabel: { fontSize: fontSizes.xs, fontWeight: '700', color: colors.text.secondary, textTransform: 'uppercase', letterSpacing: 1.2, marginBottom: spacing.md, marginLeft: 2, }, grid: { flexDirection: 'row', flexWrap: 'wrap', width: '100%', }, cardFlex: { flex: 1, minWidth: 140, maxWidth: '100%', }, card: { backgroundColor: colors.background.paper, borderRadius: borderRadius.xl, padding: spacing.lg, borderWidth: 1, borderColor: `${colors.divider}99`, }, cardIconRing: { width: 52, height: 52, borderRadius: borderRadius.lg, padding: 2, marginBottom: spacing.md, alignSelf: 'flex-start', }, cardIconInner: { flex: 1, borderRadius: borderRadius.md, backgroundColor: colors.background.paper, alignItems: 'center', justifyContent: 'center', }, cardTitle: { fontSize: fontSizes.lg, fontWeight: '700', color: colors.text.primary, }, cardSubtitle: { marginTop: spacing.xs, fontSize: fontSizes.sm, color: colors.text.secondary, lineHeight: 18, minHeight: 36, }, cardFooter: { flexDirection: 'row', alignItems: 'center', marginTop: spacing.md, }, cardAction: { fontSize: fontSizes.sm, fontWeight: '600', color: colors.primary.main, }, hint: { marginTop: spacing['3xl'], textAlign: 'center', fontSize: fontSizes.sm, color: colors.text.hint, }, });