import 'dart:io'; import 'dart:typed_data'; import 'package:dart_core_sdk/gcs-api.pbgrpc.dart'; import 'package:dart_core_sdk/handlingunit.pb.dart'; import 'package:dart_core_sdk/trackingInput.pbgrpc.dart'; import 'package:dart_core_sdk/transportShared.pb.dart'; import 'package:dart_core_sdk/transportShared.pbenum.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:sampleapp/globals.dart'; import 'package:sampleapp/services/gcs.dart'; import 'package:sampleapp/widgets/components/reflex_alert.dart'; import 'package:sampleapp/widgets/components/reflex_button.dart'; import 'package:sampleapp/widgets/components/reflex_circular_progress.dart'; import 'package:sampleapp/widgets/components/reflex_dropdown_button_form_field.dart'; import 'package:sampleapp/widgets/components/reflex_signature.dart'; import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; import 'package:syncfusion_flutter_signaturepad/signaturepad.dart'; import '../../locator.dart'; import '../components/reflex_hu_info.dart'; import 'dart:ui'; import 'package:badges/badges.dart' as bd; import 'package:dart_core_sdk/shared.pb.dart' as rp; class DeliveryConfirmed extends StatefulWidget { final bool isDeliveryValid; final Handlingunit handlingUnit; final String projectID; final void Function() onNextDelivery; const DeliveryConfirmed( {super.key, required this.isDeliveryValid, required this.handlingUnit, required this.projectID, required this.onNextDelivery}); @override _DeliveryConfirmedState createState() => _DeliveryConfirmedState(); } class _DeliveryConfirmedState extends State { @override void initState() { super.initState(); } final GlobalKey signaturePadKey = GlobalKey(); var _selectedAnomalyReason = ""; final ImagePicker _picker = ImagePicker(); final List _imageFileList = []; Future _callTrackingServiceNotified( TrackingEventCode code, String reason) async { TrackingEvent event = TrackingEvent( code: code, date: rp.DateTime(dateTime: DateTime.now().toUtc().toIso8601String(), authorTimeZone: "UTC"), reason: reason); bool notified = false; await locator .get() .notified( TrackingNotifiedRequest( header: rp.RequestProjectHeader( projectID: widget.projectID, ), iD: rp.EntityID( refID: widget.handlingUnit.iD.refID, ), payload: TrackingNotifiedPayload( events: [event], ) ) ).then((value) => notified = true); return notified; } String _buildFileName(String fileName) { return "${fileName}_${widget.handlingUnit.iD.refID}_${DateTime.now().toUtc().toIso8601String().split('.')[0].replaceAll("-", "_").replaceAll(":", "_")}.png"; } void _takePhoto() async { XFile? pickedFile = await _picker.pickImage( source: ImageSource.camera, maxHeight: 720, maxWidth: 1280); if (pickedFile == null) return; setState(() => _imageFileList.add(File(pickedFile.path))); } Future _uploadPhotos(GetBucketSTSResult stsinfo, String resourceName) async { GcsService gcsService = locator.get(); for (var i = 0; i < _imageFileList.length; i++) { final String fileName = _buildFileName("photo_$i"); await gcsService .uploadImage( stsinfo.accessToken, stsinfo.bucketName, resourceName, "${widget.handlingUnit.iD.refID}/photos/$fileName", _imageFileList[i]) .catchError((e) => throw e); } } Future _handlePostSuccesfulDelivery() async { // Call the TrackingService to notify the delivery var notified = await _callTrackingServiceNotified( TrackingEventCode.TRACKING_EVENT_IFTSTA_21, ""); if (!notified) { throw Exception(AppLocalizations.of(context)!.cannotNotifyDelivery); } // Upload Signature // First get a token STS GetBucketSTSResult? stsinfo; GcsApiClient gcsApiService = locator.get(); await gcsApiService.getBucketSTS(GetBucketSTSRequest(projectID: widget.projectID)).then((v) => { stsinfo = v, }); // Look for the project id in the resourcelist final String resourceName = stsinfo!.resources.firstWhere( (element) => element.endsWith("${widget.projectID}/"), orElse: () => ""); if (resourceName == "") { throw Exception( AppLocalizations.of(context)!.missingSendingFilesRights); } // Turn the signature into a png file final String fileName = _buildFileName("signature"); final image = await signaturePadKey.currentState!.toImage(pixelRatio: 3.0); final signatureByteData = await image.toByteData(format: ImageByteFormat.png); final Uint8List signatureBytes = signatureByteData!.buffer.asUint8List( signatureByteData.offsetInBytes, signatureByteData.lengthInBytes); final String path = (await getTemporaryDirectory()).path; final String tmpfileName = "$path/signature.png"; final File signatureFile = File(tmpfileName); await signatureFile.writeAsBytes(signatureBytes); // Upload the signature to GCS GcsService gcsService = locator.get(); await gcsService .uploadImage( stsinfo!.accessToken, stsinfo!.bucketName, resourceName, "${widget.handlingUnit.iD.refID}/signatures/$fileName", signatureFile) .catchError((e) => throw e); // And the photos await _uploadPhotos(stsinfo!, resourceName); } Future _handlePostAnomalyDelivery() async { GetBucketSTSResult? stsinfo; GcsApiClient gcsApiClient = locator.get(); await gcsApiClient.getBucketSTS(GetBucketSTSRequest(projectID: widget.projectID)).then((v) => { stsinfo = v, }); // Look for the project id in the resourcelist final String resourceName = stsinfo!.resources.firstWhere( (element) => element.endsWith("${widget.projectID}/"), orElse: () => ""); if (resourceName == "") { throw Exception( AppLocalizations.of(context)!.missingSendingFilesRights); } await _uploadPhotos(stsinfo!, resourceName); var notified = await _callTrackingServiceNotified( TrackingEventCode.TRACKING_EVENT_IFTSTA_56, _selectedAnomalyReason); if (!notified) { throw Exception(AppLocalizations.of(context)!.cannotNotifyDelivery); } return true; } void _handleOnNextDelivery() async { try { showDialog( barrierDismissible: false, context: context, builder: (context) => const Center(child: ReflexCircularProgress())); if (widget.isDeliveryValid) { await _handlePostSuccesfulDelivery(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(AppLocalizations.of(context)!.deliveryOk), ), ); } else { await _handlePostAnomalyDelivery(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(AppLocalizations.of(context)!.deliveryWithAnomaly), ), ); } Navigator.of(context, rootNavigator: true).pop(); widget.onNextDelivery(); } catch (e) { Navigator.of(context, rootNavigator: true).pop(); showDialog( context: context, builder: (context) => AlertDialog( title: Text(AppLocalizations.of(context)!.error), content: Text(e.toString()), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () => {Navigator.of(context).pop(), widget.onNextDelivery()}, child: Text(AppLocalizations.of(context)!.nextDelivery), ), ], )); } } Widget _buildSignatureBox() { return ReflexSignature(signaturePadKey: signaturePadKey); } Widget _buildAnomalyInput() { final List _anomalyReasons = [ AppLocalizations.of(context)!.anomalyBadHandlingUnit, AppLocalizations.of(context)!.anomalyCustomerRefused, AppLocalizations.of(context)!.anomalyCustomerUnavailable, AppLocalizations.of(context)!.anomalyOther ]; _selectedAnomalyReason = _anomalyReasons[0]; return ReflexDropdownButtonFormField( label: AppLocalizations.of(context)!.anomalyReason, value: _anomalyReasons[0], items: _anomalyReasons.map>((String reason) { return DropdownMenuItem( value: reason, child: Text(reason), ); }).toList(), onChanged: (String? value) { setState(() { _selectedAnomalyReason = value!; }); }, ); } @override Widget build(BuildContext context) { final dateNowLocale = AppLocalizations.of(context)!.dateOn(DateTime.now(), DateTime.now()); return Scaffold( body: Padding( padding: const EdgeInsets.all(15.0), child: Column( children: [ ReflexHUInfo( hu: widget.handlingUnit, showArticles: false, ), const SizedBox( height: 10, ), SizedBox( width: double.infinity, child: ReflexAlert( bigIcon: true, title: widget.isDeliveryValid ? AppLocalizations.of(context)!.deliveryOk : AppLocalizations.of(context)!.deliveryWithAnomaly, text: dateNowLocale, icon: widget.isDeliveryValid ? Icons.check : Icons.error, color: widget.isDeliveryValid ? Globals.RP_SUCCESS_COLOR : Globals.RP_DANGER_COLOR, )), const SizedBox( height: 15, ), Flexible( child: widget.isDeliveryValid ? _buildSignatureBox() : _buildAnomalyInput()), const SizedBox( height: 10, ), Row( children: [ Expanded( child: bd.Badge( badgeStyle: const bd.BadgeStyle( badgeColor: Globals.RP_PRIMARY_COLOR, ), badgeContent: Text( _imageFileList.length.toString(), style: const TextStyle(color: Colors.white), ), child: ReflexButton( isFullWidth: true, color: Globals.RP_LIGHT_COLOR, textColor: Colors.black, text: AppLocalizations.of(context)!.addPhoto, onPressed: _takePhoto, ))), const SizedBox(width: 15), Expanded( child: ReflexButton( text: AppLocalizations.of(context)!.nextDelivery, onPressed: _handleOnNextDelivery, )), ], ), ], ))); } }