import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; import 'saved_place.dart'; /// Stores saved places on the device. /// /// Local storage, and only local storage. A saved place is the closest thing /// this app has to a home address; it never reaches a server, which is what /// lets the Data safety declaration say that no location is collected. See /// docs/privacy.md. abstract interface class PlacesStore { Future> load(); Future save(List places); } class SharedPreferencesPlacesStore implements PlacesStore { const SharedPreferencesPlacesStore(); static const String storageKey = 'nuvolari.saved_places.v1'; @override Future> load() async { final prefs = await SharedPreferences.getInstance(); final raw = prefs.getString(storageKey); if (raw == null || raw.isEmpty) return const []; try { final decoded = jsonDecode(raw); if (decoded is! List) return const []; return decoded .whereType>() .map(SavedPlace.fromJson) .toList(growable: false); } on Object { // Corrupt storage loses the places, which is bad, but throwing here would // make the app unopenable, which is worse. Starting empty is recoverable // by the user; a crash loop is not. return const []; } } @override Future save(List places) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString( storageKey, jsonEncode(places.map((place) => place.toJson()).toList(growable: false)), ); } } /// An in-memory store, for tests and for previewing without touching the device. class InMemoryPlacesStore implements PlacesStore { InMemoryPlacesStore([List? initial]) : _places = List.of(initial ?? const []); List _places; @override Future> load() async => List.unmodifiable(_places); @override Future save(List places) async { _places = List.of(places); } }