276 lines
6.6 KiB
Dart
276 lines
6.6 KiB
Dart
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
import 'package:hive/hive.dart';
|
|
|
|
part 'gamification.freezed.dart';
|
|
part 'gamification.g.dart';
|
|
|
|
@HiveType(typeId: 8)
|
|
@freezed
|
|
class GamificationStats with _$GamificationStats {
|
|
const factory GamificationStats({
|
|
@HiveField(0) @Default(0) int totalPoints,
|
|
@HiveField(1) @Default(0) int currentStreak,
|
|
@HiveField(2) @Default(0) int longestStreak,
|
|
@HiveField(3) @Default(1) int level,
|
|
@HiveField(4) @Default([]) List<String> unlockedBadges,
|
|
@HiveField(5) @Default({}) Map<String, int> categoryStreaks,
|
|
@HiveField(6) @Default(0) int routinesCompletedToday,
|
|
@HiveField(7) @Default(0) int totalRoutinesCompleted,
|
|
@HiveField(8) @Default(0) int perfectDays,
|
|
@HiveField(9) required DateTime lastUpdated,
|
|
}) = _GamificationStats;
|
|
|
|
factory GamificationStats.fromJson(Map<String, dynamic> json) =
|
|
_$$GamificationStatsImplFromJson;
|
|
}
|
|
|
|
@HiveType(typeId: 9)
|
|
class Badge extends HiveObject {
|
|
@HiveField(0)
|
|
final String id;
|
|
|
|
@HiveField(1)
|
|
final String name;
|
|
|
|
@HiveField(2)
|
|
final String description;
|
|
|
|
@HiveField(3)
|
|
final String icon;
|
|
|
|
@HiveField(4)
|
|
final int pointsRequired;
|
|
|
|
@HiveField(5)
|
|
final BadgeCategory category;
|
|
|
|
Badge({
|
|
required this.id,
|
|
required this.name,
|
|
required this.description,
|
|
required this.icon,
|
|
required this.pointsRequired,
|
|
required this.category,
|
|
});
|
|
}
|
|
|
|
@HiveType(typeId: 10)
|
|
enum BadgeCategory {
|
|
@HiveField(0)
|
|
streak,
|
|
@HiveField(1)
|
|
completion,
|
|
@HiveField(2)
|
|
category,
|
|
@HiveField(3)
|
|
special,
|
|
}
|
|
|
|
// Predefined badges
|
|
class Badges {
|
|
static final List<Badge> all = [
|
|
// Streak badges
|
|
Badge(
|
|
id: 'streak_3',
|
|
name: 'Getting Started',
|
|
description: '3 day streak',
|
|
icon: '🔥',
|
|
pointsRequired: 0,
|
|
category: BadgeCategory.streak,
|
|
),
|
|
Badge(
|
|
id: 'streak_7',
|
|
name: 'Week Warrior',
|
|
description: '7 day streak',
|
|
icon: '🔥🔥',
|
|
pointsRequired: 0,
|
|
category: BadgeCategory.streak,
|
|
),
|
|
Badge(
|
|
id: 'streak_30',
|
|
name: 'Monthly Master',
|
|
description: '30 day streak',
|
|
icon: '📅',
|
|
pointsRequired: 0,
|
|
category: BadgeCategory.streak,
|
|
),
|
|
Badge(
|
|
id: 'streak_100',
|
|
name: 'Centurion',
|
|
description: '100 day streak',
|
|
icon: '💯',
|
|
pointsRequired: 0,
|
|
category: BadgeCategory.streak,
|
|
),
|
|
|
|
// Completion badges
|
|
Badge(
|
|
id: 'first_routine',
|
|
name: 'First Step',
|
|
description: 'Complete your first routine',
|
|
icon: '👟',
|
|
pointsRequired: 0,
|
|
category: BadgeCategory.completion,
|
|
),
|
|
Badge(
|
|
id: 'routines_10',
|
|
name: 'Getting Consistent',
|
|
description: 'Complete 10 routines',
|
|
icon: '⭐',
|
|
pointsRequired: 0,
|
|
category: BadgeCategory.completion,
|
|
),
|
|
Badge(
|
|
id: 'routines_100',
|
|
name: 'Century Club',
|
|
description: 'Complete 100 routines',
|
|
icon: '🏆',
|
|
pointsRequired: 0,
|
|
category: BadgeCategory.completion,
|
|
),
|
|
Badge(
|
|
id: 'perfect_week',
|
|
name: 'Perfect Week',
|
|
description: 'Complete all routines for 7 days',
|
|
icon: '🌟',
|
|
pointsRequired: 0,
|
|
category: BadgeCategory.completion,
|
|
),
|
|
|
|
// Category badges
|
|
Badge(
|
|
id: 'hydration_master',
|
|
name: 'Hydration Hero',
|
|
description: 'Hit water goal 7 days in a row',
|
|
icon: '💧',
|
|
pointsRequired: 0,
|
|
category: BadgeCategory.category,
|
|
),
|
|
Badge(
|
|
id: 'medication_reliable',
|
|
name: 'Reliable',
|
|
description: 'Take medication on time for 14 days',
|
|
icon: '💊',
|
|
pointsRequired: 0,
|
|
category: BadgeCategory.category,
|
|
),
|
|
Badge(
|
|
id: 'sleep_schedule',
|
|
name: 'Sleep Master',
|
|
description: 'Maintain sleep schedule for 7 days',
|
|
icon: '😴',
|
|
pointsRequired: 0,
|
|
category: BadgeCategory.category,
|
|
),
|
|
|
|
// Special badges
|
|
Badge(
|
|
id: 'early_bird',
|
|
name: 'Early Bird',
|
|
description: 'Complete morning routine before 8 AM',
|
|
icon: '🐦',
|
|
pointsRequired: 0,
|
|
category: BadgeCategory.special,
|
|
),
|
|
Badge(
|
|
id: 'night_owl',
|
|
name: 'Night Owl',
|
|
description: 'Complete evening routine after 9 PM',
|
|
icon: '🦉',
|
|
pointsRequired: 0,
|
|
category: BadgeCategory.special,
|
|
),
|
|
];
|
|
}
|
|
|
|
// Level calculation
|
|
class LevelCalculator {
|
|
static int calculateLevel(int totalPoints) {
|
|
// Exponential level curve
|
|
// Level 1: 0 points
|
|
// Level 2: 100 points
|
|
// Level 3: 250 points
|
|
// Level 4: 450 points
|
|
// etc.
|
|
|
|
if (totalPoints < 100) return 1;
|
|
if (totalPoints < 250) return 2;
|
|
if (totalPoints < 450) return 3;
|
|
if (totalPoints < 700) return 4;
|
|
if (totalPoints < 1000) return 5;
|
|
|
|
// Beyond level 5: every 500 points
|
|
return 5 + ((totalPoints - 1000) ~/ 500);
|
|
}
|
|
|
|
static int pointsToNextLevel(int currentLevel, int totalPoints) {
|
|
final nextLevelThreshold = _levelThreshold(currentLevel + 1);
|
|
return nextLevelThreshold - totalPoints;
|
|
}
|
|
|
|
static double progressToNextLevel(int currentLevel, int totalPoints) {
|
|
final currentThreshold = _levelThreshold(currentLevel);
|
|
final nextThreshold = _levelThreshold(currentLevel + 1);
|
|
final progress = totalPoints - currentThreshold;
|
|
final needed = nextThreshold - currentThreshold;
|
|
return (progress / needed).clamp(0.0, 1.0);
|
|
}
|
|
|
|
static int _levelThreshold(int level) {
|
|
switch (level) {
|
|
case 1:
|
|
return 0;
|
|
case 2:
|
|
return 100;
|
|
case 3:
|
|
return 250;
|
|
case 4:
|
|
return 450;
|
|
case 5:
|
|
return 700;
|
|
default:
|
|
return 1000 + ((level - 6) * 500);
|
|
}
|
|
}
|
|
}
|
|
|
|
class BadgeChecker {
|
|
static List<Badge> checkUnlockedBadges(
|
|
GamificationStats stats,
|
|
List<String> alreadyUnlocked,
|
|
) {
|
|
final newlyUnlocked = <Badge>[];
|
|
|
|
for (final badge in Badges.all) {
|
|
if (alreadyUnlocked.contains(badge.id)) continue;
|
|
|
|
if (_meetsCriteria(badge, stats)) {
|
|
newlyUnlocked.add(badge);
|
|
}
|
|
}
|
|
|
|
return newlyUnlocked;
|
|
}
|
|
|
|
static bool _meetsCriteria(Badge badge, GamificationStats stats) {
|
|
switch (badge.id) {
|
|
case 'streak_3':
|
|
return stats.currentStreak >= 3;
|
|
case 'streak_7':
|
|
return stats.currentStreak >= 7;
|
|
case 'streak_30':
|
|
return stats.currentStreak >= 30;
|
|
case 'streak_100':
|
|
return stats.currentStreak >= 100;
|
|
case 'routines_10':
|
|
return stats.totalRoutinesCompleted >= 10;
|
|
case 'routines_100':
|
|
return stats.totalRoutinesCompleted >= 100;
|
|
case 'perfect_week':
|
|
return stats.perfectDays >= 7;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|