diff --git a/README.md b/README.md index d08a6b9..cd8aced 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -


@@ -6,11 +5,13 @@

![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 + Enjoy podcasts with Tsacdop. Tsacdop is a podcast player developed with flutter, a clean, simply beautiful and friendly app, only support Android right now. @@ -20,6 +21,7 @@ Credit to flutter team and all involved plugins, especially [webfeed](https://g The podcasts search engine is powered by [ListenNotes](https://listennotes.com). ## Features + * Podcasts group management * Playlist support * Sleep timer / Speed setting @@ -33,9 +35,10 @@ The podcasts search engine is powered by [ListenNotes](https://listennotes.com). More to come... ## Preview -HomePage | Group | Podcast | Episode |DarkMode --------|--------|--------|------| ---- -||||| + +| HomePage | Group | Podcast | Episode | DarkMode | +|------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------| +| | | | | | ## License @@ -46,11 +49,11 @@ 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"}; +``llkk +final environment = {"apiKey":"APIKEY", "shareKey":"SHAREKEY"}; ``` -You can get own api key on [RapidApi](https://rapidapi.com/listennotes/api/listennotes), basic plan is free to all, and replace "APIKEY" with it. +You can get own api key on [ListenNotes](https://www.listennotes.com/api/), basic plan is free to all, and replace "APIKEY" with it. If no api key added, the search function in the app won't work. But you can still add podcasts by serach rss link or import ompl file. Share_key is used for generate clip. @@ -61,9 +64,9 @@ This project is a starting point for a Flutter application. A few resources to get you started if this is your first Flutter project: -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) +* [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +* [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, +[online documentation](https://flutter.dev/docs), which offers tutorials, samples, guidance on mobile development, and a full API reference. diff --git a/android/app/build.gradle b/android/app/build.gradle index f071420..eedf618 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -48,8 +48,8 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.stonegate.tsacdop" minSdkVersion 19 - targetSdkVersion 28 - versionCode 15 + targetSdkVersion 29 + versionCode 16 versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/lib/home/about.dart b/lib/home/about.dart index 63fce52..04fcddc 100644 --- a/lib/home/about.dart +++ b/lib/home/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.3.2'; +const String version = '0.3.3'; class AboutApp extends StatelessWidget { _launchUrl(String url) async { diff --git a/lib/home/home.dart b/lib/home/home.dart index c478788..412784f 100644 --- a/lib/home/home.dart +++ b/lib/home/home.dart @@ -440,12 +440,14 @@ class _RecentUpdateState extends State<_RecentUpdate> String _groupName; List _group; Layout _layout; + bool _scroll; @override void initState() { super.initState(); _loadMore = false; _groupName = 'All'; _group = ['All']; + _scroll = false; } @override @@ -479,6 +481,11 @@ class _RecentUpdateState extends State<_RecentUpdate> ) : NotificationListener( onNotification: (ScrollNotification scrollInfo) { + if (scrollInfo is ScrollStartNotification && + mounted && + !_scroll) { + setState(() => _scroll = true); + } if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && snapshot.data.length == _top) @@ -705,7 +712,7 @@ class _RecentUpdateState extends State<_RecentUpdate> EpisodeGrid( episodes: snapshot.data, layout: _layout, - initNum: 9, + initNum: _scroll ? 0 : 12, ), SliverList( delegate: SliverChildBuilderDelegate( diff --git a/lib/local_storage/key_value_storage.dart b/lib/local_storage/key_value_storage.dart index be68e20..801d206 100644 --- a/lib/local_storage/key_value_storage.dart +++ b/lib/local_storage/key_value_storage.dart @@ -4,7 +4,6 @@ import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; import '../state/podcast_group.dart'; -import '../util/episodegrid.dart'; const String autoPlayKey = 'autoPlay'; const String autoAddKey = 'autoAdd'; diff --git a/lib/podcasts/podcastdetail.dart b/lib/podcasts/podcastdetail.dart index 1a27d29..b5c982f 100644 --- a/lib/podcasts/podcastdetail.dart +++ b/lib/podcasts/podcastdetail.dart @@ -23,7 +23,6 @@ import '../util/colorize.dart'; import '../util/context_extension.dart'; import '../util/custompaint.dart'; import '../state/audiostate.dart'; -import '../state/podcast_group.dart'; class PodcastDetail extends StatefulWidget { PodcastDetail({Key key, @required this.podcastLocal, this.hide = false}) @@ -41,6 +40,7 @@ class _PodcastDetailState extends State { List _hosts; int _episodeCount; Layout _layout; + bool _scroll; Future _updateRssItem(PodcastLocal podcastLocal) async { var dbHelper = DBHelper(); @@ -196,6 +196,7 @@ class _PodcastDetailState extends State { _top = 99; _reverse = false; _controller = ScrollController(); + _scroll = false; } @override @@ -257,6 +258,10 @@ class _PodcastDetailState extends State { _loadMore = false; }); } + if (_controller.offset > 0 && mounted && !_scroll ) + setState(() { + _scroll = true; + }); }), physics: const AlwaysScrollableScrollPhysics(), @@ -557,6 +562,7 @@ class _PodcastDetailState extends State { layout: _layout, reverse: _reverse, episodeCount: _episodeCount, + initNum: _scroll ? 0 : 12, ), SliverList( delegate: SliverChildBuilderDelegate( diff --git a/lib/podcasts/podcastgroup.dart b/lib/podcasts/podcastgroup.dart index 770c0ee..3096311 100644 --- a/lib/podcasts/podcastgroup.dart +++ b/lib/podcasts/podcastgroup.dart @@ -15,7 +15,6 @@ import '../util/pageroute.dart'; import '../util/colorize.dart'; import '../util/duraiton_picker.dart'; import '../util/context_extension.dart'; -import '../state/audiostate.dart'; class PodcastGroupList extends StatefulWidget { final PodcastGroup group; diff --git a/lib/settings/theme.dart b/lib/settings/theme.dart index c0dfc1e..3f791e9 100644 --- a/lib/settings/theme.dart +++ b/lib/settings/theme.dart @@ -52,8 +52,7 @@ class ThemeSetting extends StatelessWidget { .modalBarrierDismissLabel, barrierColor: Colors.black54, transitionDuration: const Duration(milliseconds: 200), - pageBuilder: (BuildContext context, - Animation animaiton, + pageBuilder: (BuildContext context, Animation animaiton, Animation secondaryAnimation) => AnnotatedRegion( value: SystemUiOverlayStyle( @@ -72,15 +71,14 @@ class ThemeSetting extends StatelessWidget { ), elevation: 1, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(10.0))), + borderRadius: + BorderRadius.all(Radius.circular(10.0))), title: Text('Theme'), content: SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ RadioListTile( title: Text('System default'), @@ -123,8 +121,8 @@ class ThemeSetting extends StatelessWidget { title: Text( 'Real Dark', ), - subtitle: Text( - 'Turn on if you think the night is not dark enough'), + subtitle: + Text('Turn on if you think the night is not dark enough'), trailing: Selector( selector: (_, setting) => setting.realDark, builder: (_, data, __) => Switch( @@ -143,8 +141,7 @@ class ThemeSetting extends StatelessWidget { .modalBarrierDismissLabel, barrierColor: Colors.black54, transitionDuration: const Duration(milliseconds: 200), - pageBuilder: (BuildContext context, - Animation animaiton, + pageBuilder: (BuildContext context, Animation animaiton, Animation secondaryAnimation) => AnnotatedRegion( value: SystemUiOverlayStyle( @@ -160,7 +157,7 @@ class ThemeSetting extends StatelessWidget { titlePadding: EdgeInsets.only( top: 20, left: 40, - right: 200, + right: context.width / 3, bottom: 0), shape: RoundedRectangleBorder( borderRadius: BorderRadius.all( diff --git a/lib/state/audiostate.dart b/lib/state/audiostate.dart index 82dc36a..5114f1f 100644 --- a/lib/state/audiostate.dart +++ b/lib/state/audiostate.dart @@ -96,9 +96,11 @@ class Playlist { } addToPlayList(EpisodeBrief episodeBrief) async { - _playlist.add(episodeBrief); - await savePlaylist(); - dbHelper.removeEpisodeNewMark(episodeBrief.enclosureUrl); + if (!_playlist.contains(episodeBrief)) { + _playlist.add(episodeBrief); + await savePlaylist(); + dbHelper.removeEpisodeNewMark(episodeBrief.enclosureUrl); + } } addToPlayListAt(EpisodeBrief episodeBrief, int index) async { diff --git a/lib/type/episodebrief.dart b/lib/type/episodebrief.dart index d1b31c1..9d9780d 100644 --- a/lib/type/episodebrief.dart +++ b/lib/type/episodebrief.dart @@ -34,7 +34,7 @@ class EpisodeBrief { this.skipSeconds); String dateToString() { - DateTime date = DateTime.fromMillisecondsSinceEpoch(pubDate,isUtc: true); + DateTime date = DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true); var diffrence = DateTime.now().toUtc().difference(date); if (diffrence.inHours < 1) { return '1 hour ago'; @@ -56,9 +56,17 @@ class EpisodeBrief { title: title, artist: feedTitle, album: feedTitle, - // duration: 0, + // duration: 0, artUri: 'file://$imagePath', extras: {'skip': skipSeconds}); } + @override + bool operator ==(Object episode) => + episode is EpisodeBrief && + episode.title == title && + episode.enclosureUrl == enclosureUrl; + + @override + int get hashCode => enclosureUrl.hashCode; } diff --git a/lib/util/episodegrid.dart b/lib/util/episodegrid.dart index db5834e..2b5d81e 100644 --- a/lib/util/episodegrid.dart +++ b/lib/util/episodegrid.dart @@ -29,6 +29,18 @@ class EpisodeGrid extends StatelessWidget { final Layout layout; final bool reverse; final int initNum; + EpisodeGrid({ + Key key, + @required this.episodes, + this.initNum = 12, + this.showDownload = false, + this.showFavorite = false, + this.showNumber = false, + this.episodeCount = 0, + this.layout = Layout.three, + this.reverse, + }) : super(key: key); + Future _isListened(EpisodeBrief episode) async { DBHelper dbHelper = DBHelper(); return await dbHelper.isListened(episode.enclosureUrl); @@ -44,18 +56,6 @@ class EpisodeGrid extends StatelessWidget { return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}'; } - EpisodeGrid({ - Key key, - @required this.episodes, - this.initNum = 12, - this.showDownload = false, - this.showFavorite = false, - this.showNumber = false, - this.episodeCount = 0, - this.layout = Layout.three, - this.reverse, - }) : super(key: key); - Widget _title(EpisodeBrief episode) => Container( alignment: layout == Layout.one ? Alignment.centerLeft : Alignment.topLeft, @@ -67,6 +67,7 @@ class EpisodeGrid extends StatelessWidget { layout == Layout.one ? TextOverflow.ellipsis : TextOverflow.fade, ), ); + Widget _circleImage(BuildContext context, {EpisodeBrief episode, Color color, bool boo}) => Container( @@ -79,6 +80,7 @@ class EpisodeGrid extends StatelessWidget { backgroundImage: FileImage(File("${episode.imagePath}")), ), ); + Widget _listenIndicater(BuildContext context, {EpisodeBrief episode, int isListened}) => Selector>( @@ -133,6 +135,7 @@ class EpisodeGrid extends StatelessWidget { : Center(), ) : Center(); + Widget _isNewIndicator(EpisodeBrief episode) => episode.isNew == 1 ? Container( padding: EdgeInsets.symmetric(horizontal: 2), @@ -158,6 +161,7 @@ class EpisodeGrid extends StatelessWidget { ), ) : Center(); + Widget _pubDate(BuildContext context, {EpisodeBrief episode, Color color}) => Text( episode.dateToString(), @@ -166,7 +170,6 @@ class EpisodeGrid extends StatelessWidget { color: color, fontStyle: FontStyle.italic), ); - @override Widget build(BuildContext context) { double _width = MediaQuery.of(context).size.width; @@ -242,6 +245,7 @@ class EpisodeGrid extends StatelessWidget { showItemDuration: Duration(milliseconds: 50), ); final scrollController = ScrollController(); + return SliverPadding( padding: const EdgeInsets.only( top: 10.0, bottom: 5.0, left: 15.0, right: 15.0), @@ -261,11 +265,12 @@ class EpisodeGrid extends StatelessWidget { Color _c = (Theme.of(context).brightness == Brightness.light) ? episodes[index].primaryColor.colorizedark() : episodes[index].primaryColor.colorizeLight(); + scrollController.addListener(() { + print(scrollController.offset); + }); return FadeTransition( - opacity: Tween( - begin: index < initNum ? 0 : 1, - end: 1, - ).animate(animation), + opacity: Tween(begin: index < initNum ? 0 : 1, end: 1) + .animate(animation), child: Selector>>( selector: (_, audio) => Tuple2(audio?.episode, diff --git a/lib/util/pageroute.dart b/lib/util/pageroute.dart index 8446498..983c08c 100644 --- a/lib/util/pageroute.dart +++ b/lib/util/pageroute.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'context_extension.dart'; //Slide Transition class SlideLeftRoute extends PageRouteBuilder { diff --git a/pubspec.yaml b/pubspec.yaml index c5b9829..73dbe17 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: tsacdop description: An easy-use podacasts player. -version: 0.3.2 +version: 0.3.3 environment: sdk: ">=2.6.0 <3.0.0" @@ -41,9 +41,9 @@ dependencies: connectivity: ^0.4.8+2 flare_flutter: ^2.0.3 rxdart: ^0.24.0 - auto_animated: ^2.1.0 wc_flutter_share: ^0.2.1 video_player: ^0.10.11 + auto_animated: ^2.1.0 just_audio: git: url: https://github.com/stonega/just_audio.git