146 lines
4.6 KiB
Dart
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();
|
|
}
|
|
}
|