flutter-rp-example/lib/widgets/scan_flow/delivery_confirmed.dart
Nathan SOULIER fff5617757 First commit
2025-03-24 10:12:56 +01:00

335 lines
12 KiB
Dart

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<DeliveryConfirmed> {
@override
void initState() {
super.initState();
}
final GlobalKey<SfSignaturePadState> signaturePadKey = GlobalKey();
var _selectedAnomalyReason = "";
final ImagePicker _picker = ImagePicker();
final List<File> _imageFileList = [];
Future<bool> _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<TrackingInputAPIClient>()
.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<void> _uploadPhotos(GetBucketSTSResult stsinfo, String resourceName) async {
GcsService gcsService = locator.get<GcsService>();
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<void> _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<GcsApiClient>();
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<GcsService>();
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<bool> _handlePostAnomalyDelivery() async {
GetBucketSTSResult? stsinfo;
GcsApiClient gcsApiClient = locator.get<GcsApiClient>();
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<String> _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<DropdownMenuItem<String>>((String reason) {
return DropdownMenuItem<String>(
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,
)),
],
),
],
)));
}
}