import 'dart:math'; import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart'; import 'package:logging/logging.dart'; import 'package:moneymgr_mobile/services/storage/expenses.dart'; /// Attempt to extract information from invoice image Future extractInfoFromBill({ required Uint8List imgBuff, required bool extractDates, }) async { final decodedImage = await decodeImageFromList(imgBuff); final byteData = await decodedImage.toByteData( format: ui.ImageByteFormat.rawRgba, ); final image = InputImage.fromBitmap( bitmap: byteData!.buffer.asUint8List(), width: decodedImage.width, height: decodedImage.height, ); final textRecognizer = TextRecognizer(script: TextRecognitionScript.latin); final extractionResult = await textRecognizer.processImage(image); Logger.root.fine("Expense text: ${extractionResult.text}"); // Check for highestCost amount on invoice final costRegexp = RegExp( r'([0-9]+([ ]*(\\.|,)[ ]*[0-9]{1,2}){0,1})([ \\t\\n]*(EUR|eur|€)|E)', multiLine: true, caseSensitive: false, ); var highestCost = 0.0; for (final match in costRegexp.allMatches(extractionResult.text)) { if (match.groupCount == 0) continue; // Process only numeric value final value = (match.group(1) ?? "").replaceAll(",", "."); highestCost = max(highestCost, double.tryParse(value) ?? 0.0); } // Check for highestCost amount on invoice final dateRegexp = RegExp( r'([0-3][0-9])(\/|-)([0-1][0-9])(\/|-)((20|)[0-9]{2})', multiLine: false, caseSensitive: false, ); final currDate = DateTime.now(); DateTime? newest; for (final match in dateRegexp.allMatches(extractionResult.text)) { if (match.groupCount < 6) continue; int year = int.tryParse(match.group(5)!) ?? currDate.year; try { final date = DateTime( year > 99 ? year : (2000 + year), int.tryParse(match.group(3)!) ?? currDate.month, int.tryParse(match.group(1)!) ?? currDate.day, ); if (newest == null) { newest = date; } else { newest = DateTime.fromMillisecondsSinceEpoch( max(newest.millisecondsSinceEpoch, date.millisecondsSinceEpoch), ); } } catch (e, s) { Logger.root.warning("Failed to parse date! $e$s"); } } return BaseExpenseInfo( label: null, cost: highestCost, time: extractDates && (newest?.isBefore(currDate) ?? false) ? newest! : currDate, ); }