import dayjs from 'dayjs'; import { LinearGradient } from 'expo-linear-gradient'; import React, { useMemo } from 'react'; import { StyleSheet, Text, View, type StyleProp, type ViewStyle } from 'react-native'; import type { ChallengeProgress } from '@/store/challengesSlice'; type ChallengeProgressCardProps = { title: string; endAt?: string; progress?: ChallengeProgress; style?: StyleProp; backgroundColors?: [string, string]; titleColor?: string; subtitleColor?: string; metaColor?: string; metaSuffixColor?: string; accentColor?: string; trackColor?: string; inactiveColor?: string; }; const DEFAULT_BACKGROUND: [string, string] = ['#ffffff', '#ffffff']; const DEFAULT_TITLE_COLOR = '#1c1f3a'; const DEFAULT_SUBTITLE_COLOR = '#707baf'; const DEFAULT_META_COLOR = '#4F5BD5'; const DEFAULT_META_SUFFIX_COLOR = '#7a86bb'; const DEFAULT_ACCENT_COLOR = '#5E8BFF'; const DEFAULT_TRACK_COLOR = '#eceffa'; const DEFAULT_INACTIVE_COLOR = '#dfe4f6'; const clampSegments = (target: number, completed: number) => { const segmentsCount = Math.max(1, Math.min(target, 18)); const completedSegments = Math.min( segmentsCount, Math.round((completed / Math.max(target, 1)) * segmentsCount) ); return { segmentsCount, completedSegments }; }; const calculateRemainingDays = (endAt?: string) => { if (!endAt) return 0; const endDate = dayjs(endAt); if (!endDate.isValid()) return 0; return Math.max(0, endDate.diff(dayjs(), 'd')); }; export const ChallengeProgressCard: React.FC = ({ title, endAt, progress, style, backgroundColors = DEFAULT_BACKGROUND, titleColor = DEFAULT_TITLE_COLOR, subtitleColor = DEFAULT_SUBTITLE_COLOR, metaColor = DEFAULT_META_COLOR, metaSuffixColor = DEFAULT_META_SUFFIX_COLOR, accentColor = DEFAULT_ACCENT_COLOR, trackColor = DEFAULT_TRACK_COLOR, inactiveColor = DEFAULT_INACTIVE_COLOR, }) => { const hasValidProgress = Boolean(progress && progress.target && progress.target > 0); const segments = useMemo(() => { if (!hasValidProgress || !progress) return undefined; return clampSegments(progress.target, progress.completed); }, [hasValidProgress, progress]); const remainingDays = useMemo(() => calculateRemainingDays(endAt), [endAt]); if (!hasValidProgress || !progress || !segments) { return null; } return ( {title} 挑战剩余 {remainingDays} 天 {progress.completed} / {progress.target} {Array.from({ length: segments.segmentsCount }).map((_, index) => { const isComplete = index < segments.completedSegments; const isFirst = index === 0; const isLast = index === segments.segmentsCount - 1; return ( ); })} ); }; const styles = StyleSheet.create({ shadow: { borderRadius: 28, shadowColor: 'rgba(104, 119, 255, 0.25)', shadowOffset: { width: 0, height: 16 }, shadowOpacity: 0.24, shadowRadius: 28, elevation: 12, }, card: { borderRadius: 28, paddingVertical: 24, paddingHorizontal: 22, }, headerRow: { flexDirection: 'row', alignItems: 'flex-start', }, headline: { flex: 1, }, title: { fontSize: 18, fontWeight: '700', }, remaining: { fontSize: 11, fontWeight: '600', alignSelf: 'flex-start', }, metaRow: { marginTop: 12, }, metaValue: { fontSize: 14, fontWeight: '700', }, metaSuffix: { fontSize: 13, fontWeight: '500', }, track: { marginTop: 12, flexDirection: 'row', alignItems: 'center', borderRadius: 12, paddingHorizontal: 6, paddingVertical: 4, }, segment: { flex: 1, height: 4, borderRadius: 4, marginHorizontal: 3, }, segmentFirst: { marginLeft: 0, }, segmentLast: { marginRight: 0, }, }); export default ChallengeProgressCard;