feat(Theme): enhance theming and UI consistency across components
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 8m15s
Frontend CI / ota-android (push) Successful in 10m56s
Frontend CI / build-android-apk (push) Successful in 1h3m22s

- Updated app.json to set userInterfaceStyle to automatic for improved theme adaptability.
- Refactored layout components to utilize useAppColors for dynamic theming, ensuring consistent color usage.
- Introduced SystemChrome component to manage system UI background color based on theme.
- Enhanced TabsLayout, ProfileStackLayout, and other components to leverage new theming structure.
- Improved QRCodeScanner, SearchBar, and CommentItem styles to align with the updated theme system.
- Consolidated styles in SystemMessageItem and TabBar for better maintainability and visual coherence.
This commit is contained in:
lafay
2026-03-25 05:16:54 +08:00
parent 90d834695f
commit 4ee3079b9f
86 changed files with 6777 additions and 5890 deletions

View File

@@ -1,10 +1,10 @@
import React from 'react';
import React, { useMemo } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Alert, ScrollView, Pressable } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useRouter, useLocalSearchParams } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { colors, spacing, borderRadius, fontSizes, shadows } from '../../theme';
import { spacing, borderRadius, fontSizes, shadows, useAppColors, type AppColors } from '../../theme';
import { routePayloadCache } from '../../stores/routePayloadCache';
import { scheduleService } from '../../services/scheduleService';
@@ -32,15 +32,146 @@ const formatWeekRanges = (weeks: number[]) => {
return ranges.join(', ');
};
function createCourseDetailStyles(colors: AppColors) {
return StyleSheet.create({
container: {
flex: 1,
},
mask: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.32)',
justifyContent: 'center',
paddingHorizontal: spacing.lg,
},
card: {
backgroundColor: colors.background.paper,
borderRadius: borderRadius.xl,
paddingVertical: spacing.lg,
paddingHorizontal: spacing.lg,
maxHeight: '82%',
...shadows.lg,
},
contentScroll: {
maxHeight: '100%',
},
contentScrollContainer: {
paddingBottom: spacing.xs,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: spacing.lg,
borderBottomWidth: 1,
borderBottomColor: `${colors.divider}AA`,
paddingBottom: spacing.md,
},
headerLeft: {
flexDirection: 'row',
alignItems: 'center',
},
iconBadge: {
width: 30,
height: 30,
borderRadius: borderRadius.full,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: `${colors.primary.main}12`,
marginRight: spacing.sm,
},
title: {
fontSize: fontSizes.xl,
fontWeight: '700',
color: colors.text.primary,
},
closeButton: {
width: 28,
height: 28,
alignItems: 'center',
justifyContent: 'center',
borderRadius: borderRadius.full,
backgroundColor: colors.background.default,
},
row: {
marginBottom: spacing.lg,
flexDirection: 'row',
alignItems: 'flex-start',
},
rowLast: {
marginBottom: 0,
},
label: {
width: 76,
fontSize: fontSizes.md,
fontWeight: '600',
color: colors.text.secondary,
lineHeight: 22,
marginTop: 1,
},
value: {
flex: 1,
fontSize: fontSizes.lg,
fontWeight: '600',
color: colors.text.primary,
lineHeight: 24,
},
timeList: {
flex: 1,
gap: spacing.sm,
},
timeItemCard: {
borderWidth: 1,
borderColor: `${colors.primary.main}20`,
borderRadius: borderRadius.md,
backgroundColor: `${colors.primary.main}08`,
paddingHorizontal: spacing.sm,
paddingVertical: spacing.sm,
},
timeItemMain: {
fontSize: fontSizes.md,
fontWeight: '600',
color: colors.text.primary,
lineHeight: 20,
},
timeItemSub: {
marginTop: spacing.xs,
fontSize: fontSizes.sm,
color: colors.text.secondary,
lineHeight: 18,
},
actionRow: {
marginTop: spacing.lg,
alignItems: 'flex-end',
},
deleteButton: {
height: 38,
paddingHorizontal: spacing.md,
borderRadius: borderRadius.full,
backgroundColor: `${colors.error.main}10`,
borderWidth: 1,
borderColor: `${colors.error.main}25`,
flexDirection: 'row',
alignItems: 'center',
gap: spacing.xs,
},
deleteButtonText: {
fontSize: fontSizes.sm,
fontWeight: '700',
color: colors.error.main,
},
});
}
const CourseDetailScreen: React.FC = () => {
const router = useRouter();
const colors = useAppColors();
const styles = useMemo(() => createCourseDetailStyles(colors), [colors]);
const { courseId } = useLocalSearchParams<{ courseId?: string }>();
const cached = courseId ? routePayloadCache.getCourseDetail(courseId) : undefined;
const course = cached?.course;
const relatedCourses = cached?.relatedCourses ?? [];
const timeLines =
relatedCourses.length > 0 ? relatedCourses : course ? [course] : [];
const timeLines = relatedCourses.length > 0 ? relatedCourses : course ? [course] : [];
const handleDeleteCourse = () => {
if (!course) return;
@@ -98,7 +229,9 @@ const CourseDetailScreen: React.FC = () => {
>
<View style={styles.row}>
<Text style={styles.label}></Text>
<Text style={styles.value} numberOfLines={2}>{course.name}</Text>
<Text style={styles.value} numberOfLines={2}>
{course.name}
</Text>
</View>
<View style={[styles.row, styles.rowLast]}>
@@ -110,9 +243,7 @@ const CourseDetailScreen: React.FC = () => {
{formatWeekRanges(item.weeks)} · {WEEKDAY_NAMES[item.dayOfWeek]}
{getMergedSectionIndex(item.startSection)}
</Text>
<Text style={styles.timeItemSub}>
{item.teacher || '未填写'}
</Text>
<Text style={styles.timeItemSub}>{item.teacher || '未填写'}</Text>
{item.location ? <Text style={styles.timeItemSub}>{item.location}</Text> : null}
</View>
))}
@@ -132,132 +263,4 @@ const CourseDetailScreen: React.FC = () => {
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
mask: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.32)',
justifyContent: 'center',
paddingHorizontal: spacing.lg,
},
card: {
backgroundColor: colors.background.paper,
borderRadius: borderRadius.xl,
paddingVertical: spacing.lg,
paddingHorizontal: spacing.lg,
maxHeight: '82%',
...shadows.lg,
},
contentScroll: {
maxHeight: '100%',
},
contentScrollContainer: {
paddingBottom: spacing.xs,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: spacing.lg,
borderBottomWidth: 1,
borderBottomColor: `${colors.divider}AA`,
paddingBottom: spacing.md,
},
headerLeft: {
flexDirection: 'row',
alignItems: 'center',
},
iconBadge: {
width: 30,
height: 30,
borderRadius: borderRadius.full,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: `${colors.primary.main}12`,
marginRight: spacing.sm,
},
title: {
fontSize: fontSizes.xl,
fontWeight: '700',
color: colors.text.primary,
},
closeButton: {
width: 28,
height: 28,
alignItems: 'center',
justifyContent: 'center',
borderRadius: borderRadius.full,
backgroundColor: colors.background.default,
},
row: {
marginBottom: spacing.lg,
flexDirection: 'row',
alignItems: 'flex-start',
},
rowLast: {
marginBottom: 0,
},
label: {
width: 76,
fontSize: fontSizes.md,
fontWeight: '600',
color: colors.text.secondary,
lineHeight: 22,
marginTop: 1,
},
value: {
flex: 1,
fontSize: fontSizes.lg,
fontWeight: '600',
color: colors.text.primary,
lineHeight: 24,
},
timeList: {
flex: 1,
gap: spacing.sm,
},
timeItemCard: {
borderWidth: 1,
borderColor: `${colors.primary.main}20`,
borderRadius: borderRadius.md,
backgroundColor: `${colors.primary.main}08`,
paddingHorizontal: spacing.sm,
paddingVertical: spacing.sm,
},
timeItemMain: {
fontSize: fontSizes.md,
fontWeight: '600',
color: colors.text.primary,
lineHeight: 20,
},
timeItemSub: {
marginTop: spacing.xs,
fontSize: fontSizes.sm,
color: colors.text.secondary,
lineHeight: 18,
},
actionRow: {
marginTop: spacing.lg,
alignItems: 'flex-end',
},
deleteButton: {
height: 38,
paddingHorizontal: spacing.md,
borderRadius: borderRadius.full,
backgroundColor: `${colors.error.main}10`,
borderWidth: 1,
borderColor: `${colors.error.main}25`,
flexDirection: 'row',
alignItems: 'center',
gap: spacing.xs,
},
deleteButtonText: {
fontSize: fontSizes.sm,
fontWeight: '700',
color: colors.error.main,
},
});
export default CourseDetailScreen;

