From fbdd2a43386e24a9ded7facaef8daa2102fa8237 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Fri, 21 Jun 2024 13:34:43 +0200 Subject: [PATCH] Erweiterungen --- lib/content_views/approach_area_page.dart | 3 +- lib/content_views/assigned_jobs_page.dart | 3 +- lib/content_views/main_page.dart | 3 +- lib/content_views/messages_page.dart | 3 +- lib/content_views/task_list_page.dart | 7 +- lib/main.dart | 13 +- lib/model/job.dart | 8 + lib/model/job.g.dart | 166 ++++++++++++++---- lib/popups/accept_jobs_popup.dart | 53 ++++-- lib/tools/message_service.dart | 16 ++ .../messages/publish_payload_message.dart | 6 +- lib/tools/mqtt_core.dart | 39 ++-- lib/tools/util.dart | 90 ++++++---- pubspec.yaml | 2 +- 14 files changed, 296 insertions(+), 116 deletions(-) diff --git a/lib/content_views/approach_area_page.dart b/lib/content_views/approach_area_page.dart index 10be342..53b6af5 100644 --- a/lib/content_views/approach_area_page.dart +++ b/lib/content_views/approach_area_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:isar/isar.dart'; +import 'package:mqtt_client/mqtt_client.dart'; import 'package:stadtbote/content_views/utils/content_view_widget.dart'; import 'package:stadtbote/content_views/utils/header_info.dart'; import 'package:stadtbote/main.dart'; @@ -211,7 +212,7 @@ class _ApproachAreaPageState extends State { setPosMessage.track!.pos!.acc = position.accuracy.toInt(); } - MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "set_zip", message: setPosMessage.toJson())); + MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "set_zip", message: setPosMessage.toJson(), qos: MqttQos.exactlyOnce)); } _initMessages() { diff --git a/lib/content_views/assigned_jobs_page.dart b/lib/content_views/assigned_jobs_page.dart index 3c69eb8..b4e91d3 100644 --- a/lib/content_views/assigned_jobs_page.dart +++ b/lib/content_views/assigned_jobs_page.dart @@ -2,6 +2,7 @@ import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:isar/isar.dart'; +import 'package:mqtt_client/mqtt_client.dart'; import 'package:stadtbote/content_views/utils/content_view_widget.dart'; import 'package:stadtbote/content_views/utils/header_info.dart'; import 'package:intl/intl.dart'; @@ -199,7 +200,7 @@ class _AssignedJobsPageState extends State { message.track.pos!.long = currentPosition != null ? currentPosition.longitude : 0.0; message.track.pos!.acc = currentPosition != null ? currentPosition.accuracy.toInt() : 0; - MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "reaction", message: message.toJson())); + MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "reaction", message: message.toJson(), qos: MqttQos.exactlyOnce)); await MyApp.isarInstance.writeTxn(() async { await MyApp.isarInstance.jobs.delete(job.id); diff --git a/lib/content_views/main_page.dart b/lib/content_views/main_page.dart index 5446ac4..3c2e2fd 100644 --- a/lib/content_views/main_page.dart +++ b/lib/content_views/main_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:isar/isar.dart'; +import 'package:mqtt_client/mqtt_client.dart'; import 'package:stadtbote/content_views/accepted_jobs_page.dart'; import 'package:stadtbote/content_views/approach_area_page.dart'; import 'package:stadtbote/content_views/assigned_jobs_page.dart'; @@ -380,6 +381,6 @@ class _MainPageState extends State { logoutMessage.pos!.acc = position.accuracy.toInt(); } - MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "logout", message: logoutMessage.toJson())); + MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "logout", message: logoutMessage.toJson(), qos: MqttQos.exactlyOnce)); } } diff --git a/lib/content_views/messages_page.dart b/lib/content_views/messages_page.dart index 51b342b..d2009bd 100644 --- a/lib/content_views/messages_page.dart +++ b/lib/content_views/messages_page.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_chat_ui/flutter_chat_ui.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart' as types; import 'package:isar/isar.dart'; +import 'package:mqtt_client/mqtt_client.dart'; import 'package:stadtbote/content_views/utils/content_view_widget.dart'; import 'package:stadtbote/content_views/utils/header_info.dart'; import 'package:stadtbote/main.dart'; @@ -109,7 +110,7 @@ class _MessagesPageState extends State { await MyApp.isarInstance.messages.put(message); }); - MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "message", message: message.toJson())); + MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "message", message: message.toJson(), qos: MqttQos.exactlyOnce)); _messages.insert(0, types.TextMessage( author: _user, diff --git a/lib/content_views/task_list_page.dart b/lib/content_views/task_list_page.dart index c6e26fd..c5bfc59 100644 --- a/lib/content_views/task_list_page.dart +++ b/lib/content_views/task_list_page.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:isar/isar.dart'; +import 'package:mqtt_client/mqtt_client.dart'; import 'package:stadtbote/content_views/utils/content_view_widget.dart'; import 'package:stadtbote/content_views/utils/header_info.dart'; import 'package:stadtbote/messages/pop_content_view_message.dart'; @@ -482,7 +483,7 @@ class _TaskListPageState extends State { message.task.pos = null; } - MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "fin_task", message: message.toJson())); + MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "fin_task", message: message.toJson(), qos: MqttQos.exactlyOnce)); } FinTourTask _createAnswerHeader(TaskInfo taskInfo) { @@ -526,7 +527,7 @@ class _TaskListPageState extends State { } } - MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "fin_tour", message: finTourMessage.toJson())); + MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "fin_tour", message: finTourMessage.toJson(), qos: MqttQos.exactlyOnce)); widget.tour.state = Constants.stateFinishedNotConfirmed; @@ -551,7 +552,7 @@ class _TaskListPageState extends State { message.time = DateTime.now().millisecondsSinceEpoch; message.data = picture.data; - MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "pic", message: message.toJson())); + MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "pic", message: message.toJson(), qos: MqttQos.exactlyOnce)); await MyApp.isarInstance.writeTxn(() async { await MyApp.isarInstance.pictures.delete(picture.id); diff --git a/lib/main.dart b/lib/main.dart index e0e6208..c73eef1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -56,7 +56,9 @@ Future main() async { Future _clearLog() async { var dateTime = DateTime.now().add(const Duration(days: -7)); - await MyApp.isarInstance.logEntrys.where().filter().timeLessThan(dateTime).deleteAll(); + await MyApp.isarInstance.writeTxn(() async { + await MyApp.isarInstance.logEntrys.where().filter().timeLessThan(dateTime).deleteAll(); + }); } void _traceUncaughtExceptions(Object error, StackTrace stackTrace) { @@ -342,6 +344,13 @@ class _MainState extends State
with SingleTickerProviderStateMixin { } void _initMessages() { + MessageService().unsubscribe(Constants.messageTopicPushContentView, this); + MessageService().unsubscribe(Constants.messageTopicPopContentView, this); + MessageService().unsubscribe(Constants.messageTopicReplaceContentView, this); + MessageService().unsubscribe(Constants.messageTopicHideRightButton, this); + MessageService().unsubscribe(Constants.messageTopicConnectionStatusChanged, this); + MessageService().unsubscribe(Constants.messageTopicJobReceived, this); + MessageService().subscribe(Constants.messageTopicPushContentView, this, (ContentViewWidget widget) { _pushContentView(widget); }); @@ -441,6 +450,8 @@ class _MainState extends State
with SingleTickerProviderStateMixin { } void _connectionStatusChanged(ConnectionStatus connectionStatus) { + Util.logInfo("_connectionStatusChanged called: $connectionStatus"); + _connectionLostValueNotifier.value = connectionStatus.status == ConnectionStatusEnum.notConnected; } diff --git a/lib/model/job.dart b/lib/model/job.dart index e7f481d..2b3351e 100644 --- a/lib/model/job.dart +++ b/lib/model/job.dart @@ -26,9 +26,17 @@ class Job { late int vhtId; @JsonKey(name: 'rem') late String rem; + @JsonKey(name: 'delayOrderTime') + late int delayOrderTime; @JsonKey(name: 'tours') late List tours; + @Ignore() + late DateTime? firstDisplayTime = null; + + @Ignore() + late int enableDelay; + Job(); factory Job.fromJson(Map json) => _$JobFromJson(json); diff --git a/lib/model/job.g.dart b/lib/model/job.g.dart index 69a23cf..e04ea3b 100644 --- a/lib/model/job.g.dart +++ b/lib/model/job.g.dart @@ -22,44 +22,49 @@ const JobSchema = CollectionSchema( name: r'crFilter', type: IsarType.string, ), - r'global': PropertySchema( + r'delayOrderTime': PropertySchema( id: 1, + name: r'delayOrderTime', + type: IsarType.long, + ), + r'global': PropertySchema( + id: 2, name: r'global', type: IsarType.bool, ), r'offsWarn': PropertySchema( - id: 2, + id: 3, name: r'offsWarn', type: IsarType.long, ), r'ordertime': PropertySchema( - id: 3, + id: 4, name: r'ordertime', type: IsarType.string, ), r'rem': PropertySchema( - id: 4, + id: 5, name: r'rem', type: IsarType.string, ), r'state': PropertySchema( - id: 5, + id: 6, name: r'state', type: IsarType.long, ), r'tours': PropertySchema( - id: 6, + id: 7, name: r'tours', type: IsarType.objectList, target: r'JobTour', ), r'ver': PropertySchema( - id: 7, + id: 8, name: r'ver', type: IsarType.string, ), r'vhtId': PropertySchema( - id: 8, + id: 9, name: r'vhtId', type: IsarType.long, ) @@ -116,19 +121,20 @@ void _jobSerialize( Map> allOffsets, ) { writer.writeString(offsets[0], object.crFilter); - writer.writeBool(offsets[1], object.global); - writer.writeLong(offsets[2], object.offsWarn); - writer.writeString(offsets[3], object.ordertime); - writer.writeString(offsets[4], object.rem); - writer.writeLong(offsets[5], object.state); + writer.writeLong(offsets[1], object.delayOrderTime); + writer.writeBool(offsets[2], object.global); + writer.writeLong(offsets[3], object.offsWarn); + writer.writeString(offsets[4], object.ordertime); + writer.writeString(offsets[5], object.rem); + writer.writeLong(offsets[6], object.state); writer.writeObjectList( - offsets[6], + offsets[7], allOffsets, JobTourSchema.serialize, object.tours, ); - writer.writeString(offsets[7], object.ver); - writer.writeLong(offsets[8], object.vhtId); + writer.writeString(offsets[8], object.ver); + writer.writeLong(offsets[9], object.vhtId); } Job _jobDeserialize( @@ -139,21 +145,22 @@ Job _jobDeserialize( ) { final object = Job(); object.crFilter = reader.readStringOrNull(offsets[0]); - object.global = reader.readBool(offsets[1]); + object.delayOrderTime = reader.readLong(offsets[1]); + object.global = reader.readBool(offsets[2]); object.id = id; - object.offsWarn = reader.readLong(offsets[2]); - object.ordertime = reader.readString(offsets[3]); - object.rem = reader.readString(offsets[4]); - object.state = reader.readLong(offsets[5]); + object.offsWarn = reader.readLong(offsets[3]); + object.ordertime = reader.readString(offsets[4]); + object.rem = reader.readString(offsets[5]); + object.state = reader.readLong(offsets[6]); object.tours = reader.readObjectList( - offsets[6], + offsets[7], JobTourSchema.deserialize, allOffsets, JobTour(), ) ?? []; - object.ver = reader.readString(offsets[7]); - object.vhtId = reader.readLong(offsets[8]); + object.ver = reader.readString(offsets[8]); + object.vhtId = reader.readLong(offsets[9]); return object; } @@ -167,16 +174,18 @@ P _jobDeserializeProp

( case 0: return (reader.readStringOrNull(offset)) as P; case 1: - return (reader.readBool(offset)) as P; - case 2: return (reader.readLong(offset)) as P; + case 2: + return (reader.readBool(offset)) as P; case 3: - return (reader.readString(offset)) as P; + return (reader.readLong(offset)) as P; case 4: return (reader.readString(offset)) as P; case 5: - return (reader.readLong(offset)) as P; + return (reader.readString(offset)) as P; case 6: + return (reader.readLong(offset)) as P; + case 7: return (reader.readObjectList( offset, JobTourSchema.deserialize, @@ -184,9 +193,9 @@ P _jobDeserializeProp

( JobTour(), ) ?? []) as P; - case 7: - return (reader.readString(offset)) as P; case 8: + return (reader.readString(offset)) as P; + case 9: return (reader.readLong(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -425,6 +434,59 @@ extension JobQueryFilter on QueryBuilder { }); } + QueryBuilder delayOrderTimeEqualTo( + int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'delayOrderTime', + value: value, + )); + }); + } + + QueryBuilder delayOrderTimeGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'delayOrderTime', + value: value, + )); + }); + } + + QueryBuilder delayOrderTimeLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'delayOrderTime', + value: value, + )); + }); + } + + QueryBuilder delayOrderTimeBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'delayOrderTime', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + QueryBuilder globalEqualTo(bool value) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.equalTo( @@ -1134,6 +1196,18 @@ extension JobQuerySortBy on QueryBuilder { }); } + QueryBuilder sortByDelayOrderTime() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'delayOrderTime', Sort.asc); + }); + } + + QueryBuilder sortByDelayOrderTimeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'delayOrderTime', Sort.desc); + }); + } + QueryBuilder sortByGlobal() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'global', Sort.asc); @@ -1232,6 +1306,18 @@ extension JobQuerySortThenBy on QueryBuilder { }); } + QueryBuilder thenByDelayOrderTime() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'delayOrderTime', Sort.asc); + }); + } + + QueryBuilder thenByDelayOrderTimeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'delayOrderTime', Sort.desc); + }); + } + QueryBuilder thenByGlobal() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'global', Sort.asc); @@ -1337,6 +1423,12 @@ extension JobQueryWhereDistinct on QueryBuilder { }); } + QueryBuilder distinctByDelayOrderTime() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'delayOrderTime'); + }); + } + QueryBuilder distinctByGlobal() { return QueryBuilder.apply(this, (query) { return query.addDistinctBy(r'global'); @@ -1394,6 +1486,12 @@ extension JobQueryProperty on QueryBuilder { }); } + QueryBuilder delayOrderTimeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'delayOrderTime'); + }); + } + QueryBuilder globalProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'global'); @@ -1457,9 +1555,13 @@ Job _$JobFromJson(Map json) => Job() ..global = json['global'] as bool ..vhtId = json['vht_id'] as int ..rem = json['rem'] as String + ..delayOrderTime = json['delayOrderTime'] as int ..tours = (json['tours'] as List) .map((e) => JobTour.fromJson(e as Map)) - .toList(); + .toList() + ..firstDisplayTime = json['firstDisplayTime'] == null + ? null + : DateTime.parse(json['firstDisplayTime'] as String); Map _$JobToJson(Job instance) => { 'id': instance.id, @@ -1471,5 +1573,7 @@ Map _$JobToJson(Job instance) => { 'global': instance.global, 'vht_id': instance.vhtId, 'rem': instance.rem, + 'delayOrderTime': instance.delayOrderTime, 'tours': instance.tours, + 'firstDisplayTime': instance.firstDisplayTime?.toIso8601String(), }; diff --git a/lib/popups/accept_jobs_popup.dart b/lib/popups/accept_jobs_popup.dart index 631fe0a..25753b7 100644 --- a/lib/popups/accept_jobs_popup.dart +++ b/lib/popups/accept_jobs_popup.dart @@ -1,7 +1,11 @@ +import 'dart:math'; + import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:isar/isar.dart'; +import 'package:mqtt_client/mqtt_client.dart'; import 'package:stadtbote/main.dart'; import 'package:stadtbote/protocoll_messages/accept_job_message.dart'; import 'package:stadtbote/model/job.dart'; @@ -17,15 +21,13 @@ import 'package:badges/badges.dart' as badges; import '../tools/messages/publish_payload_message.dart'; Future showAcceptJobsPopup(BuildContext context, List jobs, Function() onFinished) async { - var jobsSet = jobs.toSet(); - await showDialog( context: context, builder: (BuildContext context) { return WillPopScope( onWillPop: () async { - MessageService().unsubscribe(Constants.messageTopicJobDeleted, jobsSet); - MessageService().unsubscribe(Constants.messageTopicJobReceived, jobsSet); + MessageService().unsubscribe(Constants.messageTopicJobDeleted, jobs); + MessageService().unsubscribe(Constants.messageTopicJobReceived, jobs); Navigator.of(context).pop(); @@ -38,27 +40,27 @@ Future showAcceptJobsPopup(BuildContext context, List jobs, Function( shadowColor: Colors.transparent, backgroundColor: Colors.white, content: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { - if (jobsSet.isEmpty) { + if (jobs.isEmpty) { Navigator.of(context).pop(); onFinished(); } - MessageService().unsubscribe(Constants.messageTopicJobDeleted, jobsSet); - MessageService().unsubscribe(Constants.messageTopicJobReceived, jobsSet); + MessageService().unsubscribe(Constants.messageTopicJobDeleted, jobs); + MessageService().unsubscribe(Constants.messageTopicJobReceived, jobs); - MessageService().subscribe(Constants.messageTopicJobDeleted, jobsSet, (jobDeletedMessage) async { - jobsSet.removeWhere((value) => value.id == jobDeletedMessage.id); + MessageService().subscribe(Constants.messageTopicJobDeleted, jobs, (jobDeletedMessage) async { + jobs.removeWhere((value) => value.id == jobDeletedMessage.id); if (context.mounted) { setState(() {}); } }); - MessageService().subscribe(Constants.messageTopicJobReceived, jobsSet, (jobdMessage) async { - jobsSet.removeWhere((value) => value.id == jobdMessage.job.id); + MessageService().subscribe(Constants.messageTopicJobReceived, jobs, (jobdMessage) async { + jobs.removeWhere((value) => value.id == jobdMessage.job.id); - jobsSet.add(jobdMessage.job); + jobs.add(jobdMessage.job); if (context.mounted) { setState(() {}); @@ -73,9 +75,24 @@ Future showAcceptJobsPopup(BuildContext context, List jobs, Function( height: 100, padding: const EdgeInsets.all(5), child: ListView.builder( - itemCount: jobsSet.length, + itemCount: jobs.length, itemBuilder: (_, i) { - var job = jobsSet.toList()[i]; + var job = jobs[i]; + + for (var job in jobs) { + int enableDelay = job.delayOrderTime; + if (job.firstDisplayTime == null) { + MyApp.isarInstance.writeTxn(() async { + job.firstDisplayTime = DateTime.now(); + + await MyApp.isarInstance.jobs.put(job); + }); + } else { + enableDelay = max(0, DateTime.now().difference(job.firstDisplayTime!).inSeconds); + } + + job.enableDelay = enableDelay; + } return ExpandableNotifier( child: ListTile( @@ -173,17 +190,17 @@ Future showAcceptJobsPopup(BuildContext context, List jobs, Function( () { _sendAnswer(job, !job.global ? 2 : 3); - jobsSet.removeWhere((value) => value.id == job.id); + jobs.removeWhere((value) => value.id == job.id); setState(() {}); }), ), const SizedBox(width: 20), Expanded( - child: getDefaultButton('Annehmen', () { + child: getDefaultButton('Annehmen', enabledTimerActive: job.global == false && job.enableDelay > 0, enabledTimerDelay: job.enableDelay, () { _sendAnswer(job, 1); - jobsSet.removeWhere((value) => value.id == job.id); + jobs.removeWhere((value) => value.id == job.id); setState(() {}); }), @@ -217,7 +234,7 @@ _sendAnswer(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; - MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "reaction", message: message.toJson())); + MessageService().publish(Constants.messageTopicPublishPayload, arguments: PublishPayloadMessage(topic: "reaction", message: message.toJson(), qos: MqttQos.exactlyOnce)); await MyApp.isarInstance.writeTxn(() async{ await MyApp.isarInstance.jobs.delete(job.id); diff --git a/lib/tools/message_service.dart b/lib/tools/message_service.dart index e14b5d3..e060fd6 100644 --- a/lib/tools/message_service.dart +++ b/lib/tools/message_service.dart @@ -37,6 +37,22 @@ class MessageService { if (entry.owner == owner) { _callbackMaps[topic]!.remove(entry); + unsubscribe(topic, owner); + + break; + } + } + } + } + + unsubscribeAll(dynamic owner) { + for (var callbackMapsEntry in _callbackMaps.entries) { + for (var entry in callbackMapsEntry.value.toList()) { + if (entry.owner == owner) { + _callbackMaps[callbackMapsEntry.key]!.remove(entry); + + unsubscribeAll(owner); + break; } } diff --git a/lib/tools/messages/publish_payload_message.dart b/lib/tools/messages/publish_payload_message.dart index f81c93b..7a5da19 100644 --- a/lib/tools/messages/publish_payload_message.dart +++ b/lib/tools/messages/publish_payload_message.dart @@ -1,7 +1,11 @@ +import 'package:mqtt_client/mqtt_client.dart'; + class PublishPayloadMessage { final String topic; final Map message; - PublishPayloadMessage({required this.topic, required this.message}); + final MqttQos qos; + + PublishPayloadMessage({required this.topic, required this.message, required this.qos}); } \ No newline at end of file diff --git a/lib/tools/mqtt_core.dart b/lib/tools/mqtt_core.dart index a295d3e..e4af67c 100644 --- a/lib/tools/mqtt_core.dart +++ b/lib/tools/mqtt_core.dart @@ -119,31 +119,29 @@ class MqttCore { _reconnectDelay = 0; - _setupMqtt(); + await _setupMqtt(); _initSession(); } else { Util.logInfo('Verbindung zum MQTT-Broker fehlgeschlagen!'); - MessageService().publish(Constants.messageTopicConnectionStatusChanged, - arguments: ConnectionStatus(status: ConnectionStatusEnum.notConnected)); + MessageService().publish(Constants.messageTopicConnectionStatusChanged, arguments: ConnectionStatus(status: ConnectionStatusEnum.notConnected)); } } catch (ex) { Util.logInfo('Verbindung zum MQTT-Broker fehlgeschlagen!: $ex'); - MessageService().publish(Constants.messageTopicConnectionStatusChanged, - arguments: ConnectionStatus(status: ConnectionStatusEnum.notConnected)); + MessageService().publish(Constants.messageTopicConnectionStatusChanged, arguments: ConnectionStatus(status: ConnectionStatusEnum.notConnected)); } } - bool _publishText(String topic, String text, {bool enqueueOnError = true}) { + bool _publishText(String topic, String text, MqttQos qos, {bool enqueueOnError = true}) { try { var buffer = Uint8Buffer(); buffer.addAll(GZipEncoder().encode(const Utf8Encoder().convert(text)) as Iterable); var messageTopic = "$_toServerTopic$_clientId/$topic"; - _mqttClient!.publishMessage(messageTopic, MqttQos.exactlyOnce, buffer); + _mqttClient!.publishMessage(messageTopic, qos, buffer); Util.logInfo("message\n$text\nsend to $messageTopic"); Util.logInfo("size in bytes: ${buffer.length}"); @@ -158,7 +156,7 @@ class MqttCore { return false; } - void _setupMqtt() async { + Future _setupMqtt() async { Util.logInfo('_setupMqtt called'); _mqttClient!.updates?.listen((List> messages) { @@ -368,7 +366,7 @@ class MqttCore { var json = jsonEncode(deviceLog.toJson()); - _publishText("device_log", json); + _publishText("device_log", json, MqttQos.atMostOnce); } void _startBootstrapReconnectTimer() { @@ -398,19 +396,17 @@ class MqttCore { _startTrackTimer(); - MessageService().publish(Constants.messageTopicConnectionStatusChanged, - arguments: ConnectionStatus(status: ConnectionStatusEnum.notConnected)); + MessageService().publish(Constants.messageTopicConnectionStatusChanged, arguments: ConnectionStatus(status: ConnectionStatusEnum.notConnected)); } void _clientConnected() { - Util.logInfo("_clientDisconnected called"); + Util.logInfo("_clientConnected called"); connectionState = ConnectionStateEnum.connected; _initSession(); - MessageService().publish(Constants.messageTopicConnectionStatusChanged, - arguments: ConnectionStatus(status: ConnectionStatusEnum.connected)); + MessageService().publish(Constants.messageTopicConnectionStatusChanged, arguments: ConnectionStatus(status: ConnectionStatusEnum.connected)); } void _processBootstrap() async { @@ -434,7 +430,6 @@ class MqttCore { var jsonMap = handshakeMessage.toJson(); var json = Uri.encodeFull(jsonEncode(jsonMap)); - //var uri = "https://test-app-server.assecutor.org/bootstrap?json=$json"; var uri = "https://bootstrap.assecutor.de/bootstrap?json=$json"; Util.logInfo("Bootstrap request: $uri"); @@ -454,7 +449,7 @@ class MqttCore { _connectToBroker(); } catch (ex) { - Util.logException("bootstrap failed"); + Util.logException("bootstrap failed: {$ex}"); _startBootstrapReconnectTimer(); } @@ -464,7 +459,7 @@ class MqttCore { var startSessionMessage = StartSessionMessage(); var jsonMap = startSessionMessage.toJson(); var json = jsonEncode(jsonMap); - _publishText("session", json); + _publishText("session", json, MqttQos.exactlyOnce); } _sendSyncMessage() async { @@ -482,7 +477,7 @@ class MqttCore { answer.sync.jobs.add(syncJob); } - _publishText("sync", jsonEncode(answer.toJson())); + _publishText("sync", jsonEncode(answer.toJson()), MqttQos.exactlyOnce); } void _startTrackTimer() { @@ -495,7 +490,7 @@ class MqttCore { trackMessage.long = position.longitude; trackMessage.acc = position.accuracy.toInt(); - _publishText("track", jsonEncode(trackMessage.toJson())); + _publishText("track", jsonEncode(trackMessage.toJson()), MqttQos.atMostOnce); } }); } @@ -522,7 +517,7 @@ class MqttCore { if (enqueuedMessages.isNotEmpty) { for (var enqueuedMessage in enqueuedMessages) { try { - if (_publishText(enqueuedMessage.topic, enqueuedMessage.payload, enqueueOnError: false)) { + if (_publishText(enqueuedMessage.topic, enqueuedMessage.payload, MqttQos.exactlyOnce, enqueueOnError: false)) { await MyApp.isarInstance.writeTxn(() async { await MyApp.isarInstance.enqueuedMessages.delete(enqueuedMessage.id); }); @@ -541,12 +536,12 @@ class MqttCore { message.ver = job.ver; message.utc = DateTime.now().millisecondsSinceEpoch; - _publishText('job_received', jsonEncode(message.toJson())); + _publishText('job_received', jsonEncode(message.toJson()), MqttQos.exactlyOnce); } void initMessages() { MessageService().subscribe(Constants.messageTopicPublishPayload, this, (PublishPayloadMessage payloadMessage) { - _publishText(payloadMessage.topic, jsonEncode(payloadMessage.message)); + _publishText(payloadMessage.topic, jsonEncode(payloadMessage.message), payloadMessage.qos); }); MessageService().subscribe(Constants.messageTopicDisconnectClient, this, diff --git a/lib/tools/util.dart b/lib/tools/util.dart index f8f3e20..97b64fb 100644 --- a/lib/tools/util.dart +++ b/lib/tools/util.dart @@ -220,43 +220,63 @@ class Util { } } -Widget getDefaultButton(String text, Function() onPressed, {Image? image, double? buttonHeight}) { - return SizedBox( - height: buttonHeight ?? 50, - child: MaterialButton( - color: const Color.fromARGB(255, 220, 220, 220), - disabledColor: Colors.indigo[400], - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(15), +Widget getDefaultButton(String text, Function() onPressed, {Image? image, double? buttonHeight, bool enabledTimerActive = true, int enabledTimerDelay = 0}) { + bool enabledState = enabledTimerActive; + + return StatefulBuilder(builder: (BuildContext context, StateSetter setState) { + if (!enabledState) { + Future.delayed(Duration(seconds: enabledTimerDelay), () { + enabledState = true; + + if (context.mounted) { + setState(() {}); + } + }); + } + return SizedBox( + height: buttonHeight ?? 50, + child: MaterialButton( + color: const Color.fromARGB(255, 220, 220, 220), + disabledColor: const Color.fromARGB(255, 220, 220, 220), + disabledElevation: 5, + elevation: 5, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(15), + ), + ), + onPressed: () { + if (enabledState) { + Util.logInfo("Button \"$text\" clicked"); + + onPressed(); + } + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Visibility( + visible: image != null, + child: CircleAvatar( + backgroundColor: Colors.transparent, + child: image, + ), + ), + Expanded( + child: Opacity( + opacity: enabledState ? 1.0 : 0.3, + child: Text( + text, + style: const TextStyle(color: Colors.black, fontSize: 14.0), + textAlign: TextAlign.center, + ), + ), + ), + ], ), ), - onPressed: () { - Util.logInfo("Button \"$text\" clicked"); - - onPressed(); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Visibility( - visible: image != null, - child: CircleAvatar( - backgroundColor: Colors.transparent, - child: image, - ), - ), - Expanded( - child: Text( - text, - style: const TextStyle(color: Colors.black, fontSize: 14.0), - textAlign: TextAlign.center, - ), - ), - ], - ), - ), - ); + ); + }); } String mapVehicleTypeToImageName(int vehType) diff --git a/pubspec.yaml b/pubspec.yaml index 138fbfd..4d56f26 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 314.0.0+314 +version: 317.0.0+317 environment: sdk: '>=2.19.6 <3.0.0'