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 { 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 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 json = jsonDecode(raw) as Map; return LocalDataState.fromJson(json); } on FormatException { final LocalDataState fallback = LocalDataState.seed(); await _persist(fallback); return fallback; } } Future addPerson({ required String name, required String relationship, required String notes, required List 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 people = [ person, ...current.people, ]; await _setState(current.copyWith(people: people)); } Future updatePerson(PersonProfile person) async { final LocalDataState current = _requireState(); final List updated = current.people .map((PersonProfile item) => item.id == person.id ? person : item) .toList(growable: false); await _setState(current.copyWith(people: updated)); } Future deletePerson(String personId) async { final LocalDataState current = _requireState(); final List people = current.people .where((PersonProfile person) => person.id != personId) .toList(growable: false); final List moments = current.moments .where((RelationshipMoment moment) => moment.personId != personId) .toList(growable: false); await _setState(current.copyWith(people: people, moments: moments)); } Future 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 moments = [ moment, ...current.moments, ]; await _setState(current.copyWith(moments: moments)); } Future deleteMoment(String momentId) async { final LocalDataState current = _requireState(); final List moments = current.moments .where((RelationshipMoment moment) => moment.id != momentId) .toList(growable: false); await _setState(current.copyWith(moments: moments)); } Future toggleTaskDone(String taskId) async { final LocalDataState current = _requireState(); final List 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 _setState(LocalDataState next) async { state = AsyncData(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 _persist(LocalDataState state) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setString(_storageKey, jsonEncode(state.toJson())); await prefs.setInt(_schemaVersionKey, _schemaVersion); } Future _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 words = summary.split(RegExp(r'\s+')); final int take = words.length < 5 ? words.length : 5; return words.take(take).join(' '); } } final AsyncNotifierProvider localRepositoryProvider = AsyncNotifierProvider(LocalRepository.new);