feat(fasting): 新增轻断食功能模块
新增完整的轻断食功能,包括: - 断食计划列表和详情页面,支持12-12、14-10、16-8、18-6四种计划 - 断食状态实时追踪和倒计时显示 - 自定义开始时间选择器 - 断食通知提醒功能 - Redux状态管理和数据持久化 - 新增tab导航入口和路由配置
This commit is contained in:
185
components/fasting/FastingPlanList.tsx
Normal file
185
components/fasting/FastingPlanList.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
import type { FastingPlan } from '@/constants/Fasting';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
type FastingPlanListProps = {
|
||||
plans: FastingPlan[];
|
||||
activePlanId?: string | null;
|
||||
onSelectPlan: (plan: FastingPlan) => void;
|
||||
};
|
||||
|
||||
const difficultyLabel: Record<FastingPlan['difficulty'], string> = {
|
||||
新手: '适合入门',
|
||||
进阶: '脂代提升',
|
||||
强化: '平台突破',
|
||||
};
|
||||
|
||||
export function FastingPlanList({ plans, activePlanId, onSelectPlan }: FastingPlanListProps) {
|
||||
const theme = useColorScheme() ?? 'light';
|
||||
const colors = Colors[theme];
|
||||
|
||||
const sortedPlans = useMemo(
|
||||
() => plans.slice().sort((a, b) => a.fastingHours - b.fastingHours),
|
||||
[plans]
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.wrapper}>
|
||||
<View style={styles.headerRow}>
|
||||
<Text style={styles.headerTitle}>单日计划</Text>
|
||||
<View style={styles.headerBadge}>
|
||||
<Text style={styles.headerBadgeText}>精选</Text>
|
||||
</View>
|
||||
</View>
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
>
|
||||
{sortedPlans.map((plan) => {
|
||||
const isActive = plan.id === activePlanId;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={plan.id}
|
||||
style={[
|
||||
styles.card,
|
||||
{
|
||||
backgroundColor: plan.theme.backdrop,
|
||||
borderColor: isActive ? plan.theme.accent : 'transparent',
|
||||
},
|
||||
]}
|
||||
activeOpacity={0.85}
|
||||
onPress={() => onSelectPlan(plan)}
|
||||
>
|
||||
<View style={styles.cardTopRow}>
|
||||
<View style={[styles.difficultyPill, { backgroundColor: `${plan.theme.accent}1A` }]}>
|
||||
<Text style={[styles.difficultyText, { color: plan.theme.accent }]}>
|
||||
{plan.difficulty}
|
||||
</Text>
|
||||
</View>
|
||||
{plan.badge && (
|
||||
<View style={[styles.badgePill, { backgroundColor: `${plan.theme.accent}26` }]}>
|
||||
<Text style={[styles.badgeText, { color: plan.theme.accent }]}>
|
||||
{plan.badge}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.cardTitle}>{plan.title}</Text>
|
||||
<Text style={styles.cardSubtitle}>{plan.subtitle}</Text>
|
||||
<View style={styles.metaRow}>
|
||||
<View style={styles.metaItem}>
|
||||
<IconSymbol name="chart.pie.fill" color={colors.icon} size={16} />
|
||||
<Text style={styles.metaText}>{plan.fastingHours} 小时断食</Text>
|
||||
</View>
|
||||
<View style={styles.metaItem}>
|
||||
<IconSymbol name="flag.fill" color={colors.icon} size={16} />
|
||||
<Text style={styles.metaText}>{difficultyLabel[plan.difficulty]}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
wrapper: {
|
||||
marginTop: 32,
|
||||
},
|
||||
headerRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 12,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
color: '#2E3142',
|
||||
},
|
||||
headerBadge: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 14,
|
||||
backgroundColor: 'rgba(53, 52, 69, 0.08)',
|
||||
},
|
||||
headerBadgeText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
color: '#353445',
|
||||
},
|
||||
scrollContent: {
|
||||
paddingRight: 20,
|
||||
},
|
||||
card: {
|
||||
width: 220,
|
||||
borderRadius: 24,
|
||||
paddingVertical: 18,
|
||||
paddingHorizontal: 18,
|
||||
marginRight: 16,
|
||||
borderWidth: 2,
|
||||
},
|
||||
cardTopRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
difficultyPill: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 16,
|
||||
},
|
||||
difficultyText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
},
|
||||
badgePill: {
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 14,
|
||||
},
|
||||
badgeText: {
|
||||
fontSize: 11,
|
||||
fontWeight: '600',
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
color: '#2E3142',
|
||||
marginBottom: 6,
|
||||
},
|
||||
cardSubtitle: {
|
||||
fontSize: 13,
|
||||
fontWeight: '500',
|
||||
color: '#5B6572',
|
||||
marginBottom: 12,
|
||||
},
|
||||
metaRow: {
|
||||
marginTop: 'auto',
|
||||
},
|
||||
metaItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 6,
|
||||
},
|
||||
metaText: {
|
||||
marginLeft: 6,
|
||||
fontSize: 12,
|
||||
color: '#5B6572',
|
||||
fontWeight: '500',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user