From 808c544bce87c438631f29d92202bc1948a14a85 Mon Sep 17 00:00:00 2001 From: Christian Fiebrig Date: Tue, 21 Jan 2025 14:43:36 +0100 Subject: [PATCH] apply flyer to use cache --- lib/app/services/converters.dart | 4 + .../converters/campaign_action_parsing.dart | 14 +++ .../flyer_create_model_parsing.dart | 22 +++++ .../flyer_update_model_parsing.dart | 20 ++++ lib/app/services/converters/poi_parsing.dart | 1 + .../gruene_api_campaigns_service.dart | 44 +-------- .../services/gruene_api_flyer_service.dart | 43 +++++++++ .../services/gruene_api_poster_service.dart | 7 ++ lib/app/widgets/app_bar.dart | 2 +- .../helper/campaign_action_cache.dart | 91 +++++++++++++++---- .../helper/campaign_action_cache_timer.dart | 22 +++++ .../campaigns/helper/file_cache_manager.dart | 12 +-- .../models/flyer/flyer_create_model.dart | 10 ++ .../models/flyer/flyer_detail_model.dart | 35 +++++++ .../models/flyer/flyer_update_model.dart | 22 ++++- .../campaigns/screens/doors_screen.dart | 17 +--- .../campaigns/screens/flyer_edit.dart | 2 + .../campaigns/screens/flyer_screen.dart | 40 ++++---- .../campaigns/screens/map_consumer.dart | 14 ++- .../campaigns/screens/posters_screen.dart | 16 +--- lib/main.dart | 10 ++ 21 files changed, 324 insertions(+), 124 deletions(-) create mode 100644 lib/app/services/converters/flyer_create_model_parsing.dart create mode 100644 lib/app/services/converters/flyer_update_model_parsing.dart create mode 100644 lib/app/services/gruene_api_flyer_service.dart create mode 100644 lib/features/campaigns/helper/campaign_action_cache_timer.dart diff --git a/lib/app/services/converters.dart b/lib/app/services/converters.dart index c137a578..8af847f7 100644 --- a/lib/app/services/converters.dart +++ b/lib/app/services/converters.dart @@ -6,7 +6,9 @@ import 'package:gruene_app/features/campaigns/helper/campaign_action.dart'; import 'package:gruene_app/features/campaigns/models/doors/door_create_model.dart'; import 'package:gruene_app/features/campaigns/models/doors/door_detail_model.dart'; import 'package:gruene_app/features/campaigns/models/doors/door_update_model.dart'; +import 'package:gruene_app/features/campaigns/models/flyer/flyer_create_model.dart'; import 'package:gruene_app/features/campaigns/models/flyer/flyer_detail_model.dart'; +import 'package:gruene_app/features/campaigns/models/flyer/flyer_update_model.dart'; import 'package:gruene_app/features/campaigns/models/map_layer_model.dart'; import 'package:gruene_app/features/campaigns/models/marker_item_model.dart'; import 'package:gruene_app/features/campaigns/models/posters/poster_create_model.dart'; @@ -40,3 +42,5 @@ part 'converters/map_string_dynamic_converter.dart'; part 'converters/string_extension.dart'; part 'converters/door_update_model_parsing.dart'; part 'converters/door_create_model_parsing.dart'; +part 'converters/flyer_create_model_parsing.dart'; +part 'converters/flyer_update_model_parsing.dart'; diff --git a/lib/app/services/converters/campaign_action_parsing.dart b/lib/app/services/converters/campaign_action_parsing.dart index 727f6e20..ba11c3b0 100644 --- a/lib/app/services/converters/campaign_action_parsing.dart +++ b/lib/app/services/converters/campaign_action_parsing.dart @@ -28,6 +28,20 @@ extension CampaignActionParsing on CampaignAction { return model; } + FlyerCreateModel getAsFlyerCreate() { + var data = jsonDecode(serialized!) as Map; + var model = FlyerCreateModel.fromJson(data.convertLatLongField()); + + return model; + } + + FlyerUpdateModel getAsFlyerUpdate() { + var data = jsonDecode(serialized!) as Map; + var model = FlyerUpdateModel.fromJson(data.updateIdField(poiId!).convertLatLongField()); + + return model; + } + PosterListItemModel getPosterUpdateAsPosterListItem(DateTime originalCreatedAt) { var updateModel = getAsPosterUpdate().transformToPosterDetailModel(); return PosterListItemModel( diff --git a/lib/app/services/converters/flyer_create_model_parsing.dart b/lib/app/services/converters/flyer_create_model_parsing.dart new file mode 100644 index 00000000..cf3a940f --- /dev/null +++ b/lib/app/services/converters/flyer_create_model_parsing.dart @@ -0,0 +1,22 @@ +part of '../converters.dart'; + +extension FlyerCreateModelParsing on FlyerCreateModel { + FlyerDetailModel transformToFlyerDetailModel(String temporaryId) { + return FlyerDetailModel( + id: temporaryId, + address: address, + flyerCount: flyerCount, + location: location, + createdAt: '${DateTime.now().getAsLocalDateTimeString()}*', // should mark this as preliminary + isCached: true, + ); + } + + MarkerItemModel transformToVirtualMarkerItem(int temporaryId) { + return MarkerItemModel.virtual( + id: temporaryId, + status: PoiServiceType.flyer.name, + location: location, + ); + } +} diff --git a/lib/app/services/converters/flyer_update_model_parsing.dart b/lib/app/services/converters/flyer_update_model_parsing.dart new file mode 100644 index 00000000..bc168368 --- /dev/null +++ b/lib/app/services/converters/flyer_update_model_parsing.dart @@ -0,0 +1,20 @@ +part of '../converters.dart'; + +extension FlyerUpdateModelParsing on FlyerUpdateModel { + FlyerDetailModel transformToFlyerDetailModel() { + var newFlyerDetail = oldFlyerDetail.copyWith( + address: address, + flyerCount: flyerCount, + isCached: true, + ); + return newFlyerDetail; + } + + MarkerItemModel transformToVirtualMarkerItem() { + return MarkerItemModel.virtual( + id: int.parse(id), + status: PoiServiceType.flyer.name, + location: location, + ); + } +} diff --git a/lib/app/services/converters/poi_parsing.dart b/lib/app/services/converters/poi_parsing.dart index 6f8aafdc..74dc342d 100644 --- a/lib/app/services/converters/poi_parsing.dart +++ b/lib/app/services/converters/poi_parsing.dart @@ -54,6 +54,7 @@ extension PoiParsing on Poi { address: poi.address.transformToAddressModel(), flyerCount: poi.flyerSpot!.flyerCount.toInt(), createdAt: poi.createdAt.getAsLocalDateTimeString(), + location: coords.transformToLatLng(), ); } diff --git a/lib/app/services/gruene_api_campaigns_service.dart b/lib/app/services/gruene_api_campaigns_service.dart index 780227d9..3d00a383 100644 --- a/lib/app/services/gruene_api_campaigns_service.dart +++ b/lib/app/services/gruene_api_campaigns_service.dart @@ -4,16 +4,12 @@ import 'package:flutter/foundation.dart'; import 'package:get_it/get_it.dart'; import 'package:gruene_app/app/services/converters.dart'; import 'package:gruene_app/app/services/enums.dart'; -import 'package:gruene_app/features/campaigns/models/flyer/flyer_create_model.dart'; -import 'package:gruene_app/features/campaigns/models/flyer/flyer_detail_model.dart'; -import 'package:gruene_app/features/campaigns/models/flyer/flyer_update_model.dart'; import 'package:gruene_app/features/campaigns/models/map_layer_model.dart'; import 'package:gruene_app/features/campaigns/models/marker_item_model.dart'; -import 'package:gruene_app/features/campaigns/models/posters/poster_list_item_model.dart'; import 'package:gruene_app/swagger_generated_code/gruene_api.swagger.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; -class GrueneApiCampaignsService { +abstract class GrueneApiCampaignsService { late GrueneApi grueneApi; final PoiServiceType poiType; @@ -42,25 +38,6 @@ class GrueneApiCampaignsService { return getPoisResult.body!.data.map((layerItem) => layerItem.transformToMapLayer()).toList(); } - Future createNewFlyer(FlyerCreateModel newFlyer) async { - final requestParam = CreatePoi( - coords: newFlyer.location.transformToGeoJsonCoords(), - type: poiType.transformToApiCreateType(), - address: newFlyer.address.transformToPoiAddress(), - flyerSpot: PoiFlyerSpot( - flyerCount: newFlyer.flyerCount.toDouble(), - ), - ); - // saving POI - final newPoiResponse = await grueneApi.v1CampaignsPoisPost(body: requestParam); - - return newPoiResponse.body!.transformToMarkerItem(); - } - - Future getPoiAsFlyerDetail(String poiId) { - return getPoi(poiId, (p) => p.transformPoiToFlyerDetail()); - } - Future getPoi(String poiId, T Function(Poi) transform) async { final poiResponse = await grueneApi.v1CampaignsPoisPoiIdGet(poiId: poiId); return transform(poiResponse.body!); @@ -70,23 +47,4 @@ class GrueneApiCampaignsService { // ignore: unused_local_variable final deletePoiResponse = await grueneApi.v1CampaignsPoisPoiIdDelete(poiId: poiId); } - - Future updateFlyer(FlyerUpdateModel flyerUpdate) async { - var dtoUpdate = UpdatePoi( - address: flyerUpdate.address.transformToPoiAddress(), - flyerSpot: PoiFlyerSpot( - flyerCount: flyerUpdate.flyerCount.toDouble(), - ), - ); - var updatePoiResponse = await grueneApi.v1CampaignsPoisPoiIdPut(poiId: flyerUpdate.id, body: dtoUpdate); - - return updatePoiResponse.body!.transformToMarkerItem(); - } - - Future> getMyPosters() async { - final getPoisType = poiType.transformToApiSelfGetType(); - - final getPoisResult = await grueneApi.v1CampaignsPoisSelfGet(type: getPoisType); - return getPoisResult.body!.data.map((p) => p.transformToPosterListItem()).toList(); - } } diff --git a/lib/app/services/gruene_api_flyer_service.dart b/lib/app/services/gruene_api_flyer_service.dart new file mode 100644 index 00000000..755490d1 --- /dev/null +++ b/lib/app/services/gruene_api_flyer_service.dart @@ -0,0 +1,43 @@ +import 'package:gruene_app/app/services/converters.dart'; +import 'package:gruene_app/app/services/enums.dart'; +import 'package:gruene_app/app/services/gruene_api_campaigns_service.dart'; +import 'package:gruene_app/features/campaigns/models/flyer/flyer_create_model.dart'; +import 'package:gruene_app/features/campaigns/models/flyer/flyer_detail_model.dart'; +import 'package:gruene_app/features/campaigns/models/flyer/flyer_update_model.dart'; +import 'package:gruene_app/features/campaigns/models/marker_item_model.dart'; +import 'package:gruene_app/swagger_generated_code/gruene_api.swagger.dart'; + +class GrueneApiFlyerService extends GrueneApiCampaignsService { + GrueneApiFlyerService() : super(poiType: PoiServiceType.flyer); + + Future createNewFlyer(FlyerCreateModel newFlyer) async { + final requestParam = CreatePoi( + coords: newFlyer.location.transformToGeoJsonCoords(), + type: poiType.transformToApiCreateType(), + address: newFlyer.address.transformToPoiAddress(), + flyerSpot: PoiFlyerSpot( + flyerCount: newFlyer.flyerCount.toDouble(), + ), + ); + // saving POI + final newPoiResponse = await grueneApi.v1CampaignsPoisPost(body: requestParam); + + return newPoiResponse.body!.transformToMarkerItem(); + } + + Future getPoiAsFlyerDetail(String poiId) { + return getPoi(poiId, (p) => p.transformPoiToFlyerDetail()); + } + + Future updateFlyer(FlyerUpdateModel flyerUpdate) async { + var dtoUpdate = UpdatePoi( + address: flyerUpdate.address.transformToPoiAddress(), + flyerSpot: PoiFlyerSpot( + flyerCount: flyerUpdate.flyerCount.toDouble(), + ), + ); + var updatePoiResponse = await grueneApi.v1CampaignsPoisPoiIdPut(poiId: flyerUpdate.id, body: dtoUpdate); + + return updatePoiResponse.body!.transformToMarkerItem(); + } +} diff --git a/lib/app/services/gruene_api_poster_service.dart b/lib/app/services/gruene_api_poster_service.dart index ce5a9129..88b6ddb5 100644 --- a/lib/app/services/gruene_api_poster_service.dart +++ b/lib/app/services/gruene_api_poster_service.dart @@ -86,4 +86,11 @@ class GrueneApiPosterService extends GrueneApiCampaignsService { Future getPoiAsPosterListItem(String poiId) { return getPoi(poiId, (p) => p.transformToPosterListItem()); } + + Future> getMyPosters() async { + final getPoisType = poiType.transformToApiSelfGetType(); + + final getPoisResult = await grueneApi.v1CampaignsPoisSelfGet(type: getPoisType); + return getPoisResult.body!.data.map((p) => p.transformToPosterListItem()).toList(); + } } diff --git a/lib/app/widgets/app_bar.dart b/lib/app/widgets/app_bar.dart index fdbd6b87..6325ae01 100644 --- a/lib/app/widgets/app_bar.dart +++ b/lib/app/widgets/app_bar.dart @@ -93,6 +93,6 @@ class _RefreshButtonState extends State { } void _flushCachedData() { - campaignActionCache.flushCachedItems(); + campaignActionCache.flushCache(); } } diff --git a/lib/features/campaigns/helper/campaign_action_cache.dart b/lib/features/campaigns/helper/campaign_action_cache.dart index 56a61cd8..5eef91dc 100644 --- a/lib/features/campaigns/helper/campaign_action_cache.dart +++ b/lib/features/campaigns/helper/campaign_action_cache.dart @@ -6,12 +6,16 @@ import 'package:gruene_app/app/services/campaign_action_database.dart'; import 'package:gruene_app/app/services/converters.dart'; import 'package:gruene_app/app/services/enums.dart'; import 'package:gruene_app/app/services/gruene_api_door_service.dart'; +import 'package:gruene_app/app/services/gruene_api_flyer_service.dart'; import 'package:gruene_app/app/services/gruene_api_poster_service.dart'; import 'package:gruene_app/features/campaigns/helper/campaign_action.dart'; import 'package:gruene_app/features/campaigns/helper/media_helper.dart'; import 'package:gruene_app/features/campaigns/models/doors/door_create_model.dart'; import 'package:gruene_app/features/campaigns/models/doors/door_detail_model.dart'; import 'package:gruene_app/features/campaigns/models/doors/door_update_model.dart'; +import 'package:gruene_app/features/campaigns/models/flyer/flyer_create_model.dart'; +import 'package:gruene_app/features/campaigns/models/flyer/flyer_detail_model.dart'; +import 'package:gruene_app/features/campaigns/models/flyer/flyer_update_model.dart'; import 'package:gruene_app/features/campaigns/models/marker_item_model.dart'; import 'package:gruene_app/features/campaigns/models/posters/poster_create_model.dart'; import 'package:gruene_app/features/campaigns/models/posters/poster_detail_model.dart'; @@ -22,6 +26,7 @@ import 'package:maplibre_gl/maplibre_gl.dart'; class CampaignActionCache extends ChangeNotifier { static CampaignActionCache? _instance; + static bool _isflushing = false; var campaignActionDatabase = CampaignActionDatabase.instance; MapControllerSimplified? _currentMapController; @@ -47,7 +52,7 @@ class CampaignActionCache extends ChangeNotifier { return campaignActionDatabase.getCount(); } - Future addCreateAction(PoiServiceType poiType, dynamic poiCreate) async { + Future storeNewPoi(PoiServiceType poiType, dynamic poiCreate) async { switch (poiType) { case PoiServiceType.poster: return await _addCreateAction( @@ -64,7 +69,12 @@ class CampaignActionCache extends ChangeNotifier { getMarker: (poi, tempId) => poi.transformToVirtualMarkerItem(tempId), ); case PoiServiceType.flyer: - throw UnimplementedError(); + return await _addCreateAction( + poiType: poiType, + poi: poiCreate as FlyerCreateModel, + getJson: (poi) => poi.toJson(), + getMarker: (poi, tempId) => poi.transformToVirtualMarkerItem(tempId), + ); } } @@ -82,7 +92,7 @@ class CampaignActionCache extends ChangeNotifier { return getMarker(poi, action.poiTempId); } - Future addDeleteAction(PoiServiceType poiType, String poiId) async { + Future deletePoi(PoiServiceType poiType, String poiId) async { final action = CampaignAction( poiId: int.parse(poiId), actionType: poiType.getCacheDeleteAction(), @@ -102,7 +112,7 @@ class CampaignActionCache extends ChangeNotifier { return _getDeleteMarkerModel(poiType, action.poiId!); } - Future addUpdateAction(PoiServiceType poiType, dynamic poi) async { + Future updatePoi(PoiServiceType poiType, dynamic poi) async { switch (poiType) { case PoiServiceType.poster: return await _addUpdateAction( @@ -123,7 +133,14 @@ class CampaignActionCache extends ChangeNotifier { getMarker: (poi) => poi.transformToVirtualMarkerItem(), ); case PoiServiceType.flyer: - throw UnimplementedError(); + return await _addUpdateAction( + poiType: poiType, + poi: poi as FlyerUpdateModel, + getId: (poi) => poi.id, + getJson: (poi) => poi.toJson(), + mergeUpdates: (action, poiUpdate) => poiUpdate, + getMarker: (poi) => poi.transformToVirtualMarkerItem(), + ); } } @@ -182,6 +199,7 @@ class CampaignActionCache extends ChangeNotifier { case CampaignActionType.deletePoster: var model = _getDeleteMarkerModel(PoiServiceType.poster, action.poiId!); markerItems.add(model); + case CampaignActionType.addDoor: var model = action.getAsDoorCreate(); markerItems.add(model.transformToVirtualMarkerItem(action.poiTempId)); @@ -191,25 +209,30 @@ class CampaignActionCache extends ChangeNotifier { case CampaignActionType.deleteDoor: var model = _getDeleteMarkerModel(PoiServiceType.door, action.poiId!); markerItems.add(model); + case CampaignActionType.addFlyer: + var model = action.getAsFlyerCreate(); + markerItems.add(model.transformToVirtualMarkerItem(action.poiTempId)); case CampaignActionType.editFlyer: + var model = action.getAsFlyerUpdate(); + markerItems.add(model.transformToVirtualMarkerItem()); case CampaignActionType.deleteFlyer: - // var model = _getDeleteMarkerModel(PoiServiceType.door, action.poiId!); - // markerItems.add(model); + var model = _getDeleteMarkerModel(PoiServiceType.flyer, action.poiId!); + markerItems.add(model); + case CampaignActionType.unknown: case null: throw UnimplementedError(); } - if (action.actionType == CampaignActionType.addPoster) {} } return markerItems; } Future getPoiAsPosterDetail(String poiId) async { - var detailModel = await getPoiDetail( + var detailModel = await _getPoiDetail( poiId: poiId, - addActionFilter: CampaignActionType.addDoor, - editActionFilter: CampaignActionType.editDoor, + addActionFilter: CampaignActionType.addPoster, + editActionFilter: CampaignActionType.editPoster, transformEditAction: (action) => action.getAsPosterUpdate().transformToPosterDetailModel(), transformAddAction: (action) => action.getAsPosterCreate().transformToPosterDetailModel(poiId), ); @@ -217,7 +240,7 @@ class CampaignActionCache extends ChangeNotifier { } Future getPoiAsDoorDetail(String poiId) async { - var detailModel = await getPoiDetail( + var detailModel = await _getPoiDetail( poiId: poiId, addActionFilter: CampaignActionType.addDoor, editActionFilter: CampaignActionType.editDoor, @@ -227,7 +250,18 @@ class CampaignActionCache extends ChangeNotifier { return detailModel; } - Future getPoiDetail({ + Future getPoiAsFlyerDetail(String poiId) async { + var detailModel = await _getPoiDetail( + poiId: poiId, + addActionFilter: CampaignActionType.addFlyer, + editActionFilter: CampaignActionType.editFlyer, + transformEditAction: (action) => action.getAsFlyerUpdate().transformToFlyerDetailModel(), + transformAddAction: (action) => action.getAsFlyerCreate().transformToFlyerDetailModel(poiId), + ); + return detailModel; + } + + Future _getPoiDetail({ required String poiId, required CampaignActionType addActionFilter, required CampaignActionType editActionFilter, @@ -251,10 +285,13 @@ class CampaignActionCache extends ChangeNotifier { return posterCacheList; } - void flushCachedItems() async { + void flushCache() async { + if (_isflushing) return; try { + _isflushing = true; var posterApiService = GetIt.I(); var doorApiService = GetIt.I(); + var flyerApiService = GetIt.I(); final allActions = await campaignActionDatabase.readAll(); for (int i = 0; i < allActions.length; i++) { @@ -263,7 +300,7 @@ class CampaignActionCache extends ChangeNotifier { case CampaignActionType.addPoster: var model = action.getAsPosterCreate(); var newPosterMarker = await posterApiService.createNewPoster(model); - await updateIds( + await _updateIdsInCache( oldId: action.poiTempId, newId: newPosterMarker.id!, startIndex: i + 1, @@ -279,7 +316,7 @@ class CampaignActionCache extends ChangeNotifier { case CampaignActionType.addDoor: var model = action.getAsDoorCreate(); var newDoorMarker = await doorApiService.createNewDoor(model); - await updateIds( + await _updateIdsInCache( oldId: action.poiTempId, newId: newDoorMarker.id!, startIndex: i + 1, @@ -292,14 +329,27 @@ class CampaignActionCache extends ChangeNotifier { await doorApiService.updateDoor(model); campaignActionDatabase.delete(action.id!); + case CampaignActionType.addFlyer: + var model = action.getAsFlyerCreate(); + var newDoorMarker = await flyerApiService.createNewFlyer(model); + await _updateIdsInCache( + oldId: action.poiTempId, + newId: newDoorMarker.id!, + startIndex: i + 1, + allActions: allActions, + ); + campaignActionDatabase.delete(action.id!); + case CampaignActionType.editFlyer: + var model = action.getAsFlyerUpdate(); + await flyerApiService.updateFlyer(model); + campaignActionDatabase.delete(action.id!); + case CampaignActionType.deleteDoor: case CampaignActionType.deletePoster: + case CampaignActionType.deleteFlyer: await posterApiService.deletePoi(action.poiId!.toString()); campaignActionDatabase.delete(action.id!); - case CampaignActionType.addFlyer: - case CampaignActionType.editFlyer: - case CampaignActionType.deleteFlyer: case CampaignActionType.unknown: case null: throw UnimplementedError(); @@ -307,6 +357,7 @@ class CampaignActionCache extends ChangeNotifier { notifyListeners(); } } finally { + _isflushing = false; notifyListeners(); if (await getCachedActionCount() == 0) { MediaHelper.removeAllFiles(); @@ -317,7 +368,7 @@ class CampaignActionCache extends ChangeNotifier { } } - Future updateIds({ + Future _updateIdsInCache({ required int oldId, required int newId, required List allActions, diff --git a/lib/features/campaigns/helper/campaign_action_cache_timer.dart b/lib/features/campaigns/helper/campaign_action_cache_timer.dart new file mode 100644 index 00000000..69cbf211 --- /dev/null +++ b/lib/features/campaigns/helper/campaign_action_cache_timer.dart @@ -0,0 +1,22 @@ +import 'dart:async'; +import 'package:get_it/get_it.dart'; +import 'package:gruene_app/app/auth/repository/auth_repository.dart'; +import 'package:gruene_app/features/campaigns/helper/campaign_action_cache.dart'; + +class CampaignActionCacheTimer { + late Timer timer; + + CampaignActionCacheTimer() { + timer = Timer.periodic(Duration(minutes: 5), (timer) => _flushData()); + + // initial flush + Future.delayed(Duration(seconds: 5), () => Timer.run(_flushData)); + } + + void _flushData() async { + var authRepo = AuthRepository(); + if (await authRepo.getAccessToken() != null) { + GetIt.I().flushCache(); + } + } +} diff --git a/lib/features/campaigns/helper/file_cache_manager.dart b/lib/features/campaigns/helper/file_cache_manager.dart index 006f3b71..62561dfc 100644 --- a/lib/features/campaigns/helper/file_cache_manager.dart +++ b/lib/features/campaigns/helper/file_cache_manager.dart @@ -39,6 +39,7 @@ class FileManager { Future clearAllFiles() async { var dir = Directory(await _getStore()); + if (!(await dir.exists())) return; var allFiles = await dir.list().toList(); for (var file in allFiles) { var stat = await file.stat(); @@ -49,14 +50,3 @@ class FileManager { } } } - -// class FileCacheManager { -// static const key = 'wk-cache-manager'; -// static CacheManager instance = CacheManager( -// Config( -// key, -// stalePeriod: const Duration(days: 7), -// maxNrOfCacheObjects: 200, -// ), -// ); -// } diff --git a/lib/features/campaigns/models/flyer/flyer_create_model.dart b/lib/features/campaigns/models/flyer/flyer_create_model.dart index 7f39f199..21caab49 100644 --- a/lib/features/campaigns/models/flyer/flyer_create_model.dart +++ b/lib/features/campaigns/models/flyer/flyer_create_model.dart @@ -1,7 +1,13 @@ import 'package:gruene_app/app/services/nominatim_service.dart'; +import 'package:gruene_app/features/campaigns/models/posters/poster_create_model.dart'; +import 'package:json_annotation/json_annotation.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; +part 'flyer_create_model.g.dart'; + +@JsonSerializable() class FlyerCreateModel { + @LatLongConverter() final LatLng location; final AddressModel address; @@ -9,4 +15,8 @@ class FlyerCreateModel { final int flyerCount; FlyerCreateModel({required this.location, required this.address, required this.flyerCount}); + + factory FlyerCreateModel.fromJson(Map json) => _$FlyerCreateModelFromJson(json); + + Map toJson() => _$FlyerCreateModelToJson(this); } diff --git a/lib/features/campaigns/models/flyer/flyer_detail_model.dart b/lib/features/campaigns/models/flyer/flyer_detail_model.dart index 68e23ffa..a79ab825 100644 --- a/lib/features/campaigns/models/flyer/flyer_detail_model.dart +++ b/lib/features/campaigns/models/flyer/flyer_detail_model.dart @@ -1,5 +1,13 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:gruene_app/app/services/converters.dart'; import 'package:gruene_app/app/services/nominatim_service.dart'; +import 'package:gruene_app/features/campaigns/models/posters/poster_create_model.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:maplibre_gl/maplibre_gl.dart'; +part 'flyer_detail_model.g.dart'; + +@JsonSerializable() class FlyerDetailModel { final String id; final AddressModel address; @@ -7,11 +15,38 @@ class FlyerDetailModel { final String createdAt; final bool isCached; + @LatLongConverter() + final LatLng location; + FlyerDetailModel({ required this.id, required this.address, required this.flyerCount, required this.createdAt, + required this.location, this.isCached = false, }); + + factory FlyerDetailModel.fromJson(Map json) => + _$FlyerDetailModelFromJson(json.convertLatLongField()); + + Map toJson() => _$FlyerDetailModelToJson(this); + + FlyerDetailModel copyWith({ + String? id, + AddressModel? address, + int? flyerCount, + String? createdAt, + bool? isCached, + LatLng? location, + }) { + return FlyerDetailModel( + id: id ?? this.id, + address: address ?? this.address, + flyerCount: flyerCount ?? this.flyerCount, + createdAt: createdAt ?? this.createdAt, + isCached: isCached ?? this.isCached, + location: location ?? this.location, + ); + } } diff --git a/lib/features/campaigns/models/flyer/flyer_update_model.dart b/lib/features/campaigns/models/flyer/flyer_update_model.dart index e07ffe09..17be5a45 100644 --- a/lib/features/campaigns/models/flyer/flyer_update_model.dart +++ b/lib/features/campaigns/models/flyer/flyer_update_model.dart @@ -1,9 +1,29 @@ import 'package:gruene_app/app/services/nominatim_service.dart'; +import 'package:gruene_app/features/campaigns/models/flyer/flyer_detail_model.dart'; +import 'package:gruene_app/features/campaigns/models/posters/poster_create_model.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:maplibre_gl/maplibre_gl.dart'; +part 'flyer_update_model.g.dart'; + +@JsonSerializable() class FlyerUpdateModel { final String id; final AddressModel address; final int flyerCount; + @LatLongConverter() + final LatLng location; + final FlyerDetailModel oldFlyerDetail; + + FlyerUpdateModel({ + required this.id, + required this.address, + required this.flyerCount, + required this.location, + required this.oldFlyerDetail, + }); + + factory FlyerUpdateModel.fromJson(Map json) => _$FlyerUpdateModelFromJson(json); - FlyerUpdateModel({required this.id, required this.address, required this.flyerCount}); + Map toJson() => _$FlyerUpdateModelToJson(this); } diff --git a/lib/features/campaigns/screens/doors_screen.dart b/lib/features/campaigns/screens/doors_screen.dart index 137703d8..a70b1a93 100644 --- a/lib/features/campaigns/screens/doors_screen.dart +++ b/lib/features/campaigns/screens/doors_screen.dart @@ -10,7 +10,6 @@ import 'package:gruene_app/features/campaigns/helper/map_helper.dart'; import 'package:gruene_app/features/campaigns/models/doors/door_create_model.dart'; import 'package:gruene_app/features/campaigns/models/doors/door_detail_model.dart'; import 'package:gruene_app/features/campaigns/models/doors/door_update_model.dart'; -import 'package:gruene_app/features/campaigns/models/marker_item_model.dart'; import 'package:gruene_app/features/campaigns/screens/door_edit.dart'; import 'package:gruene_app/features/campaigns/screens/doors_add_screen.dart'; import 'package:gruene_app/features/campaigns/screens/doors_detail.dart'; @@ -27,7 +26,7 @@ class DoorsScreen extends StatefulWidget { State createState() => _DoorsScreenState(); } -class _DoorsScreenState extends MapConsumer { +class _DoorsScreenState extends MapConsumer { static const _poiType = PoiServiceType.door; final Map> doorsExclusions = >{ t.campaigns.filters.focusAreas: [t.campaigns.filters.visited_areas], @@ -38,7 +37,7 @@ class _DoorsScreenState extends MapConsumer { final _grueneApiService = GetIt.I(); - _DoorsScreenState() : super(NominatimService(), _poiType); + _DoorsScreenState() : super(_poiType); @override GrueneApiDoorService get campaignService => _grueneApiService; @@ -97,7 +96,7 @@ class _DoorsScreenState extends MapConsumer { location, null, _getAddScreen, - _saveNewAndGetMarkerItem, + saveNewAndGetMarkerItem, ); } @@ -130,7 +129,7 @@ class _DoorsScreenState extends MapConsumer { } getEditPoiWidget(DoorDetailModel door) { - return DoorEdit(door: door, onSave: _saveDoor, onDelete: deletePoi); + return DoorEdit(door: door, onSave: savePoi, onDelete: deletePoi); } super.onFeatureClick( @@ -146,18 +145,10 @@ class _DoorsScreenState extends MapConsumer { showFocusAreaInfoAtPoint(point); } - Future _saveDoor(DoorUpdateModel doorUpdate) async { - final updatedMarker = await campaignActionCache.addUpdateAction(_poiType, doorUpdate); - mapController.setMarkerSource([updatedMarker]); - } - DoorsAddScreen _getAddScreen(LatLng location, AddressModel? address, Object? additionalData) { return DoorsAddScreen( location: location, address: address!, ); } - - Future _saveNewAndGetMarkerItem(DoorCreateModel newDoor) async => - await campaignActionCache.addCreateAction(_poiType, newDoor); } diff --git a/lib/features/campaigns/screens/flyer_edit.dart b/lib/features/campaigns/screens/flyer_edit.dart index 570cfcef..977c6d3d 100644 --- a/lib/features/campaigns/screens/flyer_edit.dart +++ b/lib/features/campaigns/screens/flyer_edit.dart @@ -133,6 +133,8 @@ class _FlyerEditState extends State with AddressExtension, FlyerValid id: widget.flyer.id, address: getAddress(), flyerCount: validationResult.flyerCount, + oldFlyerDetail: widget.flyer, + location: widget.flyer.location, ); await widget.onSave(updateModel); _closeDialog(); diff --git a/lib/features/campaigns/screens/flyer_screen.dart b/lib/features/campaigns/screens/flyer_screen.dart index 6cdae708..a5d9684f 100644 --- a/lib/features/campaigns/screens/flyer_screen.dart +++ b/lib/features/campaigns/screens/flyer_screen.dart @@ -1,14 +1,15 @@ import 'dart:math'; import 'package:flutter/widgets.dart'; +import 'package:get_it/get_it.dart'; import 'package:gruene_app/app/services/enums.dart'; -import 'package:gruene_app/app/services/gruene_api_campaigns_service.dart'; +import 'package:gruene_app/app/services/gruene_api_flyer_service.dart'; import 'package:gruene_app/app/services/nominatim_service.dart'; import 'package:gruene_app/features/campaigns/helper/campaign_constants.dart'; +import 'package:gruene_app/features/campaigns/helper/map_helper.dart'; import 'package:gruene_app/features/campaigns/models/flyer/flyer_create_model.dart'; import 'package:gruene_app/features/campaigns/models/flyer/flyer_detail_model.dart'; import 'package:gruene_app/features/campaigns/models/flyer/flyer_update_model.dart'; -import 'package:gruene_app/features/campaigns/models/marker_item_model.dart'; import 'package:gruene_app/features/campaigns/screens/flyer_add_screen.dart'; import 'package:gruene_app/features/campaigns/screens/flyer_detail.dart'; import 'package:gruene_app/features/campaigns/screens/flyer_edit.dart'; @@ -25,13 +26,13 @@ class FlyerScreen extends StatefulWidget { State createState() => _FlyerScreenState(); } -class _FlyerScreenState extends MapConsumer { - static const _poiType = PoiServiceType.door; - final GrueneApiCampaignsService _grueneApiService = GrueneApiCampaignsService(poiType: _poiType); +class _FlyerScreenState extends MapConsumer { + static const _poiType = PoiServiceType.flyer; + final _grueneApiService = GetIt.I(); late List flyerFilter; - _FlyerScreenState() : super(NominatimService(), _poiType); + _FlyerScreenState() : super(_poiType); @override void initState() { @@ -87,7 +88,7 @@ class _FlyerScreenState extends MapConsumer { location, null, _getAddScreen, - _saveNewAndGetMarkerItem, + saveNewAndGetMarkerItem, ); } @@ -98,11 +99,21 @@ class _FlyerScreenState extends MapConsumer { } void _onFeatureClick(dynamic rawFeature) async { + final feature = rawFeature as Map; + final isCached = MapHelper.extractIsCachedFromFeature(feature); + getPoi(String poiId) async { final flyer = await campaignService.getPoiAsFlyerDetail(poiId); return flyer; } + getCachedPoi(String poiId) async { + final flyer = await campaignActionCache.getPoiAsFlyerDetail(poiId); + return flyer; + } + + var getPoiFromCacheOrApi = isCached ? getCachedPoi : getPoi; + getPoiDetail(FlyerDetailModel flyer) { return FlyerDetail( poi: flyer, @@ -110,12 +121,12 @@ class _FlyerScreenState extends MapConsumer { } getEditPoiWidget(FlyerDetailModel flyer) { - return FlyerEdit(flyer: flyer, onSave: _saveFlyer, onDelete: deletePoi); + return FlyerEdit(flyer: flyer, onSave: savePoi, onDelete: deletePoi); } super.onFeatureClick( rawFeature, - getPoi, + getPoiFromCacheOrApi, getPoiDetail, getEditPoiWidget, desiredSize: Size(150, 92), @@ -127,7 +138,7 @@ class _FlyerScreenState extends MapConsumer { } @override - GrueneApiCampaignsService get campaignService => _grueneApiService; + GrueneApiFlyerService get campaignService => _grueneApiService; FlyerAddScreen _getAddScreen(LatLng location, AddressModel? address, Object? additionalData) { return FlyerAddScreen( @@ -135,13 +146,4 @@ class _FlyerScreenState extends MapConsumer { address: address!, ); } - - Future _saveNewAndGetMarkerItem(FlyerCreateModel newFlyer) async { - return await campaignService.createNewFlyer(newFlyer); - } - - Future _saveFlyer(FlyerUpdateModel flyerUpdate) async { - final updatedMarker = await campaignService.updateFlyer(flyerUpdate); - mapController.setMarkerSource([updatedMarker]); - } } diff --git a/lib/features/campaigns/screens/map_consumer.dart b/lib/features/campaigns/screens/map_consumer.dart index 37311b3a..5a27d3d1 100644 --- a/lib/features/campaigns/screens/map_consumer.dart +++ b/lib/features/campaigns/screens/map_consumer.dart @@ -29,7 +29,7 @@ typedef GetPoiDetailWidgetCallback = Widget Function(T); typedef GetPoiEditWidgetCallback = Widget Function(T); typedef OnDeletePoiCallback = Future Function(String poiId); -abstract class MapConsumer extends State with FocusAreaInfo { +abstract class MapConsumer extends State with FocusAreaInfo { late MapController mapController; final NominatimService _nominatimService; @@ -41,7 +41,7 @@ abstract class MapConsumer extends State with Focus final campaignActionCache = GetIt.I(); final PoiServiceType poiType; - MapConsumer(this._nominatimService, this.poiType); + MapConsumer(this.poiType) : _nominatimService = GetIt.I(); GrueneApiCampaignsService get campaignService; @@ -152,7 +152,7 @@ abstract class MapConsumer extends State with Focus } Future deletePoi(String poiId) async { - var markerItem = await campaignActionCache.addDeleteAction(poiType, poiId); + var markerItem = await campaignActionCache.deletePoi(poiType, poiId); mapController.setMarkerSource([markerItem]); } @@ -335,4 +335,12 @@ abstract class MapConsumer extends State with Focus var markerItems = await campaignActionCache.getMarkerItems(poiType); mapController.setMarkerSource(markerItems); } + + Future savePoi(PoiUpdateType poiUpdate) async { + final updatedMarker = await campaignActionCache.updatePoi(poiType, poiUpdate); + mapController.setMarkerSource([updatedMarker]); + } + + Future saveNewAndGetMarkerItem(PoiCreateType newPoi) async => + await campaignActionCache.storeNewPoi(poiType, newPoi); } diff --git a/lib/features/campaigns/screens/posters_screen.dart b/lib/features/campaigns/screens/posters_screen.dart index ca1ab81c..26b8d785 100644 --- a/lib/features/campaigns/screens/posters_screen.dart +++ b/lib/features/campaigns/screens/posters_screen.dart @@ -10,7 +10,6 @@ import 'package:gruene_app/app/theme/theme.dart'; import 'package:gruene_app/features/campaigns/helper/campaign_constants.dart'; import 'package:gruene_app/features/campaigns/helper/map_helper.dart'; import 'package:gruene_app/features/campaigns/helper/media_helper.dart'; -import 'package:gruene_app/features/campaigns/models/marker_item_model.dart'; import 'package:gruene_app/features/campaigns/models/posters/poster_create_model.dart'; import 'package:gruene_app/features/campaigns/models/posters/poster_detail_model.dart'; import 'package:gruene_app/features/campaigns/models/posters/poster_list_item_model.dart'; @@ -34,13 +33,13 @@ class PostersScreen extends StatefulWidget { State createState() => _PostersScreenState(); } -class _PostersScreenState extends MapConsumer { +class _PostersScreenState extends MapConsumer { static const _poiType = PoiServiceType.poster; final _grueneApiService = GetIt.I(); late List postersFilter; - _PostersScreenState() : super(NominatimService(), _poiType); + _PostersScreenState() : super(_poiType); @override GrueneApiPosterService get campaignService => _grueneApiService; @@ -139,10 +138,6 @@ class _PostersScreenState extends MapConsumer { ); } - Future saveNewAndGetMarkerItem(PosterCreateModel newPoster) async { - return await campaignActionCache.addCreateAction(_poiType, newPoster); - } - void _addPOIClicked(LatLng location) async { super.addPOIClicked( location, @@ -180,7 +175,7 @@ class _PostersScreenState extends MapConsumer { } Widget _getEditPosterWidget(PosterDetailModel poster) { - return PosterEdit(poster: poster, onSave: _savePoster, onDelete: deletePoi); + return PosterEdit(poster: poster, onSave: savePoi, onDelete: deletePoi); } void _onFeatureClick(dynamic rawFeature) async { @@ -208,11 +203,6 @@ class _PostersScreenState extends MapConsumer { showFocusAreaInfoAtPoint(point); } - Future _savePoster(PosterUpdateModel posterUpdate) async { - final updatedMarker = await campaignActionCache.addUpdateAction(_poiType, posterUpdate); - mapController.setMarkerSource([updatedMarker]); - } - void showMyPosters() async { final theme = Theme.of(context); diff --git a/lib/main.dart b/lib/main.dart index 5c9d89c5..4210ad75 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -11,9 +13,12 @@ import 'package:gruene_app/app/auth/repository/auth_repository.dart'; import 'package:gruene_app/app/router.dart'; import 'package:gruene_app/app/services/gruene_api_core.dart'; import 'package:gruene_app/app/services/gruene_api_door_service.dart'; +import 'package:gruene_app/app/services/gruene_api_flyer_service.dart'; import 'package:gruene_app/app/services/gruene_api_poster_service.dart'; +import 'package:gruene_app/app/services/nominatim_service.dart'; import 'package:gruene_app/app/theme/theme.dart'; import 'package:gruene_app/features/campaigns/helper/campaign_action_cache.dart'; +import 'package:gruene_app/features/campaigns/helper/campaign_action_cache_timer.dart'; import 'package:gruene_app/features/campaigns/helper/campaign_session_settings.dart'; import 'package:gruene_app/features/campaigns/helper/file_cache_manager.dart'; import 'package:gruene_app/features/mfa/bloc/mfa_bloc.dart'; @@ -43,14 +48,19 @@ Future main() async { GetIt.I.registerSingleton(await createGrueneApiClient()); GetIt.I.registerSingleton(CampaignSessionSettings()); GetIt.I.registerSingleton(CampaignActionCache()); + GetIt.I.registerSingleton(CampaignActionCacheTimer()); GetIt.I.registerSingleton(FileManager()); GetIt.I.registerFactory(MfaFactory.create); GetIt.I.registerFactory(() => GrueneApiPosterService()); GetIt.I.registerFactory(() => GrueneApiDoorService()); + GetIt.I.registerFactory(() => GrueneApiFlyerService()); + GetIt.I.registerFactory(() => NominatimService()); WidgetsFlutterBinding.ensureInitialized(); + // setupCachePeriodicFlushing(); + runApp(TranslationProvider(child: const MyApp())); }