checkpoint: focus seed 1.0.1 localized dev switch
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user