LifeFlow/android/lib/models/activity.dart

176 lines
4.3 KiB
Dart

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<String, dynamic> 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<String, int> categoryCompletion,
}) = _ActivityStats;
factory ActivityStats.fromJson(Map<String, dynamic> 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<Activity> 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<Activity> activities) {
if (activities.isEmpty) return 0;
// Group by day
final dailyCompletion = <DateTime, bool>{};
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;
}
}