123 lines
3.9 KiB
Dart
123 lines
3.9 KiB
Dart
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<List<Tracker>> {
|
|
TrackerService(this._storage, {Uuid? uuid})
|
|
: _uuid = uuid ?? const Uuid(),
|
|
super(_storage.loadAll());
|
|
|
|
final TrackerStorage _storage;
|
|
final Uuid _uuid;
|
|
|
|
Future<void> _persist(List<Tracker> trackers) async {
|
|
state = trackers;
|
|
await _storage.saveAll(trackers);
|
|
}
|
|
|
|
Future<Tracker> 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<Tracker> next = <Tracker>[...state, tracker];
|
|
await _persist(next);
|
|
return tracker;
|
|
}
|
|
|
|
Future<void> updateMeta(String id, {String? name, Color? color}) async {
|
|
final List<Tracker> 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<void> delete(String id) async {
|
|
final List<Tracker> next = state
|
|
.where((Tracker tracker) => tracker.id != id)
|
|
.toList(growable: false);
|
|
await _persist(next);
|
|
}
|
|
|
|
Future<void> toggleChapter(String id, int bookIndex, int chapter) async {
|
|
final String key = Bible.chapterKey(bookIndex, chapter);
|
|
final List<Tracker> next = state
|
|
.map((Tracker tracker) {
|
|
if (tracker.id != id) return tracker;
|
|
final Set<String> updated = <String>{...tracker.readChapters};
|
|
if (!updated.remove(key)) {
|
|
updated.add(key);
|
|
}
|
|
return tracker.copyWith(readChapters: updated);
|
|
})
|
|
.toList(growable: false);
|
|
await _persist(next);
|
|
}
|
|
|
|
Future<void> setBookRead(
|
|
String id,
|
|
int bookIndex, {
|
|
required bool read,
|
|
}) async {
|
|
final Book book = Bible.books[bookIndex];
|
|
final List<Tracker> next = state
|
|
.map((Tracker tracker) {
|
|
if (tracker.id != id) return tracker;
|
|
final Set<String> updated = <String>{...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<void> reset(String id) async {
|
|
final List<Tracker> next = state
|
|
.map(
|
|
(Tracker tracker) => tracker.id == id
|
|
? tracker.copyWith(readChapters: const <String>{})
|
|
: tracker,
|
|
)
|
|
.toList(growable: false);
|
|
await _persist(next);
|
|
}
|
|
}
|
|
|
|
final StateNotifierProvider<TrackerService, List<Tracker>>
|
|
trackerServiceProvider = StateNotifierProvider<TrackerService, List<Tracker>>(
|
|
(Ref ref) => TrackerService(ref.read(trackerStorageProvider)),
|
|
);
|
|
|
|
/// Convenience selector for a single tracker by id.
|
|
final ProviderFamily<Tracker?, String> trackerByIdProvider =
|
|
Provider.family<Tracker?, String>((Ref ref, String id) {
|
|
final List<Tracker> trackers = ref.watch(trackerServiceProvider);
|
|
for (final Tracker tracker in trackers) {
|
|
if (tracker.id == id) return tracker;
|
|
}
|
|
return null;
|
|
});
|