import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hive/hive.dart'; import 'routine.dart'; part 'activity.freezed.dart'; part 'activity.g.dart'; @HiveType(typeId: 5) enum Mood { @HiveField(0) terrible, @HiveField(1) bad, @HiveField(2) neutral, @HiveField(3) good, @HiveField(4) great, } @HiveType(typeId: 6) @freezed class Activity with _$Activity { const factory Activity({ @HiveField(0) required String id, @HiveField(1) required String routineId, @HiveField(2) required DateTime timestamp, @HiveField(3) @Default(true) bool completed, @HiveField(4) String? notes, @HiveField(5) Mood? mood, @HiveField(6) @Default(0) int pointsEarned, @HiveField(7) DateTime? scheduledTime, }) = _Activity; factory Activity.fromJson(Map json) = _$$ActivityImplFromJson; } extension MoodExtension on Mood { String get displayName { switch (this) { case Mood.terrible: return 'Terrible'; case Mood.bad: return 'Bad'; case Mood.neutral: return 'Okay'; case Mood.good: return 'Good'; case Mood.great: return 'Great!'; } } String get emoji { switch (this) { case Mood.terrible: return '😫'; case Mood.bad: return '😕'; case Mood.neutral: return '😐'; case Mood.good: return '🙂'; case Mood.great: return '😄'; } } } @HiveType(typeId: 7) @freezed class ActivityStats with _$ActivityStats { const factory ActivityStats({ @HiveField(0) required int totalCompleted, @HiveField(1) required int totalSkipped, @HiveField(2) required double completionRate, @HiveField(3) required int currentStreak, @HiveField(4) required int longestStreak, @HiveField(5) required int totalPoints, @HiveField(6) @Default({}) Map categoryCompletion, }) = _ActivityStats; factory ActivityStats.fromJson(Map json) = _$$ActivityStatsImplFromJson; } // Helper class for daily progress class DailyProgress { final DateTime date; final int totalRoutines; final int completedRoutines; final int totalPoints; final bool allCompleted; DailyProgress({ required this.date, required this.totalRoutines, required this.completedRoutines, required this.totalPoints, }) : allCompleted = totalRoutines > 0 && totalRoutines == completedRoutines; double get completionPercentage = totalRoutines > 0 ? (completedRoutines / totalRoutines) * 100 : 0; } // Helper for streak calculation class StreakCalculator { static int calculateCurrentStreak(List activities) { if (activities.isEmpty) return 0; // Sort by date descending final sorted = activities.toList() ..sort((a, b) => b.timestamp.compareTo(a.timestamp)); int streak = 0; DateTime checkDate = DateTime.now(); for (final activity in sorted) { final activityDate = DateTime( activity.timestamp.year, activity.timestamp.month, activity.timestamp.day, ); final checkDateOnly = DateTime( checkDate.year, checkDate.month, checkDate.day, ); if (activityDate.isAtSameMomentAs(checkDateOnly) && activity.completed) { streak++; checkDate = checkDate.subtract(const Duration(days: 1)); } else if (activityDate.isBefore(checkDateOnly)) { break; } } return streak; } static int calculateLongestStreak(List activities) { if (activities.isEmpty) return 0; // Group by day final dailyCompletion = {}; for (final activity in activities) { final date = DateTime( activity.timestamp.year, activity.timestamp.month, activity.timestamp.day, ); dailyCompletion[date] = (dailyCompletion[date] ?? true) && activity.completed; } // Sort dates final dates = dailyCompletion.keys.toList()..sort(); int longestStreak = 0; int currentStreak = 0; for (int i = 0; i < dates.length; i++) { if (dailyCompletion[dates[i]] == true) { currentStreak++; longestStreak = longestStreak > currentStreak ? longestStreak : currentStreak; } else { currentStreak = 0; } } return longestStreak; } }