diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index c52a79d..2f5623e 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -308,6 +308,7 @@ class MessageLookup extends MessageLookupByLibrary { "share" : MessageLookupByLibrary.simpleMessage("Share"), "showNotesFonts" : MessageLookupByLibrary.simpleMessage("Show notes font"), "size" : MessageLookupByLibrary.simpleMessage("Size"), + "skipSecondsAtEnd" : MessageLookupByLibrary.simpleMessage("Skip seconds at end"), "skipSecondsAtStart" : MessageLookupByLibrary.simpleMessage("Skip seconds at start"), "skipSilence" : MessageLookupByLibrary.simpleMessage("Skip silence"), "skipToNext" : MessageLookupByLibrary.simpleMessage("Skip to next"), diff --git a/lib/generated/intl/messages_es.dart b/lib/generated/intl/messages_es.dart index 73a03d8..e081106 100644 --- a/lib/generated/intl/messages_es.dart +++ b/lib/generated/intl/messages_es.dart @@ -308,6 +308,7 @@ class MessageLookup extends MessageLookupByLibrary { "share" : MessageLookupByLibrary.simpleMessage("Compartir"), "showNotesFonts" : MessageLookupByLibrary.simpleMessage("Show notes font"), "size" : MessageLookupByLibrary.simpleMessage("Tamaño"), + "skipSecondsAtEnd" : MessageLookupByLibrary.simpleMessage("Skip seconds at end"), "skipSecondsAtStart" : MessageLookupByLibrary.simpleMessage("Saltar segundos al inicio"), "skipSilence" : MessageLookupByLibrary.simpleMessage("Saltar silencios"), "skipToNext" : MessageLookupByLibrary.simpleMessage("Skip to next"), diff --git a/lib/generated/intl/messages_fr.dart b/lib/generated/intl/messages_fr.dart index ddab438..270fefc 100644 --- a/lib/generated/intl/messages_fr.dart +++ b/lib/generated/intl/messages_fr.dart @@ -308,6 +308,7 @@ class MessageLookup extends MessageLookupByLibrary { "share" : MessageLookupByLibrary.simpleMessage("Partager"), "showNotesFonts" : MessageLookupByLibrary.simpleMessage("Show notes font"), "size" : MessageLookupByLibrary.simpleMessage("Taille"), + "skipSecondsAtEnd" : MessageLookupByLibrary.simpleMessage("Skip seconds at end"), "skipSecondsAtStart" : MessageLookupByLibrary.simpleMessage("Passer les premières secondes du début"), "skipSilence" : MessageLookupByLibrary.simpleMessage("Skip silence"), "skipToNext" : MessageLookupByLibrary.simpleMessage("Skip to next"), diff --git a/lib/generated/intl/messages_zh-Hans.dart b/lib/generated/intl/messages_zh-Hans.dart index c235a29..fbf8493 100644 --- a/lib/generated/intl/messages_zh-Hans.dart +++ b/lib/generated/intl/messages_zh-Hans.dart @@ -308,6 +308,7 @@ class MessageLookup extends MessageLookupByLibrary { "share" : MessageLookupByLibrary.simpleMessage("分享"), "showNotesFonts" : MessageLookupByLibrary.simpleMessage("节目简介字体"), "size" : MessageLookupByLibrary.simpleMessage("大小"), + "skipSecondsAtEnd" : MessageLookupByLibrary.simpleMessage("结束跳过秒数"), "skipSecondsAtStart" : MessageLookupByLibrary.simpleMessage("开头跳过秒数"), "skipSilence" : MessageLookupByLibrary.simpleMessage("跳过无声"), "skipToNext" : MessageLookupByLibrary.simpleMessage("下一首"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index d6aa3dd..fb27f08 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -2362,6 +2362,16 @@ class S { ); } + /// `Skip seconds at end` + String get skipSecondsAtEnd { + return Intl.message( + 'Skip seconds at end', + name: 'skipSecondsAtEnd', + desc: '', + args: [], + ); + } + /// `Skip seconds at start` String get skipSecondsAtStart { return Intl.message( diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 51934b1..ed308f1 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -535,6 +535,8 @@ "@showNotesFonts": {}, "size": "Size", "@size": {}, + "skipSecondsAtEnd": "Skip seconds at end", + "@skipSecondsAtEnd": {}, "skipSecondsAtStart": "Skip seconds at start", "@skipSecondsAtStart": {}, "skipSilence": "Skip silence", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 1b4e64a..710cf4a 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -535,6 +535,8 @@ "@showNotesFonts": {}, "size": "Tamaño", "@size": {}, + "skipSecondsAtEnd": "Skip seconds at end", + "@skipSecondsAtEnd": {}, "skipSecondsAtStart": "Saltar segundos al inicio", "@skipSecondsAtStart": {}, "skipSilence": "Saltar silencios", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index bd89a68..dc5e6ad 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -535,6 +535,8 @@ "@showNotesFonts": {}, "size": "Taille", "@size": {}, + "skipSecondsAtEnd": "Skip seconds at end", + "@skipSecondsAtEnd": {}, "skipSecondsAtStart": "Passer les premières secondes du début", "@skipSecondsAtStart": {}, "skipSilence": "Skip silence", diff --git a/lib/l10n/intl_zh_Hans.arb b/lib/l10n/intl_zh_Hans.arb index 60b3fae..c1e5e1e 100644 --- a/lib/l10n/intl_zh_Hans.arb +++ b/lib/l10n/intl_zh_Hans.arb @@ -535,6 +535,8 @@ "@showNotesFonts": {}, "size": "大小", "@size": {}, + "skipSecondsAtEnd": "结束跳过秒数", + "@skipSecondsAtEnd": {}, "skipSecondsAtStart": "开头跳过秒数", "@skipSecondsAtStart": {}, "skipSilence": "跳过无声", diff --git a/lib/local_storage/sqflite_localpodcast.dart b/lib/local_storage/sqflite_localpodcast.dart index 61aeaac..0afd891 100644 --- a/lib/local_storage/sqflite_localpodcast.dart +++ b/lib/local_storage/sqflite_localpodcast.dart @@ -27,7 +27,7 @@ class DBHelper { var documentsDirectory = await getDatabasesPath(); var path = join(documentsDirectory, "podcasts.db"); var theDb = await openDatabase(path, - version: 3, onCreate: _onCreate, onUpgrade: _onUpgrade); + version: 4, onCreate: _onCreate, onUpgrade: _onUpgrade); return theDb; } @@ -37,14 +37,15 @@ class DBHelper { imageUrl TEXT,rssUrl TEXT UNIQUE, primaryColor TEXT, author TEXT, description TEXT, add_date INTEGER, imagePath TEXT, provider TEXT, link TEXT, background_image TEXT DEFAULT '', hosts TEXT DEFAULT '',update_count INTEGER DEFAULT 0, - episode_count INTEGER DEFAULT 0, skip_seconds INTEGER DEFAULT 0, auto_download INTEGER DEFAULT 0)"""); + episode_count INTEGER DEFAULT 0, skip_seconds INTEGER DEFAULT 0, + auto_download INTEGER DEFAULT 0, skip_seconds_end INTEGER DEFAULT 0)"""); await db .execute("""CREATE TABLE Episodes(id INTEGER PRIMARY KEY,title TEXT, enclosure_url TEXT UNIQUE, enclosure_length INTEGER, pubDate TEXT, description TEXT, feed_id TEXT, feed_link TEXT, milliseconds INTEGER, duration INTEGER DEFAULT 0, explicit INTEGER DEFAULT 0, liked INTEGER DEFAULT 0, - liked_date INTEGER DEFAULT 0, downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0, media_id TEXT, - is_new INTEGER DEFAULT 0)"""); + liked_date INTEGER DEFAULT 0, downloaded TEXT DEFAULT 'ND', + download_date INTEGER DEFAULT 0, media_id TEXT, is_new INTEGER DEFAULT 0)"""); await db.execute( """CREATE TABLE PlayHistory(id INTEGER PRIMARY KEY, title TEXT, enclosure_url TEXT, seconds REAL, seek_value REAL, add_date INTEGER, listen_time INTEGER DEFAULT 0)"""); @@ -59,9 +60,16 @@ class DBHelper { "ALTER TABLE PodcastLocal ADD skip_seconds INTEGER DEFAULT 0 "); await db.execute( "ALTER TABLE PodcastLocal ADD auto_download INTEGER DEFAULT 0"); + await db.execute( + "ALTER TABLE PodcastLocal ADD skip_seconds_end INTEGER DEFAULT 0 "); } else if (oldVersion == 2) { await db.execute( "ALTER TABLE PodcastLocal ADD auto_download INTEGER DEFAULT 0"); + await db.execute( + "ALTER TABLE PodcastLocal ADD skip_seconds_end INTEGER DEFAULT 0 "); + } else if (oldVersion == 3) { + await db.execute( + "ALTER TABLE PodcastLocal ADD skip_seconds_end INTEGER DEFAULT 0 "); } } @@ -130,19 +138,33 @@ class DBHelper { return list.first['count']; } - Future getSkipSeconds(String id) async { + Future getSkipSecondsStart(String id) async { var dbClient = await database; List list = await dbClient .rawQuery('SELECT skip_seconds FROM PodcastLocal WHERE id = ?', [id]); return list.first['skip_seconds']; } - Future saveSkipSeconds(String id, int seconds) async { + Future saveSkipSecondsStart(String id, int seconds) async { var dbClient = await database; return await dbClient.rawUpdate( "UPDATE PodcastLocal SET skip_seconds = ? WHERE id = ?", [seconds, id]); } + Future getSkipSecondsEnd(String id) async { + var dbClient = await database; + List list = await dbClient.rawQuery( + 'SELECT skip_seconds_end FROM PodcastLocal WHERE id = ?', [id]); + return list.first['skip_seconds_end']; + } + + Future saveSkipSecondsEnd(String id, int seconds) async { + var dbClient = await database; + return await dbClient.rawUpdate( + "UPDATE PodcastLocal SET skip_seconds_end = ? WHERE id = ?", + [seconds, id]); + } + Future getAutoDownload(String id) async { var dbClient = await database; List list = await dbClient @@ -1336,8 +1358,8 @@ class DBHelper { EpisodeBrief episode; List list = await dbClient.rawQuery( """SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath, - P.title as feed_title, E.duration, E.explicit, P.skip_seconds,E.is_new, - P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id + P.title as feed_title, E.duration, E.explicit, P.skip_seconds, P.skip_seconds_end, + E.is_new, P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id WHERE E.enclosure_url = ?""", [url]); if (list.isEmpty) { return null; @@ -1354,7 +1376,8 @@ class DBHelper { list.first['imagePath'], list.first['is_new'], mediaId: list.first['media_id'], - skipSeconds: list.first['skip_seconds']); + skipSecondsStart: list.first['skip_seconds'], + skipSecondsEnd: list.first['skip_seconds_end']); return episode; } } @@ -1364,9 +1387,9 @@ class DBHelper { EpisodeBrief episode; List list = await dbClient.rawQuery( """SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath, - P.title as feed_title, E.duration, E.explicit, P.skip_seconds,E.is_new, - P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id - WHERE E.media_id = ?""", [id]); + P.title as feed_title, E.duration, E.explicit, P.skip_seconds, P.skip_seconds_end, + E.is_new, P.primaryColor, E.media_id FROM Episodes E INNER JOIN + PodcastLocal P ON E.feed_id = P.id WHERE E.media_id = ?""", [id]); if (list.isEmpty) { return null; } else { @@ -1382,7 +1405,8 @@ class DBHelper { list.first['imagePath'], list.first['is_new'], mediaId: list.first['media_id'], - skipSeconds: list.first['skip_seconds']); + skipSecondsStart: list.first['skip_seconds'], + skipSecondsEnd: list.first['skip_seconds_end']); return episode; } } diff --git a/lib/podcasts/podcast_detail.dart b/lib/podcasts/podcast_detail.dart index b39cfca..fc1f9e0 100644 --- a/lib/podcasts/podcast_detail.dart +++ b/lib/podcasts/podcast_detail.dart @@ -354,7 +354,7 @@ class _PodcastDetailState extends State { case 2: generalSheet( context, - title: s.settings, + title: widget.podcastLocal.title, child: PodcastSetting(podcastLocal: widget.podcastLocal), ).then((value) => setState(() {})); break; @@ -674,7 +674,7 @@ class _PodcastDetailState extends State { @override Widget build(BuildContext context) { - var _color = widget.podcastLocal.primaryColor.colorizedark(); + final color = widget.podcastLocal.primaryColor.colorizedark(); final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( @@ -745,7 +745,7 @@ class _PodcastDetailState extends State { color: Colors.white, ), expandedHeight: 150 + context.paddingTop, - backgroundColor: _color, + backgroundColor: color, floating: true, pinned: true, flexibleSpace: LayoutBuilder( diff --git a/lib/podcasts/podcast_group.dart b/lib/podcasts/podcast_group.dart index a1f7cff..4fe979c 100644 --- a/lib/podcasts/podcast_group.dart +++ b/lib/podcasts/podcast_group.dart @@ -82,14 +82,14 @@ class __PodcastCardState extends State<_PodcastCard> Future _getSkipSecond(String id) async { var dbHelper = DBHelper(); - var seconds = await dbHelper.getSkipSeconds(id); + var seconds = await dbHelper.getSkipSecondsStart(id); _skipSeconds = seconds; return seconds; } _saveSkipSeconds(String id, int seconds) async { var dbHelper = DBHelper(); - await dbHelper.saveSkipSeconds(id, seconds); + await dbHelper.saveSkipSecondsStart(id, seconds); } _setAutoDownload(String id, bool boo) async { @@ -463,8 +463,7 @@ class __PodcastCardState extends State<_PodcastCard> ), ), FlatButton( - splashColor: - context.accentColor.withAlpha(70), + splashColor: Colors.red.withAlpha(70), onPressed: () { groupList.removePodcast( widget.podcastLocal.id); diff --git a/lib/podcasts/podcast_settings.dart b/lib/podcasts/podcast_settings.dart index 1a6ac5b..8d3af65 100644 --- a/lib/podcasts/podcast_settings.dart +++ b/lib/podcasts/podcast_settings.dart @@ -3,12 +3,14 @@ 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:image/image.dart' as img; +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/custom_widget.dart'; @@ -28,73 +30,74 @@ class PodcastSetting extends StatefulWidget { } class _PodcastSettingState extends State { + final _dbHelper = DBHelper(); MarkStatus _markStatus = MarkStatus.none; RefreshCoverStatus _coverStatus = RefreshCoverStatus.none; - int _seconds = 0; + 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) { - var dbHelper = DBHelper(); - await dbHelper.saveAutoDownload(widget.podcastLocal.id, boo: boo); + await _dbHelper.saveAutoDownload(widget.podcastLocal.id, boo: boo); } if (mounted) setState(() {}); } - Future _saveSkipSeconds(int seconds) async { - var dbHelper = DBHelper(); - await dbHelper.saveSkipSeconds(widget.podcastLocal.id, seconds); + 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 _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; }); - var dbHelper = DBHelper(); - var episodes = await dbHelper.getRssItem(podcastId, -1, reverse: true); + var episodes = await _dbHelper.getRssItem(podcastId, -1, reverse: true); for (var episode in episodes) { - var marked = await dbHelper.checkMarked(episode); + final marked = await _dbHelper.checkMarked(episode); if (!marked) { final history = PlayHistory(episode.title, episode.enclosureUrl, 0, 1); - await dbHelper.saveHistory(history); - if (mounted) { - setState(() { - _markStatus = MarkStatus.complete; - }); - } + await _dbHelper.saveHistory(history); } } + if (mounted) { + setState(() { + _markStatus = MarkStatus.complete; + }); + } } - void _confirmMarkListened(BuildContext context) => generalDialog( - context, - title: Text(context.s.markConfirm), - content: Text(context.s.markConfirmContent), - actions: [ - FlatButton( - splashColor: context.accentColor.withAlpha(70), - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - context.s.cancel, - style: TextStyle(color: Colors.grey[600]), - ), - ), - FlatButton( - splashColor: context.accentColor.withAlpha(70), - onPressed: () async { - Navigator.of(context).pop(); - await _markListened(widget.podcastLocal.id); - }, - child: Text( - context.s.confirm, - style: TextStyle(color: context.accentColor), - ), - ) - ], - ); - Future _refreshArtWork() async { setState(() => _coverStatus = RefreshCoverStatus.start); var options = BaseOptions( @@ -161,17 +164,6 @@ class _PodcastSettingState extends State { } } - Future _getAutoDownload(String id) async { - var dbHelper = DBHelper(); - return await dbHelper.getAutoDownload(id); - } - - Future _getSkipSecond(String id) async { - var dbHelper = DBHelper(); - var seconds = await dbHelper.getSkipSeconds(id); - return seconds; - } - Widget _getRefreshStatusIcon(RefreshCoverStatus status) { switch (status) { case RefreshCoverStatus.none: @@ -194,130 +186,265 @@ class _PodcastSettingState extends State { @override Widget build(BuildContext context) { final s = context.s; + final groupList = context.watch(); 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), - leading: SizedBox( - height: 18, - width: 18, - child: CustomPaint( - painter: DownloadPainter( - color: context.brightness == Brightness.light - ? Colors.grey[600] - : Colors.white, - fraction: 0, - progressColor: context.accentColor, - ), - ), - ), - title: Text(s.autoDownload), - trailing: Transform.scale( - scale: 0.9, - child: Switch( - value: snapshot.data, onChanged: _setAutoDownload), - ), - ); - }), - Divider(height: 1), - FutureBuilder( - future: _getSkipSecond(widget.podcastLocal.id), - initialData: 0, - builder: (context, snapshot) => ListTile( - onTap: () { - generalDialog( - context, - title: Text(s.skipSecondsAtStart, maxLines: 2), - content: DurationPicker( - duration: Duration(seconds: snapshot.data), - onChange: (value) => _seconds = value.inSeconds, - ), - actions: [ - FlatButton( - splashColor: context.accentColor.withAlpha(70), - onPressed: () { - Navigator.of(context).pop(); - _seconds = 0; - }, - child: Text( - s.cancel, - style: TextStyle(color: Colors.grey[600]), - ), - ), - FlatButton( - splashColor: context.accentColor.withAlpha(70), - onPressed: () { - Navigator.of(context).pop(); - _saveSkipSeconds(_seconds); - }, - child: Text( - s.confirm, - style: TextStyle(color: context.accentColor), - ), - ) - ], - ).then((value) => setState(() {})); - }, - leading: Icon(Icons.fast_forward), - title: Text(s.skipSecondsAtStart), - trailing: Padding( - padding: const EdgeInsets.only(right: 10.0), - child: Text(snapshot.data.toTime), - ), - ), - ), - Divider(height: 1), - ListTile( - onTap: () { - if (_markStatus != MarkStatus.start) { - _confirmMarkListened(context); - } - }, - title: Text(s.menuMarkAllListened), - leading: SizedBox( - height: 20, - width: 20, - child: CustomPaint( - painter: ListenedAllPainter( - context.brightness == Brightness.light + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + FutureBuilder( + future: _getAutoDownload(widget.podcastLocal.id), + initialData: false, + builder: (context, snapshot) { + return ListTile( + onTap: () => _setAutoDownload(!snapshot.data), + leading: SizedBox( + height: 22, + width: 24, + child: CustomPaint( + painter: DownloadPainter( + color: context.brightness == Brightness.light ? Colors.grey[600] : Colors.white, - stroke: 2), + fraction: 0, + progressColor: context.accentColor, + ), + ), ), - ), - trailing: Padding( - padding: const EdgeInsets.only(right: 10.0), + title: Text(s.autoDownload), + trailing: Transform.scale( + scale: 0.9, + child: + Switch(value: snapshot.data, onChanged: _setAutoDownload), + ), + ); + }), + FutureBuilder( + future: _getSkipSecondStart(widget.podcastLocal.id), + initialData: 0, + builder: (context, snapshot) => ListTile( + onTap: () { + _secondsStart = 0; + setState(() { + _removeConfirm = false; + _markConfirm = false; + _showEndTimePicker = false; + _showStartTimePicker = !_showStartTimePicker; + }); + }, + leading: Icon(Icons.fast_forward), + title: Text(s.skipSecondsAtStart), + 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); + setState(() => _showStartTimePicker = false); + }, + onChange: (value) => _secondsStart = value.inSeconds), + FutureBuilder( + future: _getSkipSecondEnd(widget.podcastLocal.id), + initialData: 0, + builder: (context, snapshot) => ListTile( + onTap: () { + _secondsEnd = 0; + setState(() { + _removeConfirm = false; + _markConfirm = false; + _showStartTimePicker = false; + _showEndTimePicker = !_showEndTimePicker; + }); + }, + leading: Icon(Icons.fast_rewind), + title: Text(s.skipSecondsAtEnd), + trailing: Padding( + padding: const EdgeInsets.only(right: 10.0), + child: Text(snapshot.data.toTime), + ), + ), + ), + if (_showEndTimePicker) + _TimePicker( + onCancel: () { + _secondsEnd = 0; + setState(() => _showEndTimePicker = false); + }, + onConfirm: () async { + await _saveSkipSecondsEnd(_secondsEnd); + setState(() => _showEndTimePicker = false); + }, + onChange: (value) => _secondsEnd = value.inSeconds, + ), + ListTile( + onTap: () { + if (_coverStatus != RefreshCoverStatus.start) { + _refreshArtWork(); + } + }, + title: Text(s.refreshArtwork), + leading: Icon(Icons.refresh), + trailing: Padding( + padding: const EdgeInsets.only(right: 15.0), child: SizedBox( height: 20, width: 20, - child: _markStatus == MarkStatus.none - ? Center() - : _markStatus == MarkStatus.start - ? CircularProgressIndicator(strokeWidth: 2) - : Icon(Icons.done)), - )), - Divider(height: 1), - ListTile( - onTap: () { - if (_coverStatus != RefreshCoverStatus.start) { - _refreshArtWork(); - } - }, - title: Text(s.refreshArtwork), - leading: Icon(Icons.refresh), - trailing: Padding( - padding: const EdgeInsets.only(right: 15.0), - child: SizedBox( - height: 20, - width: 20, - child: _getRefreshStatusIcon(_coverStatus)))), - Divider(height: 1), - ]); + child: _getRefreshStatusIcon(_coverStatus)))), + Divider(height: 1), + ListTile( + onTap: () { + setState(() { + _removeConfirm = false; + _showStartTimePicker = false; + _showEndTimePicker = false; + _markConfirm = !_markConfirm; + }); + }, + title: Text(s.menuMarkAllListened, + style: TextStyle( + color: context.accentColor, fontWeight: FontWeight.bold)), + leading: SizedBox( + height: 22, + width: 24, + child: CustomPaint( + painter: ListenedAllPainter( + context.brightness == Brightness.light + ? Colors.grey[600] + : Colors.white, + stroke: 2), + ), + ), + 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; + }); + }, + title: Text(s.remove, + style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)), + leading: Icon(Icons.delete, color: Colors.red), + ), + 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: () { + groupList.removePodcast(widget.podcastLocal.id); + 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( + key: key, + 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), + ), + ) + ], + ) + ], + ), + ); } } diff --git a/lib/state/audio_state.dart b/lib/state/audio_state.dart index fa699e2..818aec5 100644 --- a/lib/state/audio_state.dart +++ b/lib/state/audio_state.dart @@ -920,6 +920,13 @@ class AudioPlayerTask extends BackgroundAudioTask { Future _playFromStart() async { _playing = true; _session.setActive(true); + if (mediaItem.extras['skipSecondsStart'] > 0 || + mediaItem.extras['skipSecondsEnd'] > 0) { + //_audioPlayer.seek(Duration(seconds: mediaItem.extras['skip'])); + _audioPlayer.setClip( + start: Duration(seconds: mediaItem.extras['skipSecondsStart']), + end: Duration(seconds: mediaItem.extras['skipSecondsEnd'])); + } if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting || _audioPlayer.playbackEvent.state != AudioPlaybackState.none) { try { @@ -928,9 +935,6 @@ class AudioPlayerTask extends BackgroundAudioTask { _setState(processingState: AudioProcessingState.error); } } - if (mediaItem.extras['skip'] > 0) { - _audioPlayer.seek(Duration(seconds: mediaItem.extras['skip'])); - } } @override diff --git a/lib/type/episodebrief.dart b/lib/type/episodebrief.dart index f72f6bd..1f6f342 100644 --- a/lib/type/episodebrief.dart +++ b/lib/type/episodebrief.dart @@ -21,7 +21,8 @@ class EpisodeBrief extends Equatable { final String imagePath; final String mediaId; final int isNew; - final int skipSeconds; + final int skipSecondsStart; + final int skipSecondsEnd; final int downloadDate; EpisodeBrief( this.title, @@ -37,7 +38,8 @@ class EpisodeBrief extends Equatable { {this.mediaId, this.liked, this.downloaded, - this.skipSeconds, + this.skipSecondsStart, + this.skipSecondsEnd, this.description = '', this.downloadDate = 0}) : assert(enclosureUrl != null); @@ -50,7 +52,10 @@ class EpisodeBrief extends Equatable { album: feedTitle, duration: Duration.zero, artUri: 'file://$imagePath', - extras: {'skip': skipSeconds}); + extras: { + 'skipSecondsStart': skipSecondsStart, + 'skipSecondsEnd': skipSecondsEnd + }); } ImageProvider get avatarImage { @@ -72,7 +77,8 @@ class EpisodeBrief extends Equatable { primaryColor, duration, explicit, imagePath, isNew, mediaId: mediaId ?? this.mediaId, downloaded: downloaded, - skipSeconds: skipSeconds, + skipSecondsStart: skipSecondsStart, + skipSecondsEnd: skipSecondsEnd, description: description, downloadDate: downloadDate); diff --git a/lib/util/duraiton_picker.dart b/lib/util/duraiton_picker.dart index 7111053..954fd14 100644 --- a/lib/util/duraiton_picker.dart +++ b/lib/util/duraiton_picker.dart @@ -635,24 +635,20 @@ class DurationPicker extends StatelessWidget { @required this.onChange, this.snapToMins, this.width, - this.height}); + this.height, + Key key}) + : super(key: key); @override Widget build(BuildContext context) { return SizedBox( - width: width ?? _kDurationPickerWidthPortrait / 1.5, - height: height ?? _kDurationPickerHeightPortrait / 1.5, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: _Dial( - duration: duration, - onChanged: onChange, - snapToMins: snapToMins, - ), - ), - ])); + width: width ?? _kDurationPickerWidthPortrait / 1.5, + height: height ?? _kDurationPickerHeightPortrait / 1.5, + child: _Dial( + duration: duration, + onChanged: onChange, + snapToMins: snapToMins, + ), + ); } } diff --git a/lib/util/general_dialog.dart b/lib/util/general_dialog.dart index 8cd6cd2..9f12d10 100644 --- a/lib/util/general_dialog.dart +++ b/lib/util/general_dialog.dart @@ -41,25 +41,37 @@ Future generalSheet(BuildContext context, {Widget child, String title}) async => elevation: 2, context: context, builder: (context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only(top: 10.0, bottom: 2.0), - child: Container( - height: 4, - width: 25, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(2.0), - color: context.primaryColorDark), - ), + return SafeArea( + child: SingleChildScrollView( + physics: NeverScrollableScrollPhysics(), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only(top: 10.0, bottom: 2.0), + child: Container( + height: 4, + width: 25, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.0), + color: context.primaryColorDark), + ), + ), + Padding( + padding: EdgeInsets.only( + left: 50, right: 50, top: 6.0, bottom: 15), + child: Text( + title, + style: context.textTheme.headline6, + textAlign: TextAlign.center, + maxLines: 1, + overflow: TextOverflow.clip, + ), + ), + Divider(height: 1), + child, + ], ), - Padding( - padding: EdgeInsets.symmetric(vertical: 6.0), - child: Text(title, style: context.textTheme.headline6), - ), - Divider(height: 1), - child, - ], + ), ); });