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.

399 lines
11KB

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