From e3c8ee7d8f9da2c3b959c66130738aaace7c2869 Mon Sep 17 00:00:00 2001 From: Andri Joos <andri@joos.io> Date: Mon, 6 May 2024 00:16:09 +0200 Subject: [PATCH] add factories & hive storage --- .../converter/appwrite_converters/point.dart | 8 +- .../converter/appwrite_converters/route.dart | 18 +-- car_app/lib/converter/converter.dart | 2 +- .../location_point_point_converter.dart | 15 +++ .../location_converters.dart/point.dart | 8 +- .../location_converters.dart/position.dart | 11 ++ .../lib/converter/point_latlng_converter.dart | 4 +- .../converter/position_point_converter.dart | 10 -- .../{ => appwrite}/point.dart | 11 +- .../{ => appwrite}/route.dart | 24 ++-- car_app/lib/factory/appwrite/point.dart | 14 ++ car_app/lib/factory/appwrite/route.dart | 17 +++ car_app/lib/factory/interface/point.dart | 5 + car_app/lib/factory/interface/route.dart | 6 + car_app/lib/main.dart | 125 +++++++++++++++--- car_app/lib/model/appwrite/base_model.dart | 8 ++ car_app/lib/model/appwrite/point.dart | 12 ++ car_app/lib/model/appwrite/route.dart | 19 +++ car_app/lib/model/id_model/id_model.dart | 3 + car_app/lib/model/id_model/point.dart | 4 + car_app/lib/model/id_model/route.dart | 5 + car_app/lib/model/interface/README.md | 1 + car_app/lib/model/interface/point.dart | 4 + car_app/lib/model/interface/route.dart | 8 ++ car_app/lib/model/location/point.dart | 11 ++ car_app/lib/model/point.dart | 7 - car_app/lib/model/route.dart | 11 -- .../lib/service/appwrite/appwrite_point.dart | 8 +- .../lib/service/appwrite/appwrite_route.dart | 17 +-- car_app/lib/service/chached_route.dart | 38 +++--- ...location.dart => geolocator_location.dart} | 40 +++--- .../service/interface/key_value_storage.dart | 1 + car_app/lib/service/interface/location.dart | 13 ++ car_app/lib/service/interface/point.dart | 5 + car_app/lib/service/interface/route.dart | 7 +- car_app/lib/service/storage/hive_storage.dart | 68 ++++++++++ .../model_storage/appwrite/point_storage.dart | 17 +++ .../model_storage/appwrite/route_storage.dart | 21 +++ .../storage/model_storage/model_storage.dart | 20 +++ .../storage/model_storage/point_storage.dart | 34 +++++ .../storage/model_storage/route_storage.dart | 65 +++++++++ .../lib/service/storage/route_storage.dart | 34 ----- .../lib/service/storage/secure_storage.dart | 8 ++ car_app/lib/view/tracking.dart | 2 +- car_app/lib/viewmodel/tracking.dart | 38 ++++-- car_app/pubspec.lock | 16 ++- car_app/pubspec.yaml | 3 + 47 files changed, 625 insertions(+), 201 deletions(-) create mode 100644 car_app/lib/converter/location_converters.dart/location_point_point_converter.dart create mode 100644 car_app/lib/converter/location_converters.dart/position.dart delete mode 100644 car_app/lib/converter/position_point_converter.dart rename car_app/lib/converter/storage_converters/{ => appwrite}/point.dart (53%) rename car_app/lib/converter/storage_converters/{ => appwrite}/route.dart (51%) create mode 100644 car_app/lib/factory/appwrite/point.dart create mode 100644 car_app/lib/factory/appwrite/route.dart create mode 100644 car_app/lib/factory/interface/point.dart create mode 100644 car_app/lib/factory/interface/route.dart create mode 100644 car_app/lib/model/appwrite/base_model.dart create mode 100644 car_app/lib/model/appwrite/point.dart create mode 100644 car_app/lib/model/appwrite/route.dart create mode 100644 car_app/lib/model/id_model/id_model.dart create mode 100644 car_app/lib/model/id_model/point.dart create mode 100644 car_app/lib/model/id_model/route.dart create mode 100644 car_app/lib/model/interface/README.md create mode 100644 car_app/lib/model/interface/point.dart create mode 100644 car_app/lib/model/interface/route.dart create mode 100644 car_app/lib/model/location/point.dart delete mode 100644 car_app/lib/model/point.dart delete mode 100644 car_app/lib/model/route.dart rename car_app/lib/service/{location.dart => geolocator_location.dart} (82%) create mode 100644 car_app/lib/service/interface/location.dart create mode 100644 car_app/lib/service/interface/point.dart create mode 100644 car_app/lib/service/storage/hive_storage.dart create mode 100644 car_app/lib/service/storage/model_storage/appwrite/point_storage.dart create mode 100644 car_app/lib/service/storage/model_storage/appwrite/route_storage.dart create mode 100644 car_app/lib/service/storage/model_storage/model_storage.dart create mode 100644 car_app/lib/service/storage/model_storage/point_storage.dart create mode 100644 car_app/lib/service/storage/model_storage/route_storage.dart delete mode 100644 car_app/lib/service/storage/route_storage.dart diff --git a/car_app/lib/converter/appwrite_converters/point.dart b/car_app/lib/converter/appwrite_converters/point.dart index 728e2fe..7260098 100644 --- a/car_app/lib/converter/appwrite_converters/point.dart +++ b/car_app/lib/converter/appwrite_converters/point.dart @@ -1,6 +1,6 @@ -import 'package:car_app/model/point.dart'; +import 'package:car_app/model/appwrite/point.dart'; -extension PointMapConverter on Point { +extension PointMapConverter on AppwritePoint { static const String idFieldName = "id"; static const String longitudeFieldName = "longitude"; static const String latitudeFieldName = "latitude"; @@ -11,8 +11,8 @@ extension PointMapConverter on Point { latitudeFieldName: latitude, }; - static Point fromMap(Map<String, dynamic> map) { - return Point( + static AppwritePoint fromMap(Map<String, dynamic> map) { + return AppwritePoint( id: map[idFieldName], latitude: map[latitudeFieldName], longitude: map[longitudeFieldName], diff --git a/car_app/lib/converter/appwrite_converters/route.dart b/car_app/lib/converter/appwrite_converters/route.dart index 19c8fa8..65e2e7e 100644 --- a/car_app/lib/converter/appwrite_converters/route.dart +++ b/car_app/lib/converter/appwrite_converters/route.dart @@ -1,8 +1,7 @@ -import 'package:car_app/converter/appwrite_converters/point.dart'; -import 'package:car_app/model/point.dart'; -import 'package:car_app/model/route.dart'; +import 'package:car_app/model/appwrite/route.dart'; +import 'package:car_app/model/interface/point.dart'; -extension RouteMapConverter on Route { +extension RouteMapConverter on AppwriteRoute { static const String idFieldName = "id"; static const String teamIdFieldName = "teamId"; static const String pointsFieldName = "points"; @@ -10,12 +9,11 @@ extension RouteMapConverter on Route { static const String endTimeFieldName = "endTime"; static const String userIdFieldName = "userId"; - Map<String, dynamic> toMap(String userId, {Iterable<Point>? points}) { - points ??= this.points; + Map<String, dynamic> toMap(String userId, Iterable<String>? pointIds) { return { idFieldName: id, - pointsFieldName: points.map((e) => e.id).toList(), + pointsFieldName: points, teamIdFieldName: carId, startTimeFieldName: startTime.toIso8601String(), endTimeFieldName: endTime.toIso8601String(), @@ -23,10 +21,8 @@ extension RouteMapConverter on Route { }; } - static Route fromMap(Map<String, dynamic> map, {Iterable<Point>? points}) { - points ??= List<Point>.from((List<Map<String, dynamic>>.from(map[pointsFieldName])).map((x) => PointMapConverter.fromMap(x))); - - return Route( + static AppwriteRoute<T> fromMap<T extends Point>(Map<String, dynamic> map, Iterable<T> points) { + return AppwriteRoute( id: map[idFieldName], carId: map[teamIdFieldName], points: points, diff --git a/car_app/lib/converter/converter.dart b/car_app/lib/converter/converter.dart index cf1446c..3a8dbb4 100644 --- a/car_app/lib/converter/converter.dart +++ b/car_app/lib/converter/converter.dart @@ -1,3 +1,3 @@ -abstract class Converter <T1, T2> { +abstract interface class Converter <T1, T2> { T2 convert(T1 t); } diff --git a/car_app/lib/converter/location_converters.dart/location_point_point_converter.dart b/car_app/lib/converter/location_converters.dart/location_point_point_converter.dart new file mode 100644 index 0000000..0addce4 --- /dev/null +++ b/car_app/lib/converter/location_converters.dart/location_point_point_converter.dart @@ -0,0 +1,15 @@ +import 'package:car_app/converter/converter.dart'; +import 'package:car_app/factory/interface/point.dart'; +import 'package:car_app/model/interface/point.dart'; +import 'package:car_app/model/location/point.dart'; + +class LocationPointToPointConverter<T extends Point> implements Converter<LocationPoint, T> { + final PointFactory<T> pointFactory; + + LocationPointToPointConverter({required this.pointFactory}); + + @override + T convert(LocationPoint t) { + return pointFactory.createInstance(t.latitude, t.longitude); + } +} \ No newline at end of file diff --git a/car_app/lib/converter/location_converters.dart/point.dart b/car_app/lib/converter/location_converters.dart/point.dart index 66dc1a5..7127cd8 100644 --- a/car_app/lib/converter/location_converters.dart/point.dart +++ b/car_app/lib/converter/location_converters.dart/point.dart @@ -1,6 +1,6 @@ -import 'package:car_app/model/point.dart'; +import 'package:car_app/model/location/point.dart'; -extension PointMapConverter on Point { +extension PointMapConverter on LocationPoint { static const String longitudeFieldName = "longitude"; static const String latitudeFieldName = "latitude"; @@ -9,8 +9,8 @@ extension PointMapConverter on Point { latitudeFieldName: latitude, }; - static Point fromMap(Map<String, dynamic> map) { - return Point( + static LocationPoint fromMap(Map<String, dynamic> map) { + return LocationPoint( latitude: map[latitudeFieldName], longitude: map[longitudeFieldName], ); diff --git a/car_app/lib/converter/location_converters.dart/position.dart b/car_app/lib/converter/location_converters.dart/position.dart new file mode 100644 index 0000000..af3010a --- /dev/null +++ b/car_app/lib/converter/location_converters.dart/position.dart @@ -0,0 +1,11 @@ +import 'package:car_app/model/location/point.dart'; +import 'package:geolocator/geolocator.dart'; + +extension LocationPointConverter on Position { + LocationPoint toLocationPoint() { + return LocationPoint( + latitude: latitude, + longitude: longitude + ); + } +} diff --git a/car_app/lib/converter/point_latlng_converter.dart b/car_app/lib/converter/point_latlng_converter.dart index 183ade0..2ad50b2 100644 --- a/car_app/lib/converter/point_latlng_converter.dart +++ b/car_app/lib/converter/point_latlng_converter.dart @@ -1,8 +1,8 @@ import 'package:car_app/converter/converter.dart'; -import 'package:car_app/model/point.dart'; +import 'package:car_app/model/interface/point.dart'; import 'package:latlong2/latlong.dart'; -class PointToLatLngConverter extends Converter<Point, LatLng> { +class PointToLatLngConverter implements Converter<Point, LatLng> { @override LatLng convert(Point t) { return LatLng(t.latitude.toDouble(), t.longitude.toDouble()); diff --git a/car_app/lib/converter/position_point_converter.dart b/car_app/lib/converter/position_point_converter.dart deleted file mode 100644 index 700d17d..0000000 --- a/car_app/lib/converter/position_point_converter.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:car_app/converter/converter.dart'; -import 'package:car_app/model/point.dart'; -import 'package:geolocator/geolocator.dart'; - -class PositionToPointConverter extends Converter<Position, Point> { - @override - Point convert(Position t) { - return Point(latitude: t.latitude, longitude: t.longitude); - } -} \ No newline at end of file diff --git a/car_app/lib/converter/storage_converters/point.dart b/car_app/lib/converter/storage_converters/appwrite/point.dart similarity index 53% rename from car_app/lib/converter/storage_converters/point.dart rename to car_app/lib/converter/storage_converters/appwrite/point.dart index 66dc1a5..7260098 100644 --- a/car_app/lib/converter/storage_converters/point.dart +++ b/car_app/lib/converter/storage_converters/appwrite/point.dart @@ -1,16 +1,19 @@ -import 'package:car_app/model/point.dart'; +import 'package:car_app/model/appwrite/point.dart'; -extension PointMapConverter on Point { +extension PointMapConverter on AppwritePoint { + static const String idFieldName = "id"; static const String longitudeFieldName = "longitude"; static const String latitudeFieldName = "latitude"; Map<String, dynamic> toMap() => { + idFieldName: id, longitudeFieldName: longitude, latitudeFieldName: latitude, }; - static Point fromMap(Map<String, dynamic> map) { - return Point( + static AppwritePoint fromMap(Map<String, dynamic> map) { + return AppwritePoint( + id: map[idFieldName], latitude: map[latitudeFieldName], longitude: map[longitudeFieldName], ); diff --git a/car_app/lib/converter/storage_converters/route.dart b/car_app/lib/converter/storage_converters/appwrite/route.dart similarity index 51% rename from car_app/lib/converter/storage_converters/route.dart rename to car_app/lib/converter/storage_converters/appwrite/route.dart index 5102a2e..5e1853f 100644 --- a/car_app/lib/converter/storage_converters/route.dart +++ b/car_app/lib/converter/storage_converters/appwrite/route.dart @@ -1,35 +1,29 @@ -import 'package:car_app/converter/storage_converters/point.dart'; -import 'package:car_app/model/point.dart'; -import 'package:car_app/model/route.dart'; +import 'package:car_app/model/appwrite/route.dart'; +import 'package:car_app/model/interface/point.dart'; -extension RouteMapConverter on Route { +extension RouteMapConverter on AppwriteRoute { static const String idFieldName = "id"; static const String teamIdFieldName = "teamId"; static const String pointsFieldName = "points"; static const String startTimeFieldName = "startTime"; static const String endTimeFieldName = "endTime"; - Map<String, dynamic> toMap() { - List<Map<String, dynamic>> jsonPoints = []; - - for (var point in points) { - jsonPoints.add(point.toMap()); - } - + Map<String, dynamic> toMap(Iterable<String> pointIds) { return { idFieldName: id, - pointsFieldName: jsonPoints, + pointsFieldName: pointIds, teamIdFieldName: carId, startTimeFieldName: startTime.toIso8601String(), endTimeFieldName: endTime.toIso8601String(), }; } - static Route fromMap(Map<String, dynamic> map) { - return Route( + static AppwriteRoute<T> fromMap<T extends Point>(Map<String, dynamic> map, Iterable<T> points) { + // List<Point>.from((List<Map<String, dynamic>>.from(map[pointsFieldName])).map((x) => PointMapConverter.fromMap(x))) + return AppwriteRoute( id: map[idFieldName], carId: map[teamIdFieldName], - points: List<Point>.from((List<Map<String, dynamic>>.from(map[pointsFieldName])).map((x) => PointMapConverter.fromMap(x))), + points: points, startTime: DateTime.parse(map[startTimeFieldName]), endTime: DateTime.parse(map[endTimeFieldName]), ); diff --git a/car_app/lib/factory/appwrite/point.dart b/car_app/lib/factory/appwrite/point.dart new file mode 100644 index 0000000..dfd1a84 --- /dev/null +++ b/car_app/lib/factory/appwrite/point.dart @@ -0,0 +1,14 @@ +import 'package:appwrite/appwrite.dart'; +import 'package:car_app/factory/interface/point.dart'; +import 'package:car_app/model/appwrite/point.dart'; + +class AppwritePointFactory implements PointFactory<AppwritePoint> { + @override + AppwritePoint createInstance(num latitude, num longitude) { + return AppwritePoint( + id: ID.unique(), + latitude: latitude, + longitude: longitude + ); + } +} diff --git a/car_app/lib/factory/appwrite/route.dart b/car_app/lib/factory/appwrite/route.dart new file mode 100644 index 0000000..896c405 --- /dev/null +++ b/car_app/lib/factory/appwrite/route.dart @@ -0,0 +1,17 @@ +import 'package:appwrite/appwrite.dart'; +import 'package:car_app/factory/interface/route.dart'; +import 'package:car_app/model/appwrite/route.dart'; +import 'package:car_app/model/interface/point.dart'; + +class AppwriteRouteFactory<T extends Point> implements RouteFactory<AppwriteRoute<T>, T> { + @override + AppwriteRoute<T> createInstance(String? carId, Iterable<T> points, DateTime startTime, DateTime endTime) { + return AppwriteRoute( + id: ID.unique(), + carId: carId, + points: points, + startTime: startTime, + endTime: endTime + ); + } +} diff --git a/car_app/lib/factory/interface/point.dart b/car_app/lib/factory/interface/point.dart new file mode 100644 index 0000000..6f07b14 --- /dev/null +++ b/car_app/lib/factory/interface/point.dart @@ -0,0 +1,5 @@ +import 'package:car_app/model/interface/point.dart'; + +abstract interface class PointFactory<T extends Point> { + T createInstance(num latitude, num longitude); +} diff --git a/car_app/lib/factory/interface/route.dart b/car_app/lib/factory/interface/route.dart new file mode 100644 index 0000000..be99202 --- /dev/null +++ b/car_app/lib/factory/interface/route.dart @@ -0,0 +1,6 @@ +import 'package:car_app/model/interface/point.dart'; +import 'package:car_app/model/interface/route.dart'; + +abstract interface class RouteFactory<T extends Route<T1>, T1 extends Point> { + T createInstance(String? carId, Iterable<T1> points, DateTime startTime, DateTime endTime); +} diff --git a/car_app/lib/main.dart b/car_app/lib/main.dart index a8d3d42..bc7f380 100644 --- a/car_app/lib/main.dart +++ b/car_app/lib/main.dart @@ -1,16 +1,30 @@ +import 'dart:convert'; + +import 'package:car_app/converter/location_converters.dart/location_point_point_converter.dart'; +import 'package:car_app/exception/key_not_found.dart'; +import 'package:car_app/factory/appwrite/point.dart'; +import 'package:car_app/factory/appwrite/route.dart'; +import 'package:car_app/factory/interface/route.dart' as route_factory; +import 'package:car_app/model/appwrite/point.dart'; +import 'package:car_app/model/appwrite/route.dart'; import 'package:car_app/service/appwrite/appwrite_authentication.dart'; import 'package:car_app/service/appwrite/appwrite_calendar_event.dart'; import 'package:car_app/service/appwrite/appwrite_car.dart'; +import 'package:car_app/service/appwrite/appwrite_point.dart'; import 'package:car_app/service/appwrite/appwrite_route.dart'; import 'package:car_app/service/cached_username_password_authentication.dart'; import 'package:car_app/service/chached_route.dart'; +import 'package:car_app/service/geolocator_location.dart'; import 'package:car_app/service/interface/authentication.dart'; import 'package:car_app/service/interface/calendar_event.dart'; import 'package:car_app/service/interface/car.dart'; +import 'package:car_app/service/interface/location.dart'; import 'package:car_app/service/interface/logging.dart'; import 'package:car_app/service/interface/route.dart'; import 'package:car_app/service/logging/logger/logger.dart'; -import 'package:car_app/service/storage/route_storage.dart'; +import 'package:car_app/service/storage/hive_storage.dart'; +import 'package:car_app/service/storage/model_storage/appwrite/point_storage.dart'; +import 'package:car_app/service/storage/model_storage/appwrite/route_storage.dart'; import 'package:car_app/service/storage/secure_storage.dart'; import 'package:car_app/view/calendar.dart'; import 'package:car_app/view/startup.dart'; @@ -20,55 +34,113 @@ import 'package:car_app/viewmodel/authentication.dart'; import 'package:car_app/viewmodel/calendar.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:path/path.dart' as path; import 'package:car_app/viewmodel/tracking.dart'; +import 'package:hive/hive.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; +const String hiveEncryptionKeyFieldName = "hiveEncryptionKey"; void main() async { WidgetsFlutterBinding.ensureInitialized(); var applicationDocumentsDirectory = await getApplicationDocumentsDirectory(); + var hiveEncryptionKey = await getOrMakeHiveEncryptionKey(); + runApp(App( - applicationDocumentsPath: applicationDocumentsDirectory.path + applicationDocumentsPath: applicationDocumentsDirectory.path, + hiveEncryptionKey: hiveEncryptionKey, )); } +Future<List<int>> getOrMakeHiveEncryptionKey() async { + var secureStorage = SecureStorageService(); + + List<int> hiveEncryptionKey; + try { + var decodedHiveKey = await secureStorage.get(hiveEncryptionKeyFieldName); + hiveEncryptionKey = base64Decode(decodedHiveKey); + } on KeyNotFoundException catch (_) { + hiveEncryptionKey = Hive.generateSecureKey(); + await secureStorage.add(hiveEncryptionKeyFieldName, base64Encode(hiveEncryptionKey)); + } + + return hiveEncryptionKey; +} + class App extends StatelessWidget { final String applicationDocumentsPath; + final List<int> hiveEncryptionKey; - const App({super.key, required this.applicationDocumentsPath}); + const App({ + super.key, + required this.applicationDocumentsPath, + required this.hiveEncryptionKey, + }); @override Widget build(BuildContext context) { + // We must create the services here to ensure types + + // Factories + var appwriteRouteFactory = AppwriteRouteFactory<AppwritePoint>(); + var appwritePointFactory = AppwritePointFactory(); + + // Services + var secureStorageService = SecureStorageService(); + var loggingService = Logger(filePath: applicationDocumentsPath, isRelease: kReleaseMode); + var authenticationService = CachedUsernamePasswordAuthenticationService( + authenticationService: AppwriteAuthentication(), + storageService: secureStorageService, + ); + + var appwritePointService = AppwritePointService(); + var appwritePointStorageService = AppwritePointStorageService( + storageService: getCachingHiveStorageService("point", applicationDocumentsPath), + ); + var appwriteRouteService = CachedRouteService( + routeService: AppwriteRouteService<AppwritePoint>( + authenticationService: authenticationService, + pointService: appwritePointService, + ), + storageService: AppwriteRouteStorageService( + storageService: getCachingHiveStorageService("route", applicationDocumentsPath), + pointStorageService: appwritePointStorageService, + ) + ); + var appwriteCarService = AppwriteCarService( + authenticationService: authenticationService, + ); + var appwriteCalendarEventService = AppwriteCalendarEventService(); + var locationService = GeolocatorLocationService<AppwritePoint>( + locationPointToPointConverter: LocationPointToPointConverter( + pointFactory: appwritePointFactory, + ), + ); + return MultiProvider( providers: [ - // Providers - Provider<LoggingService>(create: (context) => Logger(filePath: applicationDocumentsPath, isRelease: kReleaseMode)), - Provider<AuthenticationService>(create: (context) => CachedUsernamePasswordAuthenticationService( - authenticationService: AppwriteAuthentication(), - storageService: SecureStorageService(), - )), - Provider<RouteService>(create: (context) => CachedRouteService( - routeService: AppwriteRouteService( - authenticationService: Provider.of<AuthenticationService>(context, listen: false) - ), - storageService: RouteStorageService( - storageService: SecureStorageService(), - ) - )), - Provider<CarService>(create: (context) => AppwriteCarService( - authenticationService: Provider.of(context, listen: false), - )), - Provider<CalendarEventService>(create: (context) => AppwriteCalendarEventService()), + // Factories + Provider<route_factory.RouteFactory<AppwriteRoute<AppwritePoint>, AppwritePoint>>(create: (context) => appwriteRouteFactory), + + // Service providers + Provider<LoggingService>(create: (context) => loggingService), + Provider<AuthenticationService>(create: (context) => authenticationService), + Provider<RouteService<AppwriteRoute<AppwritePoint>, AppwritePoint>>(create: (context) => appwriteRouteService), + Provider<CarService>(create: (context) => appwriteCarService), + Provider<CalendarEventService>(create: (context) => appwriteCalendarEventService), + Provider<LocationService<AppwritePoint>>(create: (context) => locationService), // ChangeNotifierProviders // currently no better (auto) injection method found, best case would be something similar to https://git.420joos.dev/ost/seproj/studiapp/-/blob/master/StudiApp/StudiApp/MauiProgram.cs - ChangeNotifierProvider(create: (context) => TrackingViewModel( + ChangeNotifierProvider<TrackingViewModel>(create: (context) => TrackingViewModel<AppwriteRoute<AppwritePoint>, AppwritePoint>( authenticationService: Provider.of(context, listen: false), routeService: Provider.of(context, listen: false), carService: Provider.of(context, listen: false), loggingService: Provider.of(context, listen: false), + locationService: Provider.of(context, listen: false), + routeFactory: Provider.of(context, listen: false), )), ChangeNotifierProvider(create: (context) => AuthenticationViewModel( authenticationService: Provider.of(context, listen: false), @@ -96,4 +168,13 @@ class App extends StatelessWidget { ), ); } + + HiveStorageService getCachingHiveStorageService(String boxName, String applicationDocumentsDirectory) { + return HiveStorageService( + dbName: "cache", + dbPath: path.join(applicationDocumentsDirectory, "db"), + boxName: boxName, + encryptionKey: hiveEncryptionKey, + ); + } } diff --git a/car_app/lib/model/appwrite/base_model.dart b/car_app/lib/model/appwrite/base_model.dart new file mode 100644 index 0000000..bcc4eb3 --- /dev/null +++ b/car_app/lib/model/appwrite/base_model.dart @@ -0,0 +1,8 @@ +import 'package:car_app/model/id_model/id_model.dart'; + +abstract class AppwriteBaseModel implements IdModel<String> { + @override + final String id; + + AppwriteBaseModel({required this.id}); +} diff --git a/car_app/lib/model/appwrite/point.dart b/car_app/lib/model/appwrite/point.dart new file mode 100644 index 0000000..044ff17 --- /dev/null +++ b/car_app/lib/model/appwrite/point.dart @@ -0,0 +1,12 @@ +import 'package:car_app/model/appwrite/base_model.dart'; +import 'package:car_app/model/id_model/point.dart'; + +class AppwritePoint extends AppwriteBaseModel implements PointIdModel<String> { + @override + final num latitude; + + @override + final num longitude; + + AppwritePoint({required super.id, required this.latitude, required this.longitude}); +} diff --git a/car_app/lib/model/appwrite/route.dart b/car_app/lib/model/appwrite/route.dart new file mode 100644 index 0000000..bf3413a --- /dev/null +++ b/car_app/lib/model/appwrite/route.dart @@ -0,0 +1,19 @@ +import 'package:car_app/model/appwrite/base_model.dart'; +import 'package:car_app/model/id_model/route.dart'; +import 'package:car_app/model/interface/point.dart'; + +class AppwriteRoute<T extends Point> extends AppwriteBaseModel implements RouteIdModel<String, T> { + @override + final String? carId; + + @override + final Iterable<T> points; + + @override + final DateTime startTime; + + @override + final DateTime endTime; + + AppwriteRoute({required super.id, required this.carId, required this.points, required this.startTime, required this.endTime}); +} diff --git a/car_app/lib/model/id_model/id_model.dart b/car_app/lib/model/id_model/id_model.dart new file mode 100644 index 0000000..7ef45e7 --- /dev/null +++ b/car_app/lib/model/id_model/id_model.dart @@ -0,0 +1,3 @@ +abstract interface class IdModel<T> { + T get id; +} diff --git a/car_app/lib/model/id_model/point.dart b/car_app/lib/model/id_model/point.dart new file mode 100644 index 0000000..f34b315 --- /dev/null +++ b/car_app/lib/model/id_model/point.dart @@ -0,0 +1,4 @@ +import 'package:car_app/model/id_model/id_model.dart'; +import 'package:car_app/model/interface/point.dart'; + +abstract interface class PointIdModel<T> implements IdModel<T>, Point {} diff --git a/car_app/lib/model/id_model/route.dart b/car_app/lib/model/id_model/route.dart new file mode 100644 index 0000000..cc78bf8 --- /dev/null +++ b/car_app/lib/model/id_model/route.dart @@ -0,0 +1,5 @@ +import 'package:car_app/model/id_model/id_model.dart'; +import 'package:car_app/model/interface/point.dart'; +import 'package:car_app/model/interface/route.dart'; + +abstract interface class RouteIdModel<T, T1 extends Point> implements IdModel<T>, Route<T1> {} diff --git a/car_app/lib/model/interface/README.md b/car_app/lib/model/interface/README.md new file mode 100644 index 0000000..de4155e --- /dev/null +++ b/car_app/lib/model/interface/README.md @@ -0,0 +1 @@ +All derivations of these models should override == and hashcode diff --git a/car_app/lib/model/interface/point.dart b/car_app/lib/model/interface/point.dart new file mode 100644 index 0000000..b54623c --- /dev/null +++ b/car_app/lib/model/interface/point.dart @@ -0,0 +1,4 @@ +abstract interface class Point { + num get longitude; + num get latitude; +} diff --git a/car_app/lib/model/interface/route.dart b/car_app/lib/model/interface/route.dart new file mode 100644 index 0000000..199e0c8 --- /dev/null +++ b/car_app/lib/model/interface/route.dart @@ -0,0 +1,8 @@ +import 'package:car_app/model/interface/point.dart'; + +abstract interface class Route<T extends Point> { + String? get carId; + Iterable<T> get points; + DateTime get startTime; + DateTime get endTime; +} diff --git a/car_app/lib/model/location/point.dart b/car_app/lib/model/location/point.dart new file mode 100644 index 0000000..3f6b0d8 --- /dev/null +++ b/car_app/lib/model/location/point.dart @@ -0,0 +1,11 @@ +import 'package:car_app/model/interface/point.dart'; + +class LocationPoint implements Point { + @override + final num latitude; + + @override + final num longitude; + + LocationPoint({required this.latitude, required this.longitude}); +} diff --git a/car_app/lib/model/point.dart b/car_app/lib/model/point.dart deleted file mode 100644 index 8392b22..0000000 --- a/car_app/lib/model/point.dart +++ /dev/null @@ -1,7 +0,0 @@ -class Point { - final String? id; - final num longitude; - final num latitude; - - Point({this.id, required this.longitude, required this.latitude}); -} \ No newline at end of file diff --git a/car_app/lib/model/route.dart b/car_app/lib/model/route.dart deleted file mode 100644 index 1aa6db5..0000000 --- a/car_app/lib/model/route.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:car_app/model/point.dart'; - -class Route { - final String? id; - final String? carId; - final Iterable<Point> points; - final DateTime startTime; - final DateTime endTime; - - Route({this.id, this.carId, required this.points, required this.startTime, required this.endTime}); -} diff --git a/car_app/lib/service/appwrite/appwrite_point.dart b/car_app/lib/service/appwrite/appwrite_point.dart index 9b6d26f..8671ade 100644 --- a/car_app/lib/service/appwrite/appwrite_point.dart +++ b/car_app/lib/service/appwrite/appwrite_point.dart @@ -1,11 +1,13 @@ import 'package:car_app/converter/appwrite_converters/point.dart'; -import 'package:car_app/model/point.dart'; +import 'package:car_app/model/appwrite/point.dart'; import 'package:car_app/service/appwrite/appwrite_api_function.dart'; +import 'package:car_app/service/interface/point.dart'; -class AppwritePointService extends AppwriteApiFunctionService { +class AppwritePointService extends AppwriteApiFunctionService implements PointService<AppwritePoint> { static const String path = "/point"; - Future<Point> add(Point point) async { + @override + Future<AppwritePoint> add(AppwritePoint point) async { var action = "add point"; var execution = await executeApiFunction(action, "POST", path, body: point.toMap()); diff --git a/car_app/lib/service/appwrite/appwrite_route.dart b/car_app/lib/service/appwrite/appwrite_route.dart index 7f847b1..b5a9eda 100644 --- a/car_app/lib/service/appwrite/appwrite_route.dart +++ b/car_app/lib/service/appwrite/appwrite_route.dart @@ -1,28 +1,29 @@ import 'package:car_app/converter/appwrite_converters/route.dart'; -import 'package:car_app/model/route.dart'; +import 'package:car_app/model/appwrite/route.dart'; +import 'package:car_app/model/id_model/point.dart'; import 'package:car_app/service/appwrite/appwrite_api_function.dart'; -import 'package:car_app/service/appwrite/appwrite_point.dart'; import 'package:car_app/service/interface/authentication.dart'; +import 'package:car_app/service/interface/point.dart'; import 'package:car_app/service/interface/route.dart'; -class AppwriteRouteService extends AppwriteApiFunctionService implements RouteService { +class AppwriteRouteService<T extends PointIdModel> extends AppwriteApiFunctionService implements RouteService<AppwriteRoute<T>, T> { static const String path = "/route"; - final AppwritePointService pointService = AppwritePointService(); + final PointService<T> pointService; final AuthenticationService authenticationService; - AppwriteRouteService({required this.authenticationService}); + AppwriteRouteService({required this.pointService, required this.authenticationService}); @override - Future<Route> add(Route route) async { + Future<AppwriteRoute<T>> add(AppwriteRoute<T> route) async { var points = await Stream.fromFutures(route.points.map((e) => pointService.add(e))).toList(); var action = "add route"; var user = await authenticationService.user; - var execution = await executeApiFunction(action, "POST", path, body: route.toMap(user.id, points: points)); + var execution = await executeApiFunction(action, "POST", path, body: route.toMap(user.id, points.map((e) => e.id))); throwIfExecutionBodyIsEmpty(execution, action); - return instanceFromExecution(execution, (m) => RouteMapConverter.fromMap(m, points: points), action); + return instanceFromExecution(execution, (m) => RouteMapConverter.fromMap(m, points), action); } } diff --git a/car_app/lib/service/chached_route.dart b/car_app/lib/service/chached_route.dart index 44cea9c..0fbc025 100644 --- a/car_app/lib/service/chached_route.dart +++ b/car_app/lib/service/chached_route.dart @@ -1,38 +1,37 @@ import 'package:car_app/exception/key_not_found.dart'; import 'package:car_app/exception/server.dart'; -import 'package:car_app/model/route.dart'; -import 'package:car_app/service/interface/key_value_storage.dart'; +import 'package:car_app/model/interface/point.dart'; +import 'package:car_app/model/interface/route.dart'; import 'package:car_app/service/interface/route.dart'; +import 'package:car_app/service/storage/model_storage/route_storage.dart'; -class CachedRouteService implements RouteService { - final RouteService routeService; - final KeyValueStorageService<String, List<Route>> storageService; - final String storageKey = "routes"; +class CachedRouteService<T extends Route<T1>, T1 extends Point> implements RouteService<T, T1> { + final RouteService<T, T1> routeService; + final RouteStorageService<T, T1> storageService; CachedRouteService({required this.routeService, required this.storageService}); @override - Future<Route> add(Route route) async { + Future<T> add(T route) async { var isSynchronized = await _synchronize(); if(isSynchronized) { try { return routeService.add(route); } on ServerException catch (_) { - await _addRouteToStorage(route); + await storageService.add(route); return route; } } - await _addRouteToStorage(route); + await storageService.add(route); return route; } Future<bool> _synchronize() async { - List<Route> unsuccessfulRoutes = []; + List<T> unsuccessfulRoutes = []; try { - var routes = await storageService.get(storageKey); - for (var route in routes) { + await for (var route in storageService.getAll()) { try { await routeService.add(route); return true; @@ -43,17 +42,10 @@ class CachedRouteService implements RouteService { } on KeyNotFoundException catch (_) {} // No routes stored yet - storageService.add(storageKey, unsuccessfulRoutes); - return false; - } - - Future<void> _addRouteToStorage(Route route) async { - List<Route> routes = []; - try { - routes = await storageService.get(storageKey); - } on KeyNotFoundException catch (_) {} // Ignore exception, as no routes are stored yet + for (var route in unsuccessfulRoutes) { + await storageService.add(route); + } - routes.add(route); - return storageService.add(storageKey, routes); + return false; } } diff --git a/car_app/lib/service/location.dart b/car_app/lib/service/geolocator_location.dart similarity index 82% rename from car_app/lib/service/location.dart rename to car_app/lib/service/geolocator_location.dart index ef4af0e..f00a37a 100644 --- a/car_app/lib/service/location.dart +++ b/car_app/lib/service/geolocator_location.dart @@ -3,9 +3,11 @@ import 'dart:ui'; import 'dart:io' show Platform; import 'package:car_app/converter/location_converters.dart/point.dart'; -import 'package:car_app/converter/position_point_converter.dart'; +import 'package:car_app/converter/location_converters.dart/position.dart'; import 'package:car_app/exception/permission.dart'; -import 'package:car_app/model/point.dart'; +import 'package:car_app/model/interface/point.dart'; +import 'package:car_app/model/location/point.dart'; +import 'package:car_app/service/interface/location.dart'; import 'package:flutter_background_service/flutter_background_service.dart'; import 'package:flutter_background_service_android/flutter_background_service_android.dart'; import 'package:geolocator/geolocator.dart'; @@ -13,24 +15,21 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:car_app/converter/converter.dart'; -enum Mode { - foreground, - background, -} - -class LocationService { - static final LocationService _locationService = LocationService._privateConstructor(); +class GeolocatorLocationService<T extends Point> implements LocationService<T> { final FlutterBackgroundService _backgroundService = FlutterBackgroundService(); - final StreamController<Point> _locationStream = StreamController.broadcast(); + final StreamController<T> _locationStream = StreamController.broadcast(); + final Converter<LocationPoint, T> locationPointToPointConverter; late StreamSubscription _backgroundServiceSubscription; - factory LocationService() => _locationService; - - Stream<Point> get locationUpdate => _locationStream.stream; + @override + Stream<T> get locationUpdate => _locationStream.stream; - LocationService._privateConstructor(); + GeolocatorLocationService({ + required this.locationPointToPointConverter, + }); + @override Future<void> start() async { var permissionStatus = await Permission.locationWhenInUse.request(); if (permissionStatus != PermissionStatus.granted) { @@ -46,15 +45,15 @@ class LocationService { ); _backgroundServiceSubscription = _backgroundService.on('update').listen((event) { if (event != null && event.containsKey('point')) { - var point = PointMapConverter.fromMap(event['point']); + var position = PointMapConverter.fromMap(event['point']); + var point = locationPointToPointConverter.convert(position); _locationStream.add(point); } }); _backgroundService.startService(); } - static void _onServiceStart(ServiceInstance service) async { - final Converter<Position, Point> converter = PositionToPointConverter(); + static void _onServiceStart<T extends Point>(ServiceInstance service) async { final StreamController<Position> positionUpdate = StreamController<Position>.broadcast(); late Stream<Position> positionStream; final List<Position> backgroundStore = []; @@ -62,9 +61,8 @@ class LocationService { StreamSubscription<Position>? backgroundSubscription; void sendUpdate(Position position) { - var point = converter.convert(position); - service.invoke('update',{ - 'point': point.toMap(), + service.invoke('update', { + 'point': position.toLocationPoint().toMap() }); } @@ -134,11 +132,13 @@ class LocationService { }); } + @override Future<void> stop() async { _backgroundService.invoke('stop'); _backgroundServiceSubscription.cancel(); } + @override void setMode(Mode mode) { if (mode == Mode.foreground) { _backgroundService.invoke('setAsForeground'); diff --git a/car_app/lib/service/interface/key_value_storage.dart b/car_app/lib/service/interface/key_value_storage.dart index 53655c0..be17d94 100644 --- a/car_app/lib/service/interface/key_value_storage.dart +++ b/car_app/lib/service/interface/key_value_storage.dart @@ -2,4 +2,5 @@ abstract interface class KeyValueStorageService<T, T1> { Future<void> add(T key, T1 value); Future<T1> remove(T key); Future<T1> get(T key); + Stream<MapEntry<T, T1>> getAll(); } diff --git a/car_app/lib/service/interface/location.dart b/car_app/lib/service/interface/location.dart new file mode 100644 index 0000000..61ca611 --- /dev/null +++ b/car_app/lib/service/interface/location.dart @@ -0,0 +1,13 @@ +import 'package:car_app/model/interface/point.dart'; + +enum Mode { + foreground, + background, +} + +abstract interface class LocationService<T extends Point> { + Stream<T> get locationUpdate; + Future<void> start(); + Future<void> stop(); + void setMode(Mode mode); +} diff --git a/car_app/lib/service/interface/point.dart b/car_app/lib/service/interface/point.dart new file mode 100644 index 0000000..4631786 --- /dev/null +++ b/car_app/lib/service/interface/point.dart @@ -0,0 +1,5 @@ +import 'package:car_app/model/interface/point.dart'; + +abstract interface class PointService<T extends Point> { + Future<T> add(T point); +} diff --git a/car_app/lib/service/interface/route.dart b/car_app/lib/service/interface/route.dart index 29cf487..e3965c9 100644 --- a/car_app/lib/service/interface/route.dart +++ b/car_app/lib/service/interface/route.dart @@ -1,6 +1,7 @@ -import 'package:car_app/model/route.dart'; +import 'package:car_app/model/interface/point.dart'; +import 'package:car_app/model/interface/route.dart'; -abstract interface class RouteService { - Future<Route> add(Route route); +abstract interface class RouteService<T extends Route<T1>, T1 extends Point> { + Future<T> add(T route); // Future<List<RouteInformation>> getAll(); } diff --git a/car_app/lib/service/storage/hive_storage.dart b/car_app/lib/service/storage/hive_storage.dart new file mode 100644 index 0000000..7b555a4 --- /dev/null +++ b/car_app/lib/service/storage/hive_storage.dart @@ -0,0 +1,68 @@ +import 'package:car_app/exception/key_not_found.dart'; +import 'package:car_app/service/interface/key_value_storage.dart'; +import 'package:hive/hive.dart'; + +class HiveStorageService implements KeyValueStorageService<String, Map<String, dynamic>> { + final String dbPath; + final String dbName; + final String boxName; + final List<int> encryptionKey; + + HiveStorageService({ + required this.dbName, + required this.dbPath, + required this.boxName, + required this.encryptionKey, + }); + + @override + Future<void> add(String key, Map<String, dynamic> value) async { + var box = await getBox(); + box.put(key, value); + } + + @override + Future<Map<String, dynamic>> get(String key) async { + var box = await getBox(); + var value = await box.get(key); + + if(value == null) { + throw KeyNotFoundException(key); + } + return castHiveResult(value); + } + + @override + Future<Map<String, dynamic>> remove(String key) async { + var box = await getBox(); + var value = await get(key); + + await box.delete(key); + return value; + } + + Future<CollectionBox<Map<dynamic, dynamic>>> getBox() async { + var collection = await BoxCollection.open( + dbName, + {boxName}, + path: dbPath, + key: HiveAesCipher(encryptionKey) + ); + + return await collection.openBox<Map<dynamic, dynamic>>(boxName); + } + + Map<String, dynamic> castHiveResult(Map<dynamic, dynamic> map) { + return Map<String, dynamic>.from(map); + } + + @override + Stream<MapEntry<String, Map<String, dynamic>>> getAll() async* { + var box = await getBox(); + var keys = await box.getAllKeys(); + + for(var key in keys) { + yield MapEntry(key, await get(key)); + } + } +} diff --git a/car_app/lib/service/storage/model_storage/appwrite/point_storage.dart b/car_app/lib/service/storage/model_storage/appwrite/point_storage.dart new file mode 100644 index 0000000..ae5328e --- /dev/null +++ b/car_app/lib/service/storage/model_storage/appwrite/point_storage.dart @@ -0,0 +1,17 @@ +import 'package:car_app/converter/storage_converters/appwrite/point.dart'; +import 'package:car_app/model/appwrite/point.dart'; +import 'package:car_app/service/storage/model_storage/point_storage.dart'; + +class AppwritePointStorageService extends PointStorageService<AppwritePoint> { + AppwritePointStorageService({required super.storageService}); + + @override + AppwritePoint pointFromMap(Map<String, dynamic> map) { + return PointMapConverter.fromMap(map); + } + + @override + Map<String, dynamic> pointToMap(AppwritePoint model) { + return model.toMap(); + } +} diff --git a/car_app/lib/service/storage/model_storage/appwrite/route_storage.dart b/car_app/lib/service/storage/model_storage/appwrite/route_storage.dart new file mode 100644 index 0000000..bc173f7 --- /dev/null +++ b/car_app/lib/service/storage/model_storage/appwrite/route_storage.dart @@ -0,0 +1,21 @@ +import 'package:car_app/converter/storage_converters/appwrite/route.dart'; +import 'package:car_app/model/appwrite/route.dart'; +import 'package:car_app/model/interface/point.dart'; +import 'package:car_app/service/storage/model_storage/route_storage.dart'; + +class AppwriteRouteStorageService<T extends Point> extends RouteStorageService<AppwriteRoute<T>, T> { + @override + final String pointIdsFieldName = RouteMapConverter.pointsFieldName; + + AppwriteRouteStorageService({required super.storageService, required super.pointStorageService}); + + @override + AppwriteRoute<T> routeFromMapWithPoints(Map<String, dynamic> map, Iterable<T> points) { + return RouteMapConverter.fromMap<T>(map, points); + } + + @override + Map<String, dynamic> routeToMapWithPointIds(AppwriteRoute<T> route, Iterable<String> pointIds) { + return route.toMap(pointIds); + } +} diff --git a/car_app/lib/service/storage/model_storage/model_storage.dart b/car_app/lib/service/storage/model_storage/model_storage.dart new file mode 100644 index 0000000..ea47e4a --- /dev/null +++ b/car_app/lib/service/storage/model_storage/model_storage.dart @@ -0,0 +1,20 @@ +import 'dart:async'; + +import 'package:car_app/service/interface/key_value_storage.dart'; +import 'package:uuid/uuid.dart'; + +abstract class ModelStorageService<T> { + final KeyValueStorageService<String, Map<String, dynamic>> storageService; + final uuid = const Uuid(); + + ModelStorageService({required this.storageService}); + + Future<String> add(T model); + Future<T> get(String key); + Future<T> remove(String key); + Stream<T> getAll(); + + String generateId() { + return uuid.v4(); + } +} diff --git a/car_app/lib/service/storage/model_storage/point_storage.dart b/car_app/lib/service/storage/model_storage/point_storage.dart new file mode 100644 index 0000000..b24d8a7 --- /dev/null +++ b/car_app/lib/service/storage/model_storage/point_storage.dart @@ -0,0 +1,34 @@ +import 'package:car_app/model/interface/point.dart'; +import 'package:car_app/service/storage/model_storage/model_storage.dart'; + +abstract class PointStorageService<T extends Point> extends ModelStorageService<T> { + PointStorageService({required super.storageService}); + + Map<String, dynamic> pointToMap(T model); + T pointFromMap(Map<String, dynamic> map); + + @override + Future<String> add(T model) async { + var id = generateId(); + await storageService.add(id, pointToMap(model)); + + return id; + } + + @override + Future<T> get(String key) async { + Map<String, dynamic> map = await storageService.get(key); + return pointFromMap(map); + } + + @override + Future<T> remove(String key) async { + var map = await storageService.remove(key); + return pointFromMap(map); + } + + @override + Stream<T> getAll() { + return storageService.getAll().map((e) => pointFromMap(e.value)); + } +} diff --git a/car_app/lib/service/storage/model_storage/route_storage.dart b/car_app/lib/service/storage/model_storage/route_storage.dart new file mode 100644 index 0000000..e4efa5e --- /dev/null +++ b/car_app/lib/service/storage/model_storage/route_storage.dart @@ -0,0 +1,65 @@ +import 'package:car_app/model/interface/point.dart'; +import 'package:car_app/model/interface/route.dart'; +import 'package:car_app/service/storage/model_storage/model_storage.dart'; +import 'package:car_app/service/storage/model_storage/point_storage.dart'; + +abstract class RouteStorageService<T extends Route<T1>, T1 extends Point> extends ModelStorageService<T> { + final PointStorageService<T1> pointStorageService; + + String get pointIdsFieldName; + + RouteStorageService({required super.storageService, required this.pointStorageService}); + + T routeFromMapWithPoints(Map<String, dynamic> map, Iterable<T1> points); + Map<String, dynamic> routeToMapWithPointIds(T route, Iterable<String> pointIds); + + @override + Future<String> add(T model) async { + var pointIds = <String>[]; + for (var point in model.points) { + pointIds.add(await pointStorageService.add(point)); + } + + var id = generateId(); + await storageService.add(id, routeToMapWithPointIds(model, pointIds)); + return id; + } + + @override + Future<T> get(String key) async { + return routeFromMap(await storageService.get(key)); + } + + @override + Stream<T> getAll() { + return storageService.getAll().asyncMap((e) async => await routeFromMap(e.value)); + } + + @override + Future<T> remove(String key) async { + var map = await storageService.get(key); + var item = await routeFromMap(map); + var pointIds = map[pointIdsFieldName] as List<String>; + + var futures = <Future<T1>>[]; + for (var pointId in pointIds) { + futures.add(pointStorageService.remove(pointId)); + } + + await Future.wait(futures); + await storageService.remove(key); + + return item; + } + + Future<T> routeFromMap(Map<String, dynamic> map) async { + var pointIds = map[pointIdsFieldName] as List<String>; + + var points = <T1>[]; + for (var pointId in pointIds) { + points.add(await pointStorageService.get(pointId)); + } + + return routeFromMapWithPoints(map, points); + } +} diff --git a/car_app/lib/service/storage/route_storage.dart b/car_app/lib/service/storage/route_storage.dart deleted file mode 100644 index 19eb7c4..0000000 --- a/car_app/lib/service/storage/route_storage.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'dart:convert'; - -import 'package:car_app/converter/storage_converters/route.dart'; -import 'package:car_app/model/route.dart'; -import 'package:car_app/service/interface/key_value_storage.dart'; - -class RouteStorageService implements KeyValueStorageService<String, List<Route>> { - final KeyValueStorageService<String, String> storageService; - - RouteStorageService({required this.storageService}); - - @override - Future<void> add(String key, List<Route> value) { - var json = jsonEncode(value.map((e) => e.toMap()).toList()); - return storageService.add(key, json); - } - - @override - Future<List<Route>> get(String key) async { - var json = await storageService.get(key); - return _getRoutesFromJson(json); - } - - @override - Future<List<Route>> remove(String key) async { - var json = await storageService.remove(key); - return _getRoutesFromJson(json); - } - - List<Route> _getRoutesFromJson(String json) { - var routeMaps = List<Map<String, dynamic>>.from(jsonDecode(json)); - return routeMaps.map((e) => RouteMapConverter.fromMap(e)).toList(); - } -} diff --git a/car_app/lib/service/storage/secure_storage.dart b/car_app/lib/service/storage/secure_storage.dart index 4b2e0e1..3c93e42 100644 --- a/car_app/lib/service/storage/secure_storage.dart +++ b/car_app/lib/service/storage/secure_storage.dart @@ -26,4 +26,12 @@ class SecureStorageService implements KeyValueStorageService<String, String> { await _storage.delete(key: key); return value; } + + @override + Stream<MapEntry<String, String>> getAll() async* { + var map = await _storage.readAll(); + for (var entry in map.entries) { + yield MapEntry(entry.key, entry.value); + } + } } diff --git a/car_app/lib/view/tracking.dart b/car_app/lib/view/tracking.dart index d71a1d5..b2bd7df 100644 --- a/car_app/lib/view/tracking.dart +++ b/car_app/lib/view/tracking.dart @@ -1,6 +1,6 @@ import 'package:car_app/converter/point_latlng_converter.dart'; import 'package:car_app/exception/server.dart'; -import 'package:car_app/model/point.dart'; +import 'package:car_app/model/interface/point.dart'; import 'package:car_app/view/defaults.dart'; import 'package:car_app/view/routes_titles.dart'; import 'package:car_app/viewmodel/tracking.dart'; diff --git a/car_app/lib/viewmodel/tracking.dart b/car_app/lib/viewmodel/tracking.dart index b6067dc..fffa7eb 100644 --- a/car_app/lib/viewmodel/tracking.dart +++ b/car_app/lib/viewmodel/tracking.dart @@ -1,13 +1,14 @@ import 'dart:async'; import 'package:car_app/exception/permission.dart'; -import 'package:car_app/model/point.dart'; -import 'package:car_app/model/route.dart'; +import 'package:car_app/factory/interface/route.dart'; +import 'package:car_app/model/interface/point.dart'; +import 'package:car_app/model/interface/route.dart'; import 'package:car_app/service/interface/authentication.dart'; import 'package:car_app/service/interface/car.dart'; +import 'package:car_app/service/interface/location.dart'; import 'package:car_app/service/interface/logging.dart'; import 'package:car_app/service/interface/route.dart'; -import 'package:car_app/service/location.dart'; import 'package:flutter/foundation.dart'; enum TrackingState { @@ -34,13 +35,14 @@ extension TrackingButtonText on TrackingState { } } -class TrackingViewModel extends ChangeNotifier { +class TrackingViewModel<T extends Route<T1>, T1 extends Point> extends ChangeNotifier { final AuthenticationService authenticationService; - final RouteService routeService; + final RouteService<T, T1> routeService; final CarService carService; - final LocationService _locationService = LocationService(); + final LocationService<T1> locationService; final LoggingService loggingService; - final List<Point> _points = []; + final RouteFactory<T, T1> routeFactory; + final List<T1> _points = []; late StreamSubscription <Point> _locationSubscription; late DateTime _trackingStartTime; @@ -50,14 +52,21 @@ class TrackingViewModel extends ChangeNotifier { TrackingState _trackingState = TrackingState.stopped; String get trackingButtonText => _trackingState.text; - TrackingViewModel({required this.authenticationService, required this.routeService, required this.carService, required this.loggingService}); + TrackingViewModel({ + required this.authenticationService, + required this.locationService, + required this.routeService, + required this.carService, + required this.loggingService, + required this.routeFactory, + }); void locationServiceToForeground() { - _locationService.setMode(Mode.foreground); + locationService.setMode(Mode.foreground); } void locationServiceToBackground() { - _locationService.setMode(Mode.background); + locationService.setMode(Mode.background); } void setCarId(String carId) { @@ -93,12 +102,12 @@ class TrackingViewModel extends ChangeNotifier { _setTrackingState(TrackingState.starting); - _locationSubscription = _locationService.locationUpdate.listen((p) { + _locationSubscription = locationService.locationUpdate.listen((p) { onUpdate(p); _points.add(p); }); - _locationService.start().then((_) { + locationService.start().then((_) { _setTrackingState(TrackingState.started); }) .catchError(test: (e) => e is PermissionException, (e, s) { @@ -111,9 +120,10 @@ class TrackingViewModel extends ChangeNotifier { void _stopTracking() { _setTrackingState(TrackingState.stopping); _locationSubscription.cancel(); - _locationService.stop().then((_) async { + locationService.stop().then((_) async { _setTrackingState(TrackingState.stopped); - await routeService.add(Route(points: _points, carId: _carId, startTime: _trackingStartTime, endTime: DateTime.now())); + var route = routeFactory.createInstance(_carId, _points, _trackingStartTime, DateTime.now()); + await routeService.add(route); }); } diff --git a/car_app/pubspec.lock b/car_app/pubspec.lock index c55cdd4..c5ce58e 100644 --- a/car_app/pubspec.lock +++ b/car_app/pubspec.lock @@ -384,6 +384,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" html: dependency: transitive description: @@ -553,7 +561,7 @@ packages: source: hosted version: "2.0.1" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" @@ -886,13 +894,13 @@ packages: source: hosted version: "3.1.1" uuid: - dependency: transitive + dependency: "direct main" description: name: uuid - sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" url: "https://pub.dev" source: hosted - version: "4.3.3" + version: "4.4.0" vector_math: dependency: transitive description: diff --git a/car_app/pubspec.yaml b/car_app/pubspec.yaml index ac4f2f2..ed42bcb 100644 --- a/car_app/pubspec.yaml +++ b/car_app/pubspec.yaml @@ -45,6 +45,9 @@ dependencies: path_provider: ^2.1.2 table_calendar: ^3.1.1 intl: ^0.19.0 + hive: ^2.2.3 + uuid: ^4.4.0 + path: ^1.9.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. -- GitLab