import 'dart:convert'; import 'package:dart_mq/dart_mq.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:isar/isar.dart'; import 'package:stadtbote/content_views/utils/content_view_widget.dart'; import 'package:stadtbote/messages/pop_content_view_message.dart'; import 'package:stadtbote/model/job.dart'; import 'package:stadtbote/model/job_tour.dart'; import 'package:stadtbote/model/pictures.dart'; import 'package:stadtbote/network_messages/accept_job_message.dart'; import 'package:stadtbote/network_messages/fin_task_message.dart'; import 'package:stadtbote/network_messages/fin_tour_message.dart'; import 'package:stadtbote/network_messages/pic_message.dart'; import 'package:stadtbote/popups/tasks_popups/arrived_popup.dart'; import 'package:stadtbote/popups/tasks_popups/commision_number_popup.dart'; import 'package:stadtbote/popups/tasks_popups/pickup_delivery_status_popup.dart'; import 'package:stadtbote/popups/tasks_popups/receipt_giver_popup.dart'; import 'package:stadtbote/popups/tasks_popups/signature_popup.dart'; import 'package:stadtbote/popups/tasks_popups/station_notice_popup.dart'; import 'package:stadtbote/popups/tasks_popups/take_photos_popup.dart'; import 'package:stadtbote/tools/constants.dart'; import 'package:stadtbote/tools/messages/publish_payload_message.dart'; import '../main.dart'; import '../model/task_info.dart'; import '../tools/util.dart'; class TaskListPage extends ContentViewWidget { const TaskListPage({super.key, required this.job, required this.tour, this.readOnly = false}); final Job job; final JobTour tour; final bool readOnly; @override State createState() => _TaskListPageState(); @override Future getHeaderWidget(BuildContext context) async { await Future.delayed(const Duration(microseconds: 0)); return _HeaderWidget(); } } class _TaskListPageState extends State with ProducerMixin { late bool _initCompleted = false; late List _taskList; late String _commisionNumber; @override void initState() { Future.delayed(const Duration(seconds: 0), () async { _taskList = await _fetchTaskList(); _commisionNumber = widget.tour.com ?? ""; _initCompleted = true; if (mounted) { setState(() {}); } }); super.initState(); } @override Widget build(BuildContext context) { if (!_initCompleted) { return Util.getWaitWidget(context); } var steps = _initSteps(); return Padding( padding: const EdgeInsets.all(8.0), child: SingleChildScrollView( scrollDirection: Axis.vertical, padding: const EdgeInsets.all(0), child: AbsorbPointer( absorbing: widget.readOnly, child: Column( children: [ Visibility( visible: _commisionNumber.isNotEmpty, child: Row( children: [ Text('Komm-#: $_commisionNumber', style: const TextStyle(fontSize: 20), textAlign: TextAlign.left), ], ), ), const SizedBox(height: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: steps, ), ], ), ), ), ); } List _initSteps() { List steps = []; for (int i=0; i<_taskList.length; i++) { var taskInfo = _taskList[i]; var textColor = isTaskActive(i) ? Colors.black : const Color(0x33000000); var optional = taskInfo.task.opt != null && taskInfo.task.opt == true; steps.add( ListTile( minVerticalPadding: 0, title: Container( padding: const EdgeInsets.all(0), height: 60, child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Stack( children: [ Positioned( left: 16.5, top: i == 0 ? 40 : 40, child: Container( height: i == widget.tour.tasks!.length - 1 ? 0 : 20, width: 6, color: isTaskActive(i) ? Constants.colorPrimary : Constants.colorPrimaryOpaque, ), ), Column( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( shape: BoxShape.circle, color: isTaskActive(i) ? Constants.colorPrimary : Constants.colorPrimaryOpaque, ), child: const Icon(Icons.check, size: 30.0, color: Colors.white), ), ], ), ], ), const SizedBox(width: 20), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Text( taskInfo.task.topic + (optional ? "" : " *"), textAlign: TextAlign.left, style: TextStyle( color: textColor, fontSize: 15.0, ), ), const SizedBox(height: 5), SizedBox( width: MediaQuery.of(context).size.width * 0.7, child: Text( taskInfo.additionalText ?? '', textAlign: TextAlign.left, overflow: TextOverflow.ellipsis, style: TextStyle( color: textColor, fontSize: 12.0, ), ), ), ], ), ], ), ), onTap: () { if (isTaskActive(i)) { _processTask(taskInfo); } }, ), ); } return steps; } Future> _fetchTaskList() async { List taskList = []; for(var task in widget.tour.tasks!) { var taskInfo = await MyApp.isarInstance.taskInfos.filter().sIdEqualTo(task.sId).findFirst(); if (taskInfo == null) { taskInfo = TaskInfo(); taskInfo.id = await TaskInfo.getNextId(); taskInfo.sId = task.sId; taskInfo.answer = null; taskInfo.additionalText = null; taskInfo.completed = false; } taskInfo.task = task; taskList.add(taskInfo); } return taskList; } void _processTask(TaskInfo taskInfo) { switch(taskInfo.task.type) { case 2: _processSignature(taskInfo); break; case 3: _processTaskArrivedOrFinished(taskInfo); break; case 4: _processTakePhotos(taskInfo); break; case 5: // 26.02.2025 Nach Abstimmung mit Gunnar entfernt //_processScanBarcodes(taskInfo); break; case 7: _processReceiptGiver(taskInfo); break; case 8: _processStationNotice(taskInfo); break; case 9: _processCommissionNumber(taskInfo); break; case 11: _processPickupDeliveryStatusTask(taskInfo); break; } } _processTaskArrivedOrFinished(TaskInfo taskInfo) { Util.logInfo('Task ${taskInfo.task.sId} called'); if (!mounted) return; final currentContext = context; showTaskArrivedPopup(currentContext, taskInfo, () async { DateFormat dateFormat = DateFormat("dd.MM.yyyy HH:mm"); var answer = await _createAnswerHeader(taskInfo); taskInfo.answer = jsonEncode(answer.toJson()); taskInfo.additionalText = dateFormat.format(DateTime.now()); taskInfo.completed = true; _sendArrivedMessage(taskInfo); if (mounted) { setState(() {}); } _finishTaskProcessing(taskInfo); }); } _processCommissionNumber(TaskInfo taskInfo) { Util.logInfo('Task ${taskInfo.task.sId} called'); if (!mounted) return; final currentContext = context; showComissionNumberPopup(currentContext, taskInfo, (comissionNumber) async { var answer = await _createAnswerHeader(taskInfo); answer.val = comissionNumber; taskInfo.answer = jsonEncode(answer.toJson()); taskInfo.additionalText = comissionNumber; taskInfo.completed = true; _commisionNumber = comissionNumber; if (mounted) { setState(() {}); } _finishTaskProcessing(taskInfo); }); } /*_processScanBarcodes(TaskInfo taskInfo) { Util.logInfo('Task ${taskInfo.task.sId} called'); showScanBarcodesPopup(context, taskInfo, (List barcodes) async { var answer = await _createAnswerHeader(taskInfo); answer.codes = []; for(var barcode in barcodes) { answer.codes!.add(barcode); } taskInfo.answer = jsonEncode(answer.toJson()); taskInfo.additionalText = "Barcodes gescannt"; taskInfo.completed = true; setState(() {}); _finishTaskProcessing(taskInfo); }); }*/ _processTakePhotos(TaskInfo taskInfo) { Util.logInfo('Task ${taskInfo.task.sId} called'); if (!mounted) return; final currentContext = context; showTakePhotosPopup(currentContext, taskInfo, (List? photos) async { if (photos != null) { await MyApp.isarInstance.writeTxn(() async { await MyApp.isarInstance.pictures.filter().tourIdEqualTo(widget.tour.id).deleteAll(); for (int i = 0; i < photos.length; i++) { var pictureData = photos[i]; var picture = Picture(); picture.number = i; picture.tourId = widget.tour.id; picture.data = base64Encode(pictureData); await MyApp.isarInstance.pictures.put(picture); } }); taskInfo.additionalText = "Fotos aufgenommen"; taskInfo.completed = true; if (mounted) { setState(() {}); } _finishTaskProcessing(taskInfo); } }); } void _processStationNotice(TaskInfo taskInfo) { Util.logInfo('Task ${taskInfo.task.sId} called'); if (!mounted) return; final currentContext = context; showStationNoticePopup(currentContext, taskInfo, (notice) async { var answer = await _createAnswerHeader(taskInfo); answer.val = notice; taskInfo.answer = jsonEncode(answer.toJson()); taskInfo.additionalText = notice; taskInfo.completed = true; _finishTaskProcessing(taskInfo); if (mounted) { setState(() {}); } }); } void _processReceiptGiver(TaskInfo taskInfo) { Util.logInfo('Task ${taskInfo.task.sId} called'); if (!mounted) return; final currentContext = context; showReceiptGiverPopup(currentContext, taskInfo, (receiptGiver, selectedTourEntry) async { var answer = await _createAnswerHeader(taskInfo); answer.code = selectedTourEntry.code; answer.val = receiptGiver; taskInfo.answer = jsonEncode(answer.toJson()); taskInfo.additionalText = receiptGiver; taskInfo.completed = true; if (mounted) { setState(() {}); } _finishTaskProcessing(taskInfo); }); } void _processPickupDeliveryStatusTask(TaskInfo taskInfo) { Util.logInfo('Task ${taskInfo.task.sId} called'); final currentContext = context; Future.delayed(const Duration(seconds: 1), () async { if (!mounted) return; showPickupDeliveryStatusPopup(currentContext, taskInfo, (tourEntry, remark) async { var answer = await _createAnswerHeader(taskInfo); answer.code = tourEntry!.code; answer.val = remark; taskInfo.answer = jsonEncode(answer.toJson()); taskInfo.additionalText = tourEntry.text; taskInfo.completed = true; _finishTaskProcessing(taskInfo); if (mounted) { setState(() {}); } }); }); } void _processSignature(TaskInfo taskInfo) { Util.logInfo('Task ${taskInfo.task.sId} called'); MyApp.suppressAcceptDialog = true; SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft]); final currentContext = context; Future.delayed(const Duration(seconds: 1), () async { if (!mounted) return; showSignaturePopup(currentContext, taskInfo, (String? signature) async { MyApp.suppressAcceptDialog = false; SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); if (signature != null) { var answer = await _createAnswerHeader(taskInfo); answer.sign = signature; taskInfo.answer = jsonEncode(answer.toJson()); taskInfo.additionalText = "Unterschrift erfasst"; taskInfo.completed = true; _sendFinTourMessage(); _finishTaskProcessing(taskInfo); sendMessage( routingKey: Constants.messageTopicPopContentView, payload: PopContentViewMessage(count: 2), ); } }); }); } _finishTaskProcessing(TaskInfo taskInfo) async { Util.logInfo('Task ${taskInfo.task.sId} finished'); await MyApp.isarInstance.writeTxn(() async { await MyApp.isarInstance.taskInfos.put(taskInfo); }); } bool isTaskActive(int index) { if (!widget.readOnly) { var currentTaskInfo = _taskList[index]; if (currentTaskInfo.completed && !currentTaskInfo.task.reenter!) return false; for (var taskInfo in _taskList) { if (taskInfo == currentTaskInfo) break; if (!taskInfo.task.opt! && !taskInfo.completed) return false; } if (currentTaskInfo.task.reenter!) return true; return true; } return false; } _sendArrivedMessage(TaskInfo taskInfo) async { var message = FinTask(); message.trId = widget.tour.id; message.task = Task(); message.task.sId = taskInfo.task.sId; message.task.time = DateTime.now().toIso8601String(); var position = await MyApp.getCurrentPosition(); if (position != null) { message.task.pos = FinTaskPos(); message.task.pos!.time = DateTime.now().toIso8601String(); message.task.pos!.lat = position.latitude; message.task.pos!.long = position.longitude; } else { message.task.pos = null; } sendMessage( routingKey: Constants.messageTopicPublishPayload, payload: PublishPayloadMessage(topic: "fin_task", message: message.toJson(), acknowledgeRequired: true), ); } Future _createAnswerHeader(TaskInfo taskInfo) async { var position = await MyApp.getCurrentPosition(); var finTourTask = FinTourTask(); finTourTask.sId = taskInfo.task.sId; finTourTask.time = DateTime.now().toIso8601String(); if (position != null) { finTourTask.pos = FinTaskPos(); finTourTask.pos!.lat = position.latitude; finTourTask.pos!.long = position.longitude; finTourTask.pos!.acc = position.accuracy.toInt(); } else { finTourTask.pos = null; } return finTourTask; } _sendFinTourMessage() async { var finTourMessage = FinTour(); finTourMessage.trId = widget.tour.id; finTourMessage.com = _commisionNumber; var position = await MyApp.getCurrentPosition(); if (position != null) { finTourMessage.track = Track(); finTourMessage.track!.time = DateTime.now().toIso8601String(); finTourMessage.track!.pos = FinTaskPos(); finTourMessage.track!.pos!.lat = position.latitude; finTourMessage.track!.pos!.long = position.longitude; } finTourMessage.tasks = []; for(var taskInfo in _taskList) { if (taskInfo.answer != null) { var finTourTask = FinTourTask.fromJson(jsonDecode(taskInfo.answer!)); finTourMessage.tasks.add(finTourTask); } } sendMessage( routingKey: Constants.messageTopicPublishPayload, payload: PublishPayloadMessage(topic: "fin_tour", message: finTourMessage.toJson(), acknowledgeRequired: true), ); widget.tour.state = Constants.stateFinishedNotConfirmed; await MyApp.isarInstance.writeTxn(() async { await MyApp.isarInstance.jobs.put(widget.job); }); _sendImages(); await _checkJobFinished(); } _sendImages() { Future.delayed(const Duration(seconds: 1), () async { var pictures = await MyApp.isarInstance.pictures.where().filter().tourIdEqualTo(widget.tour.id).findAll(); if (pictures.isNotEmpty) { for (var picture in pictures) { var message = PicData(); message.trId = picture.tourId; message.num = picture.number; message.time = DateTime.now().millisecondsSinceEpoch; message.data = picture.data; sendMessage( routingKey: Constants.messageTopicPublishPayload, payload: PublishPayloadMessage(topic: "pic", message: message.toJson(), acknowledgeRequired: true), ); await MyApp.isarInstance.writeTxn(() async { await MyApp.isarInstance.pictures.delete(picture.id); }); } } }); } Future _checkJobFinished() async { var jobFinished = true; for (var tour in widget.job.tours) { if (tour.state == 0) { jobFinished = false; break; } } if (jobFinished) { widget.job.state = 2; await MyApp.isarInstance.writeTxn(() async { await MyApp.isarInstance.jobs.put(widget.job); }); } } } class _HeaderWidget extends StatefulWidget { @override State<_HeaderWidget> createState() => _HeaderWidgetState(); } class _HeaderWidgetState extends State<_HeaderWidget> with ProducerMixin { @override void initState() { super.initState(); } @override Widget build(BuildContext context) { var cellWidth = (MediaQuery.of(context).size.width - 60 - 60 - 25) / 2; return Padding( padding: const EdgeInsets.all(8), child: Stack( alignment: Alignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Visibility( visible: true, child: Container( width: 60, height: 60, decoration: const BoxDecoration( shape: BoxShape.circle, color: Colors.white, ), child: FittedBox( child: GestureDetector( onTap: () async { sendMessage( routingKey: Constants.messageTopicPopContentView, payload: PopContentViewMessage(count: 1), ); }, child: const CircleAvatar( radius: 10, backgroundColor: Constants.colorPrimary, child: Padding(padding: EdgeInsets.all(1.0), child: Icon(Icons.arrow_back, size: 20.0, color: Colors.white)), ), ), ), ), ), Visibility( visible: false, child: Container( width: 60, height: 60, decoration: const BoxDecoration( shape: BoxShape.circle, color: Colors.red, ), child: FittedBox( child: GestureDetector( onTap: () async { sendMessage( routingKey: Constants.messageTopicPopContentView, payload: PopContentViewMessage(count: 1), ); }, child: const CircleAvatar( backgroundColor: Constants.colorPrimary, radius: 10, child: Padding(padding: EdgeInsets.all(1.0), child: Icon(Icons.info_outline, size: 20.0, color: Colors.white))), ), ), ), ), ], ), SizedBox( height: 100, width: cellWidth * 2, child: const Center( child: Text( textAlign: TextAlign.center, "Liste der Aufgaben", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18, color: Colors.white), ), ), ), ], ), ); } }