diff --git a/flutter-app/lib/camera.dart b/flutter-app/lib/camera.dart index 0f2207f..e0b1aad 100644 --- a/flutter-app/lib/camera.dart +++ b/flutter-app/lib/camera.dart @@ -20,6 +20,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:multi_media_picker/multi_media_picker.dart'; import 'countdown_timer.dart'; import 'models.dart'; @@ -38,14 +39,21 @@ class Camera extends StatefulWidget { final String label; final String labelKey; final UserModel userModel; - - Camera(this.dataset, this.label, this.labelKey, this.userModel); + final bool isUploading; + List resultList; + Camera(this.dataset, this.label, this.labelKey, this.userModel, + this.isUploading); Future init() async { - try { - cameras = await availableCameras(); - } on CameraException catch (e) { - logError(e.code, e.description); + if (!isUploading) { + try { + cameras = await availableCameras(); + } on CameraException catch (e) { + logError(e.code, e.description); + } + } else { + resultList = + await MultiMediaPicker.pickImages(source: ImageSource.gallery); } } @@ -67,12 +75,14 @@ class _CameraState extends State { @override void initState() { super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) { - for (CameraDescription cameraDescription in cameras) { - if (cameraDescription.lensDirection == CameraLensDirection.back) - onNewCameraSelected(cameraDescription); - } - }); + if (!widget.isUploading) { + WidgetsBinding.instance.addPostFrameCallback((_) { + for (CameraDescription cameraDescription in cameras) { + if (cameraDescription.lensDirection == CameraLensDirection.back) + onNewCameraSelected(cameraDescription); + } + }); + } } @override @@ -83,54 +93,74 @@ class _CameraState extends State { @override Widget build(BuildContext context) { + if (widget.isUploading && widget.resultList != null) { + widget.resultList + .forEach((eachImage) => {uploadImageToStorage(eachImage.path)}); + } return Scaffold( key: _scaffoldKey, appBar: AppBar( backgroundColor: Theme.of(context).accentColor, title: Text('Capture sample for ${widget.label}'), ), - body: Container( - decoration: new BoxDecoration(color: Colors.black), - child: Column( - children: [ - Expanded( - child: Container( - child: Padding( - padding: const EdgeInsets.all(1.0), - child: Center( - child: _cameraPreviewWidget(), - ), - ), - decoration: BoxDecoration( - color: Colors.black, - border: Border.all( - color: - controller != null && controller.value.isRecordingVideo - ? Colors.redAccent - : Colors.black45, - width: 2.0, - ), - ), + body: widget.isUploading + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('Uploading Images in the background'), + MaterialButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text('OK'), + ) + ], ), - ), - new CameraControlWidget( - controller: controller, - onRecordingStart: onVideoRecordButtonPressed, - onRecordingStop: onStopButtonPressed, - onPictureTaken: onTakePictureButtonPressed, - ), - Padding( - padding: const EdgeInsets.all(5.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, + ) + : Container( + decoration: new BoxDecoration(color: Colors.black), + child: Column( children: [ - _thumbnailWidget(), + Expanded( + child: Container( + child: Padding( + padding: const EdgeInsets.all(1.0), + child: Center( + child: _cameraPreviewWidget(), + ), + ), + decoration: BoxDecoration( + color: Colors.black, + border: Border.all( + color: controller != null && + controller.value.isRecordingVideo + ? Colors.redAccent + : Colors.black45, + width: 2.0, + ), + ), + ), + ), + new CameraControlWidget( + controller: controller, + onRecordingStart: onVideoRecordButtonPressed, + onRecordingStop: onStopButtonPressed, + onPictureTaken: onTakePictureButtonPressed, + ), + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _thumbnailWidget(), + ], + ), + ), ], ), ), - ], - ), - ), ); } @@ -200,60 +230,7 @@ class _CameraState extends State { void onTakePictureButtonPressed() async { final filePath = await takePicture(); - final automlStorage = InheritedStorage.of(context).autoMlStorage; - - if (mounted) { - setState(() { - imagePath = filePath; - }); - - final filename = - new DateTime.now().millisecondsSinceEpoch.toString() + '.jpg'; - - // upload to storage and firestore - final StorageReference ref = automlStorage - .ref() - .child('datasets') - .child(widget.dataset.name) - .child(widget.label) - .child(filename); - - final File file = File(filePath).absolute; - // upload the file - StorageUploadTask uploadTask = ref.putFile( - file, - StorageMetadata( - contentType: 'image/jpeg', - customMetadata: {'activity': 'imgUpload'}, - ), - ); - - final snapshot = await uploadTask.onComplete; - final downloadUrl = await snapshot.ref.getDownloadURL(); - - Firestore.instance.collection('images').add({ - 'parent_key': widget.labelKey, - 'dataset_parent_key': widget.dataset.id, - 'type': toString(SampleType - .TRAIN), // newly created images are categorized as training. - 'filename': filename, - 'uploadPath': - 'datasets/${widget.dataset.name}/${widget.label}/$filename', - 'gcsURI': downloadUrl, - 'uploader': widget.userModel.user.email, - }); - - final labelRef = - Firestore.instance.collection('labels').document(widget.labelKey); - - // increment count for the label - await Firestore.instance.runTransaction((Transaction tx) async { - DocumentSnapshot snapshot = await tx.get(labelRef); - await tx.update(labelRef, { - 'total_images': snapshot.data['total_images'] + 1 - }); - }); - } + uploadImageToStorage(filePath); } void onVideoRecordButtonPressed() { @@ -375,6 +352,63 @@ class _CameraState extends State { logError(e.code, e.description); showInSnackBar('Error: ${e.code}\n${e.description}'); } + + void uploadImageToStorage(String filePath) async { + final automlStorage = InheritedStorage.of(context).autoMlStorage; + + if (mounted) { + setState(() { + imagePath = filePath; + }); + + final filename = + new DateTime.now().millisecondsSinceEpoch.toString() + '.jpg'; + + // upload to storage and firestore + final StorageReference ref = automlStorage + .ref() + .child('datasets') + .child(widget.dataset.name) + .child(widget.label) + .child(filename); + + final File file = File(filePath).absolute; + // upload the file + StorageUploadTask uploadTask = ref.putFile( + file, + StorageMetadata( + contentType: 'image/jpeg', + customMetadata: {'activity': 'imgUpload'}, + ), + ); + + final snapshot = await uploadTask.onComplete; + final downloadUrl = await snapshot.ref.getDownloadURL(); + + Firestore.instance.collection('images').add({ + 'parent_key': widget.labelKey, + 'dataset_parent_key': widget.dataset.id, + 'type': toString(SampleType + .TRAIN), // newly created images are categorized as training. + 'filename': filename, + 'uploadPath': + 'datasets/${widget.dataset.name}/${widget.label}/$filename', + 'gcsURI': downloadUrl, + 'uploader': widget.userModel.user.email, + }); + + final labelRef = + Firestore.instance.collection('labels').document(widget.labelKey); + + // increment count for the label + await Firestore.instance.runTransaction((Transaction tx) async { + DocumentSnapshot snapshot = await tx.get(labelRef); + await tx.update(labelRef, { + 'total_images': snapshot.data['total_images'] + 1 + }); + }); + } + } } /// Widget to control start / stop of camera diff --git a/flutter-app/lib/label_samples_grid.dart b/flutter-app/lib/label_samples_grid.dart index c58f0c5..92c1986 100644 --- a/flutter-app/lib/label_samples_grid.dart +++ b/flutter-app/lib/label_samples_grid.dart @@ -65,19 +65,51 @@ class ListLabelSamples extends StatelessWidget { preferredSize: new AppBar().preferredSize, ), floatingActionButton: model.isLoggedIn() - ? new FloatingActionButton.extended( - icon: const Icon(Icons.add_a_photo), - label: const Text('Add Images'), - onPressed: () async { - final c = Camera(dataset, labelName, labelKey, model); - await c.init(); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => c, + ? Align( + alignment: Alignment.bottomRight, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + new FloatingActionButton.extended( + icon: const Icon(Icons.add_a_photo), + heroTag: 1, + label: const Text('Add Images'), + onPressed: () async { + final c = + Camera(dataset, labelName, labelKey, model, false); + await c.init(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => c, + ), + ); + }, ), - ); - }, + Padding( + padding: const EdgeInsets.all(4.0), + ), + new FloatingActionButton.extended( + icon: const Icon(Icons.file_upload), + heroTag: 2, + label: const Text('Upload Images'), + onPressed: () async { + final c = + Camera(dataset, labelName, labelKey, model, true); + await c.init(); + if (c.resultList != null && c.resultList.isNotEmpty) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => c, + ), + ); + } + }, + ), + ], + ), ) : Container(), // body of the screen diff --git a/flutter-app/pubspec.yaml b/flutter-app/pubspec.yaml index bcd349b..1ab3cf4 100644 --- a/flutter-app/pubspec.yaml +++ b/flutter-app/pubspec.yaml @@ -41,7 +41,7 @@ dependencies: intro_slider: ^1.2.0 url_launcher: ^5.0.2 shared_preferences: ^0.5.2 - + multi_media_picker: ^0.2.3 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2