BioFlux/lib/services/hydration_service.dart
2026-01-22 16:33:07 +08:00

146 lines
4.6 KiB
Dart

import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:intl/intl.dart';
import '../models/drink_type.dart';
class HydrationService extends ChangeNotifier {
final Box _dataBox = Hive.box('hydrationData');
final Box _settingsBox = Hive.box('userSettings');
double _currentIntake = 0.0;
double _purityScore = 1.0;
DateTime _lastDrinkTime = DateTime.now();
Map<DrinkType, double> _breakdown = {
DrinkType.Water: 0,
DrinkType.Coffee: 0,
DrinkType.Tea: 0,
DrinkType.Soda: 0,
};
double _weight = 60.0;
double _workoutMinutes = 0.0;
bool _remindersEnabled = true;
double _reminderInterval = 60.0;
List<double> _history = [];
Timer? _decayTimer;
HydrationService() {
_loadData();
_startDecayCheck();
}
double get currentIntake => _currentIntake;
double get dailyGoal => (_weight * 35.0) + (_workoutMinutes * 8.0);
double get percentage => (_currentIntake / dailyGoal).clamp(0.0, 1.0);
List<double> get history => _history;
double get weight => _weight;
double get workoutMinutes => _workoutMinutes;
bool get remindersEnabled => _remindersEnabled;
double get reminderInterval => _reminderInterval;
Map<DrinkType, double> get breakdown => _breakdown;
Color get dynamicWaterColor {
Color baseColor = const Color(0xFF00BFFF);
Color impurityColor = const Color(0xFFD2B48C);
Color blendedWithImpurity = Color.lerp(impurityColor, baseColor, _purityScore)!;
final minutesSince = DateTime.now().difference(_lastDrinkTime).inMinutes;
double freshness = 1.0;
if (minutesSince > 120) {
double overdueHours = (minutesSince - 120) / 60.0;
freshness = (1.0 - (overdueHours * 0.2)).clamp(0.4, 1.0);
}
Color dryColor = Colors.grey.shade400;
return Color.lerp(dryColor, blendedWithImpurity, freshness)!;
}
String get statusText {
final minutesSince = DateTime.now().difference(_lastDrinkTime).inMinutes;
if (minutesSince > 120) return "Body is withering...";
if (_purityScore < 0.7) return "Purify your system";
return "Metabolism Optimal";
}
void _loadData() {
final todayKey = DateFormat('yyyyMMdd').format(DateTime.now());
_currentIntake = _dataBox.get(todayKey, defaultValue: 0.0);
_purityScore = _dataBox.get('purity_$todayKey', defaultValue: 1.0);
int? lastTs = _dataBox.get('last_drink_ts');
_lastDrinkTime = lastTs != null ? DateTime.fromMillisecondsSinceEpoch(lastTs) : DateTime.now();
_weight = _settingsBox.get('weight', defaultValue: 60.0);
_workoutMinutes = _settingsBox.get('workout', defaultValue: 0.0);
_remindersEnabled = _settingsBox.get('reminders', defaultValue: true);
_reminderInterval = _settingsBox.get('reminderInterval', defaultValue: 60.0);
if (_history.isEmpty) {
for (int i = 0; i < 7; i++) {
_history.add(1500 + math.Random().nextInt(1500).toDouble());
}
}
_breakdown = {
DrinkType.Water: _currentIntake * 0.7,
DrinkType.Coffee: _currentIntake * 0.2,
DrinkType.Tea: _currentIntake * 0.1,
DrinkType.Soda: 0,
};
notifyListeners();
}
void updateSettings({double? weight, double? workout, bool? reminders, double? interval}) {
if (weight != null) _weight = weight;
if (workout != null) _workoutMinutes = workout;
if (reminders != null) _remindersEnabled = reminders;
if (interval != null) _reminderInterval = interval;
_settingsBox.put('weight', _weight);
_settingsBox.put('workout', _workoutMinutes);
_settingsBox.put('reminders', _remindersEnabled);
_settingsBox.put('reminderInterval', _reminderInterval);
notifyListeners();
}
void addWater(double amount, {DrinkType type = DrinkType.Water}) {
double efficientAmount = amount * type.hydrationFactor;
_currentIntake += efficientAmount;
_breakdown[type] = (_breakdown[type] ?? 0) + efficientAmount;
if (type == DrinkType.Water) {
_purityScore = (_purityScore + 0.15).clamp(0.0, 1.0);
} else {
_purityScore = (_purityScore - (1.0 - type.purityFactor)).clamp(0.0, 1.0);
}
_lastDrinkTime = DateTime.now();
final todayKey = DateFormat('yyyyMMdd').format(DateTime.now());
_dataBox.put(todayKey, _currentIntake);
_dataBox.put('purity_$todayKey', _purityScore);
_dataBox.put('last_drink_ts', _lastDrinkTime.millisecondsSinceEpoch);
notifyListeners();
}
void _startDecayCheck() {
_decayTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
notifyListeners();
});
}
@override
void dispose() {
_decayTimer?.cancel();
super.dispose();
}
}