Skip to content

Commit

Permalink
#260 update servers:
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
Stift committed Jan 16, 2025
1 parent c47ff5c commit 01a864e
Show file tree
Hide file tree
Showing 38 changed files with 1,036 additions and 138 deletions.
2 changes: 1 addition & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
include: package:flutter_lints/flutter.yaml

analyzer:
exclude: [build/**, lib/swagger_generated_code/**]
exclude: [build/**, lib/swagger_generated_code/**, lib/objectbox_generated_code/*.g.dart]
errors:
always_use_package_imports: error
directives_ordering: error
Expand Down
4 changes: 4 additions & 0 deletions build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ targets:
$default:
sources:
- swaggers/**
- lib/$lib$
# - $package$
- lib/**
- pubspec.yaml
builders:
chopper_generator:
options:
Expand Down
133 changes: 133 additions & 0 deletions lib/app/services/campaign_action_database.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import 'package:gruene_app/features/campaigns/helper/campaign_action.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

class CampaignActionDatabase {
static final CampaignActionDatabase instance = CampaignActionDatabase._internal();

static Database? _database;

CampaignActionDatabase._internal();

Future<Database> get database async {
return _database ??= await _initDatabase();
}

Future<Database> _initDatabase() async {
final databasePath = await getDatabasesPath();
final path = join(databasePath, 'campaign_action_db.db');
// await File(path).delete();
return await openDatabase(
path,
version: 1,
onCreate: _createDatabase,
);
}

Future<void> _createDatabase(Database db, _) async {
return await db.execute('''
CREATE TABLE ${CampaignActionFields.tableName} (
${CampaignActionFields.id} ${CampaignActionFields.idType},
${CampaignActionFields.poiId} ${CampaignActionFields.intTypeNullable},
${CampaignActionFields.poiTempId} ${CampaignActionFields.intType},
${CampaignActionFields.actionType} ${CampaignActionFields.intType},
${CampaignActionFields.serialized} ${CampaignActionFields.textTypeNullable}
)
''');
}

Future<CampaignAction> create(CampaignAction campaignAction) async {
final db = await instance.database;
final id = await db.insert(CampaignActionFields.tableName, campaignAction.toMap());
return campaignAction.copyWith(id: id);
}

// Future<NoteModel> read(int id) async {
// final db = await instance.database;
// final maps = await db.query(
// CampaignActionFields.tableName,
// columns: CampaignActionFields.values,
// where: '${CampaignActionFields.id} = ?',
// whereArgs: [id],
// );

// if (maps.isNotEmpty) {
// return NoteModel.fromJson(maps.first);
// } else {
// throw Exception('ID $id not found');
// }
// }

Future<List<CampaignAction>> readAll() async {
final db = await instance.database;
const orderBy = '${CampaignActionFields.poiTempId} DESC';
final result = await db.query(CampaignActionFields.tableName, orderBy: orderBy);
return result.map((json) => CampaignAction.fromMap(json)).toList();
}

Future<List<CampaignAction>> readAllByActionType(List<int> posterActions) async {
final db = await instance.database;
const orderBy = '${CampaignActionFields.poiTempId} DESC';
final result = await db.query(
CampaignActionFields.tableName,
orderBy: orderBy,
where: '${CampaignActionFields.actionType} IN (${List.filled(posterActions.length, '?').join(',')})',
whereArgs: posterActions,
);
return result.map((json) => CampaignAction.fromMap(json)).toList();
}

Future<List<CampaignAction>> getActionsWithPoiId(String poiId) async {
final db = await instance.database;
const orderBy = '${CampaignActionFields.poiTempId} DESC';
final result = await db.query(
CampaignActionFields.tableName,
orderBy: orderBy,
where: '${CampaignActionFields.poiId} = ? OR ${CampaignActionFields.poiTempId} = ?',
whereArgs: [poiId, poiId],
);
return result.map((json) => CampaignAction.fromMap(json)).toList();
}
// Future<int> update(NoteModel note) async {
// final db = await instance.database;
// return db.update(
// CampaignActionFields.tableName,
// note.toJson(),
// where: '${CampaignActionFields.id} = ?',
// whereArgs: [note.id],
// );
// }

Future<int> delete(int id) async {
final db = await instance.database;
return await db.delete(
CampaignActionFields.tableName,
where: '${CampaignActionFields.id} = ?',
whereArgs: [id],
);
}

Future<void> close() async {
final db = await instance.database;
db.close();
}

Future<int> getCount() async {
final db = await instance.database;
int count = Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM ${CampaignActionFields.tableName}')) ?? 0;
return count;
}
}

class CampaignActionFields {
static const String tableName = 'campaign_action';
static const String idType = 'INTEGER PRIMARY KEY AUTOINCREMENT';
static const String textTypeNullable = 'TEXT';
static const String intType = 'INTEGER NOT NULL';
static const String intTypeNullable = 'INTEGER';
static const String id = '_id';
static const String poiId = 'poiId';
static const String poiTempId = 'poiTempId';
static const String actionType = 'actionType';
static const String serialized = 'serialized';
}
9 changes: 9 additions & 0 deletions lib/app/services/converters.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import 'dart:convert';

import 'package:gruene_app/app/services/enums.dart';
import 'package:gruene_app/app/services/nominatim_service.dart';
import 'package:gruene_app/features/campaigns/helper/campaign_action.dart';
import 'package:gruene_app/features/campaigns/models/doors/door_detail_model.dart';
import 'package:gruene_app/features/campaigns/models/flyer/flyer_detail_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';
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';
import 'package:gruene_app/features/campaigns/models/posters/poster_update_model.dart';
import 'package:gruene_app/features/campaigns/widgets/enhanced_wheel_slider.dart';
import 'package:gruene_app/features/campaigns/widgets/text_input_field.dart';
import 'package:gruene_app/i18n/translations.g.dart';
Expand All @@ -25,3 +30,7 @@ part 'converters/poi_address_parsing.dart';
part 'converters/focus_area_parsing.dart';
part 'converters/poi_parsing.dart';
part 'converters/slider_range_parsing.dart';
part 'converters/date_time_parsing.dart';
part 'converters/poster_create_model_parsing.dart';
part 'converters/poster_update_model_parsing.dart';
part 'converters/campaign_action_parsing.dart';
25 changes: 25 additions & 0 deletions lib/app/services/converters/campaign_action_parsing.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
part of '../converters.dart';

extension CampaignActionParsing on CampaignAction {
PosterCreateModel getSerializedAsPosterCreate() {
var data = jsonDecode(serialized!) as Map<String, dynamic>;
if (data['photo'] != null) {
data['photo'] = (data['photo'] as List<dynamic>).cast<int>();
}
data['location'] = (data['location'] as List<dynamic>).cast<double>();

var model = PosterCreateModel.fromJson(data);
return model;
}

PosterUpdateModel getSerializedAsPosterUpdate() {
var data = jsonDecode(serialized!) as Map<String, dynamic>;
if (data['newPhoto'] != null) {
data['newPhoto'] = (data['newPhoto'] as List<dynamic>).cast<int>();
}
data['location'] = (data['location'] as List<dynamic>).cast<double>();

var model = PosterUpdateModel.fromJson(data);
return model;
}
}
13 changes: 13 additions & 0 deletions lib/app/services/converters/date_time_parsing.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
part of '../converters.dart';

extension DateTimeParsing on DateTime {
String getAsLocalDateTimeString() {
DateTime utcDateTime = this;
DateTime localDateTime = utcDateTime.toLocal();
final dateString = DateFormat(t.campaigns.poster.date_format).format(localDateTime);
final timeString = DateFormat(t.campaigns.poster.time_format).format(localDateTime);
return t.campaigns.poster.datetime_display_template
.replaceAll('{date}', dateString)
.replaceAll('{time}', timeString);
}
}
23 changes: 5 additions & 18 deletions lib/app/services/converters/poi_parsing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ extension PoiParsing on Poi {
address: poi.address.transformToAddressModel(),
openedDoors: poi.house!.countOpenedDoors.toInt(),
closedDoors: poi.house!.countClosedDoors.toInt(),
createdAt: _getUtcDateTimeAsLocalDateTimeString(poi.createdAt),
createdAt: poi.createdAt.getAsLocalDateTimeString(),
);
}

Expand All @@ -37,8 +37,9 @@ extension PoiParsing on Poi {
imageUrl: _getImageUrl(poi),
address: poi.address.transformToAddressModel(),
status: poi.poster!.status.transformToModelPosterStatus(),
location: coords.transformToLatLng(),
comment: poi.poster!.comment ?? '',
createdAt: _getUtcDateTimeAsLocalDateTimeString(poi.createdAt),
createdAt: poi.createdAt.getAsLocalDateTimeString(),
);
}

Expand All @@ -51,7 +52,7 @@ extension PoiParsing on Poi {
id: poi.id,
address: poi.address.transformToAddressModel(),
flyerCount: poi.flyerSpot!.flyerCount.toInt(),
createdAt: _getUtcDateTimeAsLocalDateTimeString(poi.createdAt),
createdAt: poi.createdAt.getAsLocalDateTimeString(),
);
}

Expand All @@ -67,25 +68,11 @@ extension PoiParsing on Poi {
address: poi.address.transformToAddressModel(),
status: poi.poster!.status.translatePosterStatus(),
lastChangeStatus: poi._getLastChangeStatus(),
lastChangeDateTime: poi._getLastChangeDateTimeInfo(),
lastChangeDateTime: poi.updatedAt.getAsLocalDateTimeString(),
createdAt: poi.createdAt,
);
}

String _getLastChangeDateTimeInfo() {
return _getUtcDateTimeAsLocalDateTimeString(updatedAt);
}

String _getUtcDateTimeAsLocalDateTimeString(DateTime utcTime) {
final localTime = utcTime.toLocal();

final lastChangeDate = DateFormat(t.campaigns.poster.date_format).format(localTime);
final lastChangeTime = DateFormat(t.campaigns.poster.time_format).format(localTime);
return t.campaigns.poster.datetime_display_template
.replaceAll('{date}', lastChangeDate)
.replaceAll('{time}', lastChangeTime);
}

String _getLastChangeStatus() {
return createdAt == updatedAt ? t.campaigns.poster.created : t.campaigns.poster.updated;
}
Expand Down
13 changes: 13 additions & 0 deletions lib/app/services/converters/poi_service_type_parsing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,17 @@ extension PoiServiceTypeParsing on PoiServiceType {
return CreatePoiType.flyerSpot;
}
}

String getAsMarkerItemStatus(PosterStatus? posterStatus) {
var typeName = name;
switch (this) {
case PoiServiceType.poster:
String statusSuffix = '';
if (posterStatus != null) statusSuffix = '_${posterStatus.name}';
return '$typeName$statusSuffix';
case PoiServiceType.door:
case PoiServiceType.flyer:
return typeName;
}
}
}
25 changes: 25 additions & 0 deletions lib/app/services/converters/poster_create_model_parsing.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
part of '../converters.dart';

extension PosterCreateModelParsing on PosterCreateModel {
MarkerItemModel transformToVirtualMarkerItem(int temporaryId) {
return MarkerItemModel.virtual(
id: temporaryId,
status: PoiServiceType.poster.getAsMarkerItemStatus(PosterStatus.ok),
location: location,
);
}

PosterDetailModel transformToPosterDetailModel(int temporaryId) {
return PosterDetailModel(
id: temporaryId.toString(),
status: PosterStatus.ok,
address: address,
thumbnailUrl: imageFileLocation,
imageUrl: imageFileLocation,
location: location,
comment: '',
createdAt: '${DateTime.now().getAsLocalDateTimeString()}*', // should mark this as preliminary
isCached: true,
);
}
}
33 changes: 33 additions & 0 deletions lib/app/services/converters/poster_update_model_parsing.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
part of '../converters.dart';

extension PosterUpdateModelParsing on PosterUpdateModel {
MarkerItemModel transformToVirtualMarkerItem() {
return MarkerItemModel.virtual(
id: int.parse(id),
status: PoiServiceType.poster.getAsMarkerItemStatus(status),
location: location,
);
}

PosterDetailModel transformToPosterDetailModel(int temporaryId) {
return PosterDetailModel(
id: temporaryId.toString(),
status: status,
address: address,
thumbnailUrl: null,
imageUrl: null,
location: location,
comment: comment,
createdAt: '${DateTime.now().getAsLocalDateTimeString()}*', // should mark this as preliminary
isCached: true,
);
}

PosterUpdateModel mergeWith(PosterUpdateModel newPosterUpdate) {
var oldPosterUdpate = this;

return newPosterUpdate.copyWith(
removePreviousPhotos: newPosterUpdate.removePreviousPhotos || oldPosterUdpate.removePreviousPhotos,
);
}
}
Loading

0 comments on commit 01a864e

Please sign in to comment.