Created
February 27, 2023 18:17
-
-
Save mhassanist/1a7ace0d8d3a511a237698a317f57071 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'dart:io'; | |
import 'dart:typed_data'; | |
import 'package:camera/camera.dart'; | |
import 'package:cloud_firestore/cloud_firestore.dart'; | |
import 'package:connectivity_plus/connectivity_plus.dart'; | |
import 'package:filesize/filesize.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:geocoding/geocoding.dart'; | |
import 'package:intl/intl.dart'; | |
import 'package:location/location.dart' as location; | |
import 'package:path/path.dart' as path; | |
import 'package:path_provider/path_provider.dart'; | |
import 'package:skycap/data/models/job_site_model.dart'; | |
import 'package:stop_watch_timer/stop_watch_timer.dart'; | |
import 'package:video_player/video_player.dart'; | |
import '../../data/models/manual_classify_upload_item.dart'; | |
import '../../providers/manual_classify_bloc.dart'; | |
import '../../providers/shared_preference_provider.dart'; | |
import '../../utils/ui_utils.dart'; | |
import '../screens/manual_classification/upload_screen_bottom_bar.dart'; | |
import 'compass_widget.dart'; | |
class JobSiteCamera extends StatefulWidget { | |
final List<CameraDescription> cameras; | |
final JobSite? project; | |
JobSiteCamera(this.cameras, this.project); | |
@override | |
_JobSiteCameraState createState() { | |
return _JobSiteCameraState(); | |
} | |
} | |
void logError(String code, String? message) { | |
if (message != null) { | |
print('Error: $code\nError Message: $message'); | |
} else { | |
print('Error: $code'); | |
} | |
} | |
class _JobSiteCameraState extends State<JobSiteCamera> | |
with WidgetsBindingObserver, TickerProviderStateMixin { | |
CameraController? controller; | |
XFile? imageOrVideoFile; | |
File? fileWithNewDirectory; | |
List<XFile> filesList = []; | |
List<File> filesWithNewDirectoryList = []; | |
VideoPlayerController? videoController; | |
VoidCallback? videoPlayerListener; | |
bool enableAudio = false; | |
late AnimationController _flashModeControlRowAnimationController; | |
late Animation<double> _flashModeControlRowAnimation; | |
late AnimationController _exposureModeControlRowAnimationController; | |
late AnimationController _focusModeControlRowAnimationController; | |
double _minAvailableZoom = 1.0; | |
double _maxAvailableZoom = 1.0; | |
double _currentScale = 1.0; | |
double _baseScale = 1.0; | |
bool isVideo = false; | |
List<ManualClassifyUploadItem> manualClassifyItemList = []; | |
ManualClassifyUploadItem? manualClassifyUploadItem; | |
String azimuth = ''; | |
// Counting pointers (number of user fingers on screen) | |
int _pointers = 0; | |
location.LocationData? _locationData; | |
location.LocationData? _locationData2; | |
Placemark? address; | |
location.Location _location = location.Location(); | |
ConnectivityResult? _connectivityResult; | |
late StreamSubscription _connectivitySubscription; | |
Color whiteColor = Colors.white; | |
location.PermissionStatus? _permissionGranted; | |
Future<Placemark> getAddressFromLatAndLong(location.LocationData position)async { | |
List<Placemark> placeMark = await placemarkFromCoordinates(position.latitude!, position.longitude!); | |
return placeMark[0]; | |
} | |
Future<void> _showExitCameraDialog() async { | |
return showDialog<void>( | |
context: context, | |
barrierDismissible: false, | |
builder: (BuildContext context) { | |
return AlertDialog( | |
title: const Text('Warning'), | |
content: SingleChildScrollView( | |
child: ListBody( | |
children: const <Widget>[ | |
Text('Captured files will be deleted.'), | |
], | |
), | |
), | |
actions: <Widget>[ | |
TextButton( | |
child: const Text('Cancel'), | |
onPressed: () { | |
Navigator.of(context).pop(); | |
}, | |
), | |
TextButton( | |
child: const Text('upload'), | |
onPressed: () { | |
Navigator.of(context).pop(); | |
}, | |
), | |
TextButton( | |
child: const Text('Delete'), | |
onPressed: () { | |
_deleteFiles(); | |
Navigator.of(context).pop(); | |
Navigator.of(context).pop(); | |
}, | |
), | |
], | |
); | |
}, | |
); | |
} | |
void _deleteFiles(){ | |
for(var xFile in filesList){ | |
final dir = Directory(xFile.path); | |
dir.deleteSync(recursive: true); | |
} | |
for(var file in filesWithNewDirectoryList){ | |
final dir = Directory(file.path); | |
dir.deleteSync(recursive: true); | |
} | |
} | |
final StopWatchTimer _stopWatchTimer = StopWatchTimer( | |
mode: StopWatchMode.countUp, | |
onChange: (value) => print('onChange $value'), | |
onChangeRawSecond: (value) => print('onChangeRawSecond $value'), | |
onChangeRawMinute: (value) => print('onChangeRawMinute $value'), | |
); | |
void getLocationPermission()async{ | |
_permissionGranted = await location.Location().hasPermission(); | |
if (_permissionGranted == location.PermissionStatus.denied) { | |
_permissionGranted = await location.Location().requestPermission(); | |
if (_permissionGranted != location.PermissionStatus.granted) { | |
return; | |
} | |
} | |
} | |
@override | |
void initState() { | |
super.initState(); | |
onNewCameraSelected(widget.cameras.first); | |
_ambiguate(WidgetsBinding.instance)?.addObserver(this); | |
_flashModeControlRowAnimationController = AnimationController( | |
duration: const Duration(milliseconds: 300), | |
vsync: this, | |
); | |
_flashModeControlRowAnimation = CurvedAnimation( | |
parent: _flashModeControlRowAnimationController, | |
curve: Curves.easeInCubic, | |
); | |
_exposureModeControlRowAnimationController = AnimationController( | |
duration: const Duration(milliseconds: 300), | |
vsync: this, | |
); | |
_focusModeControlRowAnimationController = AnimationController( | |
duration: const Duration(milliseconds: 300), | |
vsync: this, | |
); | |
_connectivitySubscription = Connectivity().onConnectivityChanged.listen(( | |
ConnectivityResult result | |
) { | |
_connectivityResult = result; | |
}); | |
_stopWatchTimer.rawTime.listen((value) => | |
print('rawTime $value ${StopWatchTimer.getDisplayTime(value)}')); | |
_stopWatchTimer.minuteTime.listen((value) => print('minuteTime $value')); | |
_stopWatchTimer.secondTime.listen((value) => print('secondTime $value')); | |
_stopWatchTimer.records.listen((value) => print('records $value')); | |
} | |
@override | |
void dispose() async{ | |
_ambiguate(WidgetsBinding.instance)?.removeObserver(this); | |
_flashModeControlRowAnimationController.dispose(); | |
_exposureModeControlRowAnimationController.dispose(); | |
controller!.dispose(); | |
_connectivitySubscription.cancel(); | |
await _stopWatchTimer.dispose(); | |
super.dispose(); | |
} | |
@override | |
void didChangeAppLifecycleState(AppLifecycleState state) { | |
final CameraController? cameraController = controller; | |
// App state changed before we got the chance to initialize. | |
if (cameraController == null || !cameraController.value.isInitialized) { | |
return; | |
} | |
if (state == AppLifecycleState.inactive) { | |
cameraController.dispose(); | |
} else if (state == AppLifecycleState.resumed) { | |
onNewCameraSelected(cameraController.description); | |
} | |
} | |
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); | |
@override | |
Widget build(BuildContext context) { | |
return WillPopScope( | |
onWillPop: ()async{ | |
if(filesList.isEmpty){ | |
Navigator.of(context).pop(); | |
}else{ | |
_showExitCameraDialog(); | |
} | |
return true; | |
}, | |
child: SafeArea( | |
child: Material( | |
color: Colors.transparent, | |
child: Stack( | |
children: [ | |
_cameraPreviewWidget(), | |
_buildLocationAndCompassInfo(), | |
_buildCameraControlSetting(), | |
_buildVideoTimerCountField(), | |
BackButton( | |
color: whiteColor, | |
onPressed: (){ | |
if(filesList.isEmpty){ | |
Navigator.of(context).pop(); | |
}else{ | |
_showExitCameraDialog(); | |
} | |
}, | |
), | |
], | |
), | |
), | |
), | |
); | |
} | |
_buildCameraControlSetting() { | |
return Container( | |
color: Colors.transparent, | |
padding: EdgeInsets.only(left: 10.0, right: 10.0), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.center, | |
mainAxisAlignment: MainAxisAlignment.end, | |
children: [ | |
_captureControlRowWidget(), | |
//_modeControlRowWidget(), | |
Padding( | |
padding: const EdgeInsets.all(5.0), | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: <Widget>[ | |
_cameraTogglesRowWidget(), | |
_buildSubmitButton(), | |
//_thumbnailWidget(), | |
], | |
), | |
), | |
], | |
), | |
); | |
} | |
_buildLocationAndCompassInfo() { | |
return Align( | |
alignment: Alignment.topCenter, | |
child: Container( | |
color: Colors.black26, | |
height: 140, | |
child: Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Column( | |
children: [ | |
Center( | |
child: _buildJobSiteIdText(), | |
), | |
SizedBox(height: 10.0,), | |
Row( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
StreamBuilder( | |
stream: _location.onLocationChanged, | |
builder: (context, snapShot) { | |
if (snapShot.hasData) { | |
_locationData = snapShot.data as location.LocationData?; | |
return Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
Text( | |
_locationData == null | |
? "" | |
: "lat: ${_locationData!.latitude}", | |
style: TextStyle(color: whiteColor, fontSize: 18), | |
), | |
Text( | |
_locationData == null | |
? "" | |
: "long: ${_locationData!.longitude}", | |
style: TextStyle(color: whiteColor, fontSize: 18), | |
), | |
FutureBuilder( | |
future: getAddressFromLatAndLong(_locationData!), | |
builder: (context, snapshot) { | |
if(snapshot.connectionState == ConnectionState.done){ | |
if(snapshot.hasData){ | |
address = snapshot.data as Placemark?; | |
return Container( | |
width: MediaQuery.of(context).size.width/2, | |
child: Text('Address: ${address!.street},' | |
'${address!.administrativeArea},' | |
'${address!.isoCountryCode}',maxLines: 2,overflow: TextOverflow.clip, | |
style: TextStyle(color: whiteColor, | |
fontSize: 18,)), | |
); | |
} | |
} | |
return Text(''); | |
} | |
) | |
], | |
); | |
} | |
return Text(''); | |
}), | |
Expanded( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.end, | |
crossAxisAlignment: CrossAxisAlignment.end, | |
children: [ | |
CompassWidget(_getAzimuthValue), | |
StreamBuilder( | |
stream: _location.onLocationChanged, | |
builder: (context, snapshot) { | |
if(snapshot.hasData){ | |
_locationData2 = snapshot.data as location.LocationData?; | |
return Text( | |
_locationData2 == null | |
? "" | |
: "Elevation: ${_locationData2!.altitude!.toStringAsFixed(2)}'", | |
style: TextStyle( | |
color: whiteColor, fontSize: 18), | |
); | |
}else{ | |
return Text(''); | |
} | |
}) | |
], | |
), | |
), | |
], | |
), | |
], | |
), | |
), | |
), | |
); | |
} | |
/// Display the preview from the camera (or a message if the preview is not available). | |
Widget _cameraPreviewWidget() { | |
final CameraController? cameraController = controller; | |
if (cameraController == null || !cameraController.value.isInitialized) { | |
return Center( | |
child: Text( | |
'Tap a camera', | |
style: TextStyle( | |
color: whiteColor, | |
fontSize: 24.0, | |
fontWeight: FontWeight.w900, | |
), | |
), | |
); | |
} else { | |
return Listener( | |
onPointerDown: (_) => _pointers++, | |
onPointerUp: (_) => _pointers--, | |
child: CameraPreview( | |
controller!, | |
child: LayoutBuilder( | |
builder: (BuildContext context, BoxConstraints constraints) { | |
getLocationPermission(); | |
return GestureDetector( | |
behavior: HitTestBehavior.opaque, | |
onScaleStart: _handleScaleStart, | |
onScaleUpdate: _handleScaleUpdate, | |
onTapDown: (TapDownDetails details) => | |
onViewFinderTap(details, constraints), | |
); | |
}), | |
), | |
); | |
} | |
} | |
void _handleScaleStart(ScaleStartDetails details) { | |
_baseScale = _currentScale; | |
} | |
Future<void> _handleScaleUpdate(ScaleUpdateDetails details) async { | |
// When there are not exactly two fingers on screen don't scale | |
if (controller == null || _pointers != 2) { | |
return; | |
} | |
_currentScale = (_baseScale * details.scale) | |
.clamp(_minAvailableZoom, _maxAvailableZoom); | |
await controller!.setZoomLevel(_currentScale); | |
} | |
/// Display a bar with buttons to change the flash and exposure modes | |
Widget _flashModeControlRowWidget() { | |
return SizeTransition( | |
sizeFactor: _flashModeControlRowAnimation, | |
child: ClipRect( | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.spaceEvenly, | |
mainAxisSize: MainAxisSize.max, | |
children: <Widget>[ | |
IconButton( | |
icon: const Icon(Icons.flash_off), | |
color: controller?.value.flashMode == FlashMode.off | |
? Colors.orange | |
: whiteColor, | |
onPressed: controller != null | |
? () => onSetFlashModeButtonPressed(FlashMode.off) | |
: null, | |
), | |
IconButton( | |
icon: const Icon(Icons.flash_auto), | |
color: controller?.value.flashMode == FlashMode.auto | |
? Colors.orange | |
: whiteColor, | |
onPressed: controller != null | |
? () => onSetFlashModeButtonPressed(FlashMode.auto) | |
: null, | |
), | |
IconButton( | |
icon: const Icon(Icons.flash_on), | |
color: controller?.value.flashMode == FlashMode.always | |
? Colors.orange | |
: whiteColor, | |
onPressed: controller != null | |
? () => onSetFlashModeButtonPressed(FlashMode.always) | |
: null, | |
), | |
IconButton( | |
icon: const Icon(Icons.highlight), | |
color: controller?.value.flashMode == FlashMode.torch | |
? Colors.orange | |
: whiteColor, | |
onPressed: controller != null | |
? () => onSetFlashModeButtonPressed(FlashMode.torch) | |
: null, | |
), | |
], | |
), | |
), | |
); | |
} | |
/// Display the control bar with buttons to take pictures and record videos. | |
Widget _captureControlRowWidget() { | |
final CameraController? cameraController = controller; | |
return Column( | |
children: [ | |
cameraController != null && | |
cameraController.value.isPreviewPaused | |
? IconButton( | |
icon: Icon( | |
Icons.cancel, | |
color: whiteColor, | |
), | |
iconSize: 80, | |
onPressed: ()async{ | |
filesList.remove(imageOrVideoFile); | |
filesWithNewDirectoryList.remove(fileWithNewDirectory); | |
manualClassifyItemList.remove(manualClassifyUploadItem); | |
final dir = Directory(imageOrVideoFile!.path); | |
dir.deleteSync(recursive: true); | |
final newDir = Directory(fileWithNewDirectory!.path); | |
newDir.deleteSync(recursive: true); | |
imageOrVideoFile = null; | |
fileWithNewDirectory = null; | |
manualClassifyUploadItem = null; | |
await cameraController.resumePreview(); | |
}, | |
) : SizedBox(), | |
Row( | |
mainAxisAlignment: MainAxisAlignment.spaceEvenly, | |
mainAxisSize: MainAxisSize.max, | |
children: <Widget>[ | |
IconButton( | |
icon: const Icon(Icons.flash_on, size: 30), | |
color: whiteColor, | |
onPressed: controller != null ? onFlashModeButtonPressed : null, | |
), | |
isVideo == true | |
? Expanded( | |
child: Stack( | |
alignment: Alignment.topCenter, | |
children: [ | |
Center( | |
child: IconButton( | |
icon: Icon( | |
cameraController != null && | |
cameraController.value.isInitialized && | |
cameraController.value.isRecordingVideo | |
? Icons.stop_circle_outlined | |
: Icons.circle, | |
color: cameraController != null && | |
cameraController.value.isInitialized && | |
cameraController.value.isRecordingVideo | |
? Colors.red | |
: whiteColor, | |
), | |
iconSize: 80, | |
onPressed: cameraController != null && | |
cameraController.value.isInitialized | |
? !cameraController.value.isRecordingVideo | |
? onVideoRecordButtonPressed | |
: onStopButtonPressed | |
: null, | |
), | |
), | |
Padding( | |
padding: EdgeInsets.only(left: 60), | |
child: Text( | |
filesList.length == 0 | |
? "" | |
: filesList.length.toString(), | |
style: TextStyle(color: whiteColor,fontSize: 18), | |
)), | |
], | |
), | |
) | |
: cameraController != null && | |
cameraController.value.isPreviewPaused | |
? Expanded( | |
child: IconButton( | |
iconSize: 80, | |
icon: const Icon(Icons.check_circle), | |
color: whiteColor, | |
onPressed: cameraController.value.isInitialized | |
? onPausePreviewButtonPressed | |
: null, | |
), | |
) | |
: Expanded( | |
child: Stack( | |
alignment: Alignment.topCenter, | |
children: [ | |
Center( | |
child: IconButton( | |
icon: Icon( | |
Icons.circle, | |
color: whiteColor, | |
), | |
iconSize: 80, | |
onPressed: cameraController != null && | |
cameraController.value.isInitialized && | |
!cameraController.value.isRecordingVideo | |
? onTakePictureButtonPressed | |
: null, | |
), | |
), | |
Padding( | |
padding: EdgeInsets.only(left: 60), | |
child: Text( | |
filesList.length == 0 | |
? "" | |
: filesList.length.toString(), | |
style: TextStyle(color: whiteColor,fontSize: 18), | |
)), | |
], | |
), | |
), | |
_buildCameraChangeLens(), | |
], | |
), | |
_flashModeControlRowWidget(), | |
], | |
); | |
} | |
/// Display a row of toggle to select the camera (or a message if no camera is available). | |
Widget _cameraTogglesRowWidget() { | |
Widget toggles = Container(); | |
if (widget.cameras.isEmpty) { | |
return const Text('No camera found'); | |
} else { | |
toggles = Row( | |
children: [ | |
Icon( | |
Icons.camera_alt, | |
size: 30, | |
color: whiteColor, | |
), | |
Switch( | |
onChanged: (bool value) { | |
setState(() { | |
isVideo = !isVideo; | |
}); | |
}, | |
value: isVideo, | |
), | |
Icon( | |
Icons.videocam, | |
size: 30, | |
color: whiteColor, | |
) | |
], | |
); | |
} | |
return toggles; | |
} | |
String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); | |
void showInSnackBar(String message) { | |
// ignore: deprecated_member_use | |
_scaffoldKey.currentState?.showSnackBar(SnackBar(content: Text(message))); | |
} | |
void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { | |
if (controller == null) { | |
return; | |
} | |
final CameraController cameraController = controller!; | |
final Offset offset = Offset( | |
details.localPosition.dx / constraints.maxWidth, | |
details.localPosition.dy / constraints.maxHeight, | |
); | |
cameraController.setExposurePoint(offset); | |
cameraController.setFocusPoint(offset); | |
} | |
Future<void> onNewCameraSelected(CameraDescription cameraDescription) async { | |
if (controller != null) { | |
await controller!.dispose(); | |
} | |
final CameraController cameraController = CameraController( | |
cameraDescription, | |
ResolutionPreset.high, | |
enableAudio: enableAudio, | |
imageFormatGroup: ImageFormatGroup.jpeg, | |
); | |
controller = cameraController; | |
// If the controller is updated then update the UI. | |
cameraController.addListener(() { | |
if (mounted) { | |
setState(() {}); | |
} | |
if (cameraController.value.hasError) { | |
showInSnackBar( | |
'Camera error ${cameraController.value.errorDescription}'); | |
} | |
}); | |
try { | |
await cameraController.initialize(); | |
await Future.wait(<Future<Object?>>[ | |
// The exposure mode is currently not supported on the web. | |
cameraController | |
.getMaxZoomLevel() | |
.then((double value) => _maxAvailableZoom = value), | |
cameraController | |
.getMinZoomLevel() | |
.then((double value) => _minAvailableZoom = value), | |
]); | |
} on CameraException catch (e) { | |
_showCameraException(e); | |
} | |
if (mounted) { | |
setState(() {}); | |
} | |
} | |
void onTakePictureButtonPressed() { | |
takePicture().then((XFile? file) async { | |
if (mounted) { | |
setState(() { | |
imageOrVideoFile = file; | |
videoController?.dispose(); | |
videoController = null; | |
}); | |
if (file != null) { | |
if(Platform.isAndroid){ | |
Uint8List exportImage = await file.readAsBytes(); | |
Directory? directory = (await getExternalStorageDirectory()); | |
Directory d = await Directory('${directory!.path}/Sky-cap/' | |
'${widget.project!.site!.code! + " " + widget.project!.site!.name!}' | |
'/${DateFormat('yyyy-MM-dd').format(DateTime.now())}/').create(recursive: true).catchError( | |
(e){ | |
print(e); | |
}); | |
final fullPath = '${d.path}${file.name}'; | |
final imgFile = File('$fullPath'); | |
try{ | |
imgFile.writeAsBytesSync(exportImage); | |
}catch(e){ | |
print(e); | |
} | |
fileWithNewDirectory = imgFile; | |
filesList.add(file); | |
filesWithNewDirectoryList.add(imgFile); | |
manualClassifyUploadItem = _putManualClassifyItem(imgFile); | |
manualClassifyItemList.add(manualClassifyUploadItem!); | |
showInSnackBar('Picture saved to ${file.path}'); | |
} | |
} | |
} | |
onPausePreviewButtonPressed(); | |
}); | |
} | |
void onFlashModeButtonPressed() { | |
if (_flashModeControlRowAnimationController.value == 1) { | |
_flashModeControlRowAnimationController.reverse(); | |
} else { | |
_flashModeControlRowAnimationController.forward(); | |
_exposureModeControlRowAnimationController.reverse(); | |
_focusModeControlRowAnimationController.reverse(); | |
} | |
} | |
void onSetFlashModeButtonPressed(FlashMode mode) { | |
setFlashMode(mode).then((_) { | |
if (mounted) { | |
setState(() {}); | |
} | |
showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); | |
}); | |
} | |
void onVideoRecordButtonPressed() { | |
_stopWatchTimer.onExecute | |
.add(StopWatchExecute.start); | |
startVideoRecording().then((_) { | |
if (mounted) { | |
setState(() {}); | |
} | |
}); | |
} | |
void onStopButtonPressed() { | |
_stopWatchTimer.onExecute | |
.add(StopWatchExecute.reset); | |
stopVideoRecording().then((XFile? file) async { | |
if (mounted) { | |
setState(() {}); | |
} | |
if (file != null) { | |
if (Platform.isAndroid) { | |
Uint8List exportImage = await file.readAsBytes(); | |
Directory? directory = (await getExternalStorageDirectory()); | |
Directory d = await Directory( | |
'${directory!.path}/Sky-cap/' | |
'${widget.project!.site!.code! + " " + widget.project!.site!.name!}' | |
'/${DateFormat('yyyy-MM-dd').format(DateTime.now())}/') | |
.create(recursive: true) | |
.catchError((e) { | |
print(e); | |
}); | |
final fullPath = '${d.path}${file.name}'; | |
final imgFile = File('$fullPath'); | |
try { | |
imgFile.writeAsBytesSync(exportImage); | |
} catch (e) { | |
print(e); | |
} | |
setState(() { | |
filesList.add(file); | |
imageOrVideoFile = file; | |
filesWithNewDirectoryList.add(imgFile); | |
fileWithNewDirectory = imgFile; | |
manualClassifyUploadItem = _putManualClassifyItem(imgFile); | |
manualClassifyItemList.add(manualClassifyUploadItem!); | |
}); | |
showInSnackBar('Video recorded to ${file.path}'); | |
} | |
//_startVideoPlayer(); | |
} | |
}); | |
} | |
Future<void> onPausePreviewButtonPressed() async { | |
final CameraController? cameraController = controller; | |
if (cameraController == null || !cameraController.value.isInitialized) { | |
showInSnackBar('Error: select a camera first.'); | |
return; | |
} | |
if (cameraController.value.isPreviewPaused) { | |
await cameraController.resumePreview(); | |
} else { | |
await cameraController.pausePreview(); | |
} | |
if (mounted) { | |
setState(() {}); | |
} | |
} | |
Future<void> startVideoRecording() async { | |
final CameraController? cameraController = controller; | |
if (cameraController == null || !cameraController.value.isInitialized) { | |
showInSnackBar('Error: select a camera first.'); | |
return; | |
} | |
if (cameraController.value.isRecordingVideo) { | |
// A recording is already started, do nothing. | |
return; | |
} | |
try { | |
await cameraController.startVideoRecording(); | |
} on CameraException catch (e) { | |
_showCameraException(e); | |
return; | |
} | |
} | |
Future<XFile?> stopVideoRecording() async { | |
final CameraController? cameraController = controller; | |
if (cameraController == null || !cameraController.value.isRecordingVideo) { | |
return null; | |
} | |
try { | |
return cameraController.stopVideoRecording(); | |
} on CameraException catch (e) { | |
_showCameraException(e); | |
return null; | |
} | |
} | |
Future<void> setFlashMode(FlashMode mode) async { | |
if (controller == null) { | |
return; | |
} | |
try { | |
await controller!.setFlashMode(mode); | |
} on CameraException catch (e) { | |
_showCameraException(e); | |
rethrow; | |
} | |
} | |
Future<void> setExposureMode(ExposureMode mode) async { | |
if (controller == null) { | |
return; | |
} | |
try { | |
await controller!.setExposureMode(mode); | |
} on CameraException catch (e) { | |
_showCameraException(e); | |
rethrow; | |
} | |
} | |
Future<void> setFocusMode(FocusMode mode) async { | |
if (controller == null) { | |
return; | |
} | |
try { | |
await controller!.setFocusMode(mode); | |
} on CameraException catch (e) { | |
_showCameraException(e); | |
rethrow; | |
} | |
} | |
Future<XFile?> takePicture() async { | |
final CameraController? cameraController = controller; | |
if (cameraController == null || !cameraController.value.isInitialized) { | |
showInSnackBar('Error: select a camera first.'); | |
return null; | |
} | |
if (cameraController.value.isTakingPicture) { | |
// A capture is already pending, do nothing. | |
return null; | |
} | |
try { | |
final XFile file = await cameraController.takePicture(); | |
return file; | |
} on CameraException catch (e) { | |
_showCameraException(e); | |
return null; | |
} | |
} | |
void _showCameraException(CameraException e) { | |
logError(e.code, e.description); | |
showInSnackBar('Error: ${e.code}\n${e.description}'); | |
} | |
_buildSubmitButton() { | |
return Expanded( | |
child: Align( | |
alignment: Alignment.centerRight, | |
child: ElevatedButton( | |
onPressed: () async { | |
if(manualClassifyItemList.isNotEmpty){ | |
for (var item in manualClassifyItemList) { | |
ManualClassifyUploadBloc().uploadSink.add(item); | |
} | |
if(_connectivityResult == ConnectivityResult.wifi | |
|| _connectivityResult == ConnectivityResult.mobile){ | |
Navigator.pop(context); | |
Navigator.push(context, MaterialPageRoute(builder: (context) => UploadScreenBottomBar(widget.project!))); | |
}else{ | |
Navigator.pop(context); | |
} | |
} | |
}, child: Text('Finish'))), | |
); | |
} | |
_buildCameraChangeLens() { | |
if(widget.cameras.first == CameraDescription(name: '0', lensDirection: CameraLensDirection.back, sensorOrientation: 90) | |
&& widget.cameras[1] == CameraDescription(name: '1', lensDirection: CameraLensDirection.front, sensorOrientation: 90)){ | |
return IconButton( | |
color: whiteColor, | |
onPressed: (){ | |
onNewCameraSelected((controller!.description.lensDirection == CameraLensDirection.back) | |
? widget.cameras[1] : widget.cameras[0]); | |
}, | |
icon: Icon((controller!.description.lensDirection == CameraLensDirection.back) | |
? Icons.camera_front : Icons.camera_rear), | |
iconSize: 30,); | |
}else{ | |
return Container(); | |
} | |
} | |
_buildJobSiteIdText() { | |
return Text('Job Site: '+widget.project!.site!.code! + " " + widget.project!.site!.name!, | |
style: TextStyle(color: whiteColor, fontSize: 18,fontWeight: FontWeight.bold)); | |
} | |
void _getAzimuthValue(String value){ | |
azimuth = value; | |
} | |
ManualClassifyUploadItem _putManualClassifyItem(File file) { | |
ManualClassifyUploadItem item = ManualClassifyUploadItem( | |
jobSiteIdValue: widget.project!.id!, | |
localFilePath: file.path, | |
fileSize: filesize(file.lengthSync()), | |
name: path.basename(file.path), | |
uniqueNameIdentifier: | |
Utils.prepFootageFileName(path.basename(file.path), null, null), | |
footageType: | |
Utils().getFootageType(fileExtension: path.extension(file.path)), | |
status: UploadStatus.notStarted, | |
uploader: LocalDataProvider().getLoggedInUser(), | |
uploadedAt: null, | |
queuedAt: DateTime.now(), | |
uploadProgress: "0", | |
latitude: _locationData!.latitude.toString(), | |
longitude: _locationData!.longitude.toString(), | |
elevation: _locationData2!.altitude.toString(), | |
azimuth: azimuth, | |
address: '${address!.street},' | |
'${address!.administrativeArea},' | |
'${address!.isoCountryCode}', | |
captureDate: Timestamp.fromDate(DateTime.now()) | |
); | |
return item; | |
} | |
_buildVideoTimerCountField() { | |
return Positioned( | |
top: 145, | |
child: Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: StreamBuilder<int>( | |
stream: _stopWatchTimer.rawTime, | |
initialData: _stopWatchTimer.rawTime.value, | |
builder: (context, snap) { | |
final value = snap.data!; | |
final displayTime = StopWatchTimer.getDisplayTime(value, | |
hours: false, milliSecond: false); | |
if (displayTime == '00:00') { | |
return Container(); | |
} else { | |
return Row( | |
children: <Widget>[ | |
Icon( | |
Icons.fiber_manual_record, | |
color: Colors.red, | |
), | |
Text( | |
displayTime, | |
style: const TextStyle( | |
fontSize: 20, | |
fontFamily: 'Helvetica', | |
color: Colors.red, | |
fontWeight: FontWeight.bold), | |
), | |
], | |
); | |
} | |
}, | |
), | |
)); | |
} | |
} | |
T? _ambiguate<T>(T? value) => value; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment