import 'dart:developer' as developer; import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:image/image.dart' as img; import 'package:line_icons/line_icons.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:webfeed/webfeed.dart'; import '../local_storage/sqflite_localpodcast.dart'; import '../state/podcast_group.dart'; import '../type/play_histroy.dart'; import '../type/podcastlocal.dart'; import '../util/extension_helper.dart'; import '../widgets/custom_widget.dart'; import '../widgets/duraiton_picker.dart'; enum MarkStatus { start, complete, none } enum RefreshCoverStatus { start, complete, error, none } class PodcastSetting extends StatefulWidget { const PodcastSetting({this.podcastLocal, Key key}) : super(key: key); final PodcastLocal podcastLocal; @override _PodcastSettingState createState() => _PodcastSettingState(); } class _PodcastSettingState extends State { final _dbHelper = DBHelper(); MarkStatus _markStatus = MarkStatus.none; RefreshCoverStatus _coverStatus = RefreshCoverStatus.none; int _secondsStart; int _secondsEnd; bool _markConfirm; bool _removeConfirm; bool _showStartTimePicker; bool _showEndTimePicker; @override void initState() { super.initState(); _secondsStart = 0; _secondsEnd = 0; _markConfirm = false; _removeConfirm = false; _showStartTimePicker = false; _showEndTimePicker = false; } Future _setAutoDownload(bool boo) async { var permission = await _checkPermmison(); if (permission) { await _dbHelper.saveAutoDownload(widget.podcastLocal.id, boo: boo); } if (mounted) setState(() {}); } Future _setNeverUpdate(bool boo) async { await _dbHelper.saveNeverUpdate(widget.podcastLocal.id, boo: boo); if (mounted) setState(() {}); } Future _setHideNewMark(bool boo) async { await _dbHelper.saveHideNewMark(widget.podcastLocal.id, boo: boo); if (mounted) setState(() {}); } Future _saveSkipSecondsStart(int seconds) async { await _dbHelper.saveSkipSecondsStart(widget.podcastLocal.id, seconds); } Future _saveSkipSecondsEnd(int seconds) async { await _dbHelper.saveSkipSecondsEnd(widget.podcastLocal.id, seconds); } Future _getAutoDownload(String id) async { return await _dbHelper.getAutoDownload(id); } Future _getNeverUpdate(String id) async { return await _dbHelper.getNeverUpdate(id); } Future _getHideNewMark(String id) async { return await _dbHelper.getHideNewMark(id); } Future _getSkipSecondStart(String id) async { return await _dbHelper.getSkipSecondsStart(id); } Future _getSkipSecondEnd(String id) async { return await _dbHelper.getSkipSecondsEnd(id); } Future _markListened(String podcastId) async { setState(() { _markStatus = MarkStatus.start; }); final episodes = await _dbHelper.getRssItem(podcastId, -1, reverse: true, hideListened: true); for (var episode in episodes) { final history = PlayHistory(episode.title, episode.enclosureUrl, 0, 1); await _dbHelper.saveHistory(history); } if (mounted) { setState(() { _markStatus = MarkStatus.complete; }); } } Future _refreshArtWork() async { setState(() => _coverStatus = RefreshCoverStatus.start); var options = BaseOptions( connectTimeout: 30000, receiveTimeout: 90000, ); var dir = await getApplicationDocumentsDirectory(); var filePath = "${dir.path}/${widget.podcastLocal.id}.png"; var dio = Dio(options); String imageUrl; try { var response = await dio.get(widget.podcastLocal.rssUrl); try { var p = RssFeed.parse(response.data); imageUrl = p.itunes.image.href ?? p.image.url; } catch (e) { developer.log(e.toString()); if (mounted) setState(() => _coverStatus = RefreshCoverStatus.error); } } catch (e) { developer.log(e.toString()); if (mounted) setState(() => _coverStatus = RefreshCoverStatus.error); } if (imageUrl != null && imageUrl.contains('http')) { try { img.Image thumbnail; var imageResponse = await dio.get>(imageUrl, options: Options( responseType: ResponseType.bytes, )); var image = img.decodeImage(imageResponse.data); thumbnail = img.copyResize(image, width: 300); if (thumbnail != null) { File(filePath)..writeAsBytesSync(img.encodePng(thumbnail)); _dbHelper.updatePodcastImage( id: widget.podcastLocal.id, filePath: filePath); print('saved image'); if (mounted) { setState(() => _coverStatus = RefreshCoverStatus.complete); } } } catch (e) { developer.log(e.toString()); if (mounted) setState(() => _coverStatus = RefreshCoverStatus.error); } } else if (_coverStatus == RefreshCoverStatus.start && mounted) { setState(() => _coverStatus = RefreshCoverStatus.complete); } } Future _checkPermmison() async { var permission = await Permission.storage.status; if (permission != PermissionStatus.granted) { var permissions = await [Permission.storage].request(); if (permissions[Permission.storage] == PermissionStatus.granted) { return true; } else { return false; } } else { return true; } } Widget _getRefreshStatusIcon(RefreshCoverStatus status) { switch (status) { case RefreshCoverStatus.none: return Center(); break; case RefreshCoverStatus.start: return CircularProgressIndicator(strokeWidth: 2); break; case RefreshCoverStatus.complete: return Icon(Icons.done); break; case RefreshCoverStatus.error: return Icon(Icons.refresh, color: Colors.red); break; default: return Center(); } } @override Widget build(BuildContext context) { final s = context.s; final groupList = context.watch(); final textStyle = context.textTheme.bodyText2; return Column( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ FutureBuilder( future: _getAutoDownload(widget.podcastLocal.id), initialData: false, builder: (context, snapshot) { return ListTile( onTap: () => _setAutoDownload(!snapshot.data), dense: true, title: Row( children: [ SizedBox( height: 18, width: 18, child: CustomPaint( painter: DownloadPainter( color: context.textColor, fraction: 0, progressColor: context.accentColor, ), ), ), SizedBox(width: 20), Text(s.autoDownload, style: textStyle), ], ), trailing: Transform.scale( scale: 0.8, child: Switch(value: snapshot.data, onChanged: _setAutoDownload), ), ); }), FutureBuilder( future: _getNeverUpdate(widget.podcastLocal.id), initialData: false, builder: (context, snapshot) { return ListTile( dense: true, onTap: () => _setNeverUpdate(!snapshot.data), title: Row( children: [ Icon(Icons.lock_outlined, size: 18), SizedBox(width: 20), Text(s.neverAutoUpdate, style: textStyle), ], ), trailing: Transform.scale( scale: 0.8, child: Switch(value: snapshot.data, onChanged: _setNeverUpdate), ), ); }), FutureBuilder( future: _getHideNewMark(widget.podcastLocal.id), initialData: false, builder: (context, snapshot) { return ListTile( dense: true, onTap: () => _setHideNewMark(!snapshot.data), title: Row( children: [ Icon(LineIcons.eraser, size: 20), SizedBox(width: 20), Text('Always hide new mark', style: textStyle), ], ), trailing: Transform.scale( scale: 0.8, child: Switch(value: snapshot.data, onChanged: _setHideNewMark), ), ); }), FutureBuilder( future: _getSkipSecondStart(widget.podcastLocal.id), initialData: 0, builder: (context, snapshot) => ListTile( onTap: () { _secondsStart = 0; setState(() { _removeConfirm = false; _markConfirm = false; _showEndTimePicker = false; _showStartTimePicker = !_showStartTimePicker; }); }, dense: true, title: Row( children: [ Icon(Icons.fast_forward_outlined, size: 18), SizedBox(width: 20), Text(s.skipSecondsAtStart, style: textStyle), ], ), trailing: Padding( padding: const EdgeInsets.only(right: 10.0), child: Text(snapshot.data.toTime), ), ), ), if (_showStartTimePicker) _TimePicker( onCancel: () { _secondsStart = 0; setState(() => _showStartTimePicker = false); }, onConfirm: () async { await _saveSkipSecondsStart(_secondsStart); if (mounted) setState(() => _showStartTimePicker = false); }, onChange: (value) => _secondsStart = value.inSeconds), ListTile( onTap: () { if (_coverStatus != RefreshCoverStatus.start) { _refreshArtWork(); } }, dense: true, title: Row( children: [ Icon(Icons.refresh, size: 18), SizedBox(width: 20), Text(s.refreshArtwork, style: textStyle), ], ), trailing: Padding( padding: const EdgeInsets.only(right: 15.0), child: SizedBox( height: 20, width: 20, child: _getRefreshStatusIcon(_coverStatus)))), Divider(height: 1), ListTile( onTap: () { setState(() { _removeConfirm = false; _showStartTimePicker = false; _showEndTimePicker = false; _markConfirm = !_markConfirm; }); }, dense: true, title: Row( children: [ SizedBox( height: 18, width: 18, child: CustomPaint( painter: ListenedAllPainter(context.accentColor, stroke: 2), ), ), SizedBox(width: 20), Text(s.menuMarkAllListened, style: textStyle.copyWith( color: context.accentColor, fontWeight: FontWeight.bold)), ], ), trailing: Padding( padding: const EdgeInsets.only(right: 10.0), child: SizedBox( height: 20, width: 20, child: _markStatus == MarkStatus.none ? Center() : _markStatus == MarkStatus.start ? CircularProgressIndicator(strokeWidth: 2) : Icon(Icons.done)), )), if (_markConfirm) Container( width: double.infinity, color: context.primaryColorDark, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ FlatButton( onPressed: () => setState(() { _markConfirm = false; }), child: Text( s.cancel, style: TextStyle(color: Colors.grey[600]), )), FlatButton( onPressed: () { if (_markStatus != MarkStatus.start) { _markListened(widget.podcastLocal.id); } setState(() { _markConfirm = false; }); }, child: Text(s.confirm, style: TextStyle(color: context.accentColor))), ], ), ), ListTile( onTap: () { setState(() { _markConfirm = false; _showStartTimePicker = false; _showEndTimePicker = false; _removeConfirm = !_removeConfirm; }); }, dense: true, title: Row( children: [ Icon(Icons.delete_outlined, color: Colors.red, size: 18), SizedBox(width: 20), Text(s.remove, style: textStyle.copyWith( color: Colors.red, fontWeight: FontWeight.bold)), ], ), ), if (_removeConfirm) Container( width: double.infinity, color: context.primaryColorDark, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ FlatButton( onPressed: () => setState(() { _removeConfirm = false; }), child: Text(s.cancel, style: TextStyle(color: Colors.grey[600])), ), FlatButton( splashColor: Colors.red.withAlpha(70), onPressed: () async { await groupList.removePodcast(widget.podcastLocal); Navigator.of(context).pop(); }, child: Text(s.confirm, style: TextStyle(color: Colors.red))), ], ), ), ], ); } } class _TimePicker extends StatelessWidget { const _TimePicker({this.onConfirm, this.onCancel, this.onChange, Key key}) : super(key: key); final VoidCallback onConfirm; final VoidCallback onCancel; final ValueChanged onChange; @override Widget build(BuildContext context) { final s = context.s; return Container( color: context.primaryColorDark, child: Column( children: [ SizedBox(height: 10), DurationPicker( onChange: onChange, ), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ FlatButton( onPressed: onCancel, child: Text( s.cancel, style: TextStyle(color: Colors.grey[600]), ), ), FlatButton( splashColor: context.accentColor.withAlpha(70), onPressed: onConfirm, child: Text( s.confirm, style: TextStyle(color: context.accentColor), ), ) ], ) ], ), ); } }