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.

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