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.

541 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(),
  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. _controller = VideoPlayerController.file(File(file.path));
  247. await _controller.setVolume(1.0);
  248. await _controller.initialize();
  249. await _controller.setLooping(true);
  250. await _controller.play();
  251. }
  252. }
  253. _onImageButtonPressed(ImageSource source, {BuildContext context}) async {
  254. if (_controller != null) {
  255. await _controller.setVolume(0.0);
  256. }
  257. if (isVideo) {
  258. final PickedFile file = await _picker.getVideo(
  259. source: source, maxDuration: const Duration(seconds: 10));
  260. await _playVideo(file);
  261. } else {
  262. await _displayPickImageDialog(context,
  263. (double maxWidth, double maxHeight, int quality) async {
  264. try {
  265. final pickedFile = await _picker.getImage(
  266. source: source,
  267. maxWidth: imageWidth,
  268. maxHeight: imageHeight,
  269. imageQuality: quality,
  270. );
  271. Get.find<ChangeImageController>().updateFile(pickedFile);
  272. Media newMedia = Media()
  273. ..isVideo = false
  274. ..isServerFile = false
  275. ..pathFile = pickedFile.path;
  276. currentItems.add(newMedia);
  277. files.add(pickedFile.path);
  278. BlocProvider.of<MediaHelperBloc>(context)
  279. ..add(ChangeListMedia(items: currentItems));
  280. widget.onChangeFiles(files);
  281. } catch (e) {
  282. Get.find<ChangeImageController>().updateFileError(e);
  283. }
  284. });
  285. }
  286. ;
  287. }
  288. @override
  289. void deactivate() {
  290. if (_controller != null) {
  291. _controller.setVolume(0.0);
  292. _controller.pause();
  293. }
  294. super.deactivate();
  295. }
  296. @override
  297. void dispose() {
  298. _disposeVideoController();
  299. super.dispose();
  300. }
  301. Future<void> _disposeVideoController() async {
  302. if (_toBeDisposed != null) {
  303. await _toBeDisposed.dispose();
  304. }
  305. _toBeDisposed = _controller;
  306. _controller = null;
  307. }
  308. Widget _previewVideo() {
  309. final Text retrieveError = _getRetrieveErrorWidget();
  310. if (retrieveError != null) {
  311. return retrieveError;
  312. }
  313. if (_controller == null) {
  314. return const Text(
  315. 'You have not yet picked a video',
  316. textAlign: TextAlign.center,
  317. );
  318. }
  319. return Padding(
  320. padding: const EdgeInsets.all(10.0),
  321. child: AspectRatioVideo(_controller),
  322. );
  323. }
  324. Widget _previewImage() {
  325. return Builder(builder: (context) {
  326. final Text retrieveError = _getRetrieveErrorWidget();
  327. if (retrieveError != null) {
  328. return retrieveError;
  329. }
  330. if (_imageFile != null) {
  331. var imageResult =
  332. Image.file(File(_imageFile.path), width: imageWidth, height: 100);
  333. Media newMedia = Media()
  334. ..isVideo = false
  335. ..isServerFile = false
  336. ..pathFile = _imageFile.path;
  337. currentItems.add(newMedia);
  338. BlocProvider.of<MediaHelperBloc>(context)
  339. .add(ChangeListMedia(items: currentItems));
  340. //widget.onChangeFiles(files);
  341. return Container(
  342. child: Text("ok"),
  343. );
  344. } else if (_pickImageError != null) {
  345. return Text(
  346. 'Pick image error: $_pickImageError',
  347. textAlign: TextAlign.center,
  348. );
  349. } else {
  350. return const Text(
  351. 'You have not yet picked an image.',
  352. textAlign: TextAlign.center,
  353. );
  354. }
  355. });
  356. }
  357. Future<void> retrieveLostData() async {
  358. final LostData response = await _picker.getLostData();
  359. if (response.isEmpty) {
  360. return;
  361. }
  362. if (response.file != null) {
  363. if (response.type == RetrieveType.video) {
  364. isVideo = true;
  365. await _playVideo(response.file);
  366. } else {
  367. isVideo = false;
  368. setState(() {
  369. _imageFile = response.file;
  370. });
  371. }
  372. } else {
  373. _retrieveDataError = response.exception.code;
  374. }
  375. }
  376. Text _getRetrieveErrorWidget() {
  377. if (_retrieveDataError != null) {
  378. final Text result = Text(_retrieveDataError);
  379. _retrieveDataError = null;
  380. return result;
  381. }
  382. return null;
  383. }
  384. Future<void> _displayPickImageDialog(
  385. BuildContext context, OnPickImageCallback onPick) async {
  386. onPick(null, null, null);
  387. }
  388. }
  389. typedef void OnPickImageCallback(
  390. double maxWidth, double maxHeight, int quality);
  391. class AspectRatioVideo extends StatefulWidget {
  392. AspectRatioVideo(this.controller);
  393. final VideoPlayerController controller;
  394. @override
  395. AspectRatioVideoState createState() => AspectRatioVideoState();
  396. }
  397. class AspectRatioVideoState extends State<AspectRatioVideo> {
  398. VideoPlayerController get controller => widget.controller;
  399. bool initialized = false;
  400. void _onVideoControllerUpdate() {
  401. if (!mounted) {
  402. return;
  403. }
  404. if (initialized != controller.value.initialized) {
  405. initialized = controller.value.initialized;
  406. setState(() {});
  407. }
  408. }
  409. @override
  410. void initState() {
  411. super.initState();
  412. controller.addListener(_onVideoControllerUpdate);
  413. }
  414. @override
  415. void dispose() {
  416. controller.removeListener(_onVideoControllerUpdate);
  417. super.dispose();
  418. }
  419. @override
  420. Widget build(BuildContext context) {
  421. if (initialized) {
  422. return Container(
  423. width: 100,
  424. height: 100,
  425. child: AspectRatio(
  426. aspectRatio: controller.value?.aspectRatio,
  427. child: VideoPlayer(controller),
  428. ),
  429. );
  430. } else {
  431. return Container();
  432. }
  433. }
  434. }
  435. class _WidgetItemMedia extends StatelessWidget {
  436. ItemMediaCallback deleteImage;
  437. final Media item;
  438. _WidgetItemMedia({@required this.item, @required this.deleteImage});
  439. BuildContext _context;
  440. @override
  441. Widget build(BuildContext context) {
  442. _context = context;
  443. return GestureDetector(
  444. onTap: () {
  445. print("Show preview image or video");
  446. },
  447. child: Stack(
  448. alignment: Alignment.bottomCenter,
  449. overflow: Overflow.visible,
  450. children: <Widget>[
  451. Positioned(
  452. child: ClipRRect(
  453. borderRadius: BorderRadius.circular(8),
  454. child: item.isServerFile
  455. ? ShimmerImage(
  456. item.pathFile,
  457. width: 93,
  458. height: 124,
  459. fit: BoxFit.cover,
  460. )
  461. : Image.file(File(item.pathFile), width: 100, height: 100),
  462. )),
  463. Positioned(
  464. top: -5,
  465. right: -5,
  466. child: IconButton(
  467. icon: Icon(
  468. Icons.cancel,
  469. color: Colors.redAccent,
  470. ),
  471. onPressed: () {
  472. print("On tap delete media");
  473. deleteImage(item);
  474. }),
  475. )
  476. ],
  477. ));
  478. }
  479. }
  480. typedef ItemMediaCallback = void Function(Media item);
  481. class ItemMediaVM {
  482. String photo;
  483. bool isVideo;
  484. Image image;
  485. ItemMediaVM(this.photo, this.isVideo, this.image);
  486. }
  487. class ChangeImageController extends GetxController {
  488. PickedFile _imageFile;
  489. dynamic _pickImageError;
  490. void initValue() {
  491. update();
  492. }
  493. void updateFile(PickedFile imageFile) {
  494. _imageFile = imageFile;
  495. update();
  496. }
  497. void updateFileError(dynamic pickImageError) {
  498. _pickImageError = pickImageError;
  499. update();
  500. }
  501. }