diff --git a/README.md b/README.md index 664aad7..4a74259 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@

![CircleCI](https://img.shields.io/circleci/build/github/stonega/tsacdop?token=efe1331861e017144f2abb363acd95197e436dad) + ![GitHub release (latest by date)](https://img.shields.io/github/v/release/stonega/tsacdop) + [![GooglePlay](https://img.shields.io/badge/Google-PlayStore-%2323CCC6)](https://play.google.com/store/apps/details?id=com.stonegate.tsacdop) ## About @@ -28,7 +30,7 @@ The podcasts search engine is powered by [ListenNotes](https://listennotes.com). * Listen and subscribe history record * Dark mode / Accent color * Download for offline playing -* Share clip(video format) on twitter +* Auto download new episodes / Auto delete outdated downloads More to come... @@ -47,7 +49,7 @@ Tsacdop is licensed under the [GPL V3.0](https://github.com/stonega/tsacdop/blob Tsacdop is using ListenNotes api 1.0 pro to search podcast, which is not free. So I can not expose the api key in the repo. If you want to build the app, you need to create a new file named .env.dart in lib folder. Add below code in .env.dart. -``` +``` final environment = {"apiKey":"APIKEY", "shareKey":"SHAREKEY"}; ``` @@ -58,7 +60,7 @@ Share_key is used for generate clip. ## Known Issue -- Playlist unstable +* Playlist unstable ## Getting Started diff --git a/lib/home/audioplayer.dart b/lib/home/audioplayer.dart index 0912b26..5d04f8d 100644 --- a/lib/home/audioplayer.dart +++ b/lib/home/audioplayer.dart @@ -11,6 +11,7 @@ import 'package:line_icons/line_icons.dart'; import '../type/episodebrief.dart'; import '../state/audiostate.dart'; import '../local_storage/sqflite_localpodcast.dart'; +import '../local_storage/key_value_storage.dart'; import '../util/pageroute.dart'; import '../util/colorize.dart'; import '../util/context_extension.dart'; @@ -42,6 +43,9 @@ String _stringForSeconds(double seconds) { return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}'; } +const List minsToSelect = [10, 15, 20, 25, 30, 45, 60, 70, 80, 90, 99]; +const List speedToSelect = [0.5, 0.6, 0.8, 1.0, 1.2, 1.5, 2.0]; + class PlayerWidget extends StatefulWidget { @override _PlayerWidgetState createState() => _PlayerWidgetState(); @@ -788,13 +792,21 @@ class SleepMode extends StatefulWidget { class SleepModeState extends State with SingleTickerProviderStateMixin { int _minSelected; - List minsToSelect = [10, 15, 20, 25, 30, 45, 60, 70, 80, 90, 99]; AnimationController _controller; Animation _animation; + + Future _getDefaultTime() async { + KeyValueStorage defaultSleepTimerStorage = + KeyValueStorage(defaultSleepTimerKey); + int defaultTime = await defaultSleepTimerStorage.getInt(defaultValue: 30); + setState(() => _minSelected = defaultTime); + } + @override void initState() { super.initState(); _minSelected = 30; + _getDefaultTime(); _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 400)); _animation = Tween(begin: 0.0, end: 1.0).animate(_controller) @@ -1085,7 +1097,6 @@ class ControlPanel extends StatefulWidget { class _ControlPanelState extends State with SingleTickerProviderStateMixin { - final _speedToSelect = [0.5, 0.6, 0.8, 1.0, 1.2, 1.5, 2.0]; double _speedSelected; double _setSpeed; AnimationController _controller; @@ -1363,7 +1374,8 @@ class _ControlPanelState extends State velocity: 50.0, pauseAfterRound: Duration.zero, startPadding: 30.0, - accelerationDuration: Duration(milliseconds: 100), + accelerationDuration: + Duration(milliseconds: 100), accelerationCurve: Curves.linear, decelerationDuration: Duration(milliseconds: 100), @@ -1459,7 +1471,7 @@ class _ControlPanelState extends State padding: EdgeInsets.all(10.0), scrollDirection: Axis.horizontal, child: Row( - children: _speedToSelect + children: speedToSelect .map((e) => InkWell( onTap: () { if (_setSpeed == 1) { diff --git a/lib/home/playlist.dart b/lib/home/playlist.dart index e1995b5..ccd7f06 100644 --- a/lib/home/playlist.dart +++ b/lib/home/playlist.dart @@ -179,7 +179,9 @@ class _PlaylistPageState extends State { child: SizedBox( width: 20, height: 15, - child: WaveLoader(color: context.accentColor,)), + child: WaveLoader( + color: context.accentColor, + )), ), ], ) @@ -211,7 +213,9 @@ class _PlaylistPageState extends State { child: SizedBox( width: 20, height: 15, - child: WaveLoader(color: context.accentColor,)), + child: WaveLoader( + color: context.accentColor, + )), ), ], ) @@ -307,7 +311,7 @@ class _DismissibleContainerState extends State { @override Widget build(BuildContext context) { var audio = Provider.of(context, listen: false); - Color _c = (Theme.of(context).brightness == Brightness.light) + Color _c = (Theme.of(context).brightness == Brightness.light) ? widget.episode.primaryColor.colorizedark() : widget.episode.primaryColor.colorizeLight(); return AnimatedContainer( @@ -363,6 +367,7 @@ class _DismissibleContainerState extends State { gravity: ToastGravity.BOTTOM, ); Scaffold.of(context).showSnackBar(SnackBar( + behavior: SnackBarBehavior.floating, backgroundColor: Colors.grey[800], content: Text('Episode removed', style: TextStyle(color: Colors.white)), diff --git a/lib/local_storage/key_value_storage.dart b/lib/local_storage/key_value_storage.dart index c4eebe9..1ac1ddc 100644 --- a/lib/local_storage/key_value_storage.dart +++ b/lib/local_storage/key_value_storage.dart @@ -6,13 +6,13 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../state/podcast_group.dart'; const String autoPlayKey = 'autoPlay'; -const String autoAddKey = 'autoAdd'; +//const String autoAddKey = 'autoAdd'; const String audioPositionKey = 'audioposition'; const String lastWorkKey = 'lastWork'; const String refreshdateKey = 'refreshdate'; const String themesKey = 'themes'; const String accentsKey = 'accents'; -const String autoUpdateKey = 'autoupdate'; +const String autoUpdateKey = 'autoAdd'; const String updateIntervalKey = 'updateInterval'; const String downloadUsingDataKey = 'downloadUsingData'; const String introKey = 'intro'; @@ -25,6 +25,12 @@ const String downloadLayoutKey = 'downloadLayoutKey'; const String autoDownloadNetworkKey = 'autoDownloadNetwork'; const String episodePopupMenuKey = 'episodePopupMenuKey'; const String autoDeleteKey = 'autoDeleteKey'; +//SleepTImer +const String autoSleepTimerKey = 'autoSleepTimerKey'; +const String autoSleepTimerStartKey = 'autoSleepTimerStartKey'; +const String autoSleepTimerEndKey = 'autoSleepTimerEndKey'; +const String defaultSleepTimerKey = 'defaultSleepTimerKey'; +const String autoSleepTimerModeKey = 'autoSleepTimerModeKey'; class KeyValueStorage { final String key; @@ -60,9 +66,9 @@ class KeyValueStorage { return prefs.setInt(key, setting); } - Future getInt() async { + Future getInt({int defaultValue = 0}) async { SharedPreferences prefs = await SharedPreferences.getInstance(); - if (prefs.getInt(key) == null) await prefs.setInt(key, 0); + if (prefs.getInt(key) == null) await prefs.setInt(key, defaultValue); return prefs.getInt(key); } diff --git a/lib/settings/layouts.dart b/lib/settings/layouts.dart index 7fbb477..c825093 100644 --- a/lib/settings/layouts.dart +++ b/lib/settings/layouts.dart @@ -29,10 +29,10 @@ class _LayoutSettingState extends State { onTap: () async { KeyValueStorage storage = KeyValueStorage(key); await storage.saveInt(option.index); - print(option.index); setState(() {}); }, - child: Container( + child: AnimatedContainer( + duration: Duration(milliseconds: 400), height: 30, width: 50, color: layout == option diff --git a/lib/settings/play_setting.dart b/lib/settings/play_setting.dart index 0162fea..1be36c7 100644 --- a/lib/settings/play_setting.dart +++ b/lib/settings/play_setting.dart @@ -1,13 +1,212 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:flutter_time_picker_spinner/flutter_time_picker_spinner.dart'; -import '../state/audiostate.dart'; +import '../state/settingstate.dart'; +import '../home/audioplayer.dart'; +import '../util/general_dialog.dart'; +import '../util/context_extension.dart'; + +String stringForMins(int mins) { + if (mins == null) return null; + return '${(mins ~/ 60)}:${(mins.truncate() % 60).toString().padLeft(2, '0')}'; +} class PlaySetting extends StatelessWidget { + Widget _modeWidget(BuildContext context) { + var settings = Provider.of(context, listen: false); + return Selector>( + selector: (_, settings) => + Tuple2(settings.autoSleepTimerMode, settings.defaultSleepTimer), + builder: (_, data, __) => Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () => settings.setAutoSleepTimerMode = 0, + child: Material( + color: Colors.transparent, + child: AnimatedContainer( + duration: Duration(milliseconds: 400), + color: data.item1 == 0 + ? context.accentColor + : context.primaryColorDark, + padding: const EdgeInsets.all(8.0), + child: Text('End of Episode', + style: TextStyle( + color: data.item1 == 0 ? Colors.white : null)), + ), + ), + ), + InkWell( + onTap: () => settings.setAutoSleepTimerMode = 1, + child: Material( + color: Colors.transparent, + child: AnimatedContainer( + duration: Duration(milliseconds: 400), + color: data.item1 == 1 + ? context.accentColor + : context.primaryColorDark, + padding: const EdgeInsets.all(8.0), + child: Text(data.item2.toString() + 'mins', + style: TextStyle( + color: data.item1 == 1 ? Colors.white : null)), + ), + ), + ), + ], + ), + ), + ); + } + + Widget _scheduleWidget(BuildContext context) { + var settings = Provider.of(context, listen: false); + return Selector>( + selector: (_, settings) => + Tuple2(settings.autoSleepTimerStart, settings.autoSleepTimerEnd), + builder: (_, data, __) => Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () { + int startTime = data.item1; + generalDialog( + context, + content: TimePickerSpinner( + minutesInterval: 15, + time: DateTime.fromMillisecondsSinceEpoch( + data.item1 * 60 * 1000, + isUtc: true), + isForce2Digits: true, + is24HourMode: false, + highlightedTextStyle: GoogleFonts.teko( + textStyle: TextStyle( + fontSize: 40, color: context.accentColor)), + normalTextStyle: GoogleFonts.teko( + textStyle: + TextStyle(fontSize: 40, color: Colors.black38)), + onTimeChange: (DateTime time) { + startTime = time.hour * 60 + time.minute; + }, + ), + actions: [ + FlatButton( + onPressed: () => Navigator.of(context).pop(), + child: Text( + 'CANCEL', + style: TextStyle(color: Colors.grey[600]), + ), + ), + FlatButton( + onPressed: () { + if (startTime != data.item2) { + settings.setAutoSleepTimerStart = startTime; + Navigator.of(context).pop(); + } else { + Fluttertoast.showToast( + msg: 'Time is equal to end time', + gravity: ToastGravity.BOTTOM, + ); + } + }, + child: Text( + 'CONFIRM', + style: TextStyle(color: context.accentColor), + ), + ) + ], + ); + }, + child: Material( + color: Colors.transparent, + child: Container( + color: context.primaryColorDark, + padding: const EdgeInsets.all(8.0), + child: Text('From ' + stringForMins(data.item1)), + ), + ), + ), + InkWell( + onTap: () { + int endTime; + generalDialog( + context, + content: TimePickerSpinner( + minutesInterval: 15, + time: DateTime.fromMillisecondsSinceEpoch( + data.item2 * 60 * 1000, + isUtc: true), + isForce2Digits: true, + highlightedTextStyle: GoogleFonts.teko( + textStyle: TextStyle( + fontSize: 40, color: context.accentColor)), + normalTextStyle: GoogleFonts.teko( + textStyle: + TextStyle(fontSize: 40, color: Colors.black38)), + is24HourMode: false, + onTimeChange: (DateTime time) { + endTime = time.hour * 60 + time.minute; + }, + ), + actions: [ + FlatButton( + onPressed: () => Navigator.of(context).pop(), + child: Text( + 'CANCEL', + style: TextStyle(color: Colors.grey[600]), + ), + ), + FlatButton( + onPressed: () { + if (endTime != data.item1) { + settings.setAutoSleepTimerEnd = endTime; + Navigator.of(context).pop(); + } else { + Fluttertoast.showToast( + msg: 'Time is equal to start time', + gravity: ToastGravity.BOTTOM, + ); + } + }, + child: Text( + 'CONFIRM', + style: TextStyle(color: context.accentColor), + ), + ) + ], + ); + }, + child: Material( + color: Colors.transparent, + child: Container( + padding: const EdgeInsets.all(8.0), + color: Colors.black54, + child: Text('To ' + stringForMins(data.item2), + style: TextStyle(color: Colors.white)), + ), + ), + ), + ], + ), + ), + ); + } + @override Widget build(BuildContext context) { - var audio = Provider.of(context, listen: false); + var settings = Provider.of(context, listen: false); return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarIconBrightness: Theme.of(context).accentColorBrightness, @@ -32,7 +231,7 @@ class PlaySetting extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Padding( - padding: EdgeInsets.all(10.0), + padding: const EdgeInsets.all(10.0), ), Container( height: 30.0, @@ -49,36 +248,103 @@ class PlaySetting extends StatelessWidget { shrinkWrap: true, scrollDirection: Axis.vertical, children: [ - ListTile( - contentPadding: - EdgeInsets.only(left: 80.0, right: 20, bottom: 0), - title: Text('Autoplay'), - subtitle: Text('Autoplay next episode in playlist'), - trailing: Selector( - selector: (_, audio) => audio.autoPlay, - builder: (_, data, __) => Transform.scale( + Selector( + selector: (_, settings) => settings.autoPlay, + builder: (_, data, __) => ListTile( + onTap: () => settings.setAutoPlay = !data, + contentPadding: + EdgeInsets.only(left: 80.0, right: 20), + title: Text('Autoplay'), + subtitle: Text('Autoplay next episode in playlist'), + trailing: Transform.scale( scale: 0.9, child: Switch( value: data, - onChanged: (boo) => audio.autoPlaySwitch = boo), + onChanged: (boo) => settings.setAutoPlay = boo), ), ), ), Divider(height: 2), - // ListTile( - // contentPadding: - // EdgeInsets.only(left: 80.0, right: 20, bottom: 0), - // title: Text('Autoadd'), - // subtitle: - // Text('Autoadd new updated episodes to playlist'), - // trailing: Selector( - // selector: (_, audio) => audio.autoAdd, - // builder: (_, data, __) => Switch( - // value: data, - // onChanged: (boo) => audio.autoAddSwitch = boo), - // ), - // ), - // Divider(height: 2), + ], + ), + Padding( + padding: const EdgeInsets.all(10.0), + ), + Container( + height: 30.0, + padding: EdgeInsets.symmetric(horizontal: 70), + alignment: Alignment.centerLeft, + child: Text('Sleep timer', + style: Theme.of(context) + .textTheme + .bodyText1 + .copyWith(color: Theme.of(context).accentColor)), + ), + ListView( + physics: const BouncingScrollPhysics(), + shrinkWrap: true, + scrollDirection: Axis.vertical, + children: [ + ListTile( + contentPadding: EdgeInsets.only(left: 80.0, right: 20), + title: Text('Default time'), + subtitle: Text('Default time for sleep timer.'), + trailing: Selector( + selector: (_, settings) => settings.defaultSleepTimer, + builder: (_, data, __) => DropdownButton( + hint: Text(data.toString() + 'mins'), + underline: Center(), + elevation: 1, + isDense: true, + value: data, + onChanged: (int value) => + settings.setDefaultSleepTimer = value, + items: + minsToSelect.map>((e) { + return DropdownMenuItem( + value: e, + child: Text(e.toString() + ' mins')); + }).toList()), + ), + ), + Selector( + selector: (_, settings) => settings.autoSleepTimer, + builder: (_, data, __) => ListTile( + onTap: () => settings.setAutoSleepTimer = !data, + contentPadding: const EdgeInsets.only( + left: 80.0, right: 20.0, bottom: 10.0, top: 10.0), + title: Text('Auto turn on sleep timer'), + subtitle: + Text('Auto start sleep timer at scheduled time.'), + trailing: Transform.scale( + scale: 0.9, + child: Switch( + value: data, + onChanged: (boo) => + settings.setAutoSleepTimer = boo), + ), + ), + ), + ListTile( + contentPadding: const EdgeInsets.only( + left: 80.0, right: 20.0, bottom: 10.0, top: 10.0), + title: Text('Auto sleep timer mode'), + subtitle: + context.width > 360 ? null : _modeWidget(context), + trailing: context.width > 360 + ? _modeWidget(context) + : null), + ListTile( + contentPadding: + EdgeInsets.only(left: 80.0, right: 20), + title: Text('Schedule'), + subtitle: context.width > 360 + ? null + : _scheduleWidget(context), + trailing: context.width > 360 + ? _scheduleWidget(context) + : null), + Divider(height: 2) ], ), ], diff --git a/lib/settings/popup_menu.dart b/lib/settings/popup_menu.dart index 7a37f88..b7925b9 100644 --- a/lib/settings/popup_menu.dart +++ b/lib/settings/popup_menu.dart @@ -88,40 +88,40 @@ class _PopupMenuSettingState extends State { elevation: 0, backgroundColor: context.primaryColor, ), - body: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - color: context.primaryColor, - height: 200, - // color: Colors.red, - child: FlareActor( - 'assets/longtap.flr', - alignment: Alignment.center, - animation: 'longtap', - fit: BoxFit.cover, - )), - Divider(height: 2), - Padding( - padding: EdgeInsets.symmetric(vertical: 10), - ), - Container( - height: 30.0, - padding: EdgeInsets.symmetric(horizontal: 80), - alignment: Alignment.centerLeft, - child: Text('Episode popup menu', - style: Theme.of(context) - .textTheme - .bodyText1 - .copyWith(color: Theme.of(context).accentColor)), - ), - FutureBuilder>( - future: _getEpisodeMenu(), - initialData: [0, 1, 12, 13, 14], - builder: (context, snapshot) { - List menu = snapshot.data; - return ListView( + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + color: context.primaryColor, + height: 200, + // color: Colors.red, + child: FlareActor( + 'assets/longtap.flr', + alignment: Alignment.center, + animation: 'longtap', + fit: BoxFit.cover, + )), + Divider(height: 2), + Padding( + padding: EdgeInsets.symmetric(vertical: 10), + ), + Container( + height: 30.0, + padding: EdgeInsets.symmetric(horizontal: 80), + alignment: Alignment.centerLeft, + child: Text('Episode popup menu', + style: Theme.of(context) + .textTheme + .bodyText1 + .copyWith(color: Theme.of(context).accentColor)), + ), + FutureBuilder>( + future: _getEpisodeMenu(), + initialData: [0, 1, 12, 13, 14], + builder: (context, snapshot) { + List menu = snapshot.data; + return Expanded( + child: ListView( shrinkWrap: true, children: menu.map((int e) { int i = e % 10; @@ -163,7 +163,6 @@ class _PopupMenuSettingState extends State { text: 'Mark Listened', description: 'Mark episode as listened'); break; - case 4: return _popupMenuItem(menu, e, icon: Icon( @@ -178,10 +177,10 @@ class _PopupMenuSettingState extends State { break; } }).toList(), - ); - }), - ], - ), + ), + ); + }), + ], )), ); } diff --git a/lib/settings/theme.dart b/lib/settings/theme.dart index 8e72ba7..f46a35e 100644 --- a/lib/settings/theme.dart +++ b/lib/settings/theme.dart @@ -115,18 +115,19 @@ class ThemeSetting extends StatelessWidget { title: Text('Theme'), subtitle: Text('System default'), ), - ListTile( - contentPadding: - EdgeInsets.only(left: 80.0, right: 20, bottom: 10), - // leading: Icon(Icons.colorize), - title: Text( - 'Real Dark', - ), - subtitle: - Text('Turn on if you think the night is not dark enough'), - trailing: Selector( - selector: (_, setting) => setting.realDark, - builder: (_, data, __) => Transform.scale( + Selector( + selector: (_, setting) => setting.realDark, + builder: (_, data, __) => ListTile( + onTap: () => settings.setRealDark = !data, + contentPadding: const EdgeInsets.only( + left: 80.0, right: 20, bottom: 10, top: 10), + // leading: Icon(Icons.colorize), + title: Text( + 'Real Dark', + ), + subtitle: Text( + 'Turn on if you think the night is not dark enough'), + trailing: Transform.scale( scale: 0.9, child: Switch( value: data, diff --git a/lib/state/audiostate.dart b/lib/state/audiostate.dart index 93468d2..67b9efa 100644 --- a/lib/state/audiostate.dart +++ b/lib/state/audiostate.dart @@ -128,7 +128,15 @@ class AudioPlayerNotifier extends ChangeNotifier { DBHelper dbHelper = DBHelper(); KeyValueStorage positionStorage = KeyValueStorage(audioPositionKey); KeyValueStorage autoPlayStorage = KeyValueStorage(autoPlayKey); - KeyValueStorage autoAddStorage = KeyValueStorage(autoAddKey); + KeyValueStorage autoSleepTimerStorage = KeyValueStorage(autoSleepTimerKey); + KeyValueStorage defaultSleepTimerStorage = + KeyValueStorage(defaultSleepTimerKey); + KeyValueStorage autoSleepTimerModeStorage = + KeyValueStorage(autoSleepTimerModeKey); + KeyValueStorage autoSleepTimerStartStorage = + KeyValueStorage(autoSleepTimerStartKey); + KeyValueStorage autoSleepTimerEndStorage = + KeyValueStorage(autoSleepTimerEndKey); EpisodeBrief _episode; Playlist _queue = Playlist(); @@ -148,12 +156,13 @@ class AudioPlayerNotifier extends ChangeNotifier { bool _startSleepTimer = false; double _switchValue = 0; SleepTimerMode _sleepTimerMode = SleepTimerMode.undefined; + //Auto stop at the end of episode when you start play at scheduled time. + bool _autoSleepTimer; + //Default sleep timer time. ShareStatus _shareStatus = ShareStatus.undefined; String _shareFile = ''; //set autoplay episode in playlist - bool _autoPlay = true; - //TODO Set auto add episodes to playlist - //bool _autoAdd = false; + bool _autoPlay; DateTime _current; int _currentPosition; double _currentSpeed = 1; @@ -177,23 +186,18 @@ class AudioPlayerNotifier extends ChangeNotifier { SleepTimerMode get sleepTimerMode => _sleepTimerMode; ShareStatus get shareStatus => _shareStatus; String get shareFile => _shareFile; - bool get autoPlay => _autoPlay; + //bool get autoPlay => _autoPlay; int get timeLeft => _timeLeft; double get switchValue => _switchValue; double get currentSpeed => _currentSpeed; bool get episodeState => _episodeState; + bool get autoSleepTimer => _autoSleepTimer; set setSwitchValue(double value) { _switchValue = value; notifyListeners(); } - set autoPlaySwitch(bool boo) { - _autoPlay = boo; - notifyListeners(); - _setAutoPlay(); - } - set setShareStatue(ShareStatus status) { _shareStatus = status; notifyListeners(); @@ -206,11 +210,12 @@ class AudioPlayerNotifier extends ChangeNotifier { Future _getAutoPlay() async { int i = await autoPlayStorage.getInt(); - _autoPlay = i == 0 ? true : false; + _autoPlay = i == 0; } - Future _setAutoPlay() async { - await autoPlayStorage.saveInt(_autoPlay ? 1 : 0); + Future _getAutoSleepTimer() async { + int i = await autoSleepTimerStorage.getInt(); + _autoSleepTimer = i == 1; } set setSleepTimerMode(SleepTimerMode timer) { @@ -222,6 +227,7 @@ class AudioPlayerNotifier extends ChangeNotifier { void addListener(VoidCallback listener) async { super.addListener(listener); _queueUpdate = false; + await _getAutoSleepTimer(); await AudioService.connect(); bool running = AudioService.running; if (running) {} @@ -285,7 +291,6 @@ class AudioPlayerNotifier extends ChangeNotifier { if (!AudioService.connected) { await AudioService.connect(); } - await AudioService.start( backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint, androidNotificationChannelName: 'Tsacdop', @@ -294,6 +299,7 @@ class AudioPlayerNotifier extends ChangeNotifier { enableQueue: true, androidStopOnRemoveTask: true, androidStopForegroundOnPause: true); + await _getAutoPlay(); if (_autoPlay) { await Future.forEach(_queue.playlist, (episode) async { await AudioService.addQueueItem(episode.toMediaItem()); @@ -301,6 +307,23 @@ class AudioPlayerNotifier extends ChangeNotifier { } else { await AudioService.addQueueItem(_queue.playlist.first.toMediaItem()); } + await _getAutoSleepTimer(); + if (_autoSleepTimer) { + int startTime = + await autoSleepTimerStartStorage.getInt(defaultValue: 1380); + int endTime = await autoSleepTimerEndStorage.getInt(defaultValue: 360); + int currentTime = DateTime.now().hour * 60 + DateTime.now().minute; + if ((startTime > endTime && + (currentTime > startTime || currentTime > endTime)) || + ((startTime < endTime) && + (currentTime > startTime && currentTime < endTime))) { + int mode = await autoSleepTimerModeStorage.getInt(); + _sleepTimerMode = SleepTimerMode.values[mode]; + int defaultTimer = + await defaultSleepTimerStorage.getInt(defaultValue: 30); + sleepTimer(defaultTimer); + } + } _playerRunning = true; await AudioService.play(); diff --git a/lib/state/download_state.dart b/lib/state/download_state.dart index 374e25d..4fe915c 100644 --- a/lib/state/download_state.dart +++ b/lib/state/download_state.dart @@ -287,7 +287,7 @@ class DownloadState extends ChangeNotifier { FlutterDownloader.remove(taskId: task.taskId); var dbHelper = DBHelper(); _episodeTasks.insert(index, EpisodeTask(episode, newTaskId)); - await dbHelper.saveDownloaded(newTaskId, episode.enclosureUrl); + await dbHelper.saveDownloaded(episode.enclosureUrl, newTaskId); } Future retryTask(EpisodeBrief episode) async { @@ -298,7 +298,7 @@ class DownloadState extends ChangeNotifier { _removeTask(episode); var dbHelper = DBHelper(); _episodeTasks.insert(index, EpisodeTask(episode, newTaskId)); - await dbHelper.saveDownloaded(newTaskId, episode.enclosureUrl); + await dbHelper.saveDownloaded(episode.enclosureUrl, newTaskId); } Future removeTask(EpisodeBrief episode) async { @@ -322,6 +322,7 @@ class DownloadState extends ChangeNotifier { _removeTask(EpisodeBrief episode) { _episodeTasks.removeWhere((element) => element.episode == episode); + notifyListeners(); } _autoDelete() async { diff --git a/lib/state/settingstate.dart b/lib/state/settingstate.dart index 487ea68..db096cd 100644 --- a/lib/state/settingstate.dart +++ b/lib/state/settingstate.dart @@ -78,12 +78,22 @@ ThemeData lightTheme = ThemeData( class SettingState extends ChangeNotifier { KeyValueStorage themeStorage = KeyValueStorage(themesKey); KeyValueStorage accentStorage = KeyValueStorage(accentsKey); - KeyValueStorage autoupdateStorage = KeyValueStorage(autoAddKey); + KeyValueStorage autoupdateStorage = KeyValueStorage(autoUpdateKey); KeyValueStorage intervalStorage = KeyValueStorage(updateIntervalKey); KeyValueStorage downloadUsingDataStorage = KeyValueStorage(downloadUsingDataKey); KeyValueStorage introStorage = KeyValueStorage(introKey); KeyValueStorage realDarkStorage = KeyValueStorage(realDarkKey); + KeyValueStorage autoPlayStorage = KeyValueStorage(autoPlayKey); + KeyValueStorage defaultSleepTimerStorage = + KeyValueStorage(defaultSleepTimerKey); + KeyValueStorage autoSleepTimerStorage = KeyValueStorage(autoSleepTimerKey); + KeyValueStorage autoSleepTimerModeStorage = + KeyValueStorage(autoSleepTimerModeKey); + KeyValueStorage autoSleepTimerStartStorage = + KeyValueStorage(autoSleepTimerStartKey); + KeyValueStorage autoSleepTimerEndStorage = + KeyValueStorage(autoSleepTimerEndKey); Future initData() async { await _getTheme(); @@ -164,16 +174,65 @@ class SettingState extends ChangeNotifier { notifyListeners(); } + int _defaultSleepTimer; + int get defaultSleepTimer => _defaultSleepTimer; + set setDefaultSleepTimer(int i) { + _defaultSleepTimer = i; + _setDefaultSleepTimer(); + notifyListeners(); + } + + bool _autoPlay; + bool get autoPlay => _autoPlay; + set setAutoPlay(bool boo) { + _autoPlay = boo; + notifyListeners(); + _saveAutoPlay(); + } + + bool _autoSleepTimer; + bool get autoSleepTimer => _autoSleepTimer; + set setAutoSleepTimer(bool boo) { + _autoSleepTimer = boo; + notifyListeners(); + _saveAutoSleepTimer(); + } + + int _autoSleepTimerMode; + int get autoSleepTimerMode => _autoSleepTimerMode; + set setAutoSleepTimerMode(int mode) { + _autoSleepTimerMode = mode; + notifyListeners(); + _saveAutoSleepTimerMode(); + } + + int _autoSleepTimerStart; + int get autoSleepTimerStart => _autoSleepTimerStart; + set setAutoSleepTimerStart(int start) { + _autoSleepTimerStart = start; + notifyListeners(); + _saveAutoSleepTimerStart(); + } + + int _autoSleepTimerEnd; + int get autoSleepTimerEnd => _autoSleepTimerEnd; + set setAutoSleepTimerEnd(int end) { + _autoSleepTimerEnd = end; + notifyListeners(); + _saveAutoSleepTimerEnd(); + } + @override void addListener(VoidCallback listener) { super.addListener(listener); _getAutoUpdate(); _getDownloadUsingData(); + _getSleepTimerData(); _getUpdateInterval().then((value) async { if (_initUpdateTag == 0) setWorkManager(24); //Restart worker if anythin changed in worker callback. - //varsion 2 add auto download new episodes + //varsion 2 add auto download new episodes else if (_autoUpdate && _initialShowIntor == 1) { await cancelWork(); setWorkManager(_initUpdateTag); @@ -250,4 +309,42 @@ class SettingState extends ChangeNotifier { Future _setRealDark() async { await realDarkStorage.saveInt(_realDark ? 1 : 0); } + + Future _getSleepTimerData() async { + _defaultSleepTimer = + await defaultSleepTimerStorage.getInt(defaultValue: 30); + int i = await autoSleepTimerStorage.getInt(); + _autoSleepTimer = i == 1; + _autoSleepTimerStart = + await autoSleepTimerStartStorage.getInt(defaultValue: 1380); + _autoSleepTimerEnd = + await autoSleepTimerEndStorage.getInt(defaultValue: 360); + int a = await autoPlayStorage.getInt(); + _autoPlay = a == 0; + _autoSleepTimerMode = await autoSleepTimerModeStorage.getInt(); + } + + Future _saveAutoPlay() async { + await autoPlayStorage.saveInt(_autoPlay ? 0 : 1); + } + + Future _setDefaultSleepTimer() async { + await defaultSleepTimerStorage.saveInt(_defaultSleepTimer); + } + + Future _saveAutoSleepTimer() async { + await autoSleepTimerStorage.saveInt(_autoSleepTimer ? 1 : 0); + } + + Future _saveAutoSleepTimerMode() async { + await autoSleepTimerModeStorage.saveInt(_autoSleepTimerMode); + } + + Future _saveAutoSleepTimerStart() async { + await autoSleepTimerStartStorage.saveInt(_autoSleepTimerStart); + } + + Future _saveAutoSleepTimerEnd() async { + await autoSleepTimerEndStorage.saveInt(_autoSleepTimerEnd); + } } diff --git a/pubspec.yaml b/pubspec.yaml index 505e936..da2bdc7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: auto_animated: ^2.1.0 feature_discovery: ^0.10.0 flutter_isolate: ^1.0.0+14 + flutter_time_picker_spinner: ^1.0.6+1 just_audio: git: url: https://github.com/stonega/just_audio.git