import React, { useEffect, useState } from 'react' import { View, Text, StyleSheet, FlatList, TouchableOpacity, ActivityIndicator } from 'react-native' import { withObservables } from '@nozbe/watermelondb/react' import { Observable } from 'rxjs' import { map, switchMap } from 'rxjs/operators' import { Q } from '@nozbe/watermelondb' import { database } from '../../src/db/index' import UserChallenge from '../../src/db/models/UserChallenge' import DailyProgress from '../../src/db/models/DailyProgress' import TaskCompletion from '../../src/db/models/TaskCompletion' import { getActiveUserChallenge, getOrCreateDailyProgress, getOrCreateTaskCompletions, toggleTaskCompletion } from '../../src/utils/progress' import { Colors } from '../../constants/theme' import { IconSymbol } from '../../components/ui/icon-symbol' interface TaskItemProps { taskCompletion: TaskCompletion onToggle: (task: TaskCompletion, isCompleted: boolean) => void } const TaskItem = withObservables(['taskCompletion'], ({ taskCompletion }) => ({ taskCompletion, requirement: taskCompletion.requirement, }))(({ taskCompletion, requirement, onToggle }: TaskItemProps & { requirement: any }) => { const isCompleted = taskCompletion.completedAt > 0 return ( onToggle(taskCompletion, !isCompleted)} > {isCompleted && } {requirement.title} {requirement.description && ( {requirement.description} )} ) }) interface ProgressScreenProps { userChallenge: UserChallenge | null dailyProgress: DailyProgress | null taskCompletions: TaskCompletion[] } const ProgressScreen = ({ userChallenge, dailyProgress, taskCompletions }: ProgressScreenProps) => { const [error, setError] = useState(null) console.log('ProgressScreen: render', { hasUserChallenge: !!userChallenge, hasDailyProgress: !!dailyProgress, taskCompletionsCount: taskCompletions.length, error }) useEffect(() => { const ensureProgress = async () => { if (userChallenge) { try { setError(null) console.log('ProgressScreen: ensuring progress for challenge', userChallenge.id) const progress = await getOrCreateDailyProgress(userChallenge) console.log('ProgressScreen: daily progress obtained', progress.id) await getOrCreateTaskCompletions(progress) console.log('ProgressScreen: task completions ensured') } catch (e: any) { console.error('Error ensuring daily progress:', e) setError(e.message || 'Failed to load progress') } } } ensureProgress() }, [userChallenge]) if (!userChallenge) { return ( No active challenge found. Go to the Explore tab to start a challenge! ) } if (error) { return ( Something went wrong {error} setError(null)} style={{ marginTop: 20, padding: 10, backgroundColor: Colors.light.tint, borderRadius: 8 }}> Retry ) } if (!dailyProgress) { // If we have a user challenge but no daily progress yet, it might be creating it. // However, if it takes too long or fails, we should probably show something else or retry. // For now, let's just log that we are waiting. console.log('ProgressScreen: waiting for dailyProgress') return ( Loading today{`'`}s progress... ) } const completedCount = taskCompletions.filter(t => t.completedAt > 0).length const totalCount = taskCompletions.length const progressPercentage = totalCount > 0 ? completedCount / totalCount : 0 return ( {userChallenge.challenge.name} Day {dailyProgress.dayNumber} {completedCount} of {totalCount} tasks completed {progressPercentage === 1 && ( Day Complete! Great Job! )} item.id} renderItem={({ item }) => ( )} contentContainerStyle={styles.listContent} /> ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', }, centered: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20, }, header: { padding: 20, backgroundColor: 'white', borderBottomWidth: 1, borderBottomColor: '#e0e0e0', }, challengeName: { fontSize: 24, fontWeight: 'bold', color: '#333', }, dayText: { fontSize: 18, color: '#666', marginTop: 4, }, progressContainer: { padding: 20, backgroundColor: 'white', marginBottom: 10, }, progressBarBackground: { height: 10, backgroundColor: '#e0e0e0', borderRadius: 5, overflow: 'hidden', }, progressBarFill: { height: '100%', backgroundColor: Colors.light.tint, }, progressText: { marginTop: 8, textAlign: 'right', color: '#666', }, listContent: { padding: 16, }, taskItem: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'white', padding: 16, borderRadius: 12, marginBottom: 12, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.05, shadowRadius: 4, elevation: 2, }, checkbox: { width: 24, height: 24, borderRadius: 12, borderWidth: 2, borderColor: Colors.light.tint, marginRight: 16, justifyContent: 'center', alignItems: 'center', }, checkboxChecked: { backgroundColor: Colors.light.tint, }, taskContent: { flex: 1, }, taskTitle: { fontSize: 16, fontWeight: '600', color: '#333', }, taskTitleCompleted: { textDecorationLine: 'line-through', color: '#999', }, taskDescription: { fontSize: 14, color: '#666', marginTop: 2, }, emptyText: { fontSize: 18, fontWeight: 'bold', color: '#333', marginBottom: 8, }, subText: { fontSize: 16, color: '#666', textAlign: 'center', }, congratsContainer: { backgroundColor: '#E8F5E9', margin: 20, marginTop: 0, padding: 15, borderRadius: 12, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: '#4CAF50', }, congratsText: { fontSize: 18, fontWeight: 'bold', color: '#2E7D32', marginLeft: 10, }, }) const enhance = withObservables([], () => { // We need to observe the active user challenge and today's progress // This is a bit complex because we need to chain observables // 1. Get active user challenge const userChallenge$ = database.get('user_challenges') .query( Q.where('status', 'active') ) .observeWithColumns(['status']) .pipe( map((challenges: UserChallenge[]) => challenges.length > 0 ? challenges[0] : null) ) return { userChallenge: userChallenge$, dailyProgress: userChallenge$.pipe( switchMap((uc: UserChallenge | null): Observable => { if (!uc) return new Observable(observer => observer.next(null)) const today = new Date() today.setHours(0,0,0,0) const todayTs = today.getTime() return database.get('daily_progress') .query( Q.where('user_challenge_id', uc.id), Q.where('progress_date', todayTs) ) .observe() .pipe( map(progresses => progresses.length > 0 ? progresses[0] : null) ) }) ), taskCompletions: userChallenge$.pipe( switchMap((uc: UserChallenge | null): Observable => { if (!uc) return new Observable(observer => { observer.next([]); observer.complete() }) const today = new Date() today.setHours(0,0,0,0) const todayTs = today.getTime() return database.get('daily_progress') .query( Q.where('user_challenge_id', uc.id), Q.where('progress_date', todayTs) ) .observe() .pipe( switchMap((progresses: DailyProgress[]): Observable => { const todayProgress = progresses.length > 0 ? progresses[0] : null if (!todayProgress) return new Observable(observer => { observer.next([]); observer.complete() }) return todayProgress.taskCompletions.observe() }) ) }) ) as Observable } }) export default enhance(ProgressScreen)