View File

@@ -23,7 +23,14 @@ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { useFocusEffect } from '@react-navigation/native';
import { useRouter } from 'expo-router';
import { colors, fontSizes, spacing, borderRadius, shadows } from '../../theme';
import {
fontSizes,
spacing,
borderRadius,
shadows,
useAppColors,
type AppColors,
} from '../../theme';
import { useResponsive } from '../../hooks/useResponsive';
import {
Course,
@@ -148,6 +155,8 @@ const getWeekDates = (weekOffset: number = 0) => {
const INITIAL_WEEK = getCurrentWeekNumber();
export const ScheduleScreen: React.FC = () => {
const colors = useAppColors();
const styles = useMemo(() => createScheduleStyles(colors), [colors]);
const router = useRouter();
// 使用响应式 hook 检测屏幕尺寸
const { width: screenWidth, height: screenHeight, isMobile, platform } = useResponsive();
@@ -1063,7 +1072,8 @@ export const ScheduleScreen: React.FC = () => {
);
};
const styles = StyleSheet.create({
function createScheduleStyles(colors: AppColors) {
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background.paper,
@@ -1483,6 +1493,7 @@ const styles = StyleSheet.create({
color: colors.text.secondary,
fontWeight: '500',
},
});
});
}
export default ScheduleScreen;