import 'dart:collection'; import 'package:habo/constants.dart'; import 'package:habo/habits/habit.dart'; import 'package:habo/model/habit_data.dart'; class StatisticsData { String title; int topStreak = 0; int actualStreak = 0; int checks = 0; int fails = 0; int skips = 0; int progress = 0; SplayTreeMap>> monthlyTracking; StatisticsData({ this.title = '', SplayTreeMap>>? monthlyTracking, }) : monthlyTracking = monthlyTracking ?? SplayTreeMap>>(); } class OverallStatisticsData { int totalChecks = 0; int totalFails = 0; int totalSkips = 0; int totalProgress = 0; } class AllStatistics { List allStatistics = []; OverallStatisticsData overallStatistics = OverallStatisticsData(); } class Statistics { static AllStatistics calculateStatistics(List habits) { final result = AllStatistics(); for (final habit in habits) { final data = StatisticsData(title: habit.habitData.title); final events = habit.habitData.events; bool usingTwoDayRule = false; final dates = events.keys.toList(); for (int i = 0; i < dates.length; i++) { final date = dates[i]; final event = events[date]!; final dayType = event[0] is DayType ? event[0] as DayType : DayType.values[event[0] as int]; // Check date gap if (i > 0) { final diff = date.difference(dates[i - 1]).inDays; if (diff > 1) { data.actualStreak = 0; usingTwoDayRule = false; } } final yearMonth = date.year * 100 + date.month; data.monthlyTracking[yearMonth] ??= {}; data.monthlyTracking[yearMonth]![dayType] ??= []; data.monthlyTracking[yearMonth]![dayType]!.add(date.day); switch (dayType) { case DayType.check: data.checks++; data.actualStreak++; if (data.actualStreak > data.topStreak) { data.topStreak = data.actualStreak; } usingTwoDayRule = false; break; case DayType.progress: data.progress++; if (habit.habitData.habitType == HabitType.numeric && event.length >= 4) { final progressValue = event[2] as double? ?? 0.0; final target = event[3] as double? ?? habit.habitData.targetValue; if (progressValue >= target) { data.actualStreak++; if (data.actualStreak > data.topStreak) { data.topStreak = data.actualStreak; } } } usingTwoDayRule = false; break; case DayType.fail: data.fails++; if (habit.habitData.twoDayRule) { if (usingTwoDayRule) { data.actualStreak = 0; } else { usingTwoDayRule = true; } } else { data.actualStreak = 0; } break; case DayType.skip: data.skips++; if (usingTwoDayRule) { data.actualStreak = 0; } break; case DayType.clear: break; } } result.allStatistics.add(data); } // Calculate overall for (final stat in result.allStatistics) { result.overallStatistics.totalChecks += stat.checks; result.overallStatistics.totalFails += stat.fails; result.overallStatistics.totalSkips += stat.skips; result.overallStatistics.totalProgress += stat.progress; } return result; } }