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.

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