diff --git a/README.md b/README.md index 28e8f75..e3dfa42 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,17 @@ A Flutter project with implementation of all firebase libraries for Android and 1. [Flutter: Publish App to PlayStore | Fully Explained Demo P5](https://youtu.be/qpruGmff5Fw) +1. [Flutter Firebase MLKIT Tutorial](https://youtu.be/vT6gNFE0GBw) + ### Screenshots + ### NOTE -* This project is still under development. +- This project is still under development. # Pull Requests @@ -48,7 +51,7 @@ I welcome and encourage all pull requests. It usually will take me within 24-48 > If you found this project helpful or you learned something from the source code and want to thank me, consider buying me a cup of :coffee: > -> * [PayPal](https://www.paypal.me/imthepk/) +> - [PayPal](https://www.paypal.me/imthepk/) # License diff --git a/android/app/build.gradle b/android/app/build.gradle index d45c420..d007654 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -33,6 +33,7 @@ android { targetSdkVersion 27 versionCode 1 versionName "1.0" + multiDexEnabled true testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } diff --git a/ios/.symlinks/plugins/cloud_firestore b/ios/.symlinks/plugins/cloud_firestore index 85ecd35..e2ca29c 120000 --- a/ios/.symlinks/plugins/cloud_firestore +++ b/ios/.symlinks/plugins/cloud_firestore @@ -1 +1 @@ -/Users/pawankumar/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-0.4.0 \ No newline at end of file +/Users/pawankumar/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-0.7.3 \ No newline at end of file diff --git a/ios/.symlinks/plugins/firebase_admob b/ios/.symlinks/plugins/firebase_admob index 1879d14..fd55376 120000 --- a/ios/.symlinks/plugins/firebase_admob +++ b/ios/.symlinks/plugins/firebase_admob @@ -1 +1 @@ -/Users/pawankumar/.pub-cache/hosted/pub.dartlang.org/firebase_admob-0.5.2 \ No newline at end of file +/Users/pawankumar/.pub-cache/hosted/pub.dartlang.org/firebase_admob-0.5.5 \ No newline at end of file diff --git a/ios/.symlinks/plugins/firebase_auth b/ios/.symlinks/plugins/firebase_auth index 0479435..1ac721d 120000 --- a/ios/.symlinks/plugins/firebase_auth +++ b/ios/.symlinks/plugins/firebase_auth @@ -1 +1 @@ -/Users/pawankumar/.pub-cache/hosted/pub.dartlang.org/firebase_auth-0.5.3 \ No newline at end of file +/Users/pawankumar/.pub-cache/hosted/pub.dartlang.org/firebase_auth-0.5.11 \ No newline at end of file diff --git a/ios/.symlinks/plugins/firebase_core b/ios/.symlinks/plugins/firebase_core new file mode 120000 index 0000000..6a8c8fc --- /dev/null +++ b/ios/.symlinks/plugins/firebase_core @@ -0,0 +1 @@ +/Users/pawankumar/.pub-cache/hosted/pub.dartlang.org/firebase_core-0.2.4 \ No newline at end of file diff --git a/ios/.symlinks/plugins/google_sign_in b/ios/.symlinks/plugins/google_sign_in index 7f6af15..d4dcc36 120000 --- a/ios/.symlinks/plugins/google_sign_in +++ b/ios/.symlinks/plugins/google_sign_in @@ -1 +1 @@ -/Users/pawankumar/.pub-cache/hosted/pub.dartlang.org/google_sign_in-3.0.0 \ No newline at end of file +/Users/pawankumar/.pub-cache/hosted/pub.dartlang.org/google_sign_in-3.0.4 \ No newline at end of file diff --git a/ios/.symlinks/plugins/image_picker b/ios/.symlinks/plugins/image_picker new file mode 120000 index 0000000..2045e54 --- /dev/null +++ b/ios/.symlinks/plugins/image_picker @@ -0,0 +1 @@ +/Users/pawankumar/.pub-cache/hosted/pub.dartlang.org/image_picker-0.4.5 \ No newline at end of file diff --git a/ios/.symlinks/plugins/mlkit b/ios/.symlinks/plugins/mlkit new file mode 120000 index 0000000..1c801b1 --- /dev/null +++ b/ios/.symlinks/plugins/mlkit @@ -0,0 +1 @@ +/Users/pawankumar/.pub-cache/hosted/pub.dartlang.org/mlkit-0.4.1 \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 123abea..f380fe4 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -28,6 +28,21 @@ PODS: - Firebase/Firestore (5.0.1): - Firebase/CoreOnly - FirebaseFirestore (= 0.12.1) + - Firebase/MLVision (5.0.1): + - Firebase/CoreOnly + - FirebaseMLVision (= 0.9.0) + - Firebase/MLVisionBarcodeModel (5.0.1): + - Firebase/CoreOnly + - FirebaseMLVisionBarcodeModel (= 0.9.0) + - Firebase/MLVisionFaceModel (5.0.1): + - Firebase/CoreOnly + - FirebaseMLVisionFaceModel (= 0.9.0) + - Firebase/MLVisionLabelModel (5.0.1): + - Firebase/CoreOnly + - FirebaseMLVisionLabelModel (= 0.9.0) + - Firebase/MLVisionTextModel (5.0.1): + - Firebase/CoreOnly + - FirebaseMLVisionTextModel (= 0.9.0) - firebase_admob (0.0.1): - Firebase/AdMob - Firebase/Core @@ -39,6 +54,9 @@ PODS: - Firebase/Auth - Firebase/Core - Flutter + - firebase_core (0.0.1): + - Firebase/Core + - Flutter - FirebaseAnalytics (5.0.0): - FirebaseCore (~> 5.0) - FirebaseInstanceID (~> 3.0) @@ -65,11 +83,44 @@ PODS: - Protobuf (~> 3.1) - FirebaseInstanceID (3.0.0): - FirebaseCore (~> 5.0) + - FirebaseMLCommon (0.9.0) + - FirebaseMLVision (0.9.0): + - FirebaseCore (~> 5.0) + - FirebaseMLCommon (~> 0.9) + - GoogleAPIClientForREST/Core (~> 1.3) + - GoogleAPIClientForREST/Vision (~> 1.3) + - GoogleMobileVision/Detector (~> 1.3.0) + - FirebaseMLVisionBarcodeModel (0.9.0): + - GoogleMobileVision/BarcodeDetector (~> 1.3.0) + - FirebaseMLVisionFaceModel (0.9.0): + - GoogleMobileVision/FaceDetector (~> 1.3.0) + - FirebaseMLVisionLabelModel (0.9.0): + - GoogleMobileVision/LabelDetector (~> 1.3.0) + - FirebaseMLVisionTextModel (0.9.0): + - GoogleMobileVision/TextDetector (~> 1.3.0) - Flutter (1.0.0) - Google-Mobile-Ads-SDK (7.30.0) - google_sign_in (0.0.1): - Flutter - GoogleSignIn (~> 4.0) + - GoogleAPIClientForREST/Core (1.3.4): + - GTMSessionFetcher (>= 1.1.7) + - GoogleAPIClientForREST/Vision (1.3.4): + - GoogleAPIClientForREST/Core + - GTMSessionFetcher (>= 1.1.7) + - GoogleMobileVision/BarcodeDetector (1.3.0): + - GoogleMobileVision/Detector (~> 1.3) + - GoogleMobileVision/Detector (1.3.0): + - GoogleToolboxForMac/Logger (~> 2.1) + - "GoogleToolboxForMac/NSData+zlib (~> 2.1)" + - GTMSessionFetcher/Core (~> 1.1) + - Protobuf (~> 3.1) + - GoogleMobileVision/FaceDetector (1.3.0): + - GoogleMobileVision/Detector (~> 1.3) + - GoogleMobileVision/LabelDetector (1.3.0): + - GoogleMobileVision/Detector (~> 1.3) + - GoogleMobileVision/TextDetector (1.3.0): + - GoogleMobileVision/Detector (~> 1.3) - GoogleSignIn (4.1.2): - "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)" - "GoogleToolboxForMac/NSString+URLArguments (~> 2.1)" @@ -78,6 +129,8 @@ PODS: - GoogleToolboxForMac/DebugUtils (2.1.4): - GoogleToolboxForMac/Defines (= 2.1.4) - GoogleToolboxForMac/Defines (2.1.4) + - GoogleToolboxForMac/Logger (2.1.4): + - GoogleToolboxForMac/Defines (= 2.1.4) - "GoogleToolboxForMac/NSData+zlib (2.1.4)": - GoogleToolboxForMac/Defines (= 2.1.4) - "GoogleToolboxForMac/NSDictionary+URLArguments (2.1.4)": @@ -111,7 +164,17 @@ PODS: - GTMSessionFetcher/Core (1.1.15) - GTMSessionFetcher/Full (1.1.15): - GTMSessionFetcher/Core (= 1.1.15) + - image_picker (0.0.1): + - Flutter - leveldb-library (1.20) + - mlkit (0.0.1): + - Firebase/Core + - Firebase/MLVision + - Firebase/MLVisionBarcodeModel + - Firebase/MLVisionFaceModel + - Firebase/MLVisionLabelModel + - Firebase/MLVisionTextModel + - Flutter - nanopb (0.3.8): - nanopb/decode (= 0.3.8) - nanopb/encode (= 0.3.8) @@ -124,8 +187,11 @@ DEPENDENCIES: - firebase_admob (from `.symlinks/plugins/firebase_admob/ios`) - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - Flutter (from `.symlinks/flutter/ios`) - google_sign_in (from `.symlinks/plugins/google_sign_in/ios`) + - image_picker (from `.symlinks/plugins/image_picker/ios`) + - mlkit (from `.symlinks/plugins/mlkit/ios`) SPEC REPOS: https://github.com/cocoapods/specs.git: @@ -137,7 +203,15 @@ SPEC REPOS: - FirebaseDatabase - FirebaseFirestore - FirebaseInstanceID + - FirebaseMLCommon + - FirebaseMLVision + - FirebaseMLVisionBarcodeModel + - FirebaseMLVisionFaceModel + - FirebaseMLVisionLabelModel + - FirebaseMLVisionTextModel - Google-Mobile-Ads-SDK + - GoogleAPIClientForREST + - GoogleMobileVision - GoogleSignIn - GoogleToolboxForMac - gRPC @@ -159,27 +233,42 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_analytics/ios" firebase_auth: :path: ".symlinks/plugins/firebase_auth/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" Flutter: :path: ".symlinks/flutter/ios" google_sign_in: :path: ".symlinks/plugins/google_sign_in/ios" + image_picker: + :path: ".symlinks/plugins/image_picker/ios" + mlkit: + :path: ".symlinks/plugins/mlkit/ios" SPEC CHECKSUMS: BoringSSL: 60dd24df4af296bf41d78e5841dbb95d75f88c0d - cloud_firestore: 00886d0b271577dfadd33be97d6bb6095f49499d + cloud_firestore: a2d49d9c7219fce31033eb1a2ee953d7733c91d6 Firebase: d6861c2059d8c32d1e6dd8932e22ada346d90a3a firebase_admob: b8cc46113ddf88a62fec340e9446607a5061a872 firebase_analytics: ad15e54404e21983408c8b0c24a9d11247be7f33 - firebase_auth: 499d5bfb249606a276f6bddf292a3b2d44139f75 + firebase_auth: 27a5a77a032d557c18f8e223aaf8ec1368f6cdbc + firebase_core: c96aa8b2fcf7f5167d32f22034f502f9304952b8 FirebaseAnalytics: 19812b49fa5f283dd6b23edf8a14b5d477029ab8 FirebaseAuth: acbeef02fe7c3a26624e309849f3fe30c84115af FirebaseCore: cafc814b2d84fc8733f09e653041cc2165332ad7 FirebaseDatabase: 697eb53e5b4fe7cd4fa8756c1f82a9fca011345f FirebaseFirestore: f686b8e83f3cf8bbc37db6e98e01029a14f01f55 FirebaseInstanceID: 83e0040351565df711a5db3d8ebe5ea21aca998a + FirebaseMLCommon: 73a2f53c8b3e6cd18e73dd1be095c3192af26a5e + FirebaseMLVision: f9756d8376dd150fb17629eaa7bdc6baa8965677 + FirebaseMLVisionBarcodeModel: dc1c6add14a3d69061eb6edacf90bb619f7aee52 + FirebaseMLVisionFaceModel: 0b451ba1d40674dfc4a334ebd40f5f837fe283a1 + FirebaseMLVisionLabelModel: ab16cb2bc13630ee13d9b0c6104b94165e86933c + FirebaseMLVisionTextModel: 8a9cb61296d4d15d60eb0c7c66a2a88790b34376 Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296 Google-Mobile-Ads-SDK: 7404f68120ae8682afeb5af001fbf4aad731c78e - google_sign_in: 64523e9b42c475b01b726ff87e450425ea6d15bc + google_sign_in: cbeb57b96679823d14df6b2240b629b983a42d79 + GoogleAPIClientForREST: f7951c455df271bc6259b3ddb4073d0026475ccf + GoogleMobileVision: 8c57c6b27b0964506cb7163049e67a38e279ea6f GoogleSignIn: d9ef55b10f0aa401a5de2747f59b725e4b9732ac GoogleToolboxForMac: 91c824d21e85b31c2aae9bb011c5027c9b4e738f gRPC: 9362451032695e2dfb7bafcd3740e3a27939e4ff @@ -188,7 +277,9 @@ SPEC CHECKSUMS: gRPC-RxLibrary: 1ed5314e8b38cd6e55c9bfa048387136ae925ce9 GTMOAuth2: c77fe325e4acd453837e72d91e3b5f13116857b2 GTMSessionFetcher: 5fa5b80fd20e439ef5f545fb2cb3ca6c6714caa2 + image_picker: ee00aab0487cedc80a304085219503cc6d0f2e22 leveldb-library: 08cba283675b7ed2d99629a4bc5fd052cd2bb6a5 + mlkit: 42aa3a7af1c9b080e52728dd7999668181e3ab3f nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3 Protobuf: 8a9838fba8dae3389230e1b7f8c104aa32389c03 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e4cff26..6ddddf8 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -280,12 +280,16 @@ inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", "${PODS_ROOT}/GTMOAuth2/Source/Touch/GTMOAuth2ViewTouch.xib", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleMobileVision/GoogleMVFaceDetectorResources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleMobileVision/GoogleMVTextDetectorResources.bundle", "${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/gRPC/gRPCCertificates.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GTMOAuth2ViewTouch.nib", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMVFaceDetectorResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMVTextDetectorResources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates.bundle", ); diff --git a/lib/main.dart b/lib/main.dart index faa84f3..0ce1814 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,8 @@ -import 'package:flutfire/CrudApp/crud_sample.dart'; -import 'package:flutfire/QuotesApp/quotes.dart'; -import 'package:flutfire/WallpaperApp/wall_screen.dart'; +import 'package:flutfire/mlkit/ml_home.dart'; + import 'package:flutter/material.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_analytics/observer.dart'; -import 'package:flutfire/QuotesApp/backdrop.dart'; void main() => runApp(new MyApp()); @@ -24,7 +22,7 @@ class MyApp extends StatelessWidget { ), navigatorObservers: [observer], // home: new WallScreen(analytics: analytics, observer: observer), - home: new CrudSample(), + home: new MLHome(), ); } } diff --git a/lib/mlkit/ml_detail.dart b/lib/mlkit/ml_detail.dart new file mode 100644 index 0000000..87cdf4f --- /dev/null +++ b/lib/mlkit/ml_detail.dart @@ -0,0 +1,395 @@ +import 'package:flutfire/mlkit/ml_home.dart'; +import 'package:flutter/material.dart'; +import 'dart:io'; +import 'dart:async'; +import 'package:mlkit/mlkit.dart'; + +class MLDetail extends StatefulWidget { + final File _file; + final String _scannerType; + + MLDetail(this._file, this._scannerType); + + @override + State createState() { + return _MLDetailState(); + } +} + +class _MLDetailState extends State { + FirebaseVisionTextDetector textDetector = FirebaseVisionTextDetector.instance; + FirebaseVisionBarcodeDetector barcodeDetector = + FirebaseVisionBarcodeDetector.instance; + FirebaseVisionLabelDetector labelDetector = + FirebaseVisionLabelDetector.instance; + FirebaseVisionFaceDetector faceDetector = FirebaseVisionFaceDetector.instance; + List _currentTextLabels = []; + List _currentBarcodeLabels = []; + List _currentLabelLabels = []; + List _currentFaceLabels = []; + + Stream sub; + StreamSubscription subscription; + + @override + void initState() { + super.initState(); + sub = new Stream.empty(); + subscription = sub.listen((_) => _getImageSize)..onDone(analyzeLabels); + } + + void analyzeLabels() async { + try { + var currentLabels; + if (widget._scannerType == TEXT_SCANNER) { + currentLabels = await textDetector.detectFromPath(widget._file.path); + if (this.mounted) { + setState(() { + _currentTextLabels = currentLabels; + }); + } + } else if (widget._scannerType == BARCODE_SCANNER) { + currentLabels = await barcodeDetector.detectFromPath(widget._file.path); + if (this.mounted) { + setState(() { + _currentBarcodeLabels = currentLabels; + }); + } + } else if (widget._scannerType == LABEL_SCANNER) { + currentLabels = await labelDetector.detectFromPath(widget._file.path); + if (this.mounted) { + setState(() { + _currentLabelLabels = currentLabels; + }); + } + } else if (widget._scannerType == FACE_SCANNER) { + currentLabels = await faceDetector.detectFromPath(widget._file.path); + if (this.mounted) { + setState(() { + _currentFaceLabels = currentLabels; + }); + } + } + } catch (e) { + print("MyEx: " + e.toString()); + } + } + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + subscription?.cancel(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text(widget._scannerType), + ), + body: Column( + children: [ + buildImage(context), + widget._scannerType == TEXT_SCANNER + ? buildTextList(_currentTextLabels) + : widget._scannerType == BARCODE_SCANNER + ? buildBarcodeList(_currentBarcodeLabels) + : widget._scannerType == FACE_SCANNER + ? buildBarcodeList(_currentFaceLabels) + : buildBarcodeList(_currentLabelLabels) + ], + )); + } + + Widget buildImage(BuildContext context) { + return Expanded( + flex: 2, + child: Container( + decoration: BoxDecoration(color: Colors.black), + child: Center( + child: widget._file == null + ? Text('No Image') + : FutureBuilder( + future: _getImageSize( + Image.file(widget._file, fit: BoxFit.fitWidth)), + builder: + (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + return Container( + foregroundDecoration: (widget._scannerType == + TEXT_SCANNER) + ? TextDetectDecoration( + _currentTextLabels, snapshot.data) + : (widget._scannerType == FACE_SCANNER) + ? FaceDetectDecoration( + _currentFaceLabels, snapshot.data) + : (widget._scannerType == BARCODE_SCANNER) + ? BarcodeDetectDecoration( + _currentBarcodeLabels, + snapshot.data) + : LabelDetectDecoration( + _currentLabelLabels, snapshot.data), + child: + Image.file(widget._file, fit: BoxFit.fitWidth)); + } else { + return CircularProgressIndicator(); + } + }, + ), + )), + ); + } + + Widget buildBarcodeList(List barcodes) { + if (barcodes.length == 0) { + return Expanded( + flex: 1, + child: Center( + child: Text('Nothing detected', + style: Theme.of(context).textTheme.subhead), + ), + ); + } + return Expanded( + flex: 1, + child: Container( + child: ListView.builder( + padding: const EdgeInsets.all(1.0), + itemCount: barcodes.length, + itemBuilder: (context, i) { + var text; + + final barcode = barcodes[i]; + switch (widget._scannerType) { + case BARCODE_SCANNER: + VisionBarcode res = barcode as VisionBarcode; + text = "Raw Value: ${res.rawValue}"; + break; + case FACE_SCANNER: + VisionFace res = barcode as VisionFace; + text = + "Raw Value: ${res.smilingProbability},${res.trackingID}"; + break; + case LABEL_SCANNER: + VisionLabel res = barcode as VisionLabel; + text = "Raw Value: ${res.label}"; + break; + } + + return _buildTextRow(text); + }), + ), + ); + } + + Widget buildTextList(List texts) { + if (texts.length == 0) { + return Expanded( + flex: 1, + child: Center( + child: Text('No text detected', + style: Theme.of(context).textTheme.subhead), + )); + } + return Expanded( + flex: 1, + child: Container( + child: ListView.builder( + padding: const EdgeInsets.all(1.0), + itemCount: texts.length, + itemBuilder: (context, i) { + return _buildTextRow(texts[i].text); + }), + ), + ); + } + + Widget _buildTextRow(text) { + return ListTile( + title: Text( + "$text", + ), + dense: true, + ); + } + + Future _getImageSize(Image image) { + Completer completer = Completer(); + image.image.resolve(ImageConfiguration()).addListener( + (ImageInfo info, bool _) => completer.complete( + Size(info.image.width.toDouble(), info.image.height.toDouble()))); + return completer.future; + } +} + +/* + This code uses the example from azihsoyn/flutter_mlkit + https://github.com/azihsoyn/flutter_mlkit/blob/master/example/lib/main.dart +*/ + +class BarcodeDetectDecoration extends Decoration { + final Size _originalImageSize; + final List _barcodes; + + BarcodeDetectDecoration(List barcodes, Size originalImageSize) + : _barcodes = barcodes, + _originalImageSize = originalImageSize; + + @override + BoxPainter createBoxPainter([VoidCallback onChanged]) { + return _BarcodeDetectPainter(_barcodes, _originalImageSize); + } +} + +class _BarcodeDetectPainter extends BoxPainter { + final List _barcodes; + final Size _originalImageSize; + _BarcodeDetectPainter(barcodes, originalImageSize) + : _barcodes = barcodes, + _originalImageSize = originalImageSize; + + @override + void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { + final paint = Paint() + ..strokeWidth = 2.0 + ..color = Colors.red + ..style = PaintingStyle.stroke; + + final _heightRatio = _originalImageSize.height / configuration.size.height; + final _widthRatio = _originalImageSize.width / configuration.size.width; + for (var barcode in _barcodes) { + final _rect = Rect.fromLTRB( + offset.dx + barcode.rect.left / _widthRatio, + offset.dy + barcode.rect.top / _heightRatio, + offset.dx + barcode.rect.right / _widthRatio, + offset.dy + barcode.rect.bottom / _heightRatio); + canvas.drawRect(_rect, paint); + } + canvas.restore(); + } +} + +class TextDetectDecoration extends Decoration { + final Size _originalImageSize; + final List _texts; + TextDetectDecoration(List texts, Size originalImageSize) + : _texts = texts, + _originalImageSize = originalImageSize; + + @override + BoxPainter createBoxPainter([VoidCallback onChanged]) { + return _TextDetectPainter(_texts, _originalImageSize); + } +} + +class _TextDetectPainter extends BoxPainter { + final List _texts; + final Size _originalImageSize; + _TextDetectPainter(texts, originalImageSize) + : _texts = texts, + _originalImageSize = originalImageSize; + + @override + void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { + final paint = Paint() + ..strokeWidth = 2.0 + ..color = Colors.red + ..style = PaintingStyle.stroke; + + final _heightRatio = _originalImageSize.height / configuration.size.height; + final _widthRatio = _originalImageSize.width / configuration.size.width; + for (var text in _texts) { + final _rect = Rect.fromLTRB( + offset.dx + text.rect.left / _widthRatio, + offset.dy + text.rect.top / _heightRatio, + offset.dx + text.rect.right / _widthRatio, + offset.dy + text.rect.bottom / _heightRatio); + canvas.drawRect(_rect, paint); + } + canvas.restore(); + } +} + +class FaceDetectDecoration extends Decoration { + final Size _originalImageSize; + final List _faces; + FaceDetectDecoration(List faces, Size originalImageSize) + : _faces = faces, + _originalImageSize = originalImageSize; + + @override + BoxPainter createBoxPainter([VoidCallback onChanged]) { + return _FaceDetectPainter(_faces, _originalImageSize); + } +} + +class _FaceDetectPainter extends BoxPainter { + final List _faces; + final Size _originalImageSize; + _FaceDetectPainter(faces, originalImageSize) + : _faces = faces, + _originalImageSize = originalImageSize; + + @override + void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { + final paint = Paint() + ..strokeWidth = 2.0 + ..color = Colors.red + ..style = PaintingStyle.stroke; + + final _heightRatio = _originalImageSize.height / configuration.size.height; + final _widthRatio = _originalImageSize.width / configuration.size.width; + for (var face in _faces) { + final _rect = Rect.fromLTRB( + offset.dx + face.rect.left / _widthRatio, + offset.dy + face.rect.top / _heightRatio, + offset.dx + face.rect.right / _widthRatio, + offset.dy + face.rect.bottom / _heightRatio); + canvas.drawRect(_rect, paint); + } + canvas.restore(); + } +} + +class LabelDetectDecoration extends Decoration { + final Size _originalImageSize; + final List _labels; + LabelDetectDecoration(List labels, Size originalImageSize) + : _labels = labels, + _originalImageSize = originalImageSize; + + @override + BoxPainter createBoxPainter([VoidCallback onChanged]) { + return _LabelDetectPainter(_labels, _originalImageSize); + } +} + +class _LabelDetectPainter extends BoxPainter { + final List _labels; + final Size _originalImageSize; + _LabelDetectPainter(labels, originalImageSize) + : _labels = labels, + _originalImageSize = originalImageSize; + + @override + void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { + final paint = Paint() + ..strokeWidth = 2.0 + ..color = Colors.red + ..style = PaintingStyle.stroke; + + final _heightRatio = _originalImageSize.height / configuration.size.height; + final _widthRatio = _originalImageSize.width / configuration.size.width; + for (var label in _labels) { + final _rect = Rect.fromLTRB( + offset.dx + label.rect.left / _widthRatio, + offset.dy + label.rect.top / _heightRatio, + offset.dx + label.rect.right / _widthRatio, + offset.dy + label.rect.bottom / _heightRatio); + canvas.drawRect(_rect, paint); + } + canvas.restore(); + } +} diff --git a/lib/mlkit/ml_home.dart b/lib/mlkit/ml_home.dart new file mode 100644 index 0000000..04373e8 --- /dev/null +++ b/lib/mlkit/ml_home.dart @@ -0,0 +1,182 @@ +import 'package:flutfire/mlkit/ml_detail.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'dart:io'; + +const String TEXT_SCANNER = 'TEXT_SCANNER'; +const String BARCODE_SCANNER = 'BARCODE_SCANNER'; +const String LABEL_SCANNER = 'LABEL_SCANNER'; +const String FACE_SCANNER = 'FACE_SCANNER'; + +class MLHome extends StatefulWidget { + MLHome({Key key}) : super(key: key); + + @override + State createState() => _MLHomeState(); +} + +class _MLHomeState extends State { + static const String CAMERA_SOURCE = 'CAMERA_SOURCE'; + static const String GALLERY_SOURCE = 'GALLERY_SOURCE'; + + final GlobalKey _scaffoldKey = GlobalKey(); + + File _file; + String _selectedScanner = TEXT_SCANNER; + + @override + Widget build(BuildContext context) { + final columns = List(); + columns.add(buildRowTitle(context, 'Select Scanner Type')); + columns.add(buildSelectScannerRowWidget(context)); + columns.add(buildRowTitle(context, 'Pick Image')); + columns.add(buildSelectImageRowWidget(context)); + + return Scaffold( + key: _scaffoldKey, + appBar: AppBar( + centerTitle: true, + title: Text('MLKit Demo'), + ), + body: SingleChildScrollView( + child: Column( + children: columns, + ), + )); + } + + Widget buildRowTitle(BuildContext context, String title) { + return Center( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), + child: Text( + title, + style: Theme.of(context).textTheme.headline, + ), + )); + } + + Widget buildSelectImageRowWidget(BuildContext context) { + return Row( + children: [ + Expanded( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: RaisedButton( + color: Colors.blue, + textColor: Colors.white, + splashColor: Colors.blueGrey, + onPressed: () { + onPickImageSelected(CAMERA_SOURCE); + }, + child: const Text('Camera')), + )), + Expanded( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: RaisedButton( + color: Colors.blue, + textColor: Colors.white, + splashColor: Colors.blueGrey, + onPressed: () { + onPickImageSelected(GALLERY_SOURCE); + }, + child: const Text('Gallery')), + )) + ], + ); + } + + Widget buildSelectScannerRowWidget(BuildContext context) { + return Wrap( + children: [ + RadioListTile( + title: Text('Text Recognition'), + groupValue: _selectedScanner, + value: TEXT_SCANNER, + onChanged: onScannerSelected, + ), + RadioListTile( + title: Text('Barcode Scanner'), + groupValue: _selectedScanner, + value: BARCODE_SCANNER, + onChanged: onScannerSelected, + ), + RadioListTile( + title: Text('Label Scanner'), + groupValue: _selectedScanner, + value: LABEL_SCANNER, + onChanged: onScannerSelected, + ), + RadioListTile( + title: Text('Face Scanner'), + groupValue: _selectedScanner, + value: FACE_SCANNER, + onChanged: onScannerSelected, + ) + ], + ); + } + + Widget buildImageRow(BuildContext context, File file) { + return SizedBox( + height: 500.0, + child: Image.file( + file, + fit: BoxFit.fitWidth, + )); + } + + Widget buildDeleteRow(BuildContext context) { + return Center( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), + child: RaisedButton( + color: Colors.red, + textColor: Colors.white, + splashColor: Colors.blueGrey, + onPressed: () { + setState(() { + _file = null; + }); + ; + }, + child: const Text('Delete Image')), + ), + ); + } + + void onScannerSelected(String scanner) { + setState(() { + _selectedScanner = scanner; + }); + } + + void onPickImageSelected(String source) async { + var imageSource; + if (source == CAMERA_SOURCE) { + imageSource = ImageSource.camera; + } else { + imageSource = ImageSource.gallery; + } + + final scaffold = _scaffoldKey.currentState; + + try { + final file = await ImagePicker.pickImage(source: imageSource); + if (file == null) { + throw Exception('File is not available'); + } + + Navigator.push( + context, + new MaterialPageRoute( + builder: (context) => MLDetail(file, _selectedScanner)), + ); + } catch (e) { + scaffold.showSnackBar(SnackBar( + content: Text(e.toString()), + )); + } + } +} diff --git a/pubspec.lock b/pubspec.lock index b3be2eb..35c11d0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -172,6 +172,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.5" io: dependency: transitive description: @@ -228,6 +235,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.9.6+1" + mlkit: + dependency: "direct main" + description: + name: mlkit + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.1" multi_server_socket: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1d791d5..944a468 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,6 +14,8 @@ dependencies: flutter_staggered_grid_view: firebase_admob: ^0.5.2 firebase_analytics: ^0.2.3 + mlkit: + image_picker: dev_dependencies: