Files
digital-pilates/app/fitness-rings-detail.tsx
richarjiang fbe0c92f0f feat(i18n): 全面实现应用核心功能模块的国际化支持
- 新增 i18n 翻译资源,覆盖睡眠、饮水、体重、锻炼、用药 AI 识别、步数、健身圆环、基础代谢及设置等核心模块
- 重构相关页面及组件(如 SleepDetail, WaterDetail, WorkoutHistory 等)使用 `useI18n` 钩子替换硬编码文本
- 升级 `utils/date` 工具库与 `DateSelector` 组件,支持基于语言环境的日期格式化与显示
- 完善登录页、注销流程及权限申请弹窗的双语提示信息
- 优化部分页面的 UI 细节与字体样式以适配多语言显示
2025-11-27 17:54:36 +08:00

887 lines
26 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { CircularRing } from '@/components/CircularRing';
import { ThemedView } from '@/components/ThemedView';
import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
import { useI18n } from '@/hooks/useI18n';
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
import {
fetchActivityRingsForDate,
fetchHourlyActiveCaloriesForDate,
fetchHourlyExerciseMinutesForDate,
fetchHourlyStandHoursForDate,
type ActivityRingsData,
type HourlyActivityData,
type HourlyExerciseData,
type HourlyStandData
} from '@/utils/health';
import { getFitnessExerciseMinutesInfoDismissed, setFitnessExerciseMinutesInfoDismissed } from '@/utils/userPreferences';
import { Ionicons } from '@expo/vector-icons';
import DateTimePicker from '@react-native-community/datetimepicker';
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
import weekday from 'dayjs/plugin/weekday';
import { router } from 'expo-router';
import React, { useEffect, useRef, useState } from 'react';
import {
Animated,
Modal,
Platform,
Pressable,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View
} from 'react-native';
// 配置 dayjs 插件
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(weekday);
// 设置默认时区为中国时区
dayjs.tz.setDefault('Asia/Shanghai');
type WeekData = {
date: Date;
data: ActivityRingsData | null;
isToday: boolean;
dayName: string;
};
export default function FitnessRingsDetailScreen() {
const { t } = useI18n();
const safeAreaTop = useSafeAreaTop()
const colorScheme = useColorScheme();
const [weekData, setWeekData] = useState<WeekData[]>([]);
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
const [selectedDayData, setSelectedDayData] = useState<ActivityRingsData | null>(null);
const [datePickerVisible, setDatePickerVisible] = useState(false);
const [pickerDate, setPickerDate] = useState<Date>(new Date());
// 每小时数据状态
const [hourlyCaloriesData, setHourlyCaloriesData] = useState<HourlyActivityData[]>([]);
const [hourlyExerciseData, setHourlyExerciseData] = useState<HourlyExerciseData[]>([]);
const [hourlyStandData, setHourlyStandData] = useState<HourlyStandData[]>([]);
const [showExerciseInfo, setShowExerciseInfo] = useState(true);
const exerciseInfoAnim = useRef(new Animated.Value(1)).current;
useEffect(() => {
// 加载周数据和选中日期的详细数据
loadWeekData(selectedDate);
loadSelectedDayData();
loadExerciseInfoPreference();
}, [selectedDate]);
const loadExerciseInfoPreference = async () => {
try {
const dismissed = await getFitnessExerciseMinutesInfoDismissed();
setShowExerciseInfo(!dismissed);
if (!dismissed) {
exerciseInfoAnim.setValue(1);
} else {
exerciseInfoAnim.setValue(0);
}
} catch (error) {
console.error(t('fitnessRingsDetail.errors.loadExerciseInfoPreference'), error);
}
};
const loadWeekData = async (targetDate: Date) => {
const target = dayjs(targetDate).tz('Asia/Shanghai');
const today = dayjs().tz('Asia/Shanghai');
const weekDays = [];
// 获取目标日期所在周的数据 (周一到周日)
// 使用 weekday() 确保周一为一周的开始 (0=Monday, 6=Sunday)
const startOfWeek = target.weekday(0); // 周一开始
for (let i = 0; i < 7; i++) {
const currentDay = startOfWeek.add(i, 'day');
const isToday = currentDay.isSame(today, 'day');
const dayNames = [
t('fitnessRingsDetail.weekDays.monday'),
t('fitnessRingsDetail.weekDays.tuesday'),
t('fitnessRingsDetail.weekDays.wednesday'),
t('fitnessRingsDetail.weekDays.thursday'),
t('fitnessRingsDetail.weekDays.friday'),
t('fitnessRingsDetail.weekDays.saturday'),
t('fitnessRingsDetail.weekDays.sunday')
];
try {
const activityRingsData = await fetchActivityRingsForDate(currentDay.toDate());
weekDays.push({
date: currentDay.toDate(),
data: activityRingsData,
isToday,
dayName: dayNames[i]
});
} catch (error) {
console.error('Failed to fetch activity rings data for', currentDay.format('YYYY-MM-DD'), error);
weekDays.push({
date: currentDay.toDate(),
data: null,
isToday,
dayName: dayNames[i]
});
}
}
setWeekData(weekDays);
};
const loadSelectedDayData = async () => {
try {
// 并行获取活动圆环数据和每小时详细数据
const [activityRingsData, hourlyCalories, hourlyExercise, hourlyStand] = await Promise.all([
fetchActivityRingsForDate(selectedDate),
fetchHourlyActiveCaloriesForDate(selectedDate),
fetchHourlyExerciseMinutesForDate(selectedDate),
fetchHourlyStandHoursForDate(selectedDate)
]);
setSelectedDayData(activityRingsData);
setHourlyCaloriesData(hourlyCalories);
setHourlyExerciseData(hourlyExercise);
setHourlyStandData(hourlyStand);
} catch (error) {
console.error('Failed to fetch selected day activity rings data', error);
setSelectedDayData(null);
setHourlyCaloriesData([]);
setHourlyExerciseData([]);
setHourlyStandData([]);
}
};
// 日期选择器相关函数
const openDatePicker = () => {
setPickerDate(selectedDate);
setDatePickerVisible(true);
};
const closeDatePicker = () => setDatePickerVisible(false);
const onConfirmDate = async (date: Date) => {
const today = dayjs().tz('Asia/Shanghai').startOf('day');
const picked = dayjs(date).tz('Asia/Shanghai').startOf('day');
const finalDate = picked.isAfter(today) ? today.toDate() : picked.toDate();
setSelectedDate(finalDate);
closeDatePicker();
};
// 格式化头部显示的日期
const formatHeaderDate = (date: Date) => {
const dayJsDate = dayjs(date).tz('Asia/Shanghai');
return `${dayJsDate.format('YYYY年MM月DD日')}`;
};
const renderWeekRingItem = (item: WeekData, index: number) => {
const isSelected = dayjs(item.date).tz('Asia/Shanghai').isSame(dayjs(selectedDate).tz('Asia/Shanghai'), 'day');
// 使用默认值确保即使没有数据也能显示圆环
const data = item.data || {
activeEnergyBurned: 0,
activeEnergyBurnedGoal: 350,
appleExerciseTime: 0,
appleExerciseTimeGoal: 30,
appleStandHours: 0,
appleStandHoursGoal: 12,
};
const { activeEnergyBurned, activeEnergyBurnedGoal, appleExerciseTime, appleExerciseTimeGoal, appleStandHours, appleStandHoursGoal } = data;
// 计算进度百分比
const caloriesProgress = Math.min(1, Math.max(0, activeEnergyBurned / activeEnergyBurnedGoal));
const exerciseProgress = Math.min(1, Math.max(0, appleExerciseTime / appleExerciseTimeGoal));
const standProgress = Math.min(1, Math.max(0, appleStandHours / appleStandHoursGoal));
// 检查是否完成了所有目标
const isComplete = caloriesProgress >= 1 && exerciseProgress >= 1 && standProgress >= 1;
return (
<TouchableOpacity
key={index}
style={[styles.weekRingItem, isSelected && styles.weekRingItemSelected]}
onPress={() => setSelectedDate(item.date)}
>
<View style={styles.weekRingContainer}>
{/* {isComplete && (
<View style={styles.weekStarContainer}>
<Text style={styles.weekStarIcon}>✓</Text>
</View>
)} */}
<View style={styles.weekRingsWrapper}>
{/* 外圈 - 活动卡路里 (红色) */}
<View style={styles.ringPosition}>
<CircularRing
size={50}
strokeWidth={3}
trackColor="rgba(255, 59, 48, 0.15)"
progressColor="#FF3B30"
progress={caloriesProgress}
showCenterText={false}
startAngleDeg={-90}
/>
</View>
{/* 中圈 - 锻炼分钟 (橙色) */}
<View style={styles.ringPosition}>
<CircularRing
size={36}
strokeWidth={2.5}
trackColor="rgba(255, 149, 0, 0.15)"
progressColor="#FF9500"
progress={exerciseProgress}
showCenterText={false}
startAngleDeg={-90}
/>
</View>
{/* 内圈 - 站立小时 (蓝色) */}
<View style={styles.ringPosition}>
<CircularRing
size={22}
strokeWidth={2}
trackColor="rgba(0, 122, 255, 0.15)"
progressColor="#007AFF"
progress={standProgress}
showCenterText={false}
startAngleDeg={-90}
/>
</View>
</View>
<Text style={[
styles.weekDayNumber,
item.isToday && styles.weekTodayNumber,
isSelected && styles.weekSelectedNumber,
{ color: isSelected ? '#007AFF' : (item.isToday ? '#007AFF' : Colors[colorScheme ?? 'light'].text) }
]}>
{dayjs(item.date).tz('Asia/Shanghai').date()}
</Text>
</View>
<Text style={[
styles.weekDayLabel,
item.isToday && styles.weekTodayLabel,
isSelected && styles.weekSelectedLabel,
{ color: isSelected ? '#007AFF' : (item.isToday ? '#007AFF' : Colors[colorScheme ?? 'light'].tabIconDefault) }
]}>
{item.dayName}
</Text>
</TouchableOpacity>
);
};
const getClosedRingCount = () => {
let count = 0;
weekData.forEach(item => {
// 使用默认值处理空数据情况
const data = item.data || {
activeEnergyBurned: 0,
activeEnergyBurnedGoal: 350,
appleExerciseTime: 0,
appleExerciseTimeGoal: 30,
appleStandHours: 0,
appleStandHoursGoal: 12,
};
const { activeEnergyBurned, activeEnergyBurnedGoal, appleExerciseTime, appleExerciseTimeGoal, appleStandHours, appleStandHoursGoal } = data;
const caloriesComplete = activeEnergyBurned >= activeEnergyBurnedGoal;
const exerciseComplete = appleExerciseTime >= appleExerciseTimeGoal;
const standComplete = appleStandHours >= appleStandHoursGoal;
if (caloriesComplete && exerciseComplete && standComplete) {
count++;
}
});
return count;
};
const handleKnowButtonPress = async () => {
try {
await setFitnessExerciseMinutesInfoDismissed(true);
Animated.timing(exerciseInfoAnim, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start(() => {
setShowExerciseInfo(false);
});
} catch (error) {
console.error(t('fitnessRingsDetail.errors.saveExerciseInfoPreference'), error);
}
};
// 渲染简单的柱状图
const renderBarChart = (data: number[], maxValue: number, color: string, unit: string) => {
// 确保始终有24小时的数据没有数据时用0填充
const chartData = Array.from({ length: 24 }, (_, index) => {
if (data && data.length > index) {
return data[index] || 0;
}
return 0;
});
// 计算最大值如果所有数据都是0使用传入的maxValue作为参考
const maxChartValue = Math.max(...chartData, 1); // 确保最小值为1避免除零
const effectiveMaxValue = Math.max(maxChartValue, maxValue);
return (
<View style={styles.chartContainer}>
<View style={styles.chartBars}>
{chartData.map((value, index) => {
const height = Math.max(2, (value / effectiveMaxValue) * 40); // 最小高度2最大40
return (
<View
key={index}
style={[
styles.chartBar,
{
flex: 1,
height: value > 0 ? height : 2, // 没有数据时显示最小高度的灰色条
backgroundColor: value > 0 ? color : '#E5E5EA',
opacity: value > 0 ? 1 : 0.5,
marginHorizontal: 0.5
}
]}
/>
);
})}
</View>
<View style={styles.chartLabels}>
{chartData.map((_, index) => {
// 只在关键时间点显示标签0点、6点、12点、18点
if (index === 0 || index === 6 || index === 12 || index === 18) {
const hour = index;
return (
<Text key={index} style={styles.chartLabel}>
{hour.toString().padStart(2, '0')}:00
</Text>
);
}
// 对于不显示标签的小时返回一个占位的View
return <View key={index} style={styles.chartLabelSpacer} />;
})}
</View>
</View>
);
};
const renderSelectedDayDetail = () => {
// 使用默认值确保即使没有数据也能显示图表
const data = selectedDayData || {
activeEnergyBurned: 0,
activeEnergyBurnedGoal: 350,
appleExerciseTime: 0,
appleExerciseTimeGoal: 30,
appleStandHours: 0,
appleStandHoursGoal: 12,
};
const { activeEnergyBurned, activeEnergyBurnedGoal, appleExerciseTime, appleExerciseTimeGoal, appleStandHours, appleStandHoursGoal } = data;
return (
<View style={styles.detailContainer}>
{/* 活动热量卡片 */}
<View style={styles.metricCard}>
<View style={styles.cardHeader}>
<Text style={styles.cardTitle}>{t('fitnessRingsDetail.cards.activeCalories.title')}</Text>
<TouchableOpacity style={styles.helpButton}>
<Text style={styles.helpIcon}>?</Text>
</TouchableOpacity>
</View>
<View style={styles.cardValue}>
<Text style={[styles.valueText, { color: '#FF3B30' }]}>
{Math.round(activeEnergyBurned)}/{activeEnergyBurnedGoal}
</Text>
<Text style={styles.unitText}>{t('fitnessRingsDetail.cards.activeCalories.unit')}</Text>
</View>
<Text style={styles.cardSubtext}>
{Math.round(activeEnergyBurned)}{t('fitnessRingsDetail.cards.activeCalories.unit')}
</Text>
{renderBarChart(
hourlyCaloriesData.map(h => h.calories),
Math.max(activeEnergyBurnedGoal / 24, 1),
'#FF3B30',
t('fitnessRingsDetail.cards.activeCalories.unit')
)}
</View>
{/* 锻炼分钟卡片 */}
<View style={styles.metricCard}>
<View style={styles.cardHeader}>
<Text style={styles.cardTitle}>{t('fitnessRingsDetail.cards.exerciseMinutes.title')}</Text>
<TouchableOpacity style={styles.helpButton}>
<Text style={styles.helpIcon}>?</Text>
</TouchableOpacity>
</View>
<View style={styles.cardValue}>
<Text style={[styles.valueText, { color: '#FF9500' }]}>
{Math.round(appleExerciseTime)}/{appleExerciseTimeGoal}
</Text>
<Text style={styles.unitText}>{t('fitnessRingsDetail.cards.exerciseMinutes.unit')}</Text>
</View>
<Text style={styles.cardSubtext}>
{Math.round(appleExerciseTime)}{t('fitnessRingsDetail.cards.exerciseMinutes.unit')}
</Text>
{renderBarChart(
hourlyExerciseData.map(h => h.minutes),
Math.max(appleExerciseTimeGoal / 8, 1),
'#FF9500',
t('fitnessRingsDetail.cards.exerciseMinutes.unit')
)}
{/* 锻炼分钟说明 */}
{showExerciseInfo && (
<Animated.View
style={[
styles.exerciseInfo,
{
opacity: exerciseInfoAnim,
transform: [
{
scale: exerciseInfoAnim.interpolate({
inputRange: [0, 1],
outputRange: [0.95, 1],
}),
},
],
}
]}
>
<Text style={styles.exerciseTitle}>{t('fitnessRingsDetail.cards.exerciseMinutes.info.title')}</Text>
<Text style={styles.exerciseDesc}>
{t('fitnessRingsDetail.cards.exerciseMinutes.info.description')}
</Text>
<Text style={styles.exerciseRecommendation}>
{t('fitnessRingsDetail.cards.exerciseMinutes.info.recommendation')}
</Text>
<TouchableOpacity style={styles.knowButton} onPress={handleKnowButtonPress}>
<Text style={styles.knowButtonText}>{t('fitnessRingsDetail.cards.exerciseMinutes.info.knowButton')}</Text>
</TouchableOpacity>
</Animated.View>
)}
</View>
{/* 活动小时数卡片 */}
<View style={styles.metricCard}>
<View style={styles.cardHeader}>
<Text style={styles.cardTitle}>{t('fitnessRingsDetail.cards.standHours.title')}</Text>
<TouchableOpacity style={styles.helpButton}>
<Text style={styles.helpIcon}>?</Text>
</TouchableOpacity>
</View>
<View style={styles.cardValue}>
<Text style={[styles.valueText, { color: '#007AFF' }]}>
{Math.round(appleStandHours)}/{appleStandHoursGoal}
</Text>
<Text style={styles.unitText}>{t('fitnessRingsDetail.cards.standHours.unit')}</Text>
</View>
<Text style={styles.cardSubtext}>
{Math.round(appleStandHours)}{t('fitnessRingsDetail.cards.standHours.unit')}
</Text>
{renderBarChart(
hourlyStandData.map(h => h.hasStood),
1,
'#007AFF',
t('fitnessRingsDetail.cards.standHours.unit')
)}
</View>
</View>
);
};
return (
<ThemedView style={styles.container}>
{/* 头部 */}
<HeaderBar
title={formatHeaderDate(selectedDate)}
onBack={() => router.back()}
right={
<TouchableOpacity style={styles.calendarButton} onPress={openDatePicker}>
<Ionicons name="calendar-outline" size={20} color="#666666" />
</TouchableOpacity>
}
withSafeTop={true}
transparent={true}
variant="default"
/>
<ScrollView
style={styles.scrollView}
contentContainerStyle={[styles.scrollContent, {
paddingTop: safeAreaTop
}]}
showsVerticalScrollIndicator={false}
>
{/* 本周圆环横向滚动 */}
<View style={styles.weekSection}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.weekScrollContent}
style={styles.weekScrollView}
>
{weekData.map((item, index) => renderWeekRingItem(item, index))}
</ScrollView>
</View>
{/* 选中日期的详细数据 */}
{renderSelectedDayDetail()}
{/* 周闭环天数统计 */}
<View style={styles.statsContainer}>
<View style={styles.statRow}>
<Text style={[styles.statLabel, { color: Colors[colorScheme ?? 'light'].text }]}>{t('fitnessRingsDetail.stats.weeklyClosedRings')}</Text>
<View style={styles.statValue}>
<Text style={[styles.statNumber, { color: Colors[colorScheme ?? 'light'].text }]}>{getClosedRingCount()}{t('fitnessRingsDetail.stats.daysUnit')}</Text>
</View>
</View>
</View>
</ScrollView>
{/* 日期选择器弹窗 */}
<Modal
visible={datePickerVisible}
transparent
animationType="fade"
onRequestClose={closeDatePicker}
>
<Pressable style={styles.modalBackdrop} onPress={closeDatePicker} />
<View style={styles.modalSheet}>
<DateTimePicker
value={pickerDate}
mode="date"
display={Platform.OS === 'ios' ? 'inline' : 'calendar'}
minimumDate={new Date(2020, 0, 1)}
maximumDate={new Date()}
{...(Platform.OS === 'ios' ? { locale: 'zh-CN' } : {})}
onChange={(event, date) => {
if (Platform.OS === 'ios') {
if (date) setPickerDate(date);
} else {
if (event.type === 'set' && date) {
onConfirmDate(date);
} else {
closeDatePicker();
}
}
}}
/>
{Platform.OS === 'ios' && (
<View style={styles.modalActions}>
<Pressable onPress={closeDatePicker} style={[styles.modalBtn]}>
<Text style={styles.modalBtnText}>{t('fitnessRingsDetail.datePicker.cancel')}</Text>
</Pressable>
<Pressable onPress={() => {
onConfirmDate(pickerDate);
}} style={[styles.modalBtn, styles.modalBtnPrimary]}>
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary]}>{t('fitnessRingsDetail.datePicker.confirm')}</Text>
</Pressable>
</View>
)}
</View>
</Modal>
</ThemedView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
calendarButton: {
width: 32,
height: 32,
alignItems: 'center',
justifyContent: 'center',
},
scrollView: {
flex: 1,
},
scrollContent: {
paddingBottom: 32,
},
weekSection: {
paddingVertical: 20,
},
weekScrollView: {
paddingHorizontal: 16,
},
weekScrollContent: {
paddingHorizontal: 8,
},
weekRingItem: {
alignItems: 'center',
marginHorizontal: 8,
padding: 8,
borderRadius: 12,
},
weekRingItemSelected: {
backgroundColor: 'rgba(0, 122, 255, 0.1)',
},
weekRingContainer: {
position: 'relative',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 8,
},
weekStarContainer: {
position: 'absolute',
top: -8,
right: -8,
zIndex: 10,
},
weekStarIcon: {
fontSize: 12,
},
weekRingsWrapper: {
position: 'relative',
width: 50,
height: 50,
alignItems: 'center',
justifyContent: 'center',
},
ringPosition: {
position: 'absolute',
alignItems: 'center',
justifyContent: 'center',
},
weekDayNumber: {
fontSize: 11,
fontWeight: '600',
marginTop: 6,
},
weekTodayNumber: {
color: '#007AFF',
},
weekSelectedNumber: {
fontWeight: '700',
},
weekDayLabel: {
fontSize: 10,
fontWeight: '500',
marginTop: 2,
},
weekTodayLabel: {
color: '#007AFF',
},
weekSelectedLabel: {
fontWeight: '600',
},
detailContainer: {
paddingHorizontal: 16,
paddingVertical: 20,
},
// 卡片样式
metricCard: {
backgroundColor: '#FFFFFF',
borderRadius: 16,
padding: 20,
marginBottom: 16,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.06,
shadowRadius: 8,
elevation: 3,
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16,
},
cardTitle: {
fontSize: 18,
fontWeight: '600',
color: '#1C1C1E',
},
helpButton: {
width: 24,
height: 24,
borderRadius: 12,
backgroundColor: '#F2F2F7',
alignItems: 'center',
justifyContent: 'center',
},
helpIcon: {
fontSize: 14,
fontWeight: '600',
color: '#8E8E93',
},
cardValue: {
flexDirection: 'row',
alignItems: 'baseline',
marginBottom: 8,
},
valueText: {
fontSize: 32,
fontWeight: '700',
letterSpacing: -1,
},
unitText: {
fontSize: 18,
fontWeight: '500',
color: '#8E8E93',
marginLeft: 4,
},
cardSubtext: {
fontSize: 14,
color: '#8E8E93',
marginBottom: 20,
},
// 图表样式
chartContainer: {
marginTop: 16,
},
chartBars: {
flexDirection: 'row',
alignItems: 'flex-end',
height: 60,
marginBottom: 8,
paddingHorizontal: 2,
},
chartBar: {
borderRadius: 1.5,
},
chartLabels: {
flexDirection: 'row',
paddingHorizontal: 2,
justifyContent: 'space-between',
},
chartLabel: {
fontSize: 10,
color: '#8E8E93',
fontWeight: '500',
textAlign: 'center',
flex: 6, // 给显示标签的元素更多空间
},
chartLabelSpacer: {
flex: 1, // 占位元素使用较少空间
},
// 锻炼信息样式
exerciseInfo: {
marginTop: 20,
padding: 16,
backgroundColor: '#F2F2F7',
borderRadius: 12,
},
exerciseTitle: {
fontSize: 16,
fontWeight: '600',
color: '#1C1C1E',
marginBottom: 8,
},
exerciseDesc: {
fontSize: 14,
color: '#3C3C43',
lineHeight: 20,
marginBottom: 12,
},
exerciseRecommendation: {
fontSize: 14,
color: '#3C3C43',
lineHeight: 20,
marginBottom: 16,
},
knowButton: {
alignSelf: 'flex-end',
paddingHorizontal: 16,
paddingVertical: 8,
backgroundColor: '#007AFF',
borderRadius: 20,
},
knowButtonText: {
fontSize: 14,
fontWeight: '600',
color: '#FFFFFF',
},
noDataText: {
fontSize: 16,
textAlign: 'center',
marginTop: 40,
},
statsContainer: {
marginHorizontal: 16,
marginTop: 32,
padding: 16,
backgroundColor: 'rgba(0, 0, 0, 0.05)',
borderRadius: 12,
},
statRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
statLabel: {
fontSize: 16,
fontWeight: '500',
},
statValue: {
flexDirection: 'row',
alignItems: 'center',
},
statNumber: {
fontSize: 16,
fontWeight: '600',
marginLeft: 4,
},
starIcon: {
fontSize: 16,
},
// 日期选择器样式
modalBackdrop: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.4)',
},
modalSheet: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
padding: 16,
backgroundColor: '#FFFFFF',
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
},
modalActions: {
flexDirection: 'row',
justifyContent: 'flex-end',
marginTop: 8,
gap: 12,
},
modalBtn: {
paddingHorizontal: 14,
paddingVertical: 10,
borderRadius: 10,
backgroundColor: '#F1F5F9',
},
modalBtnPrimary: {
backgroundColor: '#7a5af8',
},
modalBtnText: {
color: '#334155',
fontWeight: '700',
},
modalBtnTextPrimary: {
color: '#FFFFFF',
fontWeight: '700',
},
});