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

@@ -1,11 +1,12 @@
import 'package:anyway/core/dio_client.dart';
import 'package:anyway/data/repositories/local_onboarding_repository.dart';
import 'package:anyway/domain/repositories/onboarding_repository.dart';
import 'package:anyway/domain/repositories/trip_repository.dart';
import 'package:anyway/data/repositories/backend_trip_repository.dart';
import 'package:anyway/data/datasources/trip_local_datasource.dart';
import 'package:anyway/data/datasources/trip_remote_datasource.dart';
import 'package:anyway/data/repositories/backend_trip_repository.dart';
import 'package:anyway/data/repositories/local_onboarding_repository.dart';
import 'package:anyway/data/repositories/preferences_repository_impl.dart';
import 'package:anyway/domain/repositories/onboarding_repository.dart';
import 'package:anyway/domain/repositories/preferences_repository.dart';
import 'package:anyway/domain/repositories/trip_repository.dart';
import 'dart:io';
import 'package:dio/dio.dart';
@@ -25,12 +26,6 @@ final dioProvider = Provider<Dio>((ref) {
));
});
/// Provide a simple wrapper client if needed elsewhere
final dioClientProvider = Provider<DioClient>((ref) {
final dio = ref.read(dioProvider);
return DioClient(baseUrl: dio.options.baseUrl);
});
/// Onboarding repository backed by SharedPreferences
final onboardingRepositoryProvider = Provider<OnboardingRepository>((ref) {
return LocalOnboardingRepository();
@@ -45,5 +40,6 @@ final preferencesRepositoryProvider = Provider<PreferencesRepository>((ref) {
final tripRepositoryProvider = Provider<TripRepository>((ref) {
final dio = ref.read(dioProvider);
final remote = TripRemoteDataSourceImpl(dio: dio);
return BackendTripRepository(remote: remote);
final local = TripLocalDataSourceImpl();
return BackendTripRepository(remote: remote, local: local);
});

View File

@@ -1,16 +1,86 @@
import 'dart:async';
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/presentation/providers/landmark_providers.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:anyway/presentation/providers/core_providers.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Provides a function that creates a trip given preferences.
final createTripProvider = Provider<Future<Trip> Function(Preferences)>((ref) {
return (Preferences prefs) async {
final repo = ref.read(tripRepositoryProvider);
final landmarks = await repo.searchLandmarks(prefs);
ref.read(intermediateLandmarksProvider.notifier).setLandmarks(landmarks);
return repo.getTrip(preferences: prefs, landmarks: landmarks);
};
return (Preferences prefs) async {
final repo = ref.read(tripRepositoryProvider);
final landmarks = await repo.searchLandmarks(prefs);
ref.read(intermediateLandmarksProvider.notifier).setLandmarks(landmarks);
final trip = await repo.getTrip(preferences: prefs, landmarks: landmarks);
await ref.read(currentTripsProvider.notifier).saveTrip(trip);
return trip;
};
});
class CurrentTripsNotifier extends AsyncNotifier<List<Trip>> {
TripRepository get _repository => ref.read(tripRepositoryProvider);
Future<List<Trip>>? _pendingLoad;
List<Trip> _currentTrips() => state.asData?.value ?? const <Trip>[];
@override
Future<List<Trip>> build() async {
final load = _repository.getSavedTrips();
_pendingLoad = load;
try {
final trips = await load;
return List.unmodifiable(trips);
} finally {
if (identical(_pendingLoad, load)) {
_pendingLoad = null;
}
}
}
Future<void> refresh() async {
state = const AsyncValue.loading();
final load = _repository.getSavedTrips();
_pendingLoad = load;
state = await AsyncValue.guard(() async {
final trips = await load;
return List.unmodifiable(trips);
});
if (identical(_pendingLoad, load)) {
_pendingLoad = null;
}
}
Future<void> saveTrip(Trip trip) async {
final previous = state;
final updatedTrips = [..._currentTrips()]
..removeWhere((t) => t.uuid == trip.uuid)
..insert(0, trip);
state = AsyncValue.data(List.unmodifiable(updatedTrips));
try {
await _repository.saveTrip(trip);
} catch (error) {
state = previous;
rethrow;
}
}
Future<void> deleteTrip(String uuid) async {
final previous = state;
final updatedTrips = _currentTrips()
.where((trip) => trip.uuid != uuid)
.toList(growable: false);
state = AsyncValue.data(List.unmodifiable(updatedTrips));
try {
await _repository.deleteSavedTrip(uuid);
} catch (error) {
state = previous;
rethrow;
}
}
}
final currentTripsProvider = AsyncNotifierProvider<CurrentTripsNotifier, List<Trip>>(CurrentTripsNotifier.new);