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.

451 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/utils/const_color.dart';
  6. import 'package:farm_tpf/utils/const_string.dart';
  7. import 'package:flutter/cupertino.dart';
  8. import 'package:flutter/foundation.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. @override
  26. Widget build(BuildContext context) {
  27. return Container(
  28. padding: EdgeInsets.all(8),
  29. child: Column(
  30. children: <Widget>[
  31. _actionButton(),
  32. SizedBox(
  33. height: 4.0,
  34. ),
  35. _buildListPoster(),
  36. defaultTargetPlatform == TargetPlatform.android
  37. ? FutureBuilder<void>(
  38. future: retrieveLostData(),
  39. builder:
  40. (BuildContext context, AsyncSnapshot<void> snapshot) {
  41. switch (snapshot.connectionState) {
  42. case ConnectionState.none:
  43. case ConnectionState.waiting:
  44. return const Text(
  45. 'You have not yet picked an image.',
  46. textAlign: TextAlign.center,
  47. );
  48. case ConnectionState.done:
  49. return isVideo ? _previewVideo() : _previewImage();
  50. default:
  51. if (snapshot.hasError) {
  52. return Text(
  53. 'Pick image/video error: ${snapshot.error}}',
  54. textAlign: TextAlign.center,
  55. );
  56. } else {
  57. return const Text(
  58. 'You have not yet picked an image.',
  59. textAlign: TextAlign.center,
  60. );
  61. }
  62. }
  63. },
  64. )
  65. : (isVideo ? _previewVideo() : _previewImage()),
  66. ],
  67. ));
  68. }
  69. _buildListPoster() {
  70. return WrapContentHozListView(
  71. itemBuilder: (context, index) {
  72. var item = items[index];
  73. return _WidgetItemMedia(item);
  74. },
  75. separatorBuilder: (context, index) {
  76. return SizedBox(width: 14);
  77. },
  78. list: items,
  79. );
  80. }
  81. Widget viewResult() {
  82. FutureBuilder<void>(
  83. future: retrieveLostData(),
  84. builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
  85. switch (snapshot.connectionState) {
  86. case ConnectionState.none:
  87. case ConnectionState.waiting:
  88. return const Text(
  89. 'You have not yet picked an image.',
  90. textAlign: TextAlign.center,
  91. );
  92. case ConnectionState.done:
  93. return isVideo ? _previewVideo() : _previewImage();
  94. default:
  95. if (snapshot.hasError) {
  96. return Text(
  97. 'Pick image/video error: ${snapshot.error}}',
  98. textAlign: TextAlign.center,
  99. );
  100. } else {
  101. return const Text(
  102. 'You have not yet picked an image.',
  103. textAlign: TextAlign.center,
  104. );
  105. }
  106. }
  107. },
  108. );
  109. }
  110. Widget multipleChoice() {
  111. return CupertinoAlertDialog(
  112. title: Text(label_title_select_media),
  113. actions: <Widget>[
  114. CupertinoDialogAction(
  115. child: const Text(label_select_image_from_library),
  116. onPressed: () {
  117. Navigator.pop(context, 'Discard');
  118. isVideo = false;
  119. _onImageButtonPressed(ImageSource.gallery, context: context);
  120. }),
  121. CupertinoDialogAction(
  122. child: const Text(label_take_photo),
  123. onPressed: () {
  124. Navigator.pop(context, 'Discard');
  125. isVideo = false;
  126. _onImageButtonPressed(ImageSource.camera, context: context);
  127. }),
  128. CupertinoDialogAction(
  129. child: const Text(label_select_video_from_library),
  130. onPressed: () {
  131. Navigator.pop(context, 'Discard');
  132. isVideo = true;
  133. _onImageButtonPressed(ImageSource.gallery);
  134. }),
  135. CupertinoDialogAction(
  136. child: const Text(label_record_video),
  137. onPressed: () {
  138. Navigator.pop(context, 'Discard');
  139. isVideo = true;
  140. _onImageButtonPressed(ImageSource.camera);
  141. }),
  142. CupertinoDialogAction(
  143. child: const Text(label_cancel),
  144. textStyle: TextStyle(fontWeight: FontWeight.bold),
  145. isDefaultAction: true,
  146. onPressed: () {
  147. Navigator.pop(context, 'Cancel');
  148. }),
  149. ],
  150. );
  151. }
  152. Widget _actionButton() {
  153. return SizedBox(
  154. width: double.infinity,
  155. height: 44,
  156. child: FlatButton(
  157. onPressed: () {
  158. showDialog(
  159. context: context,
  160. barrierDismissible: true,
  161. builder: (context) => Opacity(
  162. child: multipleChoice(),
  163. opacity: 1,
  164. ));
  165. },
  166. color: COLOR_CONST.DEFAULT,
  167. shape: RoundedRectangleBorder(
  168. borderRadius: new BorderRadius.circular(7.0),
  169. ),
  170. child: Center(
  171. child: Row(
  172. mainAxisAlignment: MainAxisAlignment.center,
  173. children: <Widget>[
  174. Icon(Icons.image, color: Colors.white),
  175. SizedBox(
  176. width: 8.0,
  177. ),
  178. Text(
  179. button_add_media,
  180. style: TextStyle(
  181. fontWeight: FontWeight.bold, color: COLOR_CONST.WHITE),
  182. )
  183. ],
  184. ),
  185. )),
  186. );
  187. }
  188. Future<void> _playVideo(PickedFile file) async {
  189. if (file != null && mounted) {
  190. await _disposeVideoController();
  191. _controller = VideoPlayerController.file(File(file.path));
  192. await _controller.setVolume(1.0);
  193. await _controller.initialize();
  194. await _controller.setLooping(true);
  195. await _controller.play();
  196. setState(() {});
  197. }
  198. }
  199. void _onImageButtonPressed(ImageSource source, {BuildContext context}) async {
  200. if (_controller != null) {
  201. await _controller.setVolume(0.0);
  202. }
  203. if (isVideo) {
  204. final PickedFile file = await _picker.getVideo(
  205. source: source, maxDuration: const Duration(seconds: 10));
  206. await _playVideo(file);
  207. } else {
  208. await _displayPickImageDialog(context,
  209. (double maxWidth, double maxHeight, int quality) async {
  210. try {
  211. final pickedFile = await _picker.getImage(
  212. source: source,
  213. maxWidth: maxWidth,
  214. maxHeight: maxHeight,
  215. imageQuality: quality,
  216. );
  217. setState(() {
  218. _imageFile = pickedFile;
  219. });
  220. } catch (e) {
  221. setState(() {
  222. _pickImageError = e;
  223. });
  224. }
  225. });
  226. }
  227. }
  228. @override
  229. void deactivate() {
  230. if (_controller != null) {
  231. _controller.setVolume(0.0);
  232. _controller.pause();
  233. }
  234. super.deactivate();
  235. }
  236. @override
  237. void dispose() {
  238. _disposeVideoController();
  239. super.dispose();
  240. }
  241. Future<void> _disposeVideoController() async {
  242. if (_toBeDisposed != null) {
  243. await _toBeDisposed.dispose();
  244. }
  245. _toBeDisposed = _controller;
  246. _controller = null;
  247. }
  248. Widget _previewVideo() {
  249. final Text retrieveError = _getRetrieveErrorWidget();
  250. if (retrieveError != null) {
  251. return retrieveError;
  252. }
  253. if (_controller == null) {
  254. return const Text(
  255. 'You have not yet picked a video',
  256. textAlign: TextAlign.center,
  257. );
  258. }
  259. return Padding(
  260. padding: const EdgeInsets.all(10.0),
  261. child: AspectRatioVideo(_controller),
  262. );
  263. }
  264. Widget _previewImage() {
  265. final Text retrieveError = _getRetrieveErrorWidget();
  266. if (retrieveError != null) {
  267. return retrieveError;
  268. }
  269. if (_imageFile != null) {
  270. var imageResult =
  271. Image.file(File(_imageFile.path), width: 100, height: 100);
  272. setState(() {
  273. items.insert(0, ItemMediaVM(_imageFile.path, false, imageResult));
  274. });
  275. return Container(
  276. child: Text("ok"),
  277. );
  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: item.image == null
  387. ? ShimmerImage(
  388. item.photo,
  389. width: 93,
  390. height: 124,
  391. fit: BoxFit.cover,
  392. )
  393. : Image.file(File(item.photo), width: 100, height: 100),
  394. )),
  395. Positioned(
  396. top: -5,
  397. right: -5,
  398. child: IconButton(
  399. icon: Icon(
  400. Icons.cancel,
  401. color: Colors.redAccent,
  402. ),
  403. onPressed: () {
  404. print("On tap delete media");
  405. }),
  406. )
  407. ],
  408. ));
  409. }
  410. }
  411. class ItemMediaVM {
  412. String photo;
  413. bool isVideo;
  414. Image image;
  415. ItemMediaVM(this.photo, this.isVideo, this.image);
  416. }