diff --git a/lib/content_views/accepted_jobs_page.dart b/lib/content_views/accepted_jobs_page.dart index c48a8b4..b80e115 100644 --- a/lib/content_views/accepted_jobs_page.dart +++ b/lib/content_views/accepted_jobs_page.dart @@ -76,7 +76,7 @@ class _AcceptedJobsPageState extends State for (var job in _jobs) { var bottomText = - "${job.id}\n${job.tours[0].comp}\n${job.tours[0].street}\n${job.tours[0].city}"; + "${job.externalId}\n${job.tours[0].comp}\n${job.tours[0].street}\n${job.tours[0].city}"; if (job.rem.trim().isNotEmpty) { bottomText += "\n\n${job.rem.trim()}"; } diff --git a/lib/content_views/assigned_jobs_page.dart b/lib/content_views/assigned_jobs_page.dart index 49003bc..0bbbdcd 100644 --- a/lib/content_views/assigned_jobs_page.dart +++ b/lib/content_views/assigned_jobs_page.dart @@ -143,7 +143,7 @@ class _AssignedJobsPageState extends State with ProducerMixin, ), ], ), - Text('Auftragsnummer: ${job.id}'), + Text('Auftragsnummer: ${job.externalId}'), Text(job.tours[0].comp), Text(job.tours[0].street), Text(job.tours[0].city), @@ -187,9 +187,9 @@ class _AssignedJobsPageState extends State with ProducerMixin, Row(children: [ Expanded( child: getDefaultButton(!job.global ? 'Ablehnen' : 'Ignorieren', () { - job.sendMessage( + sendMessage( routingKey: Constants.messageTopicRemoveJob, - payload: RemoveJobMessage(id: job.id), + payload: RemoveJobMessage(id: job.externalId), ); _sendAnswer(job, !job.global ? 2 : 3); @@ -202,9 +202,9 @@ class _AssignedJobsPageState extends State with ProducerMixin, const SizedBox(width: 20), Expanded( child: getDefaultButton('Annehmen', () { - job.sendMessage( + sendMessage( routingKey: Constants.messageTopicRemoveJob, - payload: RemoveJobMessage(id: job.id), + payload: RemoveJobMessage(id: job.externalId), ); _sendAnswer(job, 1); @@ -231,7 +231,7 @@ class _AssignedJobsPageState extends State with ProducerMixin, var currentPosition = await MyApp.getCurrentPosition(); var message = Reaction(); - message.id = job.id; + message.id = job.externalId; // Use external ID for network communication message.ver = job.ver.toString(); message.item = item; message.track = Track(); @@ -244,7 +244,7 @@ class _AssignedJobsPageState extends State with ProducerMixin, sendMessage( routingKey: Constants.messageTopicRemoveState0JobFromMap, - payload: RemoveState0JobFromMapMessage(jobId: job.id), + payload: RemoveState0JobFromMapMessage(jobId: job.externalId), // Use external ID ); sendMessage( diff --git a/lib/content_views/finished_jobs_page.dart b/lib/content_views/finished_jobs_page.dart index 324d97e..9e03d18 100644 --- a/lib/content_views/finished_jobs_page.dart +++ b/lib/content_views/finished_jobs_page.dart @@ -69,7 +69,7 @@ class _FinishedJobsPageState extends State for (var job in _jobs) { var bottomText = - "${job.id}\n${job.tours[0].comp}\n${job.tours[0].street}\n${job.tours[0].city}"; + "${job.externalId}\n${job.tours[0].comp}\n${job.tours[0].street}\n${job.tours[0].city}"; if (job.rem.trim().isNotEmpty) { bottomText += "\n\n${job.rem.trim()}"; } diff --git a/lib/model/chat_message.dart b/lib/model/chat_message.dart index d6d598d..f895aa3 100644 --- a/lib/model/chat_message.dart +++ b/lib/model/chat_message.dart @@ -1,5 +1,5 @@ -import 'package:objectbox/objectbox.dart'; import 'package:stadtbote/main.dart'; +import 'package:stadtbote/objectbox.g.dart'; import 'package:stadtbote/tools/util.dart'; @Entity() @@ -7,6 +7,10 @@ class ChatMessage { @Id() int id = 0; + // External ID from server + @Unique() + late int externalId; + late int time; late String from; @@ -21,7 +25,8 @@ class ChatMessage { factory ChatMessage.fromJson(Map json) { final message = ChatMessage(); - message.id = json['id'] as int? ?? 0; + message.id = 0; // Let ObjectBox assign the ID + message.externalId = json['id'] as int; // Store external ID separately message.time = json['time'] as int; message.from = json['from'] as String; message.subj = json['subj'] as String; @@ -32,7 +37,7 @@ class ChatMessage { Map toJson() { return { - 'id': id, + 'id': externalId, // Use external ID for JSON 'time': time, 'from': from, 'subj': subj, @@ -42,10 +47,16 @@ class ChatMessage { } static Future mergeFromJson(Map json) async { - var availableMessage = MyApp.objectBoxStore.box().get(json["id"] as int); + final box = MyApp.objectBoxStore.box(); + final externalId = json["id"] as int; + + // Find message by external ID + final query = box.query(ChatMessage_.externalId.equals(externalId)).build(); + var availableMessage = query.findFirst(); + query.close(); if (availableMessage != null) { - availableMessage.id = json["id"]; + availableMessage.externalId = json["id"]; availableMessage.time = json["time"]; availableMessage.from = json["from"]; availableMessage.subj = json["subj"]; diff --git a/lib/model/job.dart b/lib/model/job.dart index db338e3..a18e7db 100644 --- a/lib/model/job.dart +++ b/lib/model/job.dart @@ -1,13 +1,16 @@ import 'dart:convert'; -import 'package:dart_mq/dart_mq.dart'; import 'package:objectbox/objectbox.dart'; import 'package:stadtbote/model/job_tour.dart'; @Entity() -class Job with ProducerMixin { +class Job { @Id() int id = 0; + // External ID from server + @Unique() + late int externalId; + late String ver; late int state; String? crFilter; @@ -35,7 +38,8 @@ class Job with ProducerMixin { factory Job.fromJson(Map json) { final job = Job(); - job.id = json['id'] as int? ?? 0; + job.id = 0; // Let ObjectBox assign the ID + job.externalId = json['id'] as int; // Store external ID separately job.ver = json['ver'] as String; job.state = json['state'] as int; job.crFilter = json['cr_filter'] as String?; @@ -53,7 +57,7 @@ class Job with ProducerMixin { Map toJson() { return { - 'id': id, + 'id': externalId, // Use external ID for JSON 'ver': ver, 'state': state, 'cr_filter': crFilter, diff --git a/lib/objectbox-model.json b/lib/objectbox-model.json index 9ac5b58..2ef9361 100644 --- a/lib/objectbox-model.json +++ b/lib/objectbox-model.json @@ -34,7 +34,7 @@ }, { "id": "3:808686831791135454", - "lastPropertyId": "11:3214338916427673014", + "lastPropertyId": "12:5320049580625017283", "name": "Job", "properties": [ { @@ -92,6 +92,13 @@ "id": "11:3214338916427673014", "name": "toursJson", "type": 9 + }, + { + "id": "12:5320049580625017283", + "name": "externalId", + "type": 6, + "flags": 40, + "indexId": "1:2108836818156887439" } ], "relations": [] @@ -297,7 +304,7 @@ }, { "id": "11:8606238749478059367", - "lastPropertyId": "6:7798301556204918939", + "lastPropertyId": "7:4056540326670791484", "name": "ChatMessage", "properties": [ { @@ -330,6 +337,13 @@ "id": "6:7798301556204918939", "name": "read", "type": 1 + }, + { + "id": "7:4056540326670791484", + "name": "externalId", + "type": 6, + "flags": 40, + "indexId": "2:428171949166894655" } ], "relations": [] @@ -370,7 +384,7 @@ } ], "lastEntityId": "12:4865606913057136676", - "lastIndexId": "0:0", + "lastIndexId": "2:428171949166894655", "lastRelationId": "0:0", "lastSequenceId": "0:0", "modelVersion": 5, diff --git a/lib/objectbox.g.dart b/lib/objectbox.g.dart index 3329218..4345807 100644 --- a/lib/objectbox.g.dart +++ b/lib/objectbox.g.dart @@ -65,7 +65,7 @@ final _entities = [ obx_int.ModelEntity( id: const obx_int.IdUid(3, 808686831791135454), name: 'Job', - lastPropertyId: const obx_int.IdUid(11, 3214338916427673014), + lastPropertyId: const obx_int.IdUid(12, 5320049580625017283), flags: 0, properties: [ obx_int.ModelProperty( @@ -134,6 +134,13 @@ final _entities = [ type: 9, flags: 0, ), + obx_int.ModelProperty( + id: const obx_int.IdUid(12, 5320049580625017283), + name: 'externalId', + type: 6, + flags: 40, + indexId: const obx_int.IdUid(1, 2108836818156887439), + ), ], relations: [], backlinks: [], @@ -375,7 +382,7 @@ final _entities = [ obx_int.ModelEntity( id: const obx_int.IdUid(11, 8606238749478059367), name: 'ChatMessage', - lastPropertyId: const obx_int.IdUid(6, 7798301556204918939), + lastPropertyId: const obx_int.IdUid(7, 4056540326670791484), flags: 0, properties: [ obx_int.ModelProperty( @@ -414,6 +421,13 @@ final _entities = [ type: 1, flags: 0, ), + obx_int.ModelProperty( + id: const obx_int.IdUid(7, 4056540326670791484), + name: 'externalId', + type: 6, + flags: 40, + indexId: const obx_int.IdUid(2, 428171949166894655), + ), ], relations: [], backlinks: [], @@ -499,7 +513,7 @@ obx_int.ModelDefinition getObjectBoxModel() { final model = obx_int.ModelInfo( entities: _entities, lastEntityId: const obx_int.IdUid(12, 4865606913057136676), - lastIndexId: const obx_int.IdUid(0, 0), + lastIndexId: const obx_int.IdUid(2, 428171949166894655), lastRelationId: const obx_int.IdUid(0, 0), lastSequenceId: const obx_int.IdUid(0, 0), retiredEntityUids: const [3121960281516643928, 3330049220205502332], @@ -578,7 +592,7 @@ obx_int.ModelDefinition getObjectBoxModel() { : fbb.writeString(object.crFilter!); final remOffset = fbb.writeString(object.rem); final toursJsonOffset = fbb.writeString(object.toursJson); - fbb.startTable(12); + fbb.startTable(13); fbb.addInt64(0, object.id); fbb.addOffset(1, verOffset); fbb.addInt64(2, object.state); @@ -590,6 +604,7 @@ obx_int.ModelDefinition getObjectBoxModel() { fbb.addOffset(8, remOffset); fbb.addInt64(9, object.delayOrderTime); fbb.addOffset(10, toursJsonOffset); + fbb.addInt64(11, object.externalId); fbb.finish(fbb.endTable()); return object.id; }, @@ -636,7 +651,13 @@ obx_int.ModelDefinition getObjectBoxModel() { ) ..toursJson = const fb.StringReader( asciiOptimization: true, - ).vTableGet(buffer, rootOffset, 24, ''); + ).vTableGet(buffer, rootOffset, 24, '') + ..externalId = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 26, + 0, + ); return object; }, @@ -894,13 +915,14 @@ obx_int.ModelDefinition getObjectBoxModel() { final fromOffset = fbb.writeString(object.from); final subjOffset = fbb.writeString(object.subj); final textOffset = fbb.writeString(object.text); - fbb.startTable(7); + fbb.startTable(8); fbb.addInt64(0, object.id); fbb.addInt64(1, object.time); fbb.addOffset(2, fromOffset); fbb.addOffset(3, subjOffset); fbb.addOffset(4, textOffset); fbb.addBool(5, object.read); + fbb.addInt64(6, object.externalId); fbb.finish(fbb.endTable()); return object.id; }, @@ -925,6 +947,12 @@ obx_int.ModelDefinition getObjectBoxModel() { rootOffset, 14, false, + ) + ..externalId = const fb.Int64Reader().vTableGet( + buffer, + rootOffset, + 16, + 0, ); return object; @@ -1055,6 +1083,11 @@ class Job_ { static final toursJson = obx.QueryStringProperty( _entities[1].properties[10], ); + + /// See [Job.externalId]. + static final externalId = obx.QueryIntegerProperty( + _entities[1].properties[11], + ); } /// [LocationStatus] entity fields to define ObjectBox queries. @@ -1251,6 +1284,11 @@ class ChatMessage_ { static final read = obx.QueryBooleanProperty( _entities[8].properties[5], ); + + /// See [ChatMessage.externalId]. + static final externalId = obx.QueryIntegerProperty( + _entities[8].properties[6], + ); } /// [TaskInfo] entity fields to define ObjectBox queries. diff --git a/lib/popups/accept_jobs_popup.dart b/lib/popups/accept_jobs_popup.dart index fe33443..d446ba8 100644 --- a/lib/popups/accept_jobs_popup.dart +++ b/lib/popups/accept_jobs_popup.dart @@ -13,6 +13,7 @@ import 'package:stadtbote/tools/messages/job_deleted_message.dart'; import 'package:stadtbote/tools/messages/job_received_message.dart'; import 'package:stadtbote/tools/messages/refresh_badges_message.dart'; import 'package:stadtbote/tools/messages/remove_job_message.dart'; +import 'package:stadtbote/tools/mqtt_core.dart'; import 'package:stadtbote/tools/util.dart'; import '../tools/messages/publish_payload_message.dart'; @@ -216,9 +217,9 @@ Future> _fetchJobWidgets(StateSetter dialogState, Function() onFini jobMap.remove(job.id); - job.sendMessage( + MqttCore.instance.sendMessage( routingKey: Constants.messageTopicRemoveJob, - payload: RemoveJobMessage(id: job.id), + payload: RemoveJobMessage(id: job.externalId), ); setState(() {}); @@ -233,9 +234,9 @@ Future> _fetchJobWidgets(StateSetter dialogState, Function() onFini jobMap.remove(job.id); - job.sendMessage( + MqttCore.instance.sendMessage( routingKey: Constants.messageTopicRemoveJob, - payload: RemoveJobMessage(id: job.id), + payload: RemoveJobMessage(id: job.externalId), ); setState(() {}); @@ -350,9 +351,9 @@ Future> _fetchJobWidgets(StateSetter dialogState, Function() onFini jobMap.remove(job.id); - job.sendMessage( + MqttCore.instance.sendMessage( routingKey: Constants.messageTopicRemoveJob, - payload: RemoveJobMessage(id: job.id), + payload: RemoveJobMessage(id: job.externalId), ); setState(() {}); @@ -367,9 +368,9 @@ Future> _fetchJobWidgets(StateSetter dialogState, Function() onFini jobMap.remove(job.id); - job.sendMessage( + MqttCore.instance.sendMessage( routingKey: Constants.messageTopicRemoveJob, - payload: RemoveJobMessage(id: job.id), + payload: RemoveJobMessage(id: job.externalId), ); setState(() {}); @@ -394,7 +395,7 @@ _sendAnswer(SharedMapReference mapReference, Job job, int item) async { var currentPosition = await MyApp.getCurrentPosition(); var message = Reaction(); - message.id = job.id; + message.id = job.externalId; // Use external ID for network communication message.ver = job.ver.toString(); message.item = item; message.track = Track(); @@ -405,12 +406,12 @@ _sendAnswer(SharedMapReference mapReference, Job job, int item) async { message.track.pos!.long = currentPosition != null ? currentPosition.longitude : 0.0; message.track.pos!.acc = currentPosition != null ? currentPosition.accuracy.toInt() : 0; - job.sendMessage( + MqttCore.instance.sendMessage( routingKey: Constants.messageTopicPublishPayload, payload: PublishPayloadMessage(topic: "reaction", message: message.toJson(), acknowledgeRequired: true), ); - job.sendMessage( + MqttCore.instance.sendMessage( routingKey: Constants.messageTopicUpdateBadges, payload: RefreshBadgesMessage(), ); diff --git a/lib/tools/mqtt_core.dart b/lib/tools/mqtt_core.dart index 8f68457..19134b5 100644 --- a/lib/tools/mqtt_core.dart +++ b/lib/tools/mqtt_core.dart @@ -394,6 +394,17 @@ class MqttCore with ProducerMixin, ConsumerMixin { Job newJob = Job.fromJson(payloadData); final box = MyApp.objectBoxStore.box(); + + // Check if job already exists by externalId + final query = box.query(Job_.externalId.equals(newJob.externalId)).build(); + final existingJob = query.findFirst(); + query.close(); + + if (existingJob != null) { + // Update existing job + newJob.id = existingJob.id; + } + box.put(newJob); if (newJob.state == Constants.stateAssigned) { @@ -416,10 +427,14 @@ class MqttCore with ProducerMixin, ConsumerMixin { var jobDelMessage = JobDelMessage.fromJson(payloadData); final box = MyApp.objectBoxStore.box(); - var job = box.get(jobDelMessage.id); + + // Find job by external ID + final query = box.query(Job_.externalId.equals(jobDelMessage.id)).build(); + final job = query.findFirst(); + query.close(); if (job != null) { - box.remove(jobDelMessage.id); + box.remove(job.id); // Use ObjectBox ID for removal sendMessage( routingKey: Constants.messageTopicJobDeleted, @@ -613,7 +628,7 @@ class MqttCore with ProducerMixin, ConsumerMixin { for (var job in jobs) { SyncJobMessageJob syncJob = SyncJobMessageJob(); syncJob.state = job.state; - syncJob.id = job.id; + syncJob.id = job.externalId; // Use external ID for network communication syncJob.ver = job.ver.toString(); answer.sync.jobs.add(syncJob); } @@ -687,7 +702,7 @@ class MqttCore with ProducerMixin, ConsumerMixin { void _acknowlegdeJob(Job job) { var message = AcknowledgeJobReceived(); - message.id = job.id; + message.id = job.externalId; // Use external ID for network communication message.state = job.state; message.ver = job.ver; message.utc = Util.nowUnixSecondsUtc();