import 'dart:async'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:shared_map/shared_map.dart'; import 'package:stadtbote/main.dart'; import 'package:stadtbote/model/job.dart'; import 'package:stadtbote/network_messages/accept_job_message.dart'; import 'package:stadtbote/network_messages/fin_task_message.dart'; import 'package:stadtbote/tools/extended_stateful_builder.dart'; import 'package:stadtbote/tools/constants.dart'; 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'; final ScrollController _scrollController = ScrollController(); Future showAcceptJobsPopup(SharedMapReference mapReference, Function() onFinished) async { late ExtendendStatefulBuilder extendedStatefulBuilder; bool builderCreated = false; bool isActive = true; final dialogFuture = showDialog( barrierDismissible: true, context: MyApp.appNavigatorKey.currentContext!, builder: (BuildContext context) { List widgets = []; StateSetter? extendedStatefulBuilderStateSetter; extendedStatefulBuilder = ExtendendStatefulBuilder(builder: (BuildContext context, StateSetter setState) { extendedStatefulBuilderStateSetter = setState; return SizedBox( key: GlobalKey(), height: 370, width: MediaQuery.of(context).size.width - 10, child: Container( padding: const EdgeInsets.all(5), child: FutureBuilder>( future: _fetchJobWidgets(setState, onFinished, mapReference), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Util.getWaitWidget(context); } else { return Scrollbar( thumbVisibility: true, controller: _scrollController, child: ListView( controller: _scrollController, children: snapshot.requireData, ), ); } }, ), )); }); builderCreated = true; extendedStatefulBuilder.subscribe( queueId: Constants.messageTopicJobReceived, callback: (message) { var jobReceivedMessage = message.payload as JobReceivedMessage; if (!isActive || extendedStatefulBuilderStateSetter == null) return; if (jobReceivedMessage.job.state == 0) { extendedStatefulBuilderStateSetter!(() {}); } }, ); extendedStatefulBuilder.subscribe( queueId: Constants.messageTopicJobDeleted, callback: (message) { var jobDeletedMessage = message.payload as JobDeletedMessage; if (!isActive || extendedStatefulBuilderStateSetter == null) return; if (jobDeletedMessage.job.state == 0) { extendedStatefulBuilderStateSetter!(() {}); } }, ); widgets.add(extendedStatefulBuilder); return Material( color: Colors.transparent, child: Stack( children: [ Align( alignment: Alignment.bottomCenter, child: Container( padding: const EdgeInsets.all(5), height: 480, color: Colors.white, child: Padding( padding: const EdgeInsets.all(30), child: Column( children: [ Expanded( child: Padding( padding: const EdgeInsets.all(2.0), child: Scrollbar( child: ListView(children: widgets), ), ), ), getDefaultButton("Liste schliessen", () { isActive = false; if (builderCreated) { extendedStatefulBuilder.unsubscribe(queueId: Constants.messageTopicJobReceived); extendedStatefulBuilder.unsubscribe(queueId: Constants.messageTopicJobDeleted); } Navigator.pop(context); onFinished(); }) ], ), ), )) ], ), ); }, ); // Ensure we always clean up subscriptions even if the dialog is dismissed via barrier tap or back button dialogFuture.whenComplete(() { isActive = false; if (builderCreated) { try { extendedStatefulBuilder.unsubscribe(queueId: Constants.messageTopicJobReceived); extendedStatefulBuilder.unsubscribe(queueId: Constants.messageTopicJobDeleted); } catch (_) { // ignore cleanup errors } } }); return dialogFuture; } Future> _fetchJobWidgets(StateSetter dialogState, Function() onFinished, SharedMapReference mapReference) async { List widgets = []; var jobMap = SharedMap.fromSharedReference(mapReference); var entries = await jobMap.entries(); for (var jobEntry in entries) { var job = jobEntry.value; var scrollController = ScrollController(); widgets.add(StatefulBuilder(builder: (BuildContext context, StateSetter setState) { return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), title: Container( height: 280, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), border: Border.all( color: Constants.colorPrimary, width: 1, ), ), child: Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ SizedBox( height: 200, child: Scrollbar( controller: scrollController, thumbVisibility: true, child: SingleChildScrollView( controller: scrollController, child: Column( children: [ SizedBox( width: double.infinity, child: Text( DateFormat('dd.MM.yyyy HH:mm').format(DateTime.fromMillisecondsSinceEpoch(job.ordertime * 1000, isUtc: true).toLocal()).toString(), textAlign: TextAlign.left, style: TextStyle(backgroundColor: isJobTooFarInFuture(job) ? Colors.red : Colors.transparent), ), ), SizedBox(width: double.infinity, child: Text('Auftragsnummer: ${job.id}', textAlign: TextAlign.left)), SizedBox(width: double.infinity, child: Text(job.tours[0].comp, textAlign: TextAlign.left)), SizedBox(width: double.infinity, child: Text(job.tours[0].street, textAlign: TextAlign.left)), SizedBox(width: double.infinity, child: Text(job.tours[0].city, textAlign: TextAlign.left)), Row( children: [ Image.asset("assets/${mapVehicleTypeToImageName(job.vhtId)}", width: 60, height: 60), const SizedBox(width: 10), Text(mapVehicleTypeToText(job.vhtId)), ], ), const SizedBox(height: 10), SizedBox(width: double.infinity, child: Text(job.rem, textAlign: TextAlign.left)), ], ), ), ), ), const SizedBox(height: 10), Row(children: [ Expanded( child: getDefaultButton(!job.global ? 'Ablehnen' : 'Ignorieren', () { dialogState(() { _sendAnswer(mapReference, job, !job.global ? 2 : 3); jobMap.remove(job.id); MqttCore.instance.sendMessage( routingKey: Constants.messageTopicRemoveJob, payload: RemoveJobMessage(id: job.externalId), ); setState(() {}); }); }), ), const SizedBox(width: 20), Expanded( child: getDefaultButton('Annehmen', enabledTimerActive: false, () { dialogState(() { _sendAnswer(mapReference, job, 1); jobMap.remove(job.id); MqttCore.instance.sendMessage( routingKey: Constants.messageTopicRemoveJob, payload: RemoveJobMessage(id: job.externalId), ); setState(() {}); }); }), ) ]) ], ), ), ), ); })); /*widgets.add(StatefulBuilder(builder: (BuildContext context, StateSetter setState) { return ExpandableNotifier( child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), title: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), border: Border.all( color: Constants.colorPrimary, width: 1, ), ), child: badges.Badge( position: badges.BadgePosition.topEnd(top: -13, end: -5), showBadge: false, badgeContent: const Text( 'Neu', style: TextStyle( color: Colors.white, fontSize: 18.0, ), ), badgeStyle: badges.BadgeStyle( shape: badges.BadgeShape.square, badgeColor: Constants.colorPrimary, padding: const EdgeInsets.all(5), borderRadius: BorderRadius.circular(5), borderSide: const BorderSide(color: Constants.colorPrimary, width: 0), ), child: Padding( padding: const EdgeInsets.all(12.0), child: Column( children: [ SizedBox( width: double.infinity, child: Text( DateFormat('dd.MM.yyyy HH:mm').format(DateTime.fromMillisecondsSinceEpoch(job.ordertime, isUtc: true).toLocal()).toString(), textAlign: TextAlign.left, style: TextStyle(backgroundColor: isJobTooFarInFuture(job) ? Colors.red : Colors.transparent), ), ), SizedBox(width: double.infinity, child: Text('Auftragsnummer: ${job.id}', textAlign: TextAlign.left)), SizedBox(width: double.infinity, child: Text(job.tours[0].comp, textAlign: TextAlign.left)), SizedBox(width: double.infinity, child: Text(job.tours[0].street, textAlign: TextAlign.left)), SizedBox(width: double.infinity, child: Text(job.tours[0].city, textAlign: TextAlign.left)), Row( children: [ Image.asset("assets/${mapVehicleTypeToImageName(job.vhtId)}", width: 60, height: 60), const SizedBox(width: 10), Text(mapVehicleTypeToText(job.vhtId)), ], ), const SizedBox(height: 10), ScrollOnExpand( scrollOnExpand: true, scrollOnCollapse: false, child: ExpandablePanel( controller: controller, theme: const ExpandableThemeData( headerAlignment: ExpandablePanelHeaderAlignment.center, tapBodyToCollapse: true, ), header: const Padding( padding: EdgeInsets.all(0), child: Text( "Weitere Infos....", style: TextStyle(fontSize: 15, color: Colors.black, fontWeight: FontWeight.bold), )), collapsed: const SizedBox(), expanded: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( height: 200, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: const Color(0xfffff4e4), border: Border.all( color: Colors.grey, width: 0.5, ), ), child: Padding( padding: const EdgeInsets.all(8.0), child: Text(job.rem), ), ), ], ), ), ), const SizedBox(height: 20), Row(children: [ Expanded( child: getDefaultButton(!job.global ? 'Ablehnen' : 'Ignorieren', () { dialogState(() { _sendAnswer(mapReference, job, !job.global ? 2 : 3); jobMap.remove(job.id); MqttCore.instance.sendMessage( routingKey: Constants.messageTopicRemoveJob, payload: RemoveJobMessage(id: job.externalId), ); setState(() {}); }); }), ), const SizedBox(width: 20), Expanded( child: getDefaultButton('Annehmen', enabledTimerActive: false, () { dialogState(() { _sendAnswer(mapReference, job, 1); jobMap.remove(job.id); MqttCore.instance.sendMessage( routingKey: Constants.messageTopicRemoveJob, payload: RemoveJobMessage(id: job.externalId), ); setState(() {}); }); }), ) ]) ], ), ), ), ), ), ); }))*/ } return widgets; } _sendAnswer(SharedMapReference mapReference, Job job, int item) async { var currentPosition = await MyApp.getCurrentPosition(); var message = Reaction(); message.id = job.externalId; // Use external ID for network communication message.ver = job.ver.toString(); message.item = item; message.track = Track(); message.track.time = Util.nowUnixSecondsUtc(); message.track.pos = FinTaskPos(); message.track.pos!.time = currentPosition != null ? Util.toUnixSecondsUtc(currentPosition.timestamp) : 0; message.track.pos!.lat = currentPosition != null ? currentPosition.latitude : 0.0; message.track.pos!.long = currentPosition != null ? currentPosition.longitude : 0.0; message.track.pos!.acc = currentPosition != null ? currentPosition.accuracy.toInt() : 0; MqttCore.instance.sendMessage( routingKey: Constants.messageTopicPublishPayload, payload: PublishPayloadMessage(topic: "reaction", message: message.toJson(), acknowledgeRequired: true), ); MqttCore.instance.sendMessage( routingKey: Constants.messageTopicUpdateBadges, payload: RefreshBadgesMessage(), ); }