Add animation for episodegrid

Fix bug when rss feed do not have image
Fix bug when use stop at end of episode
This commit is contained in:
stonegate 2020-05-06 20:08:41 +08:00
parent 69d2568513
commit 006598cb74
10 changed files with 119 additions and 60 deletions

View File

@ -47,7 +47,7 @@ android {
applicationId "com.stonegate.tsacdop" applicationId "com.stonegate.tsacdop"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 28 targetSdkVersion 28
versionCode 8 versionCode 9
versionName flutterVersionName versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@ -224,7 +224,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
backgroundAudioPosition / 1000, seekSliderValue); backgroundAudioPosition / 1000, seekSliderValue);
await dbHelper.saveHistory(history); await dbHelper.saveHistory(history);
AudioService.addQueueItemAt(episodeNew.toMediaItem(), 0); AudioService.addQueueItemAt(episodeNew.toMediaItem(), 0);
//if (startPosition > 0) AudioService.seekTo(startPosition);
_queue.playlist _queue.playlist
.removeWhere((item) => item.enclosureUrl == episode.enclosureUrl); .removeWhere((item) => item.enclosureUrl == episode.enclosureUrl);
_queue.playlist.insert(0, episodeNew); _queue.playlist.insert(0, episodeNew);
@ -294,9 +293,10 @@ class AudioPlayerNotifier extends ChangeNotifier {
print(event.length); print(event.length);
if (event.length == _queue.playlist.length - 1 && if (event.length == _queue.playlist.length - 1 &&
_audioState == BasicPlaybackState.skippingToNext) { _audioState == BasicPlaybackState.skippingToNext) {
if (event.length == 0 || _stopOnComplete == true) { if (event.length == 0 || _stopOnComplete) {
_queue.delFromPlaylist(_episode); _queue.delFromPlaylist(_episode);
_lastPostion = 0; _lastPostion = 0;
notifyListeners();
positionStorage.saveInt(_lastPostion); positionStorage.saveInt(_lastPostion);
final PlayHistory history = PlayHistory( final PlayHistory history = PlayHistory(
_episode.title, _episode.title,
@ -305,6 +305,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
seekSliderValue); seekSliderValue);
dbHelper.saveHistory(history); dbHelper.saveHistory(history);
} else if (event.first.id != _episode.mediaId) { } else if (event.first.id != _episode.mediaId) {
_lastPostion = 0;
notifyListeners();
positionStorage.saveInt(_lastPostion);
_queue.delFromPlaylist(_episode); _queue.delFromPlaylist(_episode);
final PlayHistory history = PlayHistory( final PlayHistory history = PlayHistory(
_episode.title, _episode.title,
@ -359,7 +362,8 @@ class AudioPlayerNotifier extends ChangeNotifier {
} else } else
_seekSliderValue = 0; _seekSliderValue = 0;
if (_backgroundAudioPosition > 0) { if (_backgroundAudioPosition > 0 &&
_backgroundAudioPosition < _backgroundAudioDuration) {
_lastPostion = _backgroundAudioPosition; _lastPostion = _backgroundAudioPosition;
positionStorage.saveInt(_lastPostion); positionStorage.saveInt(_lastPostion);
} }
@ -592,7 +596,6 @@ class AudioPlayerTask extends BackgroundAudioTask {
Future<void> onStart() async { Future<void> onStart() async {
_stopAtEnd = false; _stopAtEnd = false;
print(cacheMax);
var playerStateSubscription = _audioPlayer.playbackStateStream var playerStateSubscription = _audioPlayer.playbackStateStream
.where((state) => state == AudioPlaybackState.completed) .where((state) => state == AudioPlaybackState.completed)
.listen((state) { .listen((state) {
@ -647,6 +650,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
await AudioServiceBackground.setQueue(_queue); await AudioServiceBackground.setQueue(_queue);
// } // }
if (_queue.length == 0 || _stopAtEnd) { if (_queue.length == 0 || _stopAtEnd) {
// await Future.delayed(Duration(milliseconds: 300));
_skipState = null; _skipState = null;
onStop(); onStop();
} else { } else {
@ -742,8 +746,9 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override @override
void onStop() async { void onStop() async {
await _audioPlayer.stop(); await _audioPlayer.stop();
await _audioPlayer.dispose();
_setState(state: BasicPlaybackState.stopped); _setState(state: BasicPlaybackState.stopped);
await Future.delayed(Duration(milliseconds: 500)); await Future.delayed(Duration(milliseconds: 300));
_completer?.complete(); _completer?.complete();
} }

View File

@ -21,8 +21,11 @@ class SubscribeItem {
String title; String title;
SubscribeState subscribeState; SubscribeState subscribeState;
String id; String id;
String imgUrl;
SubscribeItem(this.url, this.title, SubscribeItem(this.url, this.title,
{this.subscribeState = SubscribeState.none, this.id = ''}); {this.subscribeState = SubscribeState.none,
this.id = '',
this.imgUrl = ''});
} }
class SubscribeWorker extends ChangeNotifier { class SubscribeWorker extends ChangeNotifier {
@ -56,7 +59,7 @@ class SubscribeWorker extends ChangeNotifier {
receivePort.distinct().listen((message) { receivePort.distinct().listen((message) {
if (message is SendPort) { if (message is SendPort) {
subSendPort = message; subSendPort = message;
subSendPort.send([_subscribeItem.url, _subscribeItem.title]); subSendPort.send([_subscribeItem.url, _subscribeItem.title, _subscribeItem.imgUrl]);
} else if (message is List) { } else if (message is List) {
_setCurrentSubscribeItem(SubscribeItem(message[1], message[0], _setCurrentSubscribeItem(SubscribeItem(message[1], message[0],
subscribeState: SubscribeState.values[message[2]], subscribeState: SubscribeState.values[message[2]],
@ -76,7 +79,7 @@ class SubscribeWorker extends ChangeNotifier {
_created = true; _created = true;
listen(); listen();
} else } else
subSendPort.send([_subscribeItem.url, _subscribeItem.title]); subSendPort.send([_subscribeItem.url, _subscribeItem.title, _subscribeItem.imgUrl]);
} }
void dispose() { void dispose() {
@ -109,8 +112,9 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
receiveTimeout: 20000, receiveTimeout: 20000,
); );
print(rss); print(rss);
Response response = await Dio(options).get(rss); try {
if (response.statusCode == 200) { Response response = await Dio(options).get(rss);
var p = RssFeed.parse(response.data); var p = RssFeed.parse(response.data);
var dir = await getApplicationDocumentsDirectory(); var dir = await getApplicationDocumentsDirectory();
@ -122,11 +126,41 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
bool checkUrl = await dbHelper.checkPodcast(realUrl); bool checkUrl = await dbHelper.checkPodcast(realUrl);
if (checkUrl) { if (checkUrl) {
Response<List<int>> imageResponse = await Dio().get<List<int>>( img.Image thumbnail;
p.itunes.image.href, try {
options: Options(responseType: ResponseType.bytes)); Response<List<int>> imageResponse = await Dio().get<List<int>>(
img.Image image = img.decodeImage(imageResponse.data); p.itunes.image.href,
img.Image thumbnail = img.copyResize(image, width: 300); 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<List<int>> imageResponse = await Dio().get<List<int>>(
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<List<int>> imageResponse = await Dio().get<List<int>>(
"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(); String uuid = Uuid().v4();
File("${dir.path}/$uuid.png") File("${dir.path}/$uuid.png")
..writeAsBytesSync(img.encodePng(thumbnail)); ..writeAsBytesSync(img.encodePng(thumbnail));
@ -173,7 +207,8 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
} else } else
sendPort.send("done"); sendPort.send("done");
} }
} else { } on DioError catch (e) {
print(e);
sendPort.send([item.title, item.url, 6]); sendPort.send([item.title, item.url, 6]);
await Future.delayed(Duration(seconds: 2)); await Future.delayed(Duration(seconds: 2));
sendPort.send([item.title, item.url, 4]); sendPort.send([item.title, item.url, 4]);
@ -187,7 +222,7 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
subReceivePort.distinct().listen((message) { subReceivePort.distinct().listen((message) {
if (message is List<String>) { if (message is List<String>) {
items.add(SubscribeItem(message[0], message[1])); items.add(SubscribeItem(message[0], message[1], imgUrl: message[2]));
if (!_running) { if (!_running) {
_subscribe(items.first); _subscribe(items.first);
_running = true; _running = true;

View File

@ -4,7 +4,7 @@ import 'package:tsacdop/util/custompaint.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
const String version = '0.2.1'; const String version = '0.2.2';
class AboutApp extends StatelessWidget { class AboutApp extends StatelessWidget {
_launchUrl(String url) async { _launchUrl(String url) async {

View File

@ -259,7 +259,7 @@ class _SearchResultState extends State<SearchResult>
var subscribeWorker = Provider.of<SubscribeWorker>(context, listen: false); var subscribeWorker = Provider.of<SubscribeWorker>(context, listen: false);
savePodcast(OnlinePodcast podcast) async { savePodcast(OnlinePodcast podcast) async {
SubscribeItem item = SubscribeItem(podcast.rss, podcast.title); SubscribeItem item = SubscribeItem(podcast.rss, podcast.title, imgUrl: podcast.image);
subscribeWorker.setSubscribeItem(item); subscribeWorker.setSubscribeItem(item);
} }

View File

@ -90,7 +90,6 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
children: <Widget>[ children: <Widget>[
Column( Column(
children: <Widget>[ children: <Widget>[
Import(),
Expanded( Expanded(
child: NestedScrollView( child: NestedScrollView(
innerScrollPositionKeyBuilder: () { innerScrollPositionKeyBuilder: () {
@ -101,34 +100,40 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
(BuildContext context, bool innerBoxScrolled) { (BuildContext context, bool innerBoxScrolled) {
return <Widget>[ return <Widget>[
SliverToBoxAdapter( SliverToBoxAdapter(
child: SizedBox( child: Column(
height: 50.0, children: <Widget>[
child: Row( SizedBox(
mainAxisAlignment: height: 50.0,
MainAxisAlignment.spaceBetween, child: Row(
children: <Widget>[ mainAxisAlignment:
IconButton( MainAxisAlignment.spaceBetween,
tooltip: 'Add', children: <Widget>[
icon: const Icon( IconButton(
Icons.add_circle_outline), tooltip: 'Add',
onPressed: () async { icon: const Icon(
await showSearch<int>( Icons.add_circle_outline),
context: context, onPressed: () async {
delegate: _delegate, await showSearch<int>(
); 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 == Import(),
Brightness.light ],
? AssetImage('assets/text.png')
: AssetImage(
'assets/text_light.png'),
height: 30,
),
PopupMenu(),
],
),
), ),
), ),
SliverList( SliverList(

View File

@ -16,7 +16,6 @@ final SettingState themeSetting = SettingState();
Future main() async { Future main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await themeSetting.initData(); await themeSetting.initData();
runApp( runApp(
MultiProvider( MultiProvider(
providers: [ providers: [

View File

@ -69,4 +69,5 @@ List<Libries> plugins = [
Libries('connectivity', bsd, 'https://pub.dev/packages/connectivity'), Libries('connectivity', bsd, 'https://pub.dev/packages/connectivity'),
Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart'), Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart'),
Libries('flutter_isolate', mit, 'https://pub.dev/packages/flutter_isolate'), Libries('flutter_isolate', mit, 'https://pub.dev/packages/flutter_isolate'),
Libries('auto_animated', mit, 'https://pub.dev/packages/auto_animated'),
]; ];

View File

@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:auto_animated/auto_animated.dart';
import 'open_container.dart'; import 'open_container.dart';
import 'package:tsacdop/class/audiostate.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( return SliverPadding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 10.0, bottom: 5.0, left: 15.0, right: 15.0), 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( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: layout == Layout.three ? 1 : 1.5, childAspectRatio: layout == Layout.three ? 1 : 1.5,
crossAxisCount: layout == Layout.three ? 3 : 2, crossAxisCount: layout == Layout.three ? 3 : 2,
mainAxisSpacing: 6.0, mainAxisSpacing: 6.0,
crossAxisSpacing: 6.0, crossAxisSpacing: 6.0,
), ),
delegate: SliverChildBuilderDelegate( itemBuilder: (context, index, animation) {
(BuildContext context, int index) { Color _c = (Theme.of(context).brightness == Brightness.light)
Color _c = (Theme.of(context).brightness == Brightness.light) ? episodes[index].primaryColor.colorizedark()
? episodes[index].primaryColor.colorizedark() : episodes[index].primaryColor.colorizeLight();
: episodes[index].primaryColor.colorizeLight(); return FadeTransition(
return Selector<AudioPlayerNotifier, opacity: Tween<double>(
begin: 0,
end: 1,
).animate(animation),
child: Selector<AudioPlayerNotifier,
Tuple2<EpisodeBrief, List<String>>>( Tuple2<EpisodeBrief, List<String>>>(
selector: (_, audio) => Tuple2(audio?.episode, selector: (_, audio) => Tuple2(audio?.episode,
audio.queue.playlist.map((e) => e.enclosureUrl).toList()), audio.queue.playlist.map((e) => e.enclosureUrl).toList()),
@ -441,10 +455,9 @@ class EpisodeGrid extends StatelessWidget {
); );
}), }),
), ),
); ),
}, );
childCount: episodes.length, },
),
), ),
); );
} }

View File

@ -53,6 +53,7 @@ dev_dependencies:
flare_flutter: ^2.0.3 flare_flutter: ^2.0.3
rxdart: ^0.24.0 rxdart: ^0.24.0
flutter_isolate: ^1.0.0+11 flutter_isolate: ^1.0.0+11
auto_animated: ^2.0.2
flutter: flutter: