Skip to content

Commit 01a864e

Browse files
committed
#260 update servers:
- use sqflite as cache provider - store all item actions in cache - use virtual markers to distinct between saved and intermediate state - images are stored on device data instead of stream to reduce mem_usage
1 parent c47ff5c commit 01a864e

38 files changed

+1036
-138
lines changed

analysis_options.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
include: package:flutter_lints/flutter.yaml
22

33
analyzer:
4-
exclude: [build/**, lib/swagger_generated_code/**]
4+
exclude: [build/**, lib/swagger_generated_code/**, lib/objectbox_generated_code/*.g.dart]
55
errors:
66
always_use_package_imports: error
77
directives_ordering: error

build.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ targets:
22
$default:
33
sources:
44
- swaggers/**
5+
- lib/$lib$
6+
# - $package$
7+
- lib/**
8+
- pubspec.yaml
59
builders:
610
chopper_generator:
711
options:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import 'package:gruene_app/features/campaigns/helper/campaign_action.dart';
2+
import 'package:path/path.dart';
3+
import 'package:sqflite/sqflite.dart';
4+
5+
class CampaignActionDatabase {
6+
static final CampaignActionDatabase instance = CampaignActionDatabase._internal();
7+
8+
static Database? _database;
9+
10+
CampaignActionDatabase._internal();
11+
12+
Future<Database> get database async {
13+
return _database ??= await _initDatabase();
14+
}
15+
16+
Future<Database> _initDatabase() async {
17+
final databasePath = await getDatabasesPath();
18+
final path = join(databasePath, 'campaign_action_db.db');
19+
// await File(path).delete();
20+
return await openDatabase(
21+
path,
22+
version: 1,
23+
onCreate: _createDatabase,
24+
);
25+
}
26+
27+
Future<void> _createDatabase(Database db, _) async {
28+
return await db.execute('''
29+
CREATE TABLE ${CampaignActionFields.tableName} (
30+
${CampaignActionFields.id} ${CampaignActionFields.idType},
31+
${CampaignActionFields.poiId} ${CampaignActionFields.intTypeNullable},
32+
${CampaignActionFields.poiTempId} ${CampaignActionFields.intType},
33+
${CampaignActionFields.actionType} ${CampaignActionFields.intType},
34+
${CampaignActionFields.serialized} ${CampaignActionFields.textTypeNullable}
35+
)
36+
''');
37+
}
38+
39+
Future<CampaignAction> create(CampaignAction campaignAction) async {
40+
final db = await instance.database;
41+
final id = await db.insert(CampaignActionFields.tableName, campaignAction.toMap());
42+
return campaignAction.copyWith(id: id);
43+
}
44+
45+
// Future<NoteModel> read(int id) async {
46+
// final db = await instance.database;
47+
// final maps = await db.query(
48+
// CampaignActionFields.tableName,
49+
// columns: CampaignActionFields.values,
50+
// where: '${CampaignActionFields.id} = ?',
51+
// whereArgs: [id],
52+
// );
53+
54+
// if (maps.isNotEmpty) {
55+
// return NoteModel.fromJson(maps.first);
56+
// } else {
57+
// throw Exception('ID $id not found');
58+
// }
59+
// }
60+
61+
Future<List<CampaignAction>> readAll() async {
62+
final db = await instance.database;
63+
const orderBy = '${CampaignActionFields.poiTempId} DESC';
64+
final result = await db.query(CampaignActionFields.tableName, orderBy: orderBy);
65+
return result.map((json) => CampaignAction.fromMap(json)).toList();
66+
}
67+
68+
Future<List<CampaignAction>> readAllByActionType(List<int> posterActions) async {
69+
final db = await instance.database;
70+
const orderBy = '${CampaignActionFields.poiTempId} DESC';
71+
final result = await db.query(
72+
CampaignActionFields.tableName,
73+
orderBy: orderBy,
74+
where: '${CampaignActionFields.actionType} IN (${List.filled(posterActions.length, '?').join(',')})',
75+
whereArgs: posterActions,
76+
);
77+
return result.map((json) => CampaignAction.fromMap(json)).toList();
78+
}
79+
80+
Future<List<CampaignAction>> getActionsWithPoiId(String poiId) async {
81+
final db = await instance.database;
82+
const orderBy = '${CampaignActionFields.poiTempId} DESC';
83+
final result = await db.query(
84+
CampaignActionFields.tableName,
85+
orderBy: orderBy,
86+
where: '${CampaignActionFields.poiId} = ? OR ${CampaignActionFields.poiTempId} = ?',
87+
whereArgs: [poiId, poiId],
88+
);
89+
return result.map((json) => CampaignAction.fromMap(json)).toList();
90+
}
91+
// Future<int> update(NoteModel note) async {
92+
// final db = await instance.database;
93+
// return db.update(
94+
// CampaignActionFields.tableName,
95+
// note.toJson(),
96+
// where: '${CampaignActionFields.id} = ?',
97+
// whereArgs: [note.id],
98+
// );
99+
// }
100+
101+
Future<int> delete(int id) async {
102+
final db = await instance.database;
103+
return await db.delete(
104+
CampaignActionFields.tableName,
105+
where: '${CampaignActionFields.id} = ?',
106+
whereArgs: [id],
107+
);
108+
}
109+
110+
Future<void> close() async {
111+
final db = await instance.database;
112+
db.close();
113+
}
114+
115+
Future<int> getCount() async {
116+
final db = await instance.database;
117+
int count = Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM ${CampaignActionFields.tableName}')) ?? 0;
118+
return count;
119+
}
120+
}
121+
122+
class CampaignActionFields {
123+
static const String tableName = 'campaign_action';
124+
static const String idType = 'INTEGER PRIMARY KEY AUTOINCREMENT';
125+
static const String textTypeNullable = 'TEXT';
126+
static const String intType = 'INTEGER NOT NULL';
127+
static const String intTypeNullable = 'INTEGER';
128+
static const String id = '_id';
129+
static const String poiId = 'poiId';
130+
static const String poiTempId = 'poiTempId';
131+
static const String actionType = 'actionType';
132+
static const String serialized = 'serialized';
133+
}

lib/app/services/converters.dart

+9
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1+
import 'dart:convert';
2+
13
import 'package:gruene_app/app/services/enums.dart';
24
import 'package:gruene_app/app/services/nominatim_service.dart';
5+
import 'package:gruene_app/features/campaigns/helper/campaign_action.dart';
36
import 'package:gruene_app/features/campaigns/models/doors/door_detail_model.dart';
47
import 'package:gruene_app/features/campaigns/models/flyer/flyer_detail_model.dart';
58
import 'package:gruene_app/features/campaigns/models/map_layer_model.dart';
69
import 'package:gruene_app/features/campaigns/models/marker_item_model.dart';
10+
import 'package:gruene_app/features/campaigns/models/posters/poster_create_model.dart';
711
import 'package:gruene_app/features/campaigns/models/posters/poster_detail_model.dart';
812
import 'package:gruene_app/features/campaigns/models/posters/poster_list_item_model.dart';
13+
import 'package:gruene_app/features/campaigns/models/posters/poster_update_model.dart';
914
import 'package:gruene_app/features/campaigns/widgets/enhanced_wheel_slider.dart';
1015
import 'package:gruene_app/features/campaigns/widgets/text_input_field.dart';
1116
import 'package:gruene_app/i18n/translations.g.dart';
@@ -25,3 +30,7 @@ part 'converters/poi_address_parsing.dart';
2530
part 'converters/focus_area_parsing.dart';
2631
part 'converters/poi_parsing.dart';
2732
part 'converters/slider_range_parsing.dart';
33+
part 'converters/date_time_parsing.dart';
34+
part 'converters/poster_create_model_parsing.dart';
35+
part 'converters/poster_update_model_parsing.dart';
36+
part 'converters/campaign_action_parsing.dart';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
part of '../converters.dart';
2+
3+
extension CampaignActionParsing on CampaignAction {
4+
PosterCreateModel getSerializedAsPosterCreate() {
5+
var data = jsonDecode(serialized!) as Map<String, dynamic>;
6+
if (data['photo'] != null) {
7+
data['photo'] = (data['photo'] as List<dynamic>).cast<int>();
8+
}
9+
data['location'] = (data['location'] as List<dynamic>).cast<double>();
10+
11+
var model = PosterCreateModel.fromJson(data);
12+
return model;
13+
}
14+
15+
PosterUpdateModel getSerializedAsPosterUpdate() {
16+
var data = jsonDecode(serialized!) as Map<String, dynamic>;
17+
if (data['newPhoto'] != null) {
18+
data['newPhoto'] = (data['newPhoto'] as List<dynamic>).cast<int>();
19+
}
20+
data['location'] = (data['location'] as List<dynamic>).cast<double>();
21+
22+
var model = PosterUpdateModel.fromJson(data);
23+
return model;
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
part of '../converters.dart';
2+
3+
extension DateTimeParsing on DateTime {
4+
String getAsLocalDateTimeString() {
5+
DateTime utcDateTime = this;
6+
DateTime localDateTime = utcDateTime.toLocal();
7+
final dateString = DateFormat(t.campaigns.poster.date_format).format(localDateTime);
8+
final timeString = DateFormat(t.campaigns.poster.time_format).format(localDateTime);
9+
return t.campaigns.poster.datetime_display_template
10+
.replaceAll('{date}', dateString)
11+
.replaceAll('{time}', timeString);
12+
}
13+
}

lib/app/services/converters/poi_parsing.dart

+5-18
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ extension PoiParsing on Poi {
2222
address: poi.address.transformToAddressModel(),
2323
openedDoors: poi.house!.countOpenedDoors.toInt(),
2424
closedDoors: poi.house!.countClosedDoors.toInt(),
25-
createdAt: _getUtcDateTimeAsLocalDateTimeString(poi.createdAt),
25+
createdAt: poi.createdAt.getAsLocalDateTimeString(),
2626
);
2727
}
2828

@@ -37,8 +37,9 @@ extension PoiParsing on Poi {
3737
imageUrl: _getImageUrl(poi),
3838
address: poi.address.transformToAddressModel(),
3939
status: poi.poster!.status.transformToModelPosterStatus(),
40+
location: coords.transformToLatLng(),
4041
comment: poi.poster!.comment ?? '',
41-
createdAt: _getUtcDateTimeAsLocalDateTimeString(poi.createdAt),
42+
createdAt: poi.createdAt.getAsLocalDateTimeString(),
4243
);
4344
}
4445

@@ -51,7 +52,7 @@ extension PoiParsing on Poi {
5152
id: poi.id,
5253
address: poi.address.transformToAddressModel(),
5354
flyerCount: poi.flyerSpot!.flyerCount.toInt(),
54-
createdAt: _getUtcDateTimeAsLocalDateTimeString(poi.createdAt),
55+
createdAt: poi.createdAt.getAsLocalDateTimeString(),
5556
);
5657
}
5758

@@ -67,25 +68,11 @@ extension PoiParsing on Poi {
6768
address: poi.address.transformToAddressModel(),
6869
status: poi.poster!.status.translatePosterStatus(),
6970
lastChangeStatus: poi._getLastChangeStatus(),
70-
lastChangeDateTime: poi._getLastChangeDateTimeInfo(),
71+
lastChangeDateTime: poi.updatedAt.getAsLocalDateTimeString(),
7172
createdAt: poi.createdAt,
7273
);
7374
}
7475

75-
String _getLastChangeDateTimeInfo() {
76-
return _getUtcDateTimeAsLocalDateTimeString(updatedAt);
77-
}
78-
79-
String _getUtcDateTimeAsLocalDateTimeString(DateTime utcTime) {
80-
final localTime = utcTime.toLocal();
81-
82-
final lastChangeDate = DateFormat(t.campaigns.poster.date_format).format(localTime);
83-
final lastChangeTime = DateFormat(t.campaigns.poster.time_format).format(localTime);
84-
return t.campaigns.poster.datetime_display_template
85-
.replaceAll('{date}', lastChangeDate)
86-
.replaceAll('{time}', lastChangeTime);
87-
}
88-
8976
String _getLastChangeStatus() {
9077
return createdAt == updatedAt ? t.campaigns.poster.created : t.campaigns.poster.updated;
9178
}

lib/app/services/converters/poi_service_type_parsing.dart

+13
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,17 @@ extension PoiServiceTypeParsing on PoiServiceType {
3333
return CreatePoiType.flyerSpot;
3434
}
3535
}
36+
37+
String getAsMarkerItemStatus(PosterStatus? posterStatus) {
38+
var typeName = name;
39+
switch (this) {
40+
case PoiServiceType.poster:
41+
String statusSuffix = '';
42+
if (posterStatus != null) statusSuffix = '_${posterStatus.name}';
43+
return '$typeName$statusSuffix';
44+
case PoiServiceType.door:
45+
case PoiServiceType.flyer:
46+
return typeName;
47+
}
48+
}
3649
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
part of '../converters.dart';
2+
3+
extension PosterCreateModelParsing on PosterCreateModel {
4+
MarkerItemModel transformToVirtualMarkerItem(int temporaryId) {
5+
return MarkerItemModel.virtual(
6+
id: temporaryId,
7+
status: PoiServiceType.poster.getAsMarkerItemStatus(PosterStatus.ok),
8+
location: location,
9+
);
10+
}
11+
12+
PosterDetailModel transformToPosterDetailModel(int temporaryId) {
13+
return PosterDetailModel(
14+
id: temporaryId.toString(),
15+
status: PosterStatus.ok,
16+
address: address,
17+
thumbnailUrl: imageFileLocation,
18+
imageUrl: imageFileLocation,
19+
location: location,
20+
comment: '',
21+
createdAt: '${DateTime.now().getAsLocalDateTimeString()}*', // should mark this as preliminary
22+
isCached: true,
23+
);
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
part of '../converters.dart';
2+
3+
extension PosterUpdateModelParsing on PosterUpdateModel {
4+
MarkerItemModel transformToVirtualMarkerItem() {
5+
return MarkerItemModel.virtual(
6+
id: int.parse(id),
7+
status: PoiServiceType.poster.getAsMarkerItemStatus(status),
8+
location: location,
9+
);
10+
}
11+
12+
PosterDetailModel transformToPosterDetailModel(int temporaryId) {
13+
return PosterDetailModel(
14+
id: temporaryId.toString(),
15+
status: status,
16+
address: address,
17+
thumbnailUrl: null,
18+
imageUrl: null,
19+
location: location,
20+
comment: comment,
21+
createdAt: '${DateTime.now().getAsLocalDateTimeString()}*', // should mark this as preliminary
22+
isCached: true,
23+
);
24+
}
25+
26+
PosterUpdateModel mergeWith(PosterUpdateModel newPosterUpdate) {
27+
var oldPosterUdpate = this;
28+
29+
return newPosterUpdate.copyWith(
30+
removePreviousPhotos: newPosterUpdate.removePreviousPhotos || oldPosterUdpate.removePreviousPhotos,
31+
);
32+
}
33+
}

0 commit comments

Comments
 (0)