@@ -110,6 +110,58 @@ class DigitScannerPageState extends LocalizedState<DigitScannerPage>
110110 static const _manualSerialNoFormKey = 'serialNoCode' ;
111111 static const _manualExpiryDateFormKey = 'expiryDate' ;
112112
113+ /// Safely parses DateTime from form control value
114+ /// Handles String, DateTime, int (milliseconds), and null values
115+ DateTime _parseExpiryDate (dynamic value) {
116+ // If already a DateTime, return it
117+ if (value is DateTime ) return value;
118+
119+ // If it's a String, try to parse with different formats
120+ if (value is String ) {
121+ final trimmed = value.trim ();
122+ if (trimmed.isEmpty) return DateTime .now ();
123+
124+ // Try common date formats
125+ final formats = [
126+ "dd/MM/yyyy" ,
127+ "dd/MM/yy" ,
128+ "dd MMM yyyy" ,
129+ "yyyy-MM-dd" ,
130+ "MM/dd/yyyy" ,
131+ ];
132+
133+ for (final format in formats) {
134+ try {
135+ return DateFormat (format).parse (trimmed);
136+ } catch (_) {
137+ // Try next format
138+ }
139+ }
140+
141+ // Try ISO8601 as fallback
142+ try {
143+ return DateTime .parse (trimmed);
144+ } catch (_) {
145+ debugPrint ('Failed to parse date string: $trimmed ' );
146+ return DateTime .now ();
147+ }
148+ }
149+
150+ // If it's an int, treat as milliseconds since epoch
151+ if (value is int ) {
152+ try {
153+ return DateTime .fromMillisecondsSinceEpoch (value);
154+ } catch (_) {
155+ debugPrint ('Failed to parse date from milliseconds: $value ' );
156+ return DateTime .now ();
157+ }
158+ }
159+
160+ // For any other type or null, return current date
161+ debugPrint ('Unexpected date type: ${value .runtimeType }' );
162+ return DateTime .now ();
163+ }
164+
113165 @override
114166 void initState () {
115167 super .initState ();
@@ -135,40 +187,47 @@ class DigitScannerPageState extends LocalizedState<DigitScannerPage>
135187 widget.initialBarcodeData! .isNotEmpty) {
136188 // Initialize with existing barcode (GS1) data for edit mode
137189 // Parse the comma-separated string (GTIN,SERIAL,BATCH,EXPIRY) back to GS1Barcode
138- final parts = widget.initialBarcodeData! .split (',' );
139- if (parts.length >= 4 ) {
140- final gtin = parts[0 ].trim ();
141- final serial = parts[1 ].trim ();
142- final batch = parts[2 ].trim ();
143- final expiryStr = parts[3 ].trim ();
190+ try {
191+ final parts = widget.initialBarcodeData! .split (',' );
192+ if (parts.length >= 4 &&
193+ parts.every ((part) => part.trim ().isNotEmpty)) {
194+ final gtin = parts[0 ].trim ();
195+ final serial = parts[1 ].trim ();
196+ final batch = parts[2 ].trim ();
197+ final expiryStr = parts[3 ].trim ();
144198
145- // Parse expiry date (format: dd MMM yyyy)
146- DateTime ? expiryDate;
147- try {
148- expiryDate = DateFormat ('dd MMM yyyy' ).parse (expiryStr);
149- } catch (_) {
150- expiryDate = DateTime .now ().add (const Duration (days: 365 ));
151- }
199+ // Parse expiry date (format: dd MMM yyyy)
200+ DateTime ? expiryDate;
201+ try {
202+ expiryDate = DateFormat ('dd MMM yyyy' ).parse (expiryStr);
203+ } catch (_) {
204+ expiryDate = DateTime .now ().add (const Duration (days: 365 ));
205+ }
152206
153- // Generate GS1 barcode string and parse it
154- final barcodeString = DigitScannerUtils ().generateGS1Barcode (
155- serialNumber: serial,
156- expiryDate: expiryDate,
157- batchNumber: batch,
158- gtin: gtin,
159- );
207+ // Generate GS1 barcode string and parse it
208+ final barcodeString = DigitScannerUtils ().generateGS1Barcode (
209+ serialNumber: serial,
210+ expiryDate: expiryDate,
211+ batchNumber: batch,
212+ gtin: gtin,
213+ );
160214
161- final parser = GS1BarcodeParser .defaultParser ();
162- final parsed = parser.parse (barcodeString);
215+ final parser = GS1BarcodeParser .defaultParser ();
216+ final parsed = parser.parse (barcodeString);
163217
164- result = [parsed];
165- context.read <DigitScannerBloc >().add (
166- DigitScannerEvent .handleScanner (
167- qrCode: [],
168- barCode: [parsed],
169- scannerId: widget.scannerId,
170- ),
171- );
218+ result = [parsed];
219+ context.read <DigitScannerBloc >().add (
220+ DigitScannerEvent .handleScanner (
221+ qrCode: [],
222+ barCode: [parsed],
223+ scannerId: widget.scannerId,
224+ ),
225+ );
226+ }
227+ } catch (e) {
228+ debugPrint ('Error parsing initial barcode data: $e ' );
229+ // Initialize with empty result on parse error
230+ result = [];
172231 }
173232 }
174233 }
@@ -461,57 +520,68 @@ class DigitScannerPageState extends LocalizedState<DigitScannerPage>
461520
462521 final bloc = context.read <DigitScannerBloc >();
463522 codes.add (form.control (_manualCodeFormKey).value);
464- final barcodeString =
465- DigitScannerUtils ().generateGS1Barcode (
466- serialNumber: form
467- .control (_manualSerialNoFormKey)
468- .value
469- .toString ()
470- .trim (),
471- expiryDate: form
472- .control (_manualExpiryDateFormKey)
473- .value as DateTime ,
474- batchNumber: form
475- .control (_manualCodeFormKey)
476- .value
477- .toString ()
478- .trim (),
479- );
480523
481- // Now parse it using your existing model
482- final parser = GS1BarcodeParser .defaultParser ();
483- final parsed = parser.parse (barcodeString);
484- // ✅ Append to existing barcodes; DO NOT touch qrCodes in GS1 mode
485- final updatedBarcodes =
486- List <GS1Barcode >.from (state.barCodes)
487- ..add (parsed);
524+ try {
525+ final barcodeString =
526+ DigitScannerUtils ().generateGS1Barcode (
527+ serialNumber: form
528+ .control (_manualSerialNoFormKey)
529+ .value
530+ .toString ()
531+ .trim (),
532+ expiryDate: _parseExpiryDate (form
533+ .control (_manualExpiryDateFormKey)
534+ .value),
535+ batchNumber: form
536+ .control (_manualCodeFormKey)
537+ .value
538+ .toString ()
539+ .trim (),
540+ );
488541
489- // Keep local mirror in sync (used by UI)
490- setState (() {
491- result = updatedBarcodes;
492- manualCode = false ;
493- });
542+ // Now parse it using your existing model
543+ final parser = GS1BarcodeParser .defaultParser ();
544+ final parsed = parser.parse (barcodeString);
545+ // ✅ Append to existing barcodes; DO NOT touch qrCodes in GS1 mode
546+ final updatedBarcodes =
547+ List <GS1Barcode >.from (state.barCodes)
548+ ..add (parsed);
494549
495- bloc.add (
496- DigitScannerEvent .handleScanner (
497- barCode: updatedBarcodes,
498- qrCode: state.qrCodes,
499- regex: widget.effectiveRegex,
500- patternMessage: widget.patternMessage,
501- scannerId: widget.scannerId,
502- ),
503- );
504- if (updatedBarcodes.length <
505- widget.effectiveQuantity) {
506- DigitScannerUtils ().buildDialog (context,
507- localizations, widget.effectiveQuantity);
508- }
509- setState (() {
510- manualCode = false ;
511- });
512- // Quantity gate for GS1
550+ // Keep local mirror in sync (used by UI)
551+ setState (() {
552+ result = updatedBarcodes;
553+ manualCode = false ;
554+ });
513555
514- initializeCameras ();
556+ bloc.add (
557+ DigitScannerEvent .handleScanner (
558+ barCode: updatedBarcodes,
559+ qrCode: state.qrCodes,
560+ regex: widget.effectiveRegex,
561+ patternMessage: widget.patternMessage,
562+ scannerId: widget.scannerId,
563+ ),
564+ );
565+ if (updatedBarcodes.length <
566+ widget.effectiveQuantity) {
567+ DigitScannerUtils ().buildDialog (context,
568+ localizations, widget.effectiveQuantity);
569+ }
570+ setState (() {
571+ manualCode = false ;
572+ });
573+
574+ initializeCameras ();
575+ } catch (e) {
576+ debugPrint ('Error parsing manual GS1 barcode: $e ' );
577+ Toast .showToast (
578+ context,
579+ type: ToastType .error,
580+ message: localizations.translate (
581+ i18.scanner.resourcesScanFailed),
582+ sentenceCaseEnabled: false ,
583+ );
584+ }
515585 } else {
516586 if (form.control (_manualCodeFormKey).value ==
517587 null ||
@@ -526,7 +596,6 @@ class DigitScannerPageState extends LocalizedState<DigitScannerPage>
526596 type: ToastType .error,
527597 message: localizations
528598 .translate (i18.scanner.enterManualCode),
529- sentenceCaseEnabled: false ,
530599 );
531600 } else {
532601 final bloc = context.read <DigitScannerBloc >();
@@ -663,15 +732,23 @@ class DigitScannerPageState extends LocalizedState<DigitScannerPage>
663732 cancelText: localizations.translate (
664733 i18.common.coreCommonCancel,
665734 ),
666- initialValue: DateFormat ('dd/MM/yy' )
667- .format (field.control.value),
735+ initialValue: DateFormat ('dd/MM/yyyy' )
736+ .format (_parseExpiryDate (
737+ field.control.value)),
668738 readOnly: false ,
669739 onChange: (value) {
670- form
671- .control (_manualExpiryDateFormKey)
672- .value = DateFormat (
673- "dd/MM/yyyy" )
674- .parse (value);
740+ try {
741+ form
742+ .control (_manualExpiryDateFormKey)
743+ .value = DateFormat (
744+ "dd/MM/yyyy" )
745+ .parse (value);
746+ } catch (e) {
747+ debugPrint ('Error parsing date: $e ' );
748+ form
749+ .control (_manualExpiryDateFormKey)
750+ .value = DateTime .now ();
751+ }
675752 },
676753 ),
677754 );
@@ -717,11 +794,13 @@ class DigitScannerPageState extends LocalizedState<DigitScannerPage>
717794 .toString ()
718795 .trim ()
719796 .isEmpty) {
720- Toast .showToast (context,
721- type: ToastType .error,
722- message: localizations
723- .translate (i18.scanner.enterManualCode),
724- sentenceCaseEnabled: false );
797+ Toast .showToast (
798+ context,
799+ type: ToastType .error,
800+ message: localizations
801+ .translate (i18.scanner.enterManualCode),
802+ sentenceCaseEnabled: false ,
803+ );
725804 } else {
726805 final bloc = context.read <DigitScannerBloc >();
727806 final updatedQRCodes =
0 commit comments