335 lines
12 KiB
Dart
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,
|
|
)),
|
|
],
|
|
),
|
|
],
|
|
)));
|
|
}
|
|
}
|