2020-10-28 13:10:43 +01:00
|
|
|
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: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';
|
2020-10-30 13:35:02 +01:00
|
|
|
import '../widgets/custom_widget.dart';
|
|
|
|
import '../widgets/duraiton_picker.dart';
|
2020-10-28 13:10:43 +01:00
|
|
|
|
|
|
|
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<PodcastSetting> {
|
|
|
|
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<void> _setAutoDownload(bool boo) async {
|
|
|
|
var permission = await _checkPermmison();
|
|
|
|
if (permission) {
|
|
|
|
await _dbHelper.saveAutoDownload(widget.podcastLocal.id, boo: boo);
|
|
|
|
}
|
|
|
|
if (mounted) setState(() {});
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _setNeverUpdate(bool boo) async {
|
|
|
|
await _dbHelper.saveNeverUpdate(widget.podcastLocal.id, boo: boo);
|
|
|
|
if (mounted) setState(() {});
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _saveSkipSecondsStart(int seconds) async {
|
|
|
|
await _dbHelper.saveSkipSecondsStart(widget.podcastLocal.id, seconds);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _saveSkipSecondsEnd(int seconds) async {
|
|
|
|
await _dbHelper.saveSkipSecondsEnd(widget.podcastLocal.id, seconds);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool> _getAutoDownload(String id) async {
|
|
|
|
return await _dbHelper.getAutoDownload(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool> _getNeverUpdate(String id) async {
|
|
|
|
return await _dbHelper.getNeverUpdate(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<int> _getSkipSecondStart(String id) async {
|
|
|
|
return await _dbHelper.getSkipSecondsStart(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<int> _getSkipSecondEnd(String id) async {
|
|
|
|
return await _dbHelper.getSkipSecondsEnd(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _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<void> _refreshArtWork() async {
|
|
|
|
setState(() => _coverStatus = RefreshCoverStatus.start);
|
|
|
|
var options = BaseOptions(
|
|
|
|
connectTimeout: 30000,
|
|
|
|
receiveTimeout: 90000,
|
|
|
|
);
|
2021-01-28 17:32:02 +01:00
|
|
|
var dir = await getApplicationDocumentsDirectory();
|
|
|
|
var filePath = "${dir.path}/${widget.podcastLocal.id}.png";
|
2020-10-28 13:10:43 +01:00
|
|
|
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);
|
|
|
|
}
|
2021-01-28 17:32:02 +01:00
|
|
|
if (imageUrl != null && imageUrl.contains('http')) {
|
2020-10-28 13:10:43 +01:00
|
|
|
try {
|
|
|
|
img.Image thumbnail;
|
|
|
|
var imageResponse = await dio.get<List<int>>(imageUrl,
|
|
|
|
options: Options(
|
|
|
|
responseType: ResponseType.bytes,
|
|
|
|
));
|
|
|
|
var image = img.decodeImage(imageResponse.data);
|
|
|
|
thumbnail = img.copyResize(image, width: 300);
|
|
|
|
if (thumbnail != null) {
|
2021-01-28 17:32:02 +01:00
|
|
|
File(filePath)..writeAsBytesSync(img.encodePng(thumbnail));
|
|
|
|
_dbHelper.updatePodcastImage(
|
|
|
|
id: widget.podcastLocal.id, filePath: filePath);
|
|
|
|
print('saved image');
|
2020-10-28 13:10:43 +01:00
|
|
|
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<bool> _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<GroupList>();
|
2020-11-03 18:54:29 +01:00
|
|
|
final textStyle = context.textTheme.bodyText2;
|
2020-10-28 13:10:43 +01:00
|
|
|
return Column(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: <Widget>[
|
|
|
|
FutureBuilder<bool>(
|
|
|
|
future: _getAutoDownload(widget.podcastLocal.id),
|
|
|
|
initialData: false,
|
|
|
|
builder: (context, snapshot) {
|
|
|
|
return ListTile(
|
|
|
|
onTap: () => _setAutoDownload(!snapshot.data),
|
2020-10-29 14:25:42 +01:00
|
|
|
dense: true,
|
2020-11-06 15:08:26 +01:00
|
|
|
title: Row(
|
|
|
|
children: [
|
|
|
|
SizedBox(
|
|
|
|
height: 18,
|
|
|
|
width: 18,
|
|
|
|
child: CustomPaint(
|
|
|
|
painter: DownloadPainter(
|
|
|
|
color: context.textColor,
|
|
|
|
fraction: 0,
|
|
|
|
progressColor: context.accentColor,
|
|
|
|
),
|
|
|
|
),
|
2020-10-28 13:10:43 +01:00
|
|
|
),
|
2020-11-06 15:08:26 +01:00
|
|
|
SizedBox(width: 20),
|
|
|
|
Text(s.autoDownload, style: textStyle),
|
|
|
|
],
|
2020-10-28 13:10:43 +01:00
|
|
|
),
|
|
|
|
trailing: Transform.scale(
|
2020-10-29 14:25:42 +01:00
|
|
|
scale: 0.8,
|
2020-10-28 13:10:43 +01:00
|
|
|
child:
|
|
|
|
Switch(value: snapshot.data, onChanged: _setAutoDownload),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}),
|
|
|
|
FutureBuilder<bool>(
|
|
|
|
future: _getNeverUpdate(widget.podcastLocal.id),
|
|
|
|
initialData: false,
|
|
|
|
builder: (context, snapshot) {
|
|
|
|
return ListTile(
|
2020-10-29 14:25:42 +01:00
|
|
|
dense: true,
|
2020-10-28 13:10:43 +01:00
|
|
|
onTap: () => _setNeverUpdate(!snapshot.data),
|
2020-11-06 15:08:26 +01:00
|
|
|
title: Row(
|
|
|
|
children: [
|
2021-01-02 19:31:26 +01:00
|
|
|
Icon(Icons.lock_outlined, size: 18),
|
2020-11-06 15:08:26 +01:00
|
|
|
SizedBox(width: 20),
|
|
|
|
Text(s.neverAutoUpdate, style: textStyle),
|
|
|
|
],
|
|
|
|
),
|
2020-10-28 13:10:43 +01:00
|
|
|
trailing: Transform.scale(
|
2020-10-29 14:25:42 +01:00
|
|
|
scale: 0.8,
|
2020-10-28 13:10:43 +01:00
|
|
|
child:
|
|
|
|
Switch(value: snapshot.data, onChanged: _setNeverUpdate),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}),
|
|
|
|
FutureBuilder<int>(
|
|
|
|
future: _getSkipSecondStart(widget.podcastLocal.id),
|
|
|
|
initialData: 0,
|
|
|
|
builder: (context, snapshot) => ListTile(
|
|
|
|
onTap: () {
|
|
|
|
_secondsStart = 0;
|
|
|
|
setState(() {
|
|
|
|
_removeConfirm = false;
|
|
|
|
_markConfirm = false;
|
|
|
|
_showEndTimePicker = false;
|
|
|
|
_showStartTimePicker = !_showStartTimePicker;
|
|
|
|
});
|
|
|
|
},
|
2020-10-29 14:25:42 +01:00
|
|
|
dense: true,
|
2020-11-06 15:08:26 +01:00
|
|
|
title: Row(
|
|
|
|
children: [
|
2021-01-02 19:31:26 +01:00
|
|
|
Icon(Icons.fast_forward_outlined, size: 18),
|
2020-11-06 15:08:26 +01:00
|
|
|
SizedBox(width: 20),
|
|
|
|
Text(s.skipSecondsAtStart, style: textStyle),
|
|
|
|
],
|
|
|
|
),
|
2020-10-28 13:10:43 +01:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
},
|
2020-10-29 14:25:42 +01:00
|
|
|
dense: true,
|
2020-11-06 15:08:26 +01:00
|
|
|
title: Row(
|
|
|
|
children: [
|
|
|
|
Icon(Icons.refresh, size: 18),
|
|
|
|
SizedBox(width: 20),
|
|
|
|
Text(s.refreshArtwork, style: textStyle),
|
|
|
|
],
|
|
|
|
),
|
2020-10-28 13:10:43 +01:00
|
|
|
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;
|
|
|
|
});
|
|
|
|
},
|
2020-10-29 14:25:42 +01:00
|
|
|
dense: true,
|
2020-11-06 15:08:26 +01:00
|
|
|
title: Row(
|
|
|
|
children: [
|
|
|
|
SizedBox(
|
|
|
|
height: 18,
|
|
|
|
width: 18,
|
|
|
|
child: CustomPaint(
|
2021-01-28 17:32:02 +01:00
|
|
|
painter: ListenedAllPainter(context.accentColor, stroke: 2),
|
2020-11-06 15:08:26 +01:00
|
|
|
),
|
|
|
|
),
|
|
|
|
SizedBox(width: 20),
|
|
|
|
Text(s.menuMarkAllListened,
|
|
|
|
style: textStyle.copyWith(
|
|
|
|
color: context.accentColor,
|
|
|
|
fontWeight: FontWeight.bold)),
|
|
|
|
],
|
2020-10-28 13:10:43 +01:00
|
|
|
),
|
|
|
|
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;
|
|
|
|
});
|
|
|
|
},
|
2020-10-29 14:25:42 +01:00
|
|
|
dense: true,
|
2020-11-06 15:08:26 +01:00
|
|
|
title: Row(
|
|
|
|
children: [
|
2021-01-02 19:31:26 +01:00
|
|
|
Icon(Icons.delete_outlined, color: Colors.red, size: 18),
|
2020-11-06 15:08:26 +01:00
|
|
|
SizedBox(width: 20),
|
|
|
|
Text(s.remove,
|
|
|
|
style: textStyle.copyWith(
|
|
|
|
color: Colors.red, fontWeight: FontWeight.bold)),
|
|
|
|
],
|
|
|
|
),
|
2020-10-28 13:10:43 +01:00
|
|
|
),
|
|
|
|
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<Duration> 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),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
)
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|