You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

371 lines
10KB

  1. import 'dart:io';
  2. import 'package:camera/camera.dart';
  3. import 'package:farm_tpf/main.dart';
  4. import 'package:farm_tpf/presentation/custom_widgets/widget_utils.dart';
  5. import 'package:file_picker/file_picker.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:get/get.dart';
  8. import 'package:path_provider/path_provider.dart';
  9. class CameraHelper extends StatefulWidget {
  10. @override
  11. _CameraHelperState createState() => _CameraHelperState();
  12. }
  13. /// Returns a suitable camera icon for [direction].
  14. IconData getCameraLensIcon(CameraLensDirection direction) {
  15. switch (direction) {
  16. case CameraLensDirection.back:
  17. return Icons.camera_rear;
  18. case CameraLensDirection.front:
  19. return Icons.camera_front;
  20. case CameraLensDirection.external:
  21. return Icons.camera;
  22. }
  23. throw ArgumentError('Unknown lens direction');
  24. }
  25. void logError(String code, String message) => print('Error: $code\nError Message: $message');
  26. class _CameraHelperState extends State<CameraHelper> with WidgetsBindingObserver {
  27. CameraController? controller;
  28. String? imagePath;
  29. String? videoPath;
  30. VoidCallback? videoPlayerListener;
  31. bool enableAudio = true;
  32. int indexCamera = 0;
  33. @override
  34. void initState() {
  35. super.initState();
  36. controller = CameraController(cameras[indexCamera], ResolutionPreset.medium);
  37. controller?.initialize().then((_) {
  38. if (!mounted) {
  39. return;
  40. }
  41. setState(() {});
  42. });
  43. WidgetsBinding.instance.addObserver(this);
  44. }
  45. @override
  46. void dispose() {
  47. WidgetsBinding.instance.removeObserver(this);
  48. super.dispose();
  49. }
  50. @override
  51. void didChangeAppLifecycleState(AppLifecycleState state) {
  52. // App state changed before we got the chance to initialize.
  53. if (controller == null || !controller!.value.isInitialized) {
  54. return;
  55. }
  56. if (state == AppLifecycleState.inactive) {
  57. controller?.dispose();
  58. } else if (state == AppLifecycleState.resumed) {
  59. if (controller != null) {
  60. onNewCameraSelected(controller!.description);
  61. }
  62. }
  63. }
  64. final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
  65. @override
  66. Widget build(BuildContext context) {
  67. return Scaffold(
  68. key: _scaffoldKey,
  69. body: SafeArea(
  70. top: false,
  71. bottom: false,
  72. child: Column(
  73. children: <Widget>[
  74. Expanded(
  75. child: Container(
  76. child: Padding(
  77. padding: const EdgeInsets.all(1.0),
  78. child: Center(
  79. child: _cameraPreviewWidget(),
  80. ),
  81. ),
  82. decoration: BoxDecoration(
  83. color: Colors.black,
  84. border: Border.all(
  85. color: controller != null && controller!.value.isRecordingVideo ? Colors.redAccent : Colors.grey,
  86. width: controller != null && controller!.value.isRecordingVideo ? 1.0 : 0.0,
  87. ),
  88. ),
  89. ),
  90. ),
  91. _captureControlRowWidget(),
  92. ],
  93. ),
  94. ),
  95. );
  96. }
  97. /// Display the preview from the camera (or a message if the preview is not available).
  98. Widget _cameraPreviewWidget() {
  99. if (controller == null || !controller!.value.isInitialized) {
  100. return const Text(
  101. 'Đang mở camera ....',
  102. style: TextStyle(
  103. color: Colors.white,
  104. fontSize: 18.0,
  105. fontWeight: FontWeight.w900,
  106. ),
  107. );
  108. } else {
  109. return AspectRatio(
  110. aspectRatio: controller!.value.aspectRatio,
  111. child: CameraPreview(controller!),
  112. );
  113. }
  114. }
  115. /// Display the control bar with buttons to take pictures and record videos.
  116. Widget _captureControlRowWidget() {
  117. return Row(
  118. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  119. mainAxisSize: MainAxisSize.max,
  120. children: <Widget>[
  121. IconButton(
  122. icon: Icon(Icons.arrow_back),
  123. onPressed: () {
  124. Get.back();
  125. }),
  126. _cameraTogglesRowWidget(),
  127. IconButton(
  128. icon: const Icon(
  129. Icons.camera_alt,
  130. size: 30,
  131. ),
  132. color: Colors.blue,
  133. onPressed: controller != null && controller!.value.isInitialized && !controller!.value.isRecordingVideo ? onTakePictureButtonPressed : null,
  134. ),
  135. // IconButton(
  136. // icon: const Icon(Icons.videocam, size: 35),
  137. // color: Colors.blue,
  138. // onPressed: controller != null && controller.value.isInitialized && !controller.value.isRecordingVideo ? onVideoRecordButtonPressed : null,
  139. // ),
  140. // IconButton(
  141. // icon: controller != null && controller.value.isRecordingPaused ? Icon(Icons.play_arrow) : Icon(Icons.pause),
  142. // color: Colors.blue,
  143. // onPressed: controller != null && controller.value.isInitialized && controller.value.isRecordingVideo
  144. // ? (controller != null && controller.value.isRecordingPaused ? onResumeButtonPressed : onPauseButtonPressed)
  145. // : null,
  146. // ),
  147. // IconButton(
  148. // icon: const Icon(Icons.stop),
  149. // color: Colors.red,
  150. // onPressed: controller != null && controller.value.isInitialized && controller.value.isRecordingVideo ? onStopButtonPressed : null,
  151. // )
  152. ],
  153. );
  154. }
  155. /// Display a row of toggle to select the camera (or a message if no camera is available).
  156. Widget _cameraTogglesRowWidget() {
  157. if (cameras.isEmpty) {
  158. return const Text('Không có camera');
  159. } else {
  160. bool disableSwitch = controller != null && controller!.value.isRecordingVideo;
  161. if (indexCamera == cameras.length - 1) {
  162. indexCamera = 0;
  163. } else {
  164. indexCamera++;
  165. }
  166. return IconButton(
  167. icon: Icon(
  168. Icons.switch_camera,
  169. color: Colors.green,
  170. ),
  171. onPressed: disableSwitch == true
  172. ? null
  173. : () {
  174. onNewCameraSelected(cameras[indexCamera]);
  175. });
  176. }
  177. }
  178. String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();
  179. void showInSnackBar(String message) {
  180. Utils.showSnackBarError(message: message);
  181. }
  182. void onNewCameraSelected(CameraDescription cameraDescription) async {
  183. if (controller != null) {
  184. await controller?.dispose();
  185. }
  186. controller = CameraController(
  187. cameraDescription,
  188. ResolutionPreset.medium,
  189. enableAudio: enableAudio,
  190. );
  191. // If the controller is updated then update the UI.
  192. controller?.addListener(() {
  193. if (mounted) setState(() {});
  194. if (controller!.value.hasError) {
  195. showInSnackBar('Lỗi camera: ${controller?.value.errorDescription}');
  196. }
  197. });
  198. try {
  199. await controller?.initialize();
  200. } on CameraException catch (e) {
  201. _showCameraException(e);
  202. }
  203. if (mounted) {
  204. setState(() {});
  205. }
  206. }
  207. void onTakePictureButtonPressed() {
  208. takePicture().then((String filePath) {
  209. if (mounted) {
  210. setState(() {
  211. imagePath = filePath;
  212. // videoController?.dispose();
  213. // videoController = null;
  214. });
  215. if (filePath != null) {
  216. print('Picture saved to $filePath');
  217. Get.back(result: [filePath, false]);
  218. }
  219. }
  220. });
  221. }
  222. void onVideoRecordButtonPressed() {
  223. startVideoRecording().then((String filePath) {
  224. if (mounted) setState(() {});
  225. if (filePath != null) {
  226. print('Saving video to $filePath');
  227. }
  228. });
  229. }
  230. void onStopButtonPressed() {
  231. stopVideoRecording().then((_) {
  232. if (mounted) setState(() {});
  233. {
  234. print('Video recorded to: $videoPath');
  235. Get.back(result: [videoPath, true]);
  236. }
  237. });
  238. }
  239. void onPauseButtonPressed() {
  240. pauseVideoRecording().then((_) {
  241. if (mounted) setState(() {});
  242. print('Video recording paused');
  243. });
  244. }
  245. void onResumeButtonPressed() {
  246. resumeVideoRecording().then((_) {
  247. if (mounted) setState(() {});
  248. print('Video recording resumed');
  249. });
  250. }
  251. Future<String> startVideoRecording() async {
  252. if (!controller!.value.isInitialized) {
  253. showInSnackBar('Vui lòng chọn camera');
  254. return '';
  255. }
  256. final Directory extDir = await getApplicationDocumentsDirectory();
  257. final String dirPath = '${extDir.path}/Movies/tpf';
  258. await Directory(dirPath).create(recursive: true);
  259. final String filePath = '$dirPath/${timestamp()}.mp4';
  260. if (controller!.value.isRecordingVideo) {
  261. // A recording is already started, do nothing.
  262. return '';
  263. }
  264. try {
  265. videoPath = filePath;
  266. // await controller?.startVideoRecording(filePath ?? '');
  267. } on CameraException catch (e) {
  268. _showCameraException(e);
  269. return '';
  270. }
  271. return filePath;
  272. }
  273. Future<void> stopVideoRecording() async {
  274. if (!controller!.value.isRecordingVideo) {
  275. return null;
  276. }
  277. try {
  278. await controller?.stopVideoRecording();
  279. } on CameraException catch (e) {
  280. _showCameraException(e);
  281. return null;
  282. }
  283. }
  284. Future<void> pauseVideoRecording() async {
  285. if (!controller!.value.isRecordingVideo) {
  286. return null;
  287. }
  288. try {
  289. await controller?.pauseVideoRecording();
  290. } on CameraException catch (e) {
  291. _showCameraException(e);
  292. rethrow;
  293. }
  294. }
  295. Future<void> resumeVideoRecording() async {
  296. if (!controller!.value.isRecordingVideo) {
  297. return null;
  298. }
  299. try {
  300. await controller?.resumeVideoRecording();
  301. } on CameraException catch (e) {
  302. _showCameraException(e);
  303. rethrow;
  304. }
  305. }
  306. Future<String> takePicture() async {
  307. if (!controller!.value.isInitialized) {
  308. showInSnackBar('Vui lòng chọn camera');
  309. return '';
  310. }
  311. final Directory extDir = await getApplicationDocumentsDirectory();
  312. final String dirPath = '${extDir.path}/Pictures/tpf';
  313. await Directory(dirPath).create(recursive: true);
  314. String filePath = '$dirPath/${timestamp()}.jpg';
  315. if (controller!.value.isTakingPicture) {
  316. // A capture is already pending, do nothing.
  317. return '';
  318. }
  319. try {
  320. var xFile = await controller?.takePicture(); //.takePicture(filePath);
  321. filePath = xFile?.path ?? '';
  322. } on CameraException catch (e) {
  323. _showCameraException(e);
  324. return '';
  325. }
  326. return filePath;
  327. }
  328. void _showCameraException(CameraException e) {
  329. logError(e.code, e.description ?? '');
  330. showInSnackBar('Lỗi: ${e.code}\n${e.description}');
  331. }
  332. }