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)