feat(wip): implement trip persistence through a local repository. Include loaded trips in the start page UI

This commit is contained in:
2025-12-30 00:51:40 +01:00
parent 81ed2fd8c3
commit 014b48591e
28 changed files with 2395 additions and 387 deletions

View File

@@ -0,0 +1,83 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
/// Defines the contract for persisting trip payloads locally.
abstract class TripLocalDataSource {
/// Returns all saved trip JSON payloads, newest first.
Future<List<Map<String, dynamic>>> loadTrips();
/// Returns a single trip payload by uuid if present.
/// TODO - should directly return Trip?
Future<Map<String, dynamic>?> getTrip(String uuid);
/// Upserts the provided trip payload (also used for editing existing trips).
Future<void> upsertTrip(Map<String, dynamic> tripJson);
/// Removes the trip with the matching uuid.
Future<void> deleteTrip(String uuid);
}
class TripLocalDataSourceImpl implements TripLocalDataSource {
TripLocalDataSourceImpl({Future<SharedPreferences>? preferences})
: _prefsFuture = preferences ?? SharedPreferences.getInstance();
static const String _storageKey = 'savedTrips';
final Future<SharedPreferences> _prefsFuture;
@override
Future<List<Map<String, dynamic>>> loadTrips() async {
final prefs = await _prefsFuture;
final stored = prefs.getStringList(_storageKey);
if (stored == null) return [];
return stored.map(_decodeTrip).toList();
}
@override
Future<Map<String, dynamic>?> getTrip(String uuid) async {
final trips = await loadTrips();
for (final trip in trips) {
if (trip['uuid'] == uuid) {
return Map<String, dynamic>.from(trip);
}
}
return null;
}
@override
Future<void> upsertTrip(Map<String, dynamic> tripJson) async {
final uuid = tripJson['uuid'];
if (uuid is! String || uuid.isEmpty) {
throw ArgumentError('Trip JSON must contain a uuid string');
}
final trips = await loadTrips();
trips.removeWhere((trip) => trip['uuid'] == uuid);
trips.insert(0, Map<String, dynamic>.from(tripJson));
await _persistTrips(trips);
}
@override
Future<void> deleteTrip(String uuid) async {
final trips = await loadTrips();
final updated = trips.where((trip) => trip['uuid'] != uuid).toList();
if (updated.length == trips.length) {
return;
}
await _persistTrips(updated);
}
Future<void> _persistTrips(List<Map<String, dynamic>> trips) async {
final prefs = await _prefsFuture;
final payload = trips.map(jsonEncode).toList();
await prefs.setStringList(_storageKey, payload);
}
Map<String, dynamic> _decodeTrip(String raw) {
final decoded = jsonDecode(raw);
if (decoded is! Map<String, dynamic>) {
throw const FormatException('Saved trip entry is not a JSON object');
}
return Map<String, dynamic>.from(decoded);
}
}

View File

@@ -1,13 +1,15 @@
import 'package:anyway/data/datasources/trip_local_datasource.dart';
import 'package:anyway/data/datasources/trip_remote_datasource.dart';
import 'package:anyway/domain/entities/landmark.dart';
import 'package:anyway/domain/entities/preferences.dart';
import 'package:anyway/domain/entities/trip.dart';
import 'package:anyway/domain/repositories/trip_repository.dart';
import 'package:anyway/data/datasources/trip_remote_datasource.dart';
class BackendTripRepository implements TripRepository {
final TripRemoteDataSource remote;
final TripLocalDataSource local;
BackendTripRepository({required this.remote});
BackendTripRepository({required this.remote, required this.local});
@override
Future<Trip> getTrip({Preferences? preferences, String? tripUUID, List<Landmark>? landmarks}) async {
@@ -86,4 +88,27 @@ class BackendTripRepository implements TripRepository {
prefsPayload['max_time_minute'] = preferences.maxTimeMinutes;
return prefsPayload;
}
// TODO - should this be moved to a separate local repository?
@override
Future<List<Trip>> getSavedTrips() async {
final rawTrips = await local.loadTrips();
return rawTrips.map(Trip.fromJson).toList(growable: false);
}
@override
Future<Trip?> getSavedTrip(String uuid) async {
final json = await local.getTrip(uuid);
return json == null ? null : Trip.fromJson(json);
}
@override
Future<void> saveTrip(Trip trip) async {
await local.upsertTrip(trip.toJson());
}
@override
Future<void> deleteSavedTrip(String uuid) async {
await local.deleteTrip(uuid);
}
}