Files
rely/lib/features/local/local_repository.dart
T
2026-02-15 15:12:23 +01:00

176 lines
5.5 KiB
Dart

import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:relationship_saver/features/local/local_models.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
/// Persisted local data source for offline-first product state.
class LocalRepository extends AsyncNotifier<LocalDataState> {
static const String _storageKey = 'local_data_state_v1';
static const String _schemaVersionKey = 'local_data_schema_version';
static const int _schemaVersion = 1;
static const Uuid _uuid = Uuid();
@override
Future<LocalDataState> build() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await _migrateIfNeeded(prefs);
final String? raw = prefs.getString(_storageKey);
if (raw == null || raw.isEmpty) {
final LocalDataState seeded = LocalDataState.seed();
await _persist(seeded);
return seeded;
}
try {
final Map<String, dynamic> json = jsonDecode(raw) as Map<String, dynamic>;
return LocalDataState.fromJson(json);
} on FormatException {
final LocalDataState fallback = LocalDataState.seed();
await _persist(fallback);
return fallback;
}
}
Future<void> addPerson({
required String name,
required String relationship,
required String notes,
required List<String> tags,
DateTime? nextMoment,
}) async {
final LocalDataState current = _requireState();
final PersonProfile person = PersonProfile(
id: 'p-${_uuid.v4()}',
name: name.trim(),
relationship: relationship.trim(),
affinityScore: 75,
nextMoment: nextMoment ?? DateTime.now().add(const Duration(days: 3)),
tags: tags,
notes: notes.trim(),
);
final List<PersonProfile> people = <PersonProfile>[
person,
...current.people,
];
await _setState(current.copyWith(people: people));
}
Future<void> updatePerson(PersonProfile person) async {
final LocalDataState current = _requireState();
final List<PersonProfile> updated = current.people
.map((PersonProfile item) => item.id == person.id ? person : item)
.toList(growable: false);
await _setState(current.copyWith(people: updated));
}
Future<void> deletePerson(String personId) async {
final LocalDataState current = _requireState();
final List<PersonProfile> people = current.people
.where((PersonProfile person) => person.id != personId)
.toList(growable: false);
final List<RelationshipMoment> moments = current.moments
.where((RelationshipMoment moment) => moment.personId != personId)
.toList(growable: false);
await _setState(current.copyWith(people: people, moments: moments));
}
Future<void> addMoment({
required String personId,
required String summary,
String type = 'capture',
}) async {
final LocalDataState current = _requireState();
final String normalized = summary.trim();
if (normalized.isEmpty) {
return;
}
final String title = _titleFromSummary(normalized);
final RelationshipMoment moment = RelationshipMoment(
id: 'm-${_uuid.v4()}',
personId: personId,
title: title,
summary: normalized,
at: DateTime.now(),
type: type,
);
final List<RelationshipMoment> moments = <RelationshipMoment>[
moment,
...current.moments,
];
await _setState(current.copyWith(moments: moments));
}
Future<void> deleteMoment(String momentId) async {
final LocalDataState current = _requireState();
final List<RelationshipMoment> moments = current.moments
.where((RelationshipMoment moment) => moment.id != momentId)
.toList(growable: false);
await _setState(current.copyWith(moments: moments));
}
Future<void> toggleTaskDone(String taskId) async {
final LocalDataState current = _requireState();
final List<DashboardTask> tasks = current.tasks
.map(
(DashboardTask task) =>
task.id == taskId ? task.copyWith(done: !task.done) : task,
)
.toList(growable: false);
await _setState(current.copyWith(tasks: tasks));
}
Future<void> _setState(LocalDataState next) async {
state = AsyncData<LocalDataState>(next);
await _persist(next);
}
LocalDataState _requireState() {
final LocalDataState? value = state.asData?.value;
if (value == null) {
throw StateError('Local data state is not ready yet');
}
return value;
}
Future<void> _persist(LocalDataState state) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString(_storageKey, jsonEncode(state.toJson()));
await prefs.setInt(_schemaVersionKey, _schemaVersion);
}
Future<void> _migrateIfNeeded(SharedPreferences prefs) async {
final int currentVersion = prefs.getInt(_schemaVersionKey) ?? 0;
if (currentVersion == _schemaVersion) {
return;
}
if (currentVersion <= 0) {
await prefs.remove(_storageKey);
await prefs.setInt(_schemaVersionKey, _schemaVersion);
return;
}
// Migration placeholder for future schema versions.
await prefs.setInt(_schemaVersionKey, _schemaVersion);
}
String _titleFromSummary(String summary) {
final List<String> words = summary.split(RegExp(r'\s+'));
final int take = words.length < 5 ? words.length : 5;
return words.take(take).join(' ');
}
}
final AsyncNotifierProvider<LocalRepository, LocalDataState>
localRepositoryProvider =
AsyncNotifierProvider<LocalRepository, LocalDataState>(LocalRepository.new);