import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:uuid/uuid.dart'; import '../data/bible.dart'; import '../models/tracker.dart'; import 'tracker_storage.dart'; /// Manages the list of [Tracker]s and exposes them as state to the UI. /// /// Initial state is loaded synchronously from [TrackerStorage] so the first /// frame already has the full list. Mutations persist asynchronously. class TrackerService extends StateNotifier> { TrackerService(this._storage, {Uuid? uuid}) : _uuid = uuid ?? const Uuid(), super(_storage.loadAll()); final TrackerStorage _storage; final Uuid _uuid; Future _persist(List trackers) async { state = trackers; await _storage.saveAll(trackers); } Future create({required String name, required Color color}) async { final Tracker tracker = Tracker( id: _uuid.v4(), name: name, colorValue: color.toARGB32(), startDate: DateTime.now(), ); final List next = [...state, tracker]; await _persist(next); return tracker; } Future updateMeta(String id, {String? name, Color? color}) async { final List next = state .map( (Tracker tracker) => tracker.id == id ? tracker.copyWith( name: name ?? tracker.name, colorValue: color?.toARGB32() ?? tracker.colorValue, ) : tracker, ) .toList(growable: false); await _persist(next); } Future delete(String id) async { final List next = state .where((Tracker tracker) => tracker.id != id) .toList(growable: false); await _persist(next); } Future toggleChapter(String id, int bookIndex, int chapter) async { final String key = Bible.chapterKey(bookIndex, chapter); final List next = state .map((Tracker tracker) { if (tracker.id != id) return tracker; final Set updated = {...tracker.readChapters}; if (!updated.remove(key)) { updated.add(key); } return tracker.copyWith(readChapters: updated); }) .toList(growable: false); await _persist(next); } Future setBookRead( String id, int bookIndex, { required bool read, }) async { final Book book = Bible.books[bookIndex]; final List next = state .map((Tracker tracker) { if (tracker.id != id) return tracker; final Set updated = {...tracker.readChapters}; for (int chapter = 1; chapter <= book.chapterCount; chapter++) { final String key = Bible.chapterKey(bookIndex, chapter); if (read) { updated.add(key); } else { updated.remove(key); } } return tracker.copyWith(readChapters: updated); }) .toList(growable: false); await _persist(next); } Future reset(String id) async { final List next = state .map( (Tracker tracker) => tracker.id == id ? tracker.copyWith(readChapters: const {}) : tracker, ) .toList(growable: false); await _persist(next); } } final StateNotifierProvider> trackerServiceProvider = StateNotifierProvider>( (Ref ref) => TrackerService(ref.read(trackerStorageProvider)), ); /// Convenience selector for a single tracker by id. final ProviderFamily trackerByIdProvider = Provider.family((Ref ref, String id) { final List trackers = ref.watch(trackerServiceProvider); for (final Tracker tracker in trackers) { if (tracker.id == id) return tracker; } return null; });