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.

448 lines
13KB

  1. import 'dart:io';
  2. import 'dart:async';
  3. import 'package:farm_tpf/presentation/custom_widgets/hoz_list_view.dart';
  4. import 'package:farm_tpf/presentation/custom_widgets/shimmer_image.dart';
  5. import 'package:farm_tpf/presentation/custom_widgets/widget_item_media.dart';
  6. import 'package:farm_tpf/utils/const_color.dart';
  7. import 'package:farm_tpf/utils/const_string.dart';
  8. import 'package:flutter/cupertino.dart';
  9. import 'package:flutter/material.dart';
  10. import 'package:image_picker/image_picker.dart';
  11. import 'package:video_player/video_player.dart';
  12. class WidgetMediaHelper extends StatefulWidget {
  13. @override
  14. _WidgetMediaHelperState createState() => _WidgetMediaHelperState();
  15. }
  16. class _WidgetMediaHelperState extends State<WidgetMediaHelper> {
  17. PickedFile _imageFile;
  18. dynamic _pickImageError;
  19. bool isVideo = false;
  20. VideoPlayerController _controller;
  21. VideoPlayerController _toBeDisposed;
  22. String _retrieveDataError;
  23. final ImagePicker _picker = ImagePicker();
  24. List<ItemMediaVM> items = [
  25. ItemMediaVM('http://lorempixel.com/640/480', true),
  26. ItemMediaVM(
  27. 'https://s3.amazonaws.com/uifaces/faces/twitter/rickdt/128.jpg', true),
  28. ItemMediaVM('http://lorempixel.com/640/480/food', true),
  29. ItemMediaVM('http://lorempixel.com/640/480/animals', true),
  30. ItemMediaVM('http://lorempixel.com/640/480/fashion', true),
  31. ];
  32. @override
  33. Widget build(BuildContext context) {
  34. return Container(
  35. padding: EdgeInsets.all(8),
  36. child: Column(
  37. children: <Widget>[
  38. _actionButton(),
  39. SizedBox(
  40. height: 4.0,
  41. ),
  42. _buildListPoster(),
  43. 1 == 1
  44. ? FutureBuilder<void>(
  45. future: retrieveLostData(),
  46. builder:
  47. (BuildContext context, AsyncSnapshot<void> snapshot) {
  48. switch (snapshot.connectionState) {
  49. case ConnectionState.none:
  50. case ConnectionState.waiting:
  51. return const Text(
  52. 'You have not yet picked an image.',
  53. textAlign: TextAlign.center,
  54. );
  55. case ConnectionState.done:
  56. return isVideo ? _previewVideo() : _previewImage();
  57. default:
  58. if (snapshot.hasError) {
  59. return Text(
  60. 'Pick image/video error: ${snapshot.error}}',
  61. textAlign: TextAlign.center,
  62. );
  63. } else {
  64. return const Text(
  65. 'You have not yet picked an image.',
  66. textAlign: TextAlign.center,
  67. );
  68. }
  69. }
  70. },
  71. )
  72. : (isVideo ? _previewVideo() : _previewImage()),
  73. ],
  74. ));
  75. }
  76. _buildListPoster() {
  77. return WrapContentHozListView(
  78. itemBuilder: (context, index) {
  79. var item = items[index];
  80. return _WidgetItemMedia(item);
  81. },
  82. separatorBuilder: (context, index) {
  83. return SizedBox(width: 14);
  84. },
  85. list: items,
  86. );
  87. }
  88. Widget viewResult() {
  89. FutureBuilder<void>(
  90. future: retrieveLostData(),
  91. builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
  92. switch (snapshot.connectionState) {
  93. case ConnectionState.none:
  94. case ConnectionState.waiting:
  95. return const Text(
  96. 'You have not yet picked an image.',
  97. textAlign: TextAlign.center,
  98. );
  99. case ConnectionState.done:
  100. return isVideo ? _previewVideo() : _previewImage();
  101. default:
  102. if (snapshot.hasError) {
  103. return Text(
  104. 'Pick image/video error: ${snapshot.error}}',
  105. textAlign: TextAlign.center,
  106. );
  107. } else {
  108. return const Text(
  109. 'You have not yet picked an image.',
  110. textAlign: TextAlign.center,
  111. );
  112. }
  113. }
  114. },
  115. );
  116. }
  117. Widget multipleChoice() {
  118. return CupertinoAlertDialog(
  119. title: Text(label_title_select_media),
  120. actions: <Widget>[
  121. CupertinoDialogAction(
  122. child: const Text(label_select_image_from_library),
  123. onPressed: () {
  124. Navigator.pop(context, 'Discard');
  125. isVideo = false;
  126. _onImageButtonPressed(ImageSource.gallery, context: context);
  127. }),
  128. CupertinoDialogAction(
  129. child: const Text(label_take_photo),
  130. onPressed: () {
  131. Navigator.pop(context, 'Discard');
  132. isVideo = false;
  133. _onImageButtonPressed(ImageSource.camera, context: context);
  134. }),
  135. CupertinoDialogAction(
  136. child: const Text(label_select_video_from_library),
  137. onPressed: () {
  138. Navigator.pop(context, 'Discard');
  139. isVideo = true;
  140. _onImageButtonPressed(ImageSource.gallery);
  141. }),
  142. CupertinoDialogAction(
  143. child: const Text(label_record_video),
  144. onPressed: () {
  145. Navigator.pop(context, 'Discard');
  146. isVideo = true;
  147. _onImageButtonPressed(ImageSource.camera);
  148. }),
  149. CupertinoDialogAction(
  150. child: const Text(label_cancel),
  151. textStyle: TextStyle(fontWeight: FontWeight.bold),
  152. isDefaultAction: true,
  153. onPressed: () {
  154. Navigator.pop(context, 'Cancel');
  155. }),
  156. ],
  157. );
  158. }
  159. Widget _actionButton() {
  160. return SizedBox(
  161. width: double.infinity,
  162. height: 44,
  163. child: FlatButton(
  164. onPressed: () {
  165. showDialog(
  166. context: context,
  167. barrierDismissible: true,
  168. builder: (context) => Opacity(
  169. child: multipleChoice(),
  170. opacity: 1,
  171. ));
  172. },
  173. color: COLOR_CONST.DEFAULT,
  174. shape: RoundedRectangleBorder(
  175. borderRadius: new BorderRadius.circular(7.0),
  176. ),
  177. child: Center(
  178. child: Row(
  179. mainAxisAlignment: MainAxisAlignment.center,
  180. children: <Widget>[
  181. Icon(Icons.image, color: Colors.white),
  182. SizedBox(
  183. width: 8.0,
  184. ),
  185. Text(
  186. button_add_media,
  187. style: TextStyle(
  188. fontWeight: FontWeight.bold, color: COLOR_CONST.WHITE),
  189. )
  190. ],
  191. ),
  192. )),
  193. );
  194. }
  195. Future<void> _playVideo(PickedFile file) async {
  196. if (file != null && mounted) {
  197. await _disposeVideoController();
  198. _controller = VideoPlayerController.file(File(file.path));
  199. await _controller.setVolume(1.0);
  200. await _controller.initialize();
  201. await _controller.setLooping(true);
  202. await _controller.play();
  203. setState(() {});
  204. }
  205. }
  206. void _onImageButtonPressed(ImageSource source, {BuildContext context}) async {
  207. if (_controller != null) {
  208. await _controller.setVolume(0.0);
  209. }
  210. if (isVideo) {
  211. final PickedFile file = await _picker.getVideo(
  212. source: source, maxDuration: const Duration(seconds: 10));
  213. await _playVideo(file);
  214. } else {
  215. await _displayPickImageDialog(context,
  216. (double maxWidth, double maxHeight, int quality) async {
  217. try {
  218. final pickedFile = await _picker.getImage(
  219. source: source,
  220. maxWidth: maxWidth,
  221. maxHeight: maxHeight,
  222. imageQuality: quality,
  223. );
  224. setState(() {
  225. _imageFile = pickedFile;
  226. });
  227. } catch (e) {
  228. setState(() {
  229. _pickImageError = e;
  230. });
  231. }
  232. });
  233. }
  234. }
  235. @override
  236. void deactivate() {
  237. if (_controller != null) {
  238. _controller.setVolume(0.0);
  239. _controller.pause();
  240. }
  241. super.deactivate();
  242. }
  243. @override
  244. void dispose() {
  245. _disposeVideoController();
  246. super.dispose();
  247. }
  248. Future<void> _disposeVideoController() async {
  249. if (_toBeDisposed != null) {
  250. await _toBeDisposed.dispose();
  251. }
  252. _toBeDisposed = _controller;
  253. _controller = null;
  254. }
  255. Widget _previewVideo() {
  256. final Text retrieveError = _getRetrieveErrorWidget();
  257. if (retrieveError != null) {
  258. return retrieveError;
  259. }
  260. if (_controller == null) {
  261. return const Text(
  262. 'You have not yet picked a video',
  263. textAlign: TextAlign.center,
  264. );
  265. }
  266. return Padding(
  267. padding: const EdgeInsets.all(10.0),
  268. child: AspectRatioVideo(_controller),
  269. );
  270. }
  271. Widget _previewImage() {
  272. final Text retrieveError = _getRetrieveErrorWidget();
  273. if (retrieveError != null) {
  274. return retrieveError;
  275. }
  276. if (_imageFile != null) {
  277. return Image.file(File(_imageFile.path), width: 100, height: 100);
  278. } else if (_pickImageError != null) {
  279. return Text(
  280. 'Pick image error: $_pickImageError',
  281. textAlign: TextAlign.center,
  282. );
  283. } else {
  284. return const Text(
  285. 'You have not yet picked an image.',
  286. textAlign: TextAlign.center,
  287. );
  288. }
  289. }
  290. Future<void> retrieveLostData() async {
  291. final LostData response = await _picker.getLostData();
  292. if (response.isEmpty) {
  293. return;
  294. }
  295. if (response.file != null) {
  296. if (response.type == RetrieveType.video) {
  297. isVideo = true;
  298. await _playVideo(response.file);
  299. } else {
  300. isVideo = false;
  301. setState(() {
  302. _imageFile = response.file;
  303. });
  304. }
  305. } else {
  306. _retrieveDataError = response.exception.code;
  307. }
  308. }
  309. Text _getRetrieveErrorWidget() {
  310. if (_retrieveDataError != null) {
  311. final Text result = Text(_retrieveDataError);
  312. _retrieveDataError = null;
  313. return result;
  314. }
  315. return null;
  316. }
  317. Future<void> _displayPickImageDialog(
  318. BuildContext context, OnPickImageCallback onPick) async {
  319. onPick(null, null, null);
  320. }
  321. }
  322. typedef void OnPickImageCallback(
  323. double maxWidth, double maxHeight, int quality);
  324. class AspectRatioVideo extends StatefulWidget {
  325. AspectRatioVideo(this.controller);
  326. final VideoPlayerController controller;
  327. @override
  328. AspectRatioVideoState createState() => AspectRatioVideoState();
  329. }
  330. class AspectRatioVideoState extends State<AspectRatioVideo> {
  331. VideoPlayerController get controller => widget.controller;
  332. bool initialized = false;
  333. void _onVideoControllerUpdate() {
  334. if (!mounted) {
  335. return;
  336. }
  337. if (initialized != controller.value.initialized) {
  338. initialized = controller.value.initialized;
  339. setState(() {});
  340. }
  341. }
  342. @override
  343. void initState() {
  344. super.initState();
  345. controller.addListener(_onVideoControllerUpdate);
  346. }
  347. @override
  348. void dispose() {
  349. controller.removeListener(_onVideoControllerUpdate);
  350. super.dispose();
  351. }
  352. @override
  353. Widget build(BuildContext context) {
  354. if (initialized) {
  355. return Container(
  356. width: 100,
  357. height: 100,
  358. child: AspectRatio(
  359. aspectRatio: controller.value?.aspectRatio,
  360. child: VideoPlayer(controller),
  361. ),
  362. );
  363. } else {
  364. return Container();
  365. }
  366. }
  367. }
  368. class _WidgetItemMedia extends StatelessWidget {
  369. final ItemMediaVM item;
  370. _WidgetItemMedia(this.item);
  371. BuildContext _context;
  372. @override
  373. Widget build(BuildContext context) {
  374. _context = context;
  375. return GestureDetector(
  376. onTap: () {
  377. print("Show preview image or video");
  378. },
  379. child: Stack(
  380. alignment: Alignment.bottomCenter,
  381. overflow: Overflow.visible,
  382. children: <Widget>[
  383. Positioned(
  384. child: ClipRRect(
  385. borderRadius: BorderRadius.circular(8),
  386. child: ShimmerImage(
  387. item.photo,
  388. width: 93,
  389. height: 124,
  390. fit: BoxFit.cover,
  391. ),
  392. )),
  393. Positioned(
  394. top: -5,
  395. right: -5,
  396. child: IconButton(
  397. icon: Icon(
  398. Icons.cancel,
  399. color: Colors.redAccent,
  400. ),
  401. onPressed: () {
  402. print("On tap delete media");
  403. }),
  404. )
  405. ],
  406. ));
  407. }
  408. }
  409. class ItemMediaVM {
  410. String photo;
  411. bool isVideo;
  412. ItemMediaVM(this.photo, this.isVideo);
  413. }