import { HeaderBar } from '@/components/ui/HeaderBar'; import { Colors } from '@/constants/Colors'; import { useAppSelector } from '@/hooks/redux'; import { useColorScheme } from '@/hooks/useColorScheme'; import { selectChallengeViewById } from '@/store/challengesSlice'; import { Ionicons } from '@expo/vector-icons'; import { BlurView } from 'expo-blur'; import { LinearGradient } from 'expo-linear-gradient'; import { useLocalSearchParams, useRouter } from 'expo-router'; import LottieView from 'lottie-react-native'; import React, { useEffect, useMemo, useState } from 'react'; import { Dimensions, Image, Platform, ScrollView, Share, StatusBar, StyleSheet, Text, TouchableOpacity, View, } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; const { width } = Dimensions.get('window'); const HERO_HEIGHT = width * 0.86; type ChallengeProgress = { completedDays: number; totalDays: number; remainingDays: number; badge: string; subtitle?: string; }; type ChallengeDetail = { image: string; periodLabel: string; durationLabel: string; requirementLabel: string; summary?: string; participantsCount: number; rankingDescription?: string; rankings: RankingItem[]; highlightTitle: string; highlightSubtitle: string; ctaLabel: string; progress?: ChallengeProgress; }; type RankingItem = { id: string; name: string; avatar: string; metric: string; badge?: string; }; const DETAIL_PRESETS: Record = { 'hydration-hippo': { image: 'https://images.unsplash.com/photo-1616628182503-5ef2941510da?auto=format&fit=crop&w=240&q=80', periodLabel: '9月01日 - 9月30日 · 剩余 4 天', durationLabel: '30 天', requirementLabel: '喝水 1500ml 15 天以上', summary: '与河马一起练就最佳补水习惯,让身体如湖水般澄澈充盈。', participantsCount: 9009, rankingDescription: '榜单实时更新,记录每位补水达人每日平均饮水量。', rankings: [ { id: 'all-1', name: '湖光暮色', avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=140&q=80', metric: '平均 3,200 ml', badge: '金冠冠军', }, { id: 'all-2', name: '温柔潮汐', avatar: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=140&q=80', metric: '平均 2,980 ml', }, { id: 'all-3', name: '晨雾河岸', avatar: 'https://images.unsplash.com/photo-1544723795-432537f48b2b?auto=format&fit=crop&w=140&q=80', metric: '平均 2,860 ml', }, { id: 'male-1', name: '北岸微风', avatar: 'https://images.unsplash.com/photo-1488426862026-3ee34a7d66df?auto=format&fit=crop&w=140&q=80', metric: '平均 3,120 ml', }, { id: 'male-2', name: '静水晚霞', avatar: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=140&q=80', metric: '平均 2,940 ml', }, { id: 'female-1', name: '露珠初晓', avatar: 'https://images.unsplash.com/photo-1544723795-3fb6469f5b39?auto=format&fit=crop&w=140&q=80', metric: '平均 3,060 ml', }, { id: 'female-2', name: '桔梗水语', avatar: 'https://images.unsplash.com/photo-1521572267360-ee0c2909d518?auto=format&fit=crop&w=140&q=80', metric: '平均 2,880 ml', }, ], highlightTitle: '加入挑战', highlightSubtitle: '畅饮打卡越多,专属奖励越丰厚', ctaLabel: '立即加入挑战', progress: { completedDays: 12, totalDays: 15, remainingDays: 3, badge: 'https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?auto=format&fit=crop&w=160&q=80', subtitle: '学河马饮,做补水人', }, }, }; const DEFAULT_DETAIL: ChallengeDetail = { image: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=240&q=80', periodLabel: '本周进行中', durationLabel: '30 天', requirementLabel: '保持专注完成每日任务', participantsCount: 3200, highlightTitle: '立即参加,点燃动力', highlightSubtitle: '邀请好友一起坚持,更容易收获成果', ctaLabel: '立即加入挑战', rankings: [], progress: { completedDays: 4, totalDays: 21, remainingDays: 5, badge: 'https://images.unsplash.com/photo-1529257414771-1960d69cc2b3?auto=format&fit=crop&w=160&q=80', subtitle: '坚持让好习惯生根发芽', }, }; export default function ChallengeDetailScreen() { const { id } = useLocalSearchParams<{ id?: string }>(); const router = useRouter(); const theme = (useColorScheme() ?? 'light') as 'light' | 'dark'; const colorTokens = Colors[theme]; const insets = useSafeAreaInsets(); const challengeSelector = useMemo(() => (id ? selectChallengeViewById(id) : undefined), [id]); const challenge = useAppSelector((state) => (challengeSelector ? challengeSelector(state) : undefined)); const detail = useMemo(() => { if (!id) return DEFAULT_DETAIL; return DETAIL_PRESETS[id] ?? { ...DEFAULT_DETAIL, periodLabel: challenge?.dateRange ?? DEFAULT_DETAIL.periodLabel, highlightTitle: `加入 ${challenge?.title ?? '挑战'}`, }; }, [challenge?.dateRange, challenge?.title, id]); const [hasJoined, setHasJoined] = useState(false); const [progress, setProgress] = useState(undefined); const [showCelebration, setShowCelebration] = useState(false); const rankingData = detail.rankings ?? []; const ctaGradientColors: [string, string] = ['#5E8BFF', '#6B6CFF']; const progressSegments = useMemo(() => { if (!progress) return undefined; const segmentsCount = Math.max(1, Math.min(progress.totalDays, 18)); const completedSegments = Math.min( segmentsCount, Math.round((progress.completedDays / Math.max(progress.totalDays, 1)) * segmentsCount), ); return { segmentsCount, completedSegments }; }, [progress]); useEffect(() => { setHasJoined(false); setProgress(undefined); }, [id]); useEffect(() => { if (!showCelebration) { return; } const timer = setTimeout(() => { setShowCelebration(false); }, 2400); return () => { clearTimeout(timer); }; }, [showCelebration]); const handleShare = async () => { if (!challenge) { return; } try { await Share.share({ title: challenge.title, message: `我正在参与「${challenge.title}」,一起坚持吧!`, url: challenge.image, }); } catch (error) { console.warn('分享失败', error); } }; const handleJoin = () => { if (hasJoined) { return; } setHasJoined(true); setProgress(detail.progress); setShowCelebration(true); }; if (!challenge) { return ( router.back()} withSafeTop transparent={false} /> 未找到该挑战,稍后再试试吧。 ); } return ( } /> {detail.periodLabel} {challenge.title} {detail.summary ? {detail.summary} : null} {progress && progressSegments ? ( {challenge.title} {progress.subtitle ? ( {progress.subtitle} ) : null} 剩余 {progress.remainingDays} 天 {progress.completedDays} / {progress.totalDays} {Array.from({ length: progressSegments.segmentsCount }).map((_, index) => { const isComplete = index < progressSegments.completedSegments; const isFirst = index === 0; const isLast = index === progressSegments.segmentsCount - 1; return ( ); })} ) : null} {challenge.dateRange} {detail.durationLabel} {detail.requirementLabel} 按日打卡自动累计 {detail.participantsCount.toLocaleString('zh-CN')} 人正在参与 {challenge.avatars.slice(0, 6).map((avatar, index) => ( 0 && styles.avatarOffset]} /> ))} 更多 排行榜 查看全部 {detail.rankingDescription ? ( {detail.rankingDescription} ) : null} {rankingData.length ? ( rankingData.map((item, index) => ( 0 && styles.rankingRowDivider]}> {index + 1} {item.name} {item.metric} {item.badge ? {item.badge} : null} )) ) : ( 榜单即将开启,快来抢占席位。 )} {!hasJoined && ( {detail.highlightTitle} {detail.highlightSubtitle} {detail.ctaLabel} )} {showCelebration && ( )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, safeArea: { flex: 1, backgroundColor: '#f3f4fb', }, headerOverlay: { position: 'absolute', left: 0, right: 0, top: 0, zIndex: 20, }, heroContainer: { height: HERO_HEIGHT, width: '100%', overflow: 'hidden', borderBottomLeftRadius: 36, borderBottomRightRadius: 36, }, heroImage: { width: '100%', height: '100%', }, scrollView: { flex: 1, }, scrollContent: { paddingBottom: Platform.select({ ios: 40, default: 28 }), }, progressCardShadow: { marginTop: 20, marginHorizontal: 24, shadowColor: 'rgba(104, 119, 255, 0.25)', shadowOffset: { width: 0, height: 16 }, shadowOpacity: 0.24, shadowRadius: 28, elevation: 12, borderRadius: 28, }, progressCard: { borderRadius: 28, paddingVertical: 24, paddingHorizontal: 22, backgroundColor: '#ffffff', }, progressHeaderRow: { flexDirection: 'row', alignItems: 'flex-start', }, progressBadgeRing: { width: 68, height: 68, borderRadius: 34, backgroundColor: '#ffffff', padding: 6, shadowColor: 'rgba(67, 82, 186, 0.16)', shadowOffset: { width: 0, height: 6 }, shadowOpacity: 0.4, shadowRadius: 12, elevation: 6, marginRight: 16, }, progressBadge: { width: '100%', height: '100%', borderRadius: 28, }, progressHeadline: { flex: 1, }, progressTitle: { fontSize: 18, fontWeight: '700', color: '#1c1f3a', }, progressSubtitle: { marginTop: 6, fontSize: 13, color: '#5f6a97', }, progressRemaining: { fontSize: 13, fontWeight: '600', color: '#707baf', marginLeft: 16, alignSelf: 'flex-start', }, progressMetaRow: { marginTop: 18, }, progressMetaValue: { fontSize: 16, fontWeight: '700', color: '#4F5BD5', }, progressMetaSuffix: { fontSize: 13, fontWeight: '500', color: '#7a86bb', }, progressBarTrack: { marginTop: 16, flexDirection: 'row', alignItems: 'center', backgroundColor: '#eceffa', borderRadius: 12, paddingHorizontal: 6, paddingVertical: 4, }, progressBarSegment: { flex: 1, height: 8, borderRadius: 4, backgroundColor: '#dfe4f6', marginHorizontal: 3, }, progressBarSegmentActive: { backgroundColor: '#5E8BFF', }, progressBarSegmentFirst: { marginLeft: 0, }, progressBarSegmentLast: { marginRight: 0, }, floatingCTAContainer: { position: 'absolute', left: 0, right: 0, bottom: 0, paddingHorizontal: 20, }, floatingCTABlur: { borderRadius: 24, overflow: 'hidden', borderWidth: 1, borderColor: 'rgba(255,255,255,0.6)', backgroundColor: 'rgba(243, 244, 251, 0.85)', }, floatingCTAContent: { flexDirection: 'row', alignItems: 'center', paddingVertical: 16, paddingHorizontal: 20, }, highlightCopy: { flex: 1, marginRight: 16, }, headerTextBlock: { paddingHorizontal: 24, marginTop: 24, alignItems: 'center', }, periodLabel: { fontSize: 14, color: '#596095', letterSpacing: 0.2, }, title: { marginTop: 10, fontSize: 24, fontWeight: '800', color: '#1c1f3a', textAlign: 'center', }, summary: { marginTop: 12, fontSize: 14, lineHeight: 20, color: '#7080b4', textAlign: 'center', }, detailCard: { marginTop: 28, marginHorizontal: 20, padding: 20, borderRadius: 28, backgroundColor: '#ffffff', shadowColor: 'rgba(30, 41, 59, 0.18)', shadowOpacity: 0.2, shadowRadius: 20, shadowOffset: { width: 0, height: 12 }, elevation: 8, gap: 20, }, detailRow: { flexDirection: 'row', alignItems: 'center', }, detailIconWrapper: { width: 42, height: 42, borderRadius: 21, backgroundColor: '#EFF1FF', alignItems: 'center', justifyContent: 'center', }, detailTextWrapper: { marginLeft: 14, }, detailLabel: { fontSize: 15, fontWeight: '600', color: '#1c1f3a', }, detailMeta: { marginTop: 4, fontSize: 12, color: '#6f7ba7', }, avatarRow: { flexDirection: 'row', alignItems: 'center', marginTop: 12, }, avatar: { width: 36, height: 36, borderRadius: 18, borderWidth: 2, borderColor: '#fff', }, avatarOffset: { marginLeft: -12, }, moreAvatarButton: { marginLeft: 12, paddingHorizontal: 12, paddingVertical: 6, borderRadius: 14, backgroundColor: '#EEF0FF', }, moreAvatarText: { fontSize: 12, color: '#4F5BD5', fontWeight: '600', }, sectionHeader: { marginTop: 36, marginHorizontal: 24, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, sectionTitle: { fontSize: 18, fontWeight: '700', color: '#1c1f3a', }, sectionAction: { fontSize: 13, fontWeight: '600', color: '#5F6BF0', }, sectionSubtitle: { marginTop: 8, marginHorizontal: 24, fontSize: 13, color: '#6f7ba7', lineHeight: 18, }, rankingCard: { marginTop: 20, marginHorizontal: 24, borderRadius: 24, backgroundColor: '#ffffff', paddingVertical: 10, shadowColor: 'rgba(30, 41, 59, 0.12)', shadowOpacity: 0.16, shadowRadius: 18, shadowOffset: { width: 0, height: 10 }, elevation: 6, }, rankingRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 12, paddingHorizontal: 18, }, rankingRowDivider: { borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: '#E5E7FF', }, rankingOrderCircle: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center', backgroundColor: '#EEF0FF', marginRight: 12, }, rankingOrder: { fontSize: 15, fontWeight: '700', color: '#4F5BD5', }, rankingAvatar: { width: 44, height: 44, borderRadius: 22, marginRight: 14, }, rankingInfo: { flex: 1, }, rankingName: { fontSize: 15, fontWeight: '700', color: '#1c1f3a', }, rankingMetric: { marginTop: 4, fontSize: 13, color: '#6f7ba7', }, rankingBadge: { fontSize: 12, color: '#A67CFF', fontWeight: '700', }, emptyRanking: { paddingVertical: 40, alignItems: 'center', }, emptyRankingText: { fontSize: 14, color: '#6f7ba7', }, highlightTitle: { fontSize: 16, fontWeight: '700', color: '#1c1f3a', }, highlightSubtitle: { marginTop: 4, fontSize: 12, color: '#5f6a97', lineHeight: 18, }, highlightButton: { borderRadius: 22, overflow: 'hidden', }, highlightButtonBackground: { borderRadius: 22, paddingVertical: 10, paddingHorizontal: 18, alignItems: 'center', justifyContent: 'center', }, highlightButtonLabel: { fontSize: 14, fontWeight: '700', color: '#ffffff', }, circularButton: { width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.24)', alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: 'rgba(255,255,255,0.45)', }, shareIcon: { fontSize: 18, color: '#ffffff', fontWeight: '700', }, missingContainer: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 32, }, missingText: { fontSize: 16, textAlign: 'center', }, celebrationOverlay: { ...StyleSheet.absoluteFillObject, alignItems: 'center', justifyContent: 'center', zIndex: 40, }, celebrationAnimation: { width: width * 1.3, height: width * 1.3, }, });