From 006598cb7463454450a9d5139302a48bed512b5e Mon Sep 17 00:00:00 2001 From: stonegate Date: Wed, 6 May 2020 20:08:41 +0800 Subject: [PATCH] Add animation for episodegrid Fix bug when rss feed do not have image Fix bug when use stop at end of episode --- android/app/build.gradle | 2 +- lib/class/audiostate.dart | 15 +++++--- lib/class/subscribe_podcast.dart | 59 +++++++++++++++++++++++------- lib/home/appbar/about.dart | 2 +- lib/home/appbar/addpodcast.dart | 2 +- lib/home/home.dart | 61 +++++++++++++++++--------------- lib/main.dart | 1 - lib/settings/licenses.dart | 1 + lib/util/episodegrid.dart | 35 ++++++++++++------ pubspec.yaml | 1 + 10 files changed, 119 insertions(+), 60 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index cb8bc4d..a2198a4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -47,7 +47,7 @@ android { applicationId "com.stonegate.tsacdop" minSdkVersion 19 targetSdkVersion 28 - versionCode 8 + versionCode 9 versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/lib/class/audiostate.dart b/lib/class/audiostate.dart index 3a00601..15b699d 100644 --- a/lib/class/audiostate.dart +++ b/lib/class/audiostate.dart @@ -224,7 +224,6 @@ class AudioPlayerNotifier extends ChangeNotifier { backgroundAudioPosition / 1000, seekSliderValue); await dbHelper.saveHistory(history); AudioService.addQueueItemAt(episodeNew.toMediaItem(), 0); - //if (startPosition > 0) AudioService.seekTo(startPosition); _queue.playlist .removeWhere((item) => item.enclosureUrl == episode.enclosureUrl); _queue.playlist.insert(0, episodeNew); @@ -294,9 +293,10 @@ class AudioPlayerNotifier extends ChangeNotifier { print(event.length); if (event.length == _queue.playlist.length - 1 && _audioState == BasicPlaybackState.skippingToNext) { - if (event.length == 0 || _stopOnComplete == true) { + if (event.length == 0 || _stopOnComplete) { _queue.delFromPlaylist(_episode); _lastPostion = 0; + notifyListeners(); positionStorage.saveInt(_lastPostion); final PlayHistory history = PlayHistory( _episode.title, @@ -305,6 +305,9 @@ class AudioPlayerNotifier extends ChangeNotifier { seekSliderValue); dbHelper.saveHistory(history); } else if (event.first.id != _episode.mediaId) { + _lastPostion = 0; + notifyListeners(); + positionStorage.saveInt(_lastPostion); _queue.delFromPlaylist(_episode); final PlayHistory history = PlayHistory( _episode.title, @@ -359,7 +362,8 @@ class AudioPlayerNotifier extends ChangeNotifier { } else _seekSliderValue = 0; - if (_backgroundAudioPosition > 0) { + if (_backgroundAudioPosition > 0 && + _backgroundAudioPosition < _backgroundAudioDuration) { _lastPostion = _backgroundAudioPosition; positionStorage.saveInt(_lastPostion); } @@ -592,7 +596,6 @@ class AudioPlayerTask extends BackgroundAudioTask { Future onStart() async { _stopAtEnd = false; - print(cacheMax); var playerStateSubscription = _audioPlayer.playbackStateStream .where((state) => state == AudioPlaybackState.completed) .listen((state) { @@ -647,6 +650,7 @@ class AudioPlayerTask extends BackgroundAudioTask { await AudioServiceBackground.setQueue(_queue); // } if (_queue.length == 0 || _stopAtEnd) { + // await Future.delayed(Duration(milliseconds: 300)); _skipState = null; onStop(); } else { @@ -742,8 +746,9 @@ class AudioPlayerTask extends BackgroundAudioTask { @override void onStop() async { await _audioPlayer.stop(); + await _audioPlayer.dispose(); _setState(state: BasicPlaybackState.stopped); - await Future.delayed(Duration(milliseconds: 500)); + await Future.delayed(Duration(milliseconds: 300)); _completer?.complete(); } diff --git a/lib/class/subscribe_podcast.dart b/lib/class/subscribe_podcast.dart index 16fb05b..7a3b076 100644 --- a/lib/class/subscribe_podcast.dart +++ b/lib/class/subscribe_podcast.dart @@ -21,8 +21,11 @@ class SubscribeItem { String title; SubscribeState subscribeState; String id; + String imgUrl; SubscribeItem(this.url, this.title, - {this.subscribeState = SubscribeState.none, this.id = ''}); + {this.subscribeState = SubscribeState.none, + this.id = '', + this.imgUrl = ''}); } class SubscribeWorker extends ChangeNotifier { @@ -56,7 +59,7 @@ class SubscribeWorker extends ChangeNotifier { receivePort.distinct().listen((message) { if (message is SendPort) { subSendPort = message; - subSendPort.send([_subscribeItem.url, _subscribeItem.title]); + subSendPort.send([_subscribeItem.url, _subscribeItem.title, _subscribeItem.imgUrl]); } else if (message is List) { _setCurrentSubscribeItem(SubscribeItem(message[1], message[0], subscribeState: SubscribeState.values[message[2]], @@ -76,7 +79,7 @@ class SubscribeWorker extends ChangeNotifier { _created = true; listen(); } else - subSendPort.send([_subscribeItem.url, _subscribeItem.title]); + subSendPort.send([_subscribeItem.url, _subscribeItem.title, _subscribeItem.imgUrl]); } void dispose() { @@ -109,8 +112,9 @@ Future subIsolateEntryPoint(SendPort sendPort) async { receiveTimeout: 20000, ); print(rss); - Response response = await Dio(options).get(rss); - if (response.statusCode == 200) { + try { + Response response = await Dio(options).get(rss); + var p = RssFeed.parse(response.data); var dir = await getApplicationDocumentsDirectory(); @@ -122,11 +126,41 @@ Future subIsolateEntryPoint(SendPort sendPort) async { bool checkUrl = await dbHelper.checkPodcast(realUrl); if (checkUrl) { - Response> imageResponse = await Dio().get>( - p.itunes.image.href, - options: Options(responseType: ResponseType.bytes)); - img.Image image = img.decodeImage(imageResponse.data); - img.Image thumbnail = img.copyResize(image, width: 300); + img.Image thumbnail; + try { + Response> imageResponse = await Dio().get>( + p.itunes.image.href, + options: Options(responseType: ResponseType.bytes)); + img.Image image = img.decodeImage(imageResponse.data); + thumbnail = img.copyResize(image, width: 300); + } on DioError catch (e) { + print(e); + try { + Response> imageResponse = await Dio().get>( + item.imgUrl, + options: Options(responseType: ResponseType.bytes)); + img.Image image = img.decodeImage(imageResponse.data); + thumbnail = img.copyResize(image, width: 300); + } on DioError catch (e) { + print(e); + try { + Response> imageResponse = await Dio().get>( + "https://ui-avatars.com/api/?size=300&background=4D91BE&color=fff&name=${item.title}&length=2&bold=true", + options: Options(responseType: ResponseType.bytes)); + thumbnail = img.decodeImage(imageResponse.data); + } on DioError catch (e) { + print(e); + sendPort.send([item.title, item.url, 6]); + await Future.delayed(Duration(seconds: 2)); + sendPort.send([item.title, item.url, 4]); + items.removeWhere((element) => element.url == item.url); + if (items.length > 0) { + await _subscribe(items.first); + } else + sendPort.send("done"); + } + } + } String uuid = Uuid().v4(); File("${dir.path}/$uuid.png") ..writeAsBytesSync(img.encodePng(thumbnail)); @@ -173,7 +207,8 @@ Future subIsolateEntryPoint(SendPort sendPort) async { } else sendPort.send("done"); } - } else { + } on DioError catch (e) { + print(e); sendPort.send([item.title, item.url, 6]); await Future.delayed(Duration(seconds: 2)); sendPort.send([item.title, item.url, 4]); @@ -187,7 +222,7 @@ Future subIsolateEntryPoint(SendPort sendPort) async { subReceivePort.distinct().listen((message) { if (message is List) { - items.add(SubscribeItem(message[0], message[1])); + items.add(SubscribeItem(message[0], message[1], imgUrl: message[2])); if (!_running) { _subscribe(items.first); _running = true; diff --git a/lib/home/appbar/about.dart b/lib/home/appbar/about.dart index 96f93dc..0df249e 100644 --- a/lib/home/appbar/about.dart +++ b/lib/home/appbar/about.dart @@ -4,7 +4,7 @@ import 'package:tsacdop/util/custompaint.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:line_icons/line_icons.dart'; -const String version = '0.2.1'; +const String version = '0.2.2'; class AboutApp extends StatelessWidget { _launchUrl(String url) async { diff --git a/lib/home/appbar/addpodcast.dart b/lib/home/appbar/addpodcast.dart index 40c996e..0d9b71a 100644 --- a/lib/home/appbar/addpodcast.dart +++ b/lib/home/appbar/addpodcast.dart @@ -259,7 +259,7 @@ class _SearchResultState extends State var subscribeWorker = Provider.of(context, listen: false); savePodcast(OnlinePodcast podcast) async { - SubscribeItem item = SubscribeItem(podcast.rss, podcast.title); + SubscribeItem item = SubscribeItem(podcast.rss, podcast.title, imgUrl: podcast.image); subscribeWorker.setSubscribeItem(item); } diff --git a/lib/home/home.dart b/lib/home/home.dart index d6de2e2..5917f9b 100644 --- a/lib/home/home.dart +++ b/lib/home/home.dart @@ -90,7 +90,6 @@ class _HomeState extends State with SingleTickerProviderStateMixin { children: [ Column( children: [ - Import(), Expanded( child: NestedScrollView( innerScrollPositionKeyBuilder: () { @@ -101,34 +100,40 @@ class _HomeState extends State with SingleTickerProviderStateMixin { (BuildContext context, bool innerBoxScrolled) { return [ SliverToBoxAdapter( - child: SizedBox( - height: 50.0, - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - IconButton( - tooltip: 'Add', - icon: const Icon( - Icons.add_circle_outline), - onPressed: () async { - await showSearch( - context: context, - delegate: _delegate, - ); - }, + child: Column( + children: [ + SizedBox( + height: 50.0, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + IconButton( + tooltip: 'Add', + icon: const Icon( + Icons.add_circle_outline), + onPressed: () async { + await showSearch( + context: context, + delegate: _delegate, + ); + }, + ), + Image( + image: Theme.of(context) + .brightness == + Brightness.light + ? AssetImage('assets/text.png') + : AssetImage( + 'assets/text_light.png'), + height: 30, + ), + PopupMenu(), + ], ), - Image( - image: Theme.of(context).brightness == - Brightness.light - ? AssetImage('assets/text.png') - : AssetImage( - 'assets/text_light.png'), - height: 30, - ), - PopupMenu(), - ], - ), + ), + Import(), + ], ), ), SliverList( diff --git a/lib/main.dart b/lib/main.dart index e275ae0..1277c04 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,7 +16,6 @@ final SettingState themeSetting = SettingState(); Future main() async { WidgetsFlutterBinding.ensureInitialized(); await themeSetting.initData(); - runApp( MultiProvider( providers: [ diff --git a/lib/settings/licenses.dart b/lib/settings/licenses.dart index cf333c6..ca23a36 100644 --- a/lib/settings/licenses.dart +++ b/lib/settings/licenses.dart @@ -69,4 +69,5 @@ List plugins = [ Libries('connectivity', bsd, 'https://pub.dev/packages/connectivity'), Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart'), Libries('flutter_isolate', mit, 'https://pub.dev/packages/flutter_isolate'), + Libries('auto_animated', mit, 'https://pub.dev/packages/auto_animated'), ]; diff --git a/lib/util/episodegrid.dart b/lib/util/episodegrid.dart index 420d574..822eaf1 100644 --- a/lib/util/episodegrid.dart +++ b/lib/util/episodegrid.dart @@ -7,6 +7,7 @@ import 'package:provider/provider.dart'; import 'package:tuple/tuple.dart'; import 'package:line_icons/line_icons.dart'; import 'package:fluttertoast/fluttertoast.dart'; +import 'package:auto_animated/auto_animated.dart'; import 'open_container.dart'; import 'package:tsacdop/class/audiostate.dart'; @@ -122,22 +123,35 @@ class EpisodeGrid extends StatelessWidget { }); } + final options = LiveOptions( + delay: Duration(milliseconds: 0), + showItemInterval: Duration(milliseconds: 100), + showItemDuration: Duration(milliseconds: 100), + ); + final scrollController = ScrollController(); return SliverPadding( padding: const EdgeInsets.only( top: 10.0, bottom: 5.0, left: 15.0, right: 15.0), - sliver: SliverGrid( + sliver: LiveSliverGrid.options( + controller: scrollController, + options: options, + itemCount: episodes.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( childAspectRatio: layout == Layout.three ? 1 : 1.5, crossAxisCount: layout == Layout.three ? 3 : 2, mainAxisSpacing: 6.0, crossAxisSpacing: 6.0, ), - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - Color _c = (Theme.of(context).brightness == Brightness.light) - ? episodes[index].primaryColor.colorizedark() - : episodes[index].primaryColor.colorizeLight(); - return Selector( + begin: 0, + end: 1, + ).animate(animation), + child: Selector>>( selector: (_, audio) => Tuple2(audio?.episode, audio.queue.playlist.map((e) => e.enclosureUrl).toList()), @@ -441,10 +455,9 @@ class EpisodeGrid extends StatelessWidget { ); }), ), - ); - }, - childCount: episodes.length, - ), + ), + ); + }, ), ); } diff --git a/pubspec.yaml b/pubspec.yaml index 8a66de5..2dee292 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,7 @@ dev_dependencies: flare_flutter: ^2.0.3 rxdart: ^0.24.0 flutter_isolate: ^1.0.0+11 + auto_animated: ^2.0.2 flutter: