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.

412 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: 20.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.video_library),
  133. onPressed: () async {
  134. FilePickerResult result = await FilePicker.platform
  135. .pickFiles(type: FileType.video, allowMultiple: true);
  136. if (result != null) {
  137. result.files.forEach((element) {
  138. print("----" + result.files.single.path);
  139. });
  140. }
  141. }),
  142. IconButton(
  143. icon: Icon(Icons.image),
  144. onPressed: () async {
  145. FilePickerResult result = await FilePicker.platform
  146. .pickFiles(type: FileType.image, allowMultiple: true);
  147. if (result != null) {
  148. result.files.forEach((element) {
  149. print("----" + result.files.single.path);
  150. });
  151. }
  152. }),
  153. IconButton(
  154. icon: Icon(Icons.arrow_back),
  155. onPressed: () {
  156. Get.back();
  157. }),
  158. _cameraTogglesRowWidget(),
  159. IconButton(
  160. icon: const Icon(Icons.camera_alt),
  161. color: Colors.blue,
  162. onPressed: controller != null &&
  163. controller.value.isInitialized &&
  164. !controller.value.isRecordingVideo
  165. ? onTakePictureButtonPressed
  166. : null,
  167. ),
  168. IconButton(
  169. icon: const Icon(Icons.videocam),
  170. color: Colors.blue,
  171. onPressed: controller != null &&
  172. controller.value.isInitialized &&
  173. !controller.value.isRecordingVideo
  174. ? onVideoRecordButtonPressed
  175. : null,
  176. ),
  177. IconButton(
  178. icon: controller != null && controller.value.isRecordingPaused
  179. ? Icon(Icons.play_arrow)
  180. : Icon(Icons.pause),
  181. color: Colors.blue,
  182. onPressed: controller != null &&
  183. controller.value.isInitialized &&
  184. controller.value.isRecordingVideo
  185. ? (controller != null && controller.value.isRecordingPaused
  186. ? onResumeButtonPressed
  187. : onPauseButtonPressed)
  188. : null,
  189. ),
  190. IconButton(
  191. icon: const Icon(Icons.stop),
  192. color: Colors.red,
  193. onPressed: controller != null &&
  194. controller.value.isInitialized &&
  195. controller.value.isRecordingVideo
  196. ? onStopButtonPressed
  197. : null,
  198. )
  199. ],
  200. );
  201. }
  202. /// Display a row of toggle to select the camera (or a message if no camera is available).
  203. Widget _cameraTogglesRowWidget() {
  204. if (cameras.isEmpty) {
  205. return const Text('Không có camera');
  206. } else {
  207. bool disableSwitch =
  208. controller != null && controller.value.isRecordingVideo;
  209. if (indexCamera == cameras.length - 1) {
  210. indexCamera = 0;
  211. } else {
  212. indexCamera++;
  213. }
  214. return IconButton(
  215. icon: Icon(
  216. Icons.switch_camera,
  217. color: Colors.green,
  218. ),
  219. onPressed: disableSwitch == true
  220. ? null
  221. : () {
  222. onNewCameraSelected(cameras[indexCamera]);
  223. });
  224. }
  225. }
  226. String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();
  227. void showInSnackBar(String message) {
  228. _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(message)));
  229. }
  230. void onNewCameraSelected(CameraDescription cameraDescription) async {
  231. if (controller != null) {
  232. await controller.dispose();
  233. }
  234. controller = CameraController(
  235. cameraDescription,
  236. ResolutionPreset.medium,
  237. enableAudio: enableAudio,
  238. );
  239. // If the controller is updated then update the UI.
  240. controller.addListener(() {
  241. if (mounted) setState(() {});
  242. if (controller.value.hasError) {
  243. showInSnackBar('Lỗi camera: ${controller.value.errorDescription}');
  244. }
  245. });
  246. try {
  247. await controller.initialize();
  248. } on CameraException catch (e) {
  249. _showCameraException(e);
  250. }
  251. if (mounted) {
  252. setState(() {});
  253. }
  254. }
  255. void onTakePictureButtonPressed() {
  256. takePicture().then((String filePath) {
  257. if (mounted) {
  258. setState(() {
  259. imagePath = filePath;
  260. videoController?.dispose();
  261. videoController = null;
  262. });
  263. if (filePath != null) print('Picture saved to $filePath');
  264. }
  265. });
  266. }
  267. void onVideoRecordButtonPressed() {
  268. startVideoRecording().then((String filePath) {
  269. if (mounted) setState(() {});
  270. if (filePath != null) print('Saving video to $filePath');
  271. });
  272. }
  273. void onStopButtonPressed() {
  274. stopVideoRecording().then((_) {
  275. if (mounted) setState(() {});
  276. print('Video recorded to: $videoPath');
  277. });
  278. }
  279. void onPauseButtonPressed() {
  280. pauseVideoRecording().then((_) {
  281. if (mounted) setState(() {});
  282. print('Video recording paused');
  283. });
  284. }
  285. void onResumeButtonPressed() {
  286. resumeVideoRecording().then((_) {
  287. if (mounted) setState(() {});
  288. print('Video recording resumed');
  289. });
  290. }
  291. Future<String> startVideoRecording() async {
  292. if (!controller.value.isInitialized) {
  293. showInSnackBar('Vui lòng chọn camera');
  294. return null;
  295. }
  296. final Directory extDir = await getApplicationDocumentsDirectory();
  297. final String dirPath = '${extDir.path}/Movies/tpf';
  298. await Directory(dirPath).create(recursive: true);
  299. final String filePath = '$dirPath/${timestamp()}.mp4';
  300. if (controller.value.isRecordingVideo) {
  301. // A recording is already started, do nothing.
  302. return null;
  303. }
  304. try {
  305. videoPath = filePath;
  306. await controller.startVideoRecording(filePath);
  307. } on CameraException catch (e) {
  308. _showCameraException(e);
  309. return null;
  310. }
  311. return filePath;
  312. }
  313. Future<void> stopVideoRecording() async {
  314. if (!controller.value.isRecordingVideo) {
  315. return null;
  316. }
  317. try {
  318. await controller.stopVideoRecording();
  319. } on CameraException catch (e) {
  320. _showCameraException(e);
  321. return null;
  322. }
  323. }
  324. Future<void> pauseVideoRecording() async {
  325. if (!controller.value.isRecordingVideo) {
  326. return null;
  327. }
  328. try {
  329. await controller.pauseVideoRecording();
  330. } on CameraException catch (e) {
  331. _showCameraException(e);
  332. rethrow;
  333. }
  334. }
  335. Future<void> resumeVideoRecording() async {
  336. if (!controller.value.isRecordingVideo) {
  337. return null;
  338. }
  339. try {
  340. await controller.resumeVideoRecording();
  341. } on CameraException catch (e) {
  342. _showCameraException(e);
  343. rethrow;
  344. }
  345. }
  346. Future<String> takePicture() async {
  347. if (!controller.value.isInitialized) {
  348. showInSnackBar('Vui lòng chọn camera');
  349. return null;
  350. }
  351. final Directory extDir = await getApplicationDocumentsDirectory();
  352. final String dirPath = '${extDir.path}/Pictures/tpf';
  353. await Directory(dirPath).create(recursive: true);
  354. final String filePath = '$dirPath/${timestamp()}.jpg';
  355. if (controller.value.isTakingPicture) {
  356. // A capture is already pending, do nothing.
  357. return null;
  358. }
  359. try {
  360. await controller.takePicture(filePath);
  361. } on CameraException catch (e) {
  362. _showCameraException(e);
  363. return null;
  364. }
  365. return filePath;
  366. }
  367. void _showCameraException(CameraException e) {
  368. logError(e.code, e.description);
  369. showInSnackBar('Lỗi: ${e.code}\n${e.description}');
  370. }
  371. }