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.

555 lines
17KB

  1. import 'dart:io';
  2. import 'dart:async';
  3. import 'package:farm_tpf/custom_model/Media.dart';
  4. import 'package:farm_tpf/presentation/custom_widgets/bloc/media_helper_bloc.dart';
  5. import 'package:farm_tpf/presentation/custom_widgets/hoz_list_view.dart';
  6. import 'package:farm_tpf/presentation/custom_widgets/shimmer_image.dart';
  7. import 'package:farm_tpf/utils/const_color.dart';
  8. import 'package:farm_tpf/utils/const_string.dart';
  9. import 'package:flutter/cupertino.dart';
  10. import 'package:flutter/foundation.dart';
  11. import 'package:flutter/material.dart';
  12. import 'package:flutter_bloc/flutter_bloc.dart';
  13. import 'package:get/get.dart';
  14. import 'package:get/state_manager.dart';
  15. import 'package:image_picker/image_picker.dart';
  16. import 'package:video_player/video_player.dart';
  17. class WidgetMediaHelper extends StatefulWidget {
  18. final Function(List<String> filePaths) onChangeFiles;
  19. WidgetMediaHelper({@required this.onChangeFiles});
  20. @override
  21. _WidgetMediaHelperState createState() => _WidgetMediaHelperState();
  22. }
  23. class _WidgetMediaHelperState extends State<WidgetMediaHelper> {
  24. final globalScaffoldKey = GlobalKey<ScaffoldState>();
  25. PickedFile _imageFile;
  26. dynamic _pickImageError;
  27. bool isVideo = false;
  28. VideoPlayerController _controller;
  29. VideoPlayerController _toBeDisposed;
  30. String _retrieveDataError;
  31. final ImagePicker _picker = ImagePicker();
  32. List<Media> currentItems = [];
  33. List<String> files = new List<String>();
  34. var changeImageController = Get.put(ChangeImageController());
  35. double imageWidth = 90;
  36. double imageHeight = 90;
  37. @override
  38. void initState() {
  39. super.initState();
  40. changeImageController.initValue();
  41. }
  42. @override
  43. Widget build(BuildContext context) {
  44. return BlocProvider<MediaHelperBloc>(
  45. create: (BuildContext contextA) =>
  46. MediaHelperBloc()..add(ChangeListMedia(items: currentItems)),
  47. child: BlocBuilder<MediaHelperBloc, MediaHelperState>(
  48. builder: (contextB, state) {
  49. if (state is MediaHelperFailure) {
  50. return Container();
  51. } else if (state is MediaHelperSuccess) {
  52. return Container(
  53. padding: EdgeInsets.all(8),
  54. child: Column(
  55. children: <Widget>[
  56. SizedBox(
  57. width: double.infinity,
  58. height: 44,
  59. child: FlatButton(
  60. onPressed: () {
  61. showDialog(
  62. context: context,
  63. barrierDismissible: true,
  64. builder: (context) => Opacity(
  65. child: multipleChoice(contextB),
  66. opacity: 1,
  67. ));
  68. },
  69. color: COLOR_CONST.DEFAULT,
  70. shape: RoundedRectangleBorder(
  71. borderRadius: new BorderRadius.circular(7.0),
  72. ),
  73. child: Center(
  74. child: Row(
  75. mainAxisAlignment: MainAxisAlignment.center,
  76. children: <Widget>[
  77. Icon(Icons.image, color: Colors.white),
  78. SizedBox(
  79. width: 8.0,
  80. ),
  81. Text(
  82. button_add_media,
  83. style: TextStyle(
  84. fontWeight: FontWeight.bold,
  85. color: COLOR_CONST.WHITE),
  86. )
  87. ],
  88. ),
  89. )),
  90. ),
  91. SizedBox(
  92. height: 4.0,
  93. ),
  94. Container(
  95. height: 150,
  96. child: _buildListPoster(),
  97. ),
  98. defaultTargetPlatform == TargetPlatform.android
  99. ? FutureBuilder<void>(
  100. future: retrieveLostData(context),
  101. builder: (BuildContext context,
  102. AsyncSnapshot<void> snapshot) {
  103. switch (snapshot.connectionState) {
  104. case ConnectionState.none:
  105. case ConnectionState.waiting:
  106. return const Text(
  107. 'You have not yet picked an image.',
  108. textAlign: TextAlign.center,
  109. );
  110. case ConnectionState.done:
  111. return isVideo
  112. ? _previewVideo()
  113. : _previewImage();
  114. default:
  115. if (snapshot.hasError) {
  116. return Text(
  117. 'Pick image/video error: ${snapshot.error}}',
  118. textAlign: TextAlign.center,
  119. );
  120. } else {
  121. return const Text(
  122. 'You have not yet picked an image.',
  123. textAlign: TextAlign.center,
  124. );
  125. }
  126. }
  127. },
  128. )
  129. : (isVideo ? _previewVideo() : _previewImage()),
  130. ],
  131. ));
  132. }
  133. return Container();
  134. }));
  135. }
  136. _buildListPoster() {
  137. return BlocBuilder<MediaHelperBloc, MediaHelperState>(
  138. builder: (context, state) {
  139. if (state is MediaHelperSuccess) {
  140. return WrapContentHozListView(
  141. itemBuilder: (context, index) {
  142. var item = state.items[index];
  143. return _WidgetItemMedia(
  144. item: item,
  145. deleteImage: (item) {
  146. files.remove(item.pathFile);
  147. currentItems.remove(item);
  148. widget.onChangeFiles(files);
  149. BlocProvider.of<MediaHelperBloc>(context)
  150. .add(ChangeListMedia(items: currentItems));
  151. });
  152. },
  153. separatorBuilder: (context, index) {
  154. return SizedBox(width: 14);
  155. },
  156. list: state.items,
  157. );
  158. }
  159. return Container();
  160. });
  161. }
  162. Widget multipleChoice(BuildContext context) {
  163. return CupertinoAlertDialog(
  164. title: Text(label_title_select_media),
  165. actions: <Widget>[
  166. CupertinoDialogAction(
  167. child: const Text(label_select_image_from_library),
  168. onPressed: () {
  169. Navigator.pop(context, 'Discard');
  170. isVideo = false;
  171. _onImageButtonPressed(ImageSource.gallery, context: context);
  172. }),
  173. CupertinoDialogAction(
  174. child: const Text(label_take_photo),
  175. onPressed: () {
  176. Navigator.pop(context, 'Discard');
  177. isVideo = false;
  178. _onImageButtonPressed(ImageSource.camera, context: context);
  179. }),
  180. CupertinoDialogAction(
  181. child: const Text(label_select_video_from_library),
  182. onPressed: () {
  183. Navigator.pop(context, 'Discard');
  184. isVideo = true;
  185. _onImageButtonPressed(ImageSource.gallery);
  186. }),
  187. CupertinoDialogAction(
  188. child: const Text(label_record_video),
  189. onPressed: () {
  190. Navigator.pop(context, 'Discard');
  191. isVideo = true;
  192. _onImageButtonPressed(ImageSource.camera);
  193. }),
  194. CupertinoDialogAction(
  195. child: const Text(label_cancel),
  196. textStyle: TextStyle(fontWeight: FontWeight.bold),
  197. isDefaultAction: true,
  198. onPressed: () {
  199. Navigator.pop(context, 'Cancel');
  200. }),
  201. ],
  202. );
  203. }
  204. Widget _actionButton() {
  205. return BlocListener<MediaHelperBloc, MediaHelperState>(
  206. listener: (context, state) {
  207. SizedBox(
  208. width: double.infinity,
  209. height: 44,
  210. child: FlatButton(
  211. onPressed: () {
  212. showDialog(
  213. context: context,
  214. barrierDismissible: true,
  215. builder: (context) => Opacity(
  216. child: Text(""),
  217. opacity: 1,
  218. ));
  219. },
  220. color: COLOR_CONST.DEFAULT,
  221. shape: RoundedRectangleBorder(
  222. borderRadius: new BorderRadius.circular(7.0),
  223. ),
  224. child: Center(
  225. child: Row(
  226. mainAxisAlignment: MainAxisAlignment.center,
  227. children: <Widget>[
  228. Icon(Icons.image, color: Colors.white),
  229. SizedBox(
  230. width: 8.0,
  231. ),
  232. Text(
  233. button_add_media,
  234. style: TextStyle(
  235. fontWeight: FontWeight.bold, color: COLOR_CONST.WHITE),
  236. )
  237. ],
  238. ),
  239. )),
  240. );
  241. });
  242. }
  243. Future<void> _playVideo(PickedFile file) async {
  244. if (file != null && mounted) {
  245. await _disposeVideoController();
  246. File f = File(file.path);
  247. _controller = VideoPlayerController.file(File(file.path));
  248. await _controller.setVolume(1.0);
  249. await _controller.initialize();
  250. await _controller.setLooping(false);
  251. await _controller.play();
  252. }
  253. }
  254. _onImageButtonPressed(ImageSource source, {BuildContext context}) async {
  255. if (_controller != null) {
  256. await _controller.setVolume(0.0);
  257. }
  258. if (isVideo) {
  259. // final video = await FilePicker.getFile(type: FileType.video);
  260. final PickedFile pickedFile = await _picker.getVideo(source: source);
  261. Get.find<ChangeImageController>().updateFile(pickedFile);
  262. Media newMedia = Media()
  263. ..isVideo = false
  264. ..isServerFile = false
  265. ..pathFile = pickedFile.path;
  266. currentItems.add(newMedia);
  267. print(pickedFile.path);
  268. files.add(pickedFile.path);
  269. _playVideo(pickedFile);
  270. // BlocProvider.of<MediaHelperBloc>(context)
  271. // ..add(ChangeListMedia(items: currentItems));
  272. // widget.onChangeFiles(files);
  273. } else {
  274. await _displayPickImageDialog(context,
  275. (double maxWidth, double maxHeight, int quality) async {
  276. try {
  277. final pickedFile = await _picker.getImage(
  278. source: source,
  279. maxWidth: imageWidth,
  280. maxHeight: imageHeight,
  281. imageQuality: quality,
  282. );
  283. Get.find<ChangeImageController>().updateFile(pickedFile);
  284. Media newMedia = Media()
  285. ..isVideo = false
  286. ..isServerFile = false
  287. ..pathFile = pickedFile.path;
  288. currentItems.add(newMedia);
  289. files.add(pickedFile.path);
  290. BlocProvider.of<MediaHelperBloc>(context)
  291. ..add(ChangeListMedia(items: currentItems));
  292. widget.onChangeFiles(files);
  293. } catch (e) {
  294. Get.find<ChangeImageController>().updateFileError(e);
  295. }
  296. });
  297. }
  298. ;
  299. }
  300. @override
  301. void deactivate() {
  302. if (_controller != null) {
  303. _controller.setVolume(0.0);
  304. _controller.pause();
  305. }
  306. super.deactivate();
  307. }
  308. @override
  309. void dispose() {
  310. _disposeVideoController();
  311. super.dispose();
  312. }
  313. Future<void> _disposeVideoController() async {
  314. if (_toBeDisposed != null) {
  315. await _toBeDisposed.dispose();
  316. }
  317. _toBeDisposed = _controller;
  318. _controller = null;
  319. }
  320. Widget _previewVideo() {
  321. final Text retrieveError = _getRetrieveErrorWidget();
  322. if (retrieveError != null) {
  323. return retrieveError;
  324. }
  325. if (_controller == null) {
  326. return const Text(
  327. 'You have not yet picked a video',
  328. textAlign: TextAlign.center,
  329. );
  330. }
  331. return Padding(
  332. padding: const EdgeInsets.all(10.0),
  333. child: AspectRatioVideo(_controller),
  334. );
  335. }
  336. Widget _previewImage() {
  337. return Builder(builder: (context) {
  338. final Text retrieveError = _getRetrieveErrorWidget();
  339. if (retrieveError != null) {
  340. return retrieveError;
  341. }
  342. if (_imageFile != null) {
  343. var imageResult =
  344. Image.file(File(_imageFile.path), width: imageWidth, height: 100);
  345. Media newMedia = Media()
  346. ..isVideo = false
  347. ..isServerFile = false
  348. ..pathFile = _imageFile.path;
  349. currentItems.add(newMedia);
  350. BlocProvider.of<MediaHelperBloc>(context)
  351. .add(ChangeListMedia(items: currentItems));
  352. //widget.onChangeFiles(files);
  353. return Container(
  354. child: Text("ok"),
  355. );
  356. } else if (_pickImageError != null) {
  357. return Text(
  358. 'Pick image error: $_pickImageError',
  359. textAlign: TextAlign.center,
  360. );
  361. } else {
  362. return const Text(
  363. 'You have not yet picked an image.',
  364. textAlign: TextAlign.center,
  365. );
  366. }
  367. });
  368. }
  369. Future<void> retrieveLostData(
  370. BuildContext context,
  371. ) async {
  372. final LostData response = await _picker.getLostData();
  373. if (response.isEmpty) {
  374. return;
  375. }
  376. if (response.file != null) {
  377. if (response.type == RetrieveType.video) {
  378. isVideo = true;
  379. await _playVideo(response.file);
  380. } else {
  381. isVideo = false;
  382. setState(() {
  383. _imageFile = response.file;
  384. });
  385. }
  386. } else {
  387. _retrieveDataError = response.exception.code;
  388. }
  389. }
  390. Text _getRetrieveErrorWidget() {
  391. if (_retrieveDataError != null) {
  392. final Text result = Text(_retrieveDataError);
  393. _retrieveDataError = null;
  394. return result;
  395. }
  396. return null;
  397. }
  398. Future<void> _displayPickImageDialog(
  399. BuildContext context, OnPickImageCallback onPick) async {
  400. onPick(null, null, null);
  401. }
  402. }
  403. typedef void OnPickImageCallback(
  404. double maxWidth, double maxHeight, int quality);
  405. class AspectRatioVideo extends StatefulWidget {
  406. AspectRatioVideo(this.controller);
  407. final VideoPlayerController controller;
  408. @override
  409. AspectRatioVideoState createState() => AspectRatioVideoState();
  410. }
  411. class AspectRatioVideoState extends State<AspectRatioVideo> {
  412. VideoPlayerController get controller => widget.controller;
  413. bool initialized = false;
  414. void _onVideoControllerUpdate() {
  415. if (!mounted) {
  416. return;
  417. }
  418. if (initialized != controller.value.initialized) {
  419. initialized = controller.value.initialized;
  420. setState(() {});
  421. }
  422. }
  423. @override
  424. void initState() {
  425. super.initState();
  426. controller.addListener(_onVideoControllerUpdate);
  427. }
  428. @override
  429. void dispose() {
  430. controller.removeListener(_onVideoControllerUpdate);
  431. super.dispose();
  432. }
  433. @override
  434. Widget build(BuildContext context) {
  435. if (initialized) {
  436. return Container(
  437. width: 100,
  438. height: 100,
  439. child: AspectRatio(
  440. aspectRatio: controller.value?.aspectRatio,
  441. child: VideoPlayer(controller),
  442. ),
  443. );
  444. } else {
  445. return Container();
  446. }
  447. }
  448. }
  449. class _WidgetItemMedia extends StatelessWidget {
  450. ItemMediaCallback deleteImage;
  451. final Media item;
  452. _WidgetItemMedia({@required this.item, @required this.deleteImage});
  453. BuildContext _context;
  454. @override
  455. Widget build(BuildContext context) {
  456. _context = context;
  457. return GestureDetector(
  458. onTap: () {
  459. print("Show preview image or video");
  460. },
  461. child: Stack(
  462. alignment: Alignment.bottomCenter,
  463. overflow: Overflow.visible,
  464. children: <Widget>[
  465. Positioned(
  466. child: ClipRRect(
  467. borderRadius: BorderRadius.circular(8),
  468. child: item.isServerFile
  469. ? ShimmerImage(
  470. item.pathFile,
  471. width: 93,
  472. height: 124,
  473. fit: BoxFit.cover,
  474. )
  475. : Image.file(File(item.pathFile), width: 100, height: 100),
  476. )),
  477. Positioned(
  478. top: -5,
  479. right: -5,
  480. child: IconButton(
  481. icon: Icon(
  482. Icons.cancel,
  483. color: Colors.redAccent,
  484. ),
  485. onPressed: () {
  486. print("On tap delete media");
  487. deleteImage(item);
  488. }),
  489. )
  490. ],
  491. ));
  492. }
  493. }
  494. typedef ItemMediaCallback = void Function(Media item);
  495. class ItemMediaVM {
  496. String photo;
  497. bool isVideo;
  498. Image image;
  499. ItemMediaVM(this.photo, this.isVideo, this.image);
  500. }
  501. class ChangeImageController extends GetxController {
  502. PickedFile _imageFile;
  503. dynamic _pickImageError;
  504. void initValue() {
  505. update();
  506. }
  507. void updateFile(PickedFile imageFile) {
  508. _imageFile = imageFile;
  509. update();
  510. }
  511. void updateFileError(dynamic pickImageError) {
  512. _pickImageError = pickImageError;
  513. update();
  514. }
  515. }