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.

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