From 342e567736c8e732a30ff21664d9a254d113c1e6 Mon Sep 17 00:00:00 2001 From: Lucas Oskorep Date: Sun, 28 Aug 2022 21:39:43 -0400 Subject: [PATCH] adding basic navbar to the app and fixing camera loading. --- .gitlab-ci.yml | 28 +++--- lib/widgets/poke_finder.dart | 120 ++++++++++++++++++------- lib/widgets/tensordex_home.dart | 152 +++++++++++++++++++++----------- 3 files changed, 202 insertions(+), 98 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e28ea67..01a9796 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -49,17 +49,17 @@ deploy-android-job: - MacOS -build-ios-debug: - stage: build - only: - - branches - script: - - sh install_tflite.sh -d - - flutter build ipa -# artifacts: -# name: ios-debug -# paths: -# - build/app/outputs/bundle/debug/app-debug.aab -# - build/app/outputs/flutter-apk/app-release.apk - tags: - - MacOS +#build-ios-debug: +# stage: build +# only: +# - branches +# script: +# - sh install_tflite.sh -d +# - flutter build ipa +## artifacts: +## name: ios-debug +## paths: +## - build/app/outputs/bundle/debug/app-debug.aab +## - build/app/outputs/flutter-apk/app-release.apk +# tags: +# - MacOS diff --git a/lib/widgets/poke_finder.dart b/lib/widgets/poke_finder.dart index c613c24..3d235c9 100644 --- a/lib/widgets/poke_finder.dart +++ b/lib/widgets/poke_finder.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; +import 'dart:math'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -34,11 +35,17 @@ class PokeFinder extends StatefulWidget { class _PokeFinderState extends State with WidgetsBindingObserver { /// true when inference is ongoing + /// + final double _zoomSliderLogFactor = 1000.0; + bool predicting = false; - bool _cameraInitialized = false; + bool _cameraReady = false; bool _classifierInitialized = false; bool _saveClassifierImage = false; - int cameraIndex = 0; + int _cameraIndex = 0; + double _minZoom = 1.0; + double _maxZoom = 1.0; + double _currentZoom = 1.0; late CameraController cameraController; @@ -91,19 +98,41 @@ class _PokeFinderState extends State with WidgetsBindingObserver { options: config.interpreters[0]); } + void swapCamera() async { + logger.i(cameras); + logger.i(_cameraIndex); + _cameraIndex += 1; + if (cameras.length <= _cameraIndex) { + _cameraIndex = 0; + } + swapToCamera(cameras[_cameraIndex]); + } + void swapToCamera(CameraDescription cameraDescription) async { + setState(() { + _cameraReady = false; + }); + _refreshIndicatorKey.currentState?.show(); cameraController = CameraController(cameraDescription, ResolutionPreset.low, enableAudio: false); cameraController.initialize().then((_) async { /// previewSize is size of each image frame captured by controller /// 352x288 on iOS, 240p (320x240) on Android with ResolutionPreset.low await cameraController.startImageStream(onLatestImageAvailable); + _maxZoom = await cameraController.getMaxZoomLevel(); + _minZoom = await cameraController.getMinZoomLevel(); setState(() { - _cameraInitialized = true; + _cameraReady = true; + _currentZoom = 1.0; }); }); } + void saveMLImage() async { + logger.i('setting save classifier to true'); + _saveClassifierImage = true; + } + /// Callback to receive each frame [CameraImage] perform inference on it onLatestImageAvailable(CameraImage cameraImage) async { if (_classifierInitialized) { @@ -144,44 +173,66 @@ class _PokeFinderState extends State with WidgetsBindingObserver { } } - void swapCamera() async { - logger.i(cameras); - logger.i(cameraIndex); - cameraIndex += 1; - if (cameras.length <= cameraIndex) { - cameraIndex = 0; - } - swapToCamera(cameras[cameraIndex]); - } - - void saveMLImage() async { - logger.i('setting save classifier to true'); - _saveClassifierImage = true; - } - - void setZoom() async { - logger.i(await cameraController.getMinZoomLevel()); - logger.i(await cameraController.getMaxZoomLevel()); - logger.i(cameraController.getMinZoomLevel()); - logger.i(cameraController.getMaxZoomLevel()); - logger.i(cameraController.setZoomLevel(0.7)); - } + final GlobalKey _refreshIndicatorKey = + GlobalKey(); @override Widget build(BuildContext context) { - // Return empty container while the camera is not initialized - if (!_cameraInitialized) { - return Container(); - } return Column( children: [ - AspectRatio( - aspectRatio: 1 / cameraController.value.aspectRatio, - child: CameraPreview(cameraController)), - TextButton(onPressed: swapCamera, child: const Text('Change Camera!')), + RefreshIndicator( + key: _refreshIndicatorKey, + onRefresh: () async { + // Replace this delay with the code to be executed during refresh + // and return a Future when code finishes execution. + while (!_cameraReady) { + await Future.delayed(const Duration(milliseconds: 100)); + } + return; + }, + child: !_cameraReady? Container(width: 100, height: 100,): Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + AspectRatio( + aspectRatio: 1 / cameraController.value.aspectRatio, + child: CameraPreview(cameraController)), + Positioned( + top: -10, + child: TextButton( + onPressed: swapCamera, + child: const Text('Change Camera!')), + ), + Positioned( + bottom: -10, + child: Row( + children: [ + SizedBox( + width: MediaQuery.of(context).size.width - 100, + child: Slider( + min: pow(_minZoom, 1 / _zoomSliderLogFactor) + .toDouble(), + max: pow(_maxZoom, 1 / _zoomSliderLogFactor) + .toDouble(), + divisions: 100, + value: _currentZoom, + onChanged: (double value) { + logger.i('Zoom updated $value'); + _currentZoom = value; + cameraController.setZoomLevel( + pow(_currentZoom, _zoomSliderLogFactor) + .toDouble()); + })), + Text( + pow(_currentZoom, _zoomSliderLogFactor) + .toStringAsFixed(2), + style: const TextStyle(color: Colors.lightBlue)) + // style: const TextStyle(color: Colors.lightBlue)) + ], + )) + ])), TextButton( onPressed: saveMLImage, child: const Text('Save Model Image')), - TextButton(onPressed: setZoom, child: const Text('Zoom!')) ], ); } @@ -203,6 +254,7 @@ class _PokeFinderState extends State with WidgetsBindingObserver { break; case AppLifecycleState.resumed: if (!cameraController.value.isStreamingImages) { + swapToCamera(cameras[_cameraIndex]); await cameraController.startImageStream(onLatestImageAvailable); } break; diff --git a/lib/widgets/tensordex_home.dart b/lib/widgets/tensordex_home.dart index f81b78e..f8e6db1 100644 --- a/lib/widgets/tensordex_home.dart +++ b/lib/widgets/tensordex_home.dart @@ -24,6 +24,7 @@ class _TensordexHomeState extends State { /// Results from the image classifier List results = [Recognition(1, 'NOTHING DETECTED', .5)]; Stats stats = Stats(); + int _selectedNavBarIndex = 0; /// Scaffold Key GlobalKey scaffoldKey = GlobalKey(); @@ -58,60 +59,111 @@ class _TensordexHomeState extends State { }); } + void _onNavBarTapped(int index) { + setState(() { + _selectedNavBarIndex = index; + }); + } + + static const TextStyle optionStyle = + TextStyle(fontSize: 30, fontWeight: FontWeight.bold); + @override Widget build(BuildContext context) { + final List _widgetOptions = [ + Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + PokeFinder( + resultsCallback: resultsCallback, statsCallback: statsCallback), + Results(results, stats), + ], + ), + const Text( + 'Index 1: Seen', + style: optionStyle, + ), + const Text( + 'Index 2: About', + style: optionStyle, + ), + const Text( + 'Index 3: Settings', + style: optionStyle, + ), + ]; return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), - drawer: Drawer( - child: ListView( - // Important: Remove any padding from the ListView. - padding: EdgeInsets.zero, - children: [ - const DrawerHeader( - decoration: BoxDecoration( - color: Colors.blue, - ), - child: Text('Drawer Header'), - ), - ListTile( - title: const Text('Item 1'), - onTap: () { - // Update the state of the app. - // ... - }, - ), - ListTile( - title: const Text('Item 2'), - onTap: () { - // Update the state of the app. - // ... - }, - ), - ], + appBar: AppBar( + title: Text(widget.title), + ), + // drawer: Drawer( + // child: ListView( + // // Important: Remove any padding from the ListView. + // padding: EdgeInsets.zero, + // children: [ + // const DrawerHeader( + // decoration: BoxDecoration( + // color: Colors.blue, + // ), + // child: Text('Drawer Header'), + // ), + // ListTile( + // title: const Text('Item 1'), + // onTap: () { + // // Update the state of the app. + // // ... + // }, + // ), + // ListTile( + // title: const Text('Item 2'), + // onTap: () { + // // Update the state of the app. + // // ... + // }, + // ), + // ], + // ), + // ), + body: Center( + child: _widgetOptions.elementAt(_selectedNavBarIndex), + ), + floatingActionButton: GestureDetector( + onLongPress: () { + _incrementCounter(); + }, + child: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.photo_camera), + ), // This trailing comma makes auto-formatting nicer for build methods. + ), + bottomNavigationBar: BottomNavigationBar( + backgroundColor: Colors.lightBlue, + type: BottomNavigationBarType.shifting, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.camera), + label: 'Camera', + backgroundColor: Colors.lightBlue, ), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - PokeFinder( - resultsCallback: resultsCallback, - statsCallback: statsCallback), - Results(results, stats), - ], + BottomNavigationBarItem( + icon: Icon(Icons.call), + label: 'Calls', + backgroundColor: Colors.deepOrange), + BottomNavigationBarItem( + icon: Icon(Icons.chat), + label: 'Chats', + backgroundColor: Colors.red), + BottomNavigationBarItem( + icon: Icon(Icons.settings), + label: 'Settings', + backgroundColor: Colors.purple, ), - ), - floatingActionButton: GestureDetector( - onLongPress: () { - _incrementCounter(); - }, - child: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.photo_camera), - ), // This trailing comma makes auto-formatting nicer for build methods. - )); + ], + currentIndex: _selectedNavBarIndex, + selectedItemColor: Colors.amber, + onTap: _onNavBarTapped, + ), + ); } }