Files
gp/lib/src/application/focus_seed_controller.dart
T

206 lines
5.6 KiB
Dart

import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import '../core/date_keys.dart';
import '../data/focus_seed_store.dart';
import '../models/app_state.dart';
import '../models/focus_session.dart';
import '../models/habit.dart';
import '../models/mood.dart';
import '../models/reflection_entry.dart';
import '../services/analytics_provider.dart';
enum FocusSeedError { openFailed, saveFailed }
class FocusSeedController extends ChangeNotifier {
FocusSeedController({
FocusSeedStore? store,
AnalyticsProvider? analytics,
List<String> defaultHabitTitles = const [],
}) : _store = store ?? FocusSeedStore(),
_analytics = analytics ?? const NoopAnalyticsProvider(),
_defaultHabitTitles = List.unmodifiable(defaultHabitTitles);
final FocusSeedStore _store;
final AnalyticsProvider _analytics;
List<String> _defaultHabitTitles;
AppState? _state;
bool _isLoading = true;
bool _isSaving = false;
FocusSeedError? _error;
AppState? get state => _state;
bool get isLoading => _isLoading;
bool get isSaving => _isSaving;
FocusSeedError? get error => _error;
void updateDefaultHabitTitles(List<String> titles) {
_defaultHabitTitles = List.unmodifiable(titles);
}
Future<void> load() async {
_isLoading = true;
_error = null;
notifyListeners();
try {
_state = await _store.load(defaultHabitTitles: _defaultHabitTitles);
} on Object {
_error = FocusSeedError.openFailed;
} finally {
_isLoading = false;
notifyListeners();
}
}
Future<bool> setTodayAnchor(String value) async {
final current = _state;
final anchor = value.trim();
if (current == null || anchor.isEmpty) {
return false;
}
_analytics.track('intention_saved');
return _commit(current.copyWith(anchor: anchor, anchorDateKey: todayKey()));
}
Future<bool> toggleHabit(Habit habit) async {
final current = _state;
if (current == null) {
return false;
}
final today = todayKey();
final completedDates = Set<String>.from(habit.completedDates);
if (completedDates.contains(today)) {
completedDates.remove(today);
} else {
completedDates.add(today);
await HapticFeedback.lightImpact();
}
final habits = current.habits
.map(
(item) =>
item.id == habit.id
? habit.copyWith(completedDates: completedDates)
: item,
)
.toList(growable: false);
_analytics.track('habit_toggled');
return _commit(current.copyWith(habits: habits));
}
Future<bool> addHabit(String value) async {
final current = _state;
final title = value.trim();
if (current == null || title.isEmpty) {
return false;
}
final now = DateTime.now();
final habit = Habit(
id: 'habit-${now.microsecondsSinceEpoch}',
title: title,
createdAt: now,
completedDates: const {},
);
_analytics.track('habit_created');
return _commit(current.copyWith(habits: [...current.habits, habit]));
}
Future<bool> renameHabit(Habit habit, String value) async {
final current = _state;
final title = value.trim();
if (current == null || title.isEmpty) {
return false;
}
final habits = current.habits
.map((item) => item.id == habit.id ? item.copyWith(title: title) : item)
.toList(growable: false);
return _commit(current.copyWith(habits: habits));
}
Future<bool> deleteHabit(Habit habit) async {
final current = _state;
if (current == null) {
return false;
}
final habits = current.habits
.where((item) => item.id != habit.id)
.toList(growable: false);
return _commit(current.copyWith(habits: habits));
}
Future<bool> completeFocusSession(int minutes) async {
final current = _state;
if (current == null) {
return false;
}
final now = DateTime.now();
final session = FocusSession(
id: 'session-${now.microsecondsSinceEpoch}',
minutes: minutes,
completedAt: now,
);
_analytics.track('focus_completed', {'minutes': minutes});
return _commit(current.copyWith(sessions: [...current.sessions, session]));
}
Future<bool> saveReflection(String value, Mood mood) async {
final current = _state;
final note = value.trim();
if (current == null || note.isEmpty) {
return false;
}
final today = todayKey();
final now = DateTime.now();
final entry = ReflectionEntry(
id: 'reflection-${now.microsecondsSinceEpoch}',
dateKey: today,
note: note,
mood: mood,
createdAt: now,
);
final reflections = [
...current.reflections.where((item) => item.dateKey != today),
entry,
];
_analytics.track('reflection_saved');
return _commit(current.copyWith(reflections: reflections));
}
Future<bool> importState(String raw) async {
try {
final decoded = jsonDecode(raw);
if (decoded is! Map<String, dynamic>) {
return false;
}
return _commit(AppState.fromJson(decoded));
} on Object {
return false;
}
}
Future<bool> clearData() async {
return _commit(AppState.starter(defaultHabitTitles: _defaultHabitTitles));
}
Future<bool> _commit(AppState nextState) async {
final previousState = _state;
_state = nextState;
_isSaving = true;
_error = null;
notifyListeners();
try {
await _store.save(nextState);
return true;
} on Object {
_state = previousState;
_error = FocusSeedError.saveFailed;
return false;
} finally {
_isSaving = false;
notifyListeners();
}
}
}