diff options
author | xAlpharax <42233094+xAlpharax@users.noreply.github.com> | 2023-09-28 19:02:06 +0300 |
---|---|---|
committer | xAlpharax <42233094+xAlpharax@users.noreply.github.com> | 2023-09-28 19:02:06 +0300 |
commit | 7606ef1d9872ca0466e57779aeaf0ce690cac69b (patch) | |
tree | 65c985972734ed2bb146abd578eaf8ced3c2eb1e | |
parent | 549a51b021217a25426625a962ede562bb069191 (diff) |
Getting basic functionalities over from the OCR app like the camera.
Changes to be committed:
modified: lib/main.dart
new file: lib/result_screen.dart
modified: pubspec.lock
modified: pubspec.yaml
modified: test/widget_test.dart
-rw-r--r-- | lib/main.dart | 284 | ||||
-rw-r--r-- | lib/result_screen.dart | 19 | ||||
-rw-r--r-- | pubspec.lock | 280 | ||||
-rw-r--r-- | pubspec.yaml | 6 | ||||
-rw-r--r-- | test/widget_test.dart | 2 |
5 files changed, 515 insertions, 76 deletions
diff --git a/lib/main.dart b/lib/main.dart index d7cd012..30c90ef 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,107 +1,249 @@ import 'package:camera/camera.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart'; +import 'package:google_mlkit_object_detection/google_mlkit_object_detection.dart'; import 'package:flutter_tflite/flutter_tflite.dart'; import 'package:flutter_tts/flutter_tts.dart'; +import 'package:yolo/result_screen.dart'; import 'package:flutter/material.dart'; import 'dart:io'; void main() { - runApp(const MyApp()); + runApp(const App()); } -class MyApp extends StatelessWidget { - const MyApp({super.key}); +class App extends StatelessWidget { + const App({super.key}); @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', + title: 'MegaView', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurpleAccent), useMaterial3: true, ), debugShowCheckedModeBanner: false, - home: const MyHomePage(title: 'Flutter Demo Home Page'), + home: const MainScreen(), ); } } -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - final String title; +class MainScreen extends StatefulWidget { + const MainScreen({super.key}); @override - State<MyHomePage> createState() => _MyHomePageState(); + State<MainScreen> createState() => _MainScreenState(); } -class _MyHomePageState extends State<MyHomePage> { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); +class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver { + bool _isPermissionGranted = false; + + late final Future<void> _future; + CameraController? _cameraController; + + final textRecognizer = TextRecognizer(); + + FlutterTts flutterTts = FlutterTts(); // TTS + + @override + void initState() { + super.initState(); + initTTS(); // TTS + + WidgetsBinding.instance.addObserver(this); + + _future = _requestCameraPermission(); + } + + Future<void> initTTS() async { // TTS + await flutterTts.setLanguage("en-US"); // Set the language you want + await flutterTts.setSpeechRate(0.5); // Adjust speech rate (1.0 is normal but too fast for my liking) + await flutterTts.setVolume(1.0); // Adjust volume (0.0 to 1.0) + await flutterTts.setPitch(1.0); // Adjust pitch (1.0 is normal) + + // You can set other configurations as well + + // Check if TTS is available + // bool isAvailable = await flutterTts.isLanguageAvailable("en-US"); + // print("TTS is available: $isAvailable"); + } + + Future<void> speak(String text) async { + await flutterTts.speak(text); // TTS + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + _stopCamera(); + textRecognizer.close(); + flutterTts.stop(); // TTS Stop + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (_cameraController == null || !_cameraController!.value.isInitialized) { + return; + } + + if (state == AppLifecycleState.inactive) { + _stopCamera(); + } else if (state == AppLifecycleState.resumed && + _cameraController != null && + _cameraController!.value.isInitialized) { + _startCamera(); + } } @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - children: <Widget>[ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, + return FutureBuilder( + future: _future, + builder: (context, snapshot) { + return Stack( + children: [ + if (_isPermissionGranted) + FutureBuilder<List<CameraDescription>>( + future: availableCameras(), + builder: (context, snapshot) { + if (snapshot.hasData) { + _initCameraController(snapshot.data!); + + return Center(child: CameraPreview(_cameraController!)); + } else { + return const LinearProgressIndicator(); + } + }, + ), + Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('MegaView Text Recognition'), + ), + backgroundColor: _isPermissionGranted ? Colors.transparent : null, + body: _isPermissionGranted + ? Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container(), + Container( + color: Theme.of(context).colorScheme.inversePrimary, + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: ElevatedButton( + onPressed: _scanImage, + style: ButtonStyle( + minimumSize: MaterialStateProperty.all<Size>( + const Size(256, 64), // Set the desired width and height + ), + ), + child: const Text('Scan text'), + ), + ), + ), + ], + ) + : Center( + child: Container( + padding: const EdgeInsets.only(left: 24.0, right: 24.0), + child: const Text( + 'Camera permission denied', + textAlign: TextAlign.center, + ), + ), + ), ), ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + ); + }, ); } -} + + Future<void> _requestCameraPermission() async { + final status = await Permission.camera.request(); + _isPermissionGranted = status == PermissionStatus.granted; + } + + void _startCamera() { + if (_cameraController != null) { + _cameraSelected(_cameraController!.description); + } + } + + void _stopCamera() { + if (_cameraController != null) { + _cameraController?.dispose(); + } + } + + void _initCameraController(List<CameraDescription> cameras) { + if (_cameraController != null) { + return; + } + + // Select the first rear camera. + CameraDescription? camera; + for (var i = 0; i < cameras.length; i++) { + final CameraDescription current = cameras[i]; + if (current.lensDirection == CameraLensDirection.back) { + camera = current; + break; + } + } + + if (camera != null) { + _cameraSelected(camera); + } + } + + Future<void> _cameraSelected(CameraDescription camera) async { + _cameraController = CameraController( + camera, + ResolutionPreset.high, + enableAudio: false, + ); + + await _cameraController!.initialize(); + await _cameraController!.setFlashMode(FlashMode.off); + + if (!mounted) { + return; + } + setState(() {}); + } + + Future<void> _scanImage() async { + if (_cameraController == null) return; + + final navigator = Navigator.of(context); + + try { + final pictureFile = await _cameraController!.takePicture(); + + final file = File(pictureFile.path); + + final inputImage = InputImage.fromFile(file); + final recognizedText = await textRecognizer.processImage(inputImage); + + speak(recognizedText.text); + + await navigator.push( + MaterialPageRoute( + builder: (BuildContext context) => + ResultScreen(text: recognizedText.text) + ), + ); + } catch (e) { + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('An error occurred when scanning text'), + ), + ); + } + } +}
\ No newline at end of file diff --git a/lib/result_screen.dart b/lib/result_screen.dart new file mode 100644 index 0000000..d5a64ea --- /dev/null +++ b/lib/result_screen.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class ResultScreen extends StatelessWidget { + final String text; + + const ResultScreen({super.key, required this.text}); + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar( + //backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('Result'), + ), + body: Container( + padding: const EdgeInsets.all(30.0), + child: Text(text), + ), + ); +}
\ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 25dd5dc..46fabca 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -153,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" file_selector_linux: dependency: transitive description: @@ -165,10 +173,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: "182c3f8350cee659f7b115e956047ee3dc672a96665883a545e81581b9a82c72" + sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 url: "https://pub.dev" source: hosted - version: "0.9.3+2" + version: "0.9.3+3" file_selector_platform_interface: dependency: transitive description: @@ -214,6 +222,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.16" + flutter_sound: + dependency: "direct main" + description: + name: flutter_sound + sha256: "090a4694b11ecc744c2010621c4ffc5fe7c3079d304ea014961a72c7b72cfe6c" + url: "https://pub.dev" + source: hosted + version: "9.2.13" + flutter_sound_platform_interface: + dependency: transitive + description: + name: flutter_sound_platform_interface + sha256: "4537eaeb58a32748c42b621ad6116f7f4c6ee0a8d6ffaa501b165fe1c9df4753" + url: "https://pub.dev" + source: hosted + version: "9.2.13" + flutter_sound_web: + dependency: transitive + description: + name: flutter_sound_web + sha256: ad4ca92671a1879e1f613e900bbbdb8170b20d57d1e4e6363018fe56b055594f + url: "https://pub.dev" + source: hosted + version: "9.2.13" flutter_test: dependency: "direct dev" description: flutter @@ -240,6 +272,126 @@ packages: description: flutter source: sdk version: "0.0.0" + google_ml_kit: + dependency: "direct main" + description: + name: google_ml_kit + sha256: "9d98ed5ff96c1295d08f96c807a70d53b56a43a7a18ae86d4ea4d09cf7310fc9" + url: "https://pub.dev" + source: hosted + version: "0.16.2" + google_mlkit_barcode_scanning: + dependency: transitive + description: + name: google_mlkit_barcode_scanning + sha256: "033401bc992315fe3d6ed9265b97bf1e620fa12ddaffda830107d6852abcde77" + url: "https://pub.dev" + source: hosted + version: "0.9.0" + google_mlkit_commons: + dependency: transitive + description: + name: google_mlkit_commons + sha256: "42173a8ba89f386486cc5b8249e84da4a4b861daa70498373627d985eb418689" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + google_mlkit_digital_ink_recognition: + dependency: transitive + description: + name: google_mlkit_digital_ink_recognition + sha256: f48a90cd2bb8dc6a0432a1ba89a1a488d4e7ba3132fe4dfd75a9eab0e0a1b90a + url: "https://pub.dev" + source: hosted + version: "0.9.0" + google_mlkit_entity_extraction: + dependency: transitive + description: + name: google_mlkit_entity_extraction + sha256: e4801253f9913a84793f338fb45923825fc2799f8608a1f0d0f5ca43b5062fbc + url: "https://pub.dev" + source: hosted + version: "0.10.0" + google_mlkit_face_detection: + dependency: transitive + description: + name: google_mlkit_face_detection + sha256: "567339c67530b22b22917622df9bba40547a4719fa108819901f95ffb7cd3399" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + google_mlkit_face_mesh_detection: + dependency: transitive + description: + name: google_mlkit_face_mesh_detection + sha256: "3f64635fe096fef3167da6f69a30b1744ddfb4590acdaa2b7757c6f30455b0f5" + url: "https://pub.dev" + source: hosted + version: "0.0.1" + google_mlkit_image_labeling: + dependency: transitive + description: + name: google_mlkit_image_labeling + sha256: "38c7225e22ec558bfd78015ef9e9ae4812cee8715f03a9c82b87d3e0b530d00c" + url: "https://pub.dev" + source: hosted + version: "0.9.0" + google_mlkit_language_id: + dependency: transitive + description: + name: google_mlkit_language_id + sha256: cb4241297552f22be638be620add294b6a6c9043eefc1f4c18597ee66ce31a98 + url: "https://pub.dev" + source: hosted + version: "0.8.0" + google_mlkit_object_detection: + dependency: "direct main" + description: + name: google_mlkit_object_detection + sha256: "52b11c335cbf45da561127de586e1ab32ba5526eb04d7a3a683c787279a47153" + url: "https://pub.dev" + source: hosted + version: "0.10.0" + google_mlkit_pose_detection: + dependency: transitive + description: + name: google_mlkit_pose_detection + sha256: "08759fc095751cf84f6758f276212881388e04210c64087341e260f9f233316e" + url: "https://pub.dev" + source: hosted + version: "0.9.0" + google_mlkit_selfie_segmentation: + dependency: transitive + description: + name: google_mlkit_selfie_segmentation + sha256: "5b2931f86e9476daaf5710f9e0d281ec06f7d0ee664a3688b791bfcfb7c4afd5" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + google_mlkit_smart_reply: + dependency: transitive + description: + name: google_mlkit_smart_reply + sha256: "97165ea8667aa94de20d782862ecd791daf0e884c48b8cb1cedd9ebafda5d52c" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + google_mlkit_text_recognition: + dependency: "direct main" + description: + name: google_mlkit_text_recognition + sha256: "588021c99536fdfb173eeecc4ee1b60ea4e0bd4be9787f52363c85346ae20364" + url: "https://pub.dev" + source: hosted + version: "0.10.0" + google_mlkit_translation: + dependency: transitive + description: + name: google_mlkit_translation + sha256: "175a0cb801b5fa01140f62344bbda6a8dcbb7977ae057f1dab1b74eddc60d71c" + url: "https://pub.dev" + source: hosted + version: "0.8.0" http: dependency: transitive description: @@ -276,10 +428,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: d32a997bcc4ee135aebca8e272b7c517927aa65a74b9c60a81a2764ef1a0462d + sha256: "0c7b83bbe2980c8a8e36e974f055e11e51675784e13a4762889feed0f3937ff2" url: "https://pub.dev" source: hosted - version: "0.8.7+5" + version: "0.8.8+1" image_picker_for_web: dependency: transitive description: @@ -352,6 +504,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + logger: + dependency: transitive + description: + name: logger + sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f" + url: "https://pub.dev" + source: hosted + version: "1.4.0" matcher: dependency: transitive description: @@ -384,6 +544,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -392,6 +560,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" permission_handler: dependency: "direct main" description: @@ -440,6 +656,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.0" + platform: + dependency: transitive + description: + name: platform + sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + url: "https://pub.dev" + source: hosted + version: "3.1.2" plugin_platform_interface: dependency: transitive description: @@ -456,6 +680,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.7.3" + provider: + dependency: transitive + description: + name: provider + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" + source: hosted + version: "6.0.5" quiver: dependency: transitive description: @@ -464,6 +696,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.1" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" sky_engine: dependency: transitive description: flutter @@ -509,6 +749,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" + url: "https://pub.dev" + source: hosted + version: "3.1.0" term_glyph: dependency: transitive description: @@ -533,6 +781,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" + source: hosted + version: "3.0.7" vector_math: dependency: transitive description: @@ -549,6 +805,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.4-beta" + win32: + dependency: transitive + description: + name: win32 + sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + url: "https://pub.dev" + source: hosted + version: "5.0.9" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + url: "https://pub.dev" + source: hosted + version: "1.0.3" xml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 65a1cdc..9dc7251 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,12 @@ dependencies: permission_handler: ^10.4.3 flutter_tts: ^3.8.2 + google_mlkit_text_recognition: ^0.10.0 + google_mlkit_object_detection: ^0.10.0 + + flutter_sound: ^9.2.13 + google_ml_kit: ^0.16.2 + cupertino_icons: ^1.0.2 dev_dependencies: diff --git a/test/widget_test.dart b/test/widget_test.dart index e980ae6..58066c6 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:yolo/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(const App()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); |