mirror of
https://github.com/stonega/tsacdop
synced 2025-02-16 11:31:45 +01:00
🐛 Bugs fixed
This commit is contained in:
parent
137fe58183
commit
ab6910d9c6
25
README.md
25
README.md
@ -1,4 +1,3 @@
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/stonega/tsacdop/master/android/app/src/main/res/mipmap-xhdpi/ic_notification.png" art = "Logo"/>
|
||||
</br>
|
||||
@ -6,11 +5,13 @@
|
||||
</p>
|
||||
|
||||
![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
|
||||
-------|--------|--------|------| ----
|
||||
<img src="https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893838840.png" art = "HomePage"/>|<img src="https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585894051734.png" art = "Groups"/>|<img src="https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893877702.png" art = "Podcast"/>|<img src="https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585896237809.png" art = "Episode"/>|<img src="https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893920721.png" art = "DarkMode"/>|
|
||||
|
||||
| HomePage | Group | Podcast | Episode | DarkMode |
|
||||
|------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------|
|
||||
| <img src="https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893838840.png" art = "HomePage"/> | <img src="https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585894051734.png" art = "Groups"/> | <img src="https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893877702.png" art = "Podcast"/> | <img src="https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585896237809.png" art = "Episode"/> | <img src="https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893920721.png" art = "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.
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -440,12 +440,14 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
||||
String _groupName;
|
||||
List<String> _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<ScrollNotification>(
|
||||
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(
|
||||
|
@ -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';
|
||||
|
@ -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<PodcastDetail> {
|
||||
List<PodcastHost> _hosts;
|
||||
int _episodeCount;
|
||||
Layout _layout;
|
||||
bool _scroll;
|
||||
Future _updateRssItem(PodcastLocal podcastLocal) async {
|
||||
var dbHelper = DBHelper();
|
||||
|
||||
@ -196,6 +196,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
||||
_top = 99;
|
||||
_reverse = false;
|
||||
_controller = ScrollController();
|
||||
_scroll = false;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -257,6 +258,10 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
||||
_loadMore = false;
|
||||
});
|
||||
}
|
||||
if (_controller.offset > 0 && mounted && !_scroll )
|
||||
setState(() {
|
||||
_scroll = true;
|
||||
});
|
||||
}),
|
||||
physics:
|
||||
const AlwaysScrollableScrollPhysics(),
|
||||
@ -557,6 +562,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
||||
layout: _layout,
|
||||
reverse: _reverse,
|
||||
episodeCount: _episodeCount,
|
||||
initNum: _scroll ? 0 : 12,
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
|
@ -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;
|
||||
|
@ -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<SystemUiOverlayStyle>(
|
||||
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: <Widget>[
|
||||
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<SettingState, bool>(
|
||||
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<SystemUiOverlayStyle>(
|
||||
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(
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<int> _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<AudioPlayerNotifier, Tuple2<EpisodeBrief, bool>>(
|
||||
@ -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<double>(
|
||||
begin: index < initNum ? 0 : 1,
|
||||
end: 1,
|
||||
).animate(animation),
|
||||
opacity: Tween<double>(begin: index < initNum ? 0 : 1, end: 1)
|
||||
.animate(animation),
|
||||
child: Selector<AudioPlayerNotifier,
|
||||
Tuple2<EpisodeBrief, List<String>>>(
|
||||
selector: (_, audio) => Tuple2(audio?.episode,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'context_extension.dart';
|
||||
|
||||
//Slide Transition
|
||||
class SlideLeftRoute extends PageRouteBuilder {
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user