Minor change

This commit is contained in:
stonegate 2020-09-26 22:26:25 +08:00
parent 93ed9d3513
commit 6086db0f8c
14 changed files with 945 additions and 635 deletions

View File

@ -173,8 +173,8 @@ For help getting started with Flutter, view our
[English]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=English&query=%24.languages%5B3%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=% [English]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=English&query=%24.languages%5B3%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=%
[Chinese Simplified]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=Chinese%20Simplified&query=%24.languages%5B2%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=% [Chinese Simplified]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=Chinese%20Simplified&query=%24.languages%5B2%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=%
[French]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=French(ppp)&query=%24.languages%5B5%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=% [French]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=French(ppp)&query=%24.languages%5B5%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=%
[Spanish]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=Spanish(Joel)&query=%24.languages%5B8%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=% [Spanish]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=Spanish(Joel)&query=%24.languages%5B7%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=%
[Portuguese]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=portuguese(Bruno)&query=%24.languages%5B10%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=% [Portuguese]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=portuguese(Bruno)&query=%24.languages%5B9%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=%
[localizely - website]: https://localizely.com/ [localizely - website]: https://localizely.com/
[google play - icon]: https://img.shields.io/badge/google-playStore-%2323CCC6 [google play - icon]: https://img.shields.io/badge/google-playStore-%2323CCC6
[google play]: https://play.google.com/store/apps/details?id=com.stonegate.tsacdop [google play]: https://play.google.com/store/apps/details?id=com.stonegate.tsacdop

View File

@ -5,7 +5,7 @@ import 'package:line_icons/line_icons.dart';
import '../util/custom_widget.dart'; import '../util/custom_widget.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
const String version = '0.4.17'; const String version = '0.4.18';
class AboutApp extends StatelessWidget { class AboutApp extends StatelessWidget {
Widget _listItem( Widget _listItem(

View File

@ -526,12 +526,13 @@ class _PlaylistButton extends StatefulWidget {
class __PlaylistButtonState extends State<_PlaylistButton> { class __PlaylistButtonState extends State<_PlaylistButton> {
bool _loadPlay; bool _loadPlay;
_getPlaylist() async { Future<void> _getPlaylist() async {
await Provider.of<AudioPlayerNotifier>(context, listen: false) await context.read<AudioPlayerNotifier>().loadPlaylist();
.loadPlaylist(); if (mounted) {
setState(() { setState(() {
_loadPlay = true; _loadPlay = true;
}); });
}
} }
@override @override
@ -545,157 +546,158 @@ class __PlaylistButtonState extends State<_PlaylistButton> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
var audio = context.watch<AudioPlayerNotifier>(); var audio = context.watch<AudioPlayerNotifier>();
final s = context.s; final s = context.s;
return MyPopupMenuButton<int>( return Material(
shape: RoundedRectangleBorder( child: MyPopupMenuButton<int>(
borderRadius: BorderRadius.all(Radius.circular(10))), shape: RoundedRectangleBorder(
elevation: 1, borderRadius: BorderRadius.all(Radius.circular(10))),
icon: Icon(Icons.playlist_play), elevation: 1,
tooltip: s.menu, icon: Icon(Icons.playlist_play),
itemBuilder: (context) => [ tooltip: s.menu,
MyPopupMenuItem( itemBuilder: (context) => [
height: 50, MyPopupMenuItem(
value: 1, height: 50,
child: Container( value: 1,
decoration: BoxDecoration( child: Container(
// color: Theme.of(context).accentColor, decoration: BoxDecoration(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0), topLeft: Radius.circular(10.0),
topRight: Radius.circular(10.0)), topRight: Radius.circular(10.0)),
), ),
child: Selector<AudioPlayerNotifier, Tuple3<bool, Playlist, int>>( child: Selector<AudioPlayerNotifier, Tuple3<bool, Playlist, int>>(
selector: (_, audio) => selector: (_, audio) =>
Tuple3(audio.playerRunning, audio.queue, audio.lastPositin), Tuple3(audio.playerRunning, audio.queue, audio.lastPositin),
builder: (_, data, __) => !_loadPlay builder: (_, data, __) => !_loadPlay
? Container( ? SizedBox(
height: 8.0, height: 8.0,
) )
: data.item1 || data.item2.playlist.length == 0 : data.item1 || data.item2.playlist.length == 0
? Container( ? SizedBox(
height: 8.0, height: 8.0,
) )
: InkWell( : InkWell(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0), topLeft: Radius.circular(10.0),
topRight: Radius.circular(10.0)), topRight: Radius.circular(10.0)),
onTap: () { onTap: () {
audio.playlistLoad(); audio.playlistLoad();
Navigator.pop<int>(context); Navigator.pop<int>(context);
}, },
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 5), padding: EdgeInsets.symmetric(vertical: 5),
), ),
Stack( Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: <Widget>[
CircleAvatar(
radius: 20,
backgroundImage: data
.item2.playlist.first.avatarImage),
Container(
height: 40.0,
width: 40.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black12),
child: Icon(
Icons.play_arrow,
color: Colors.white,
),
),
],
),
Padding(
padding: EdgeInsets.symmetric(vertical: 2),
),
Container(
height: 70,
width: 140,
child: Column(
children: <Widget>[ children: <Widget>[
Text( CircleAvatar(
(data.item3 ~/ 1000).toTime, radius: 20,
// style: backgroundImage: data
// TextStyle(color: Colors.white) .item2.playlist.first.avatarImage),
), Container(
Text( height: 40.0,
data.item2.playlist.first.title, width: 40.0,
maxLines: 2, decoration: BoxDecoration(
textAlign: TextAlign.center, shape: BoxShape.circle,
overflow: TextOverflow.fade, color: Colors.black12),
// style: TextStyle(color: Colors.white), child: Icon(
Icons.play_arrow,
color: Colors.white,
),
), ),
], ],
), ),
), Padding(
Divider( padding: EdgeInsets.symmetric(vertical: 2),
height: 1, ),
), Container(
], height: 70,
width: 140,
child: Column(
children: <Widget>[
Text(
(data.item3 ~/ 1000).toTime,
// style:
// TextStyle(color: Colors.white)
),
Text(
data.item2.playlist.first.title,
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.fade,
// style: TextStyle(color: Colors.white),
),
],
),
),
Divider(
height: 1,
),
],
),
), ),
),
),
),
),
PopupMenuItem(
value: 0,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(Icons.playlist_play),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
Text(s.homeMenuPlaylist),
],
),
),
),
PopupMenuDivider(
height: 1,
),
PopupMenuItem(
value: 2,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(Icons.history),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5.0),
),
Text(s.settingsHistory),
],
),
),
),
PopupMenuDivider(
height: 1,
),
],
onSelected: (value) {
if (value == 0) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PlaylistPage(
initPage: InitPage.playlist,
), ),
), ),
); ),
} else if (value == 2) { PopupMenuItem(
Navigator.push( value: 0,
context, child: Container(
MaterialPageRoute( padding: EdgeInsets.only(left: 10),
builder: (context) => PlaylistPage( child: Row(
initPage: InitPage.history, children: <Widget>[
Icon(Icons.playlist_play),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
Text(s.homeMenuPlaylist),
],
), ),
), ),
); ),
} PopupMenuDivider(
}, height: 1,
),
PopupMenuItem(
value: 2,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(Icons.history),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5.0),
),
Text(s.settingsHistory),
],
),
),
),
PopupMenuDivider(
height: 1,
),
],
onSelected: (value) {
if (value == 0) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PlaylistPage(
initPage: InitPage.playlist,
),
),
);
} else if (value == 2) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PlaylistPage(
initPage: InitPage.history,
),
),
);
}
},
),
); );
} }
} }
@ -707,6 +709,19 @@ class _RecentUpdate extends StatefulWidget {
class _RecentUpdateState extends State<_RecentUpdate> class _RecentUpdateState extends State<_RecentUpdate>
with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin { with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin {
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
GlobalKey<RefreshIndicatorState>();
Future _updateRssItem() async {
final refreshWorker = context.read<RefreshWorker>();
refreshWorker.start(_group);
await Future.delayed(Duration(seconds: 1));
Fluttertoast.showToast(
msg: 'Refresh started',
gravity: ToastGravity.BOTTOM,
);
}
Future<List<EpisodeBrief>> _getRssItem(int top, List<String> group, Future<List<EpisodeBrief>> _getRssItem(int top, List<String> group,
{bool hideListened}) async { {bool hideListened}) async {
var storage = KeyValueStorage(recentLayoutKey); var storage = KeyValueStorage(recentLayoutKey);
@ -718,7 +733,7 @@ class _RecentUpdateState extends State<_RecentUpdate>
} }
var dbHelper = DBHelper(); var dbHelper = DBHelper();
List<EpisodeBrief> episodes; List<EpisodeBrief> episodes;
if (group.first == 'All') { if (group.isEmpty) {
episodes = episodes =
await dbHelper.getRecentRssItem(top, hideListened: _hideListened); await dbHelper.getRecentRssItem(top, hideListened: _hideListened);
} else { } else {
@ -731,7 +746,7 @@ class _RecentUpdateState extends State<_RecentUpdate>
Future<int> _getUpdateCounts(List<String> group) async { Future<int> _getUpdateCounts(List<String> group) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
var episodes = <EpisodeBrief>[]; var episodes = <EpisodeBrief>[];
if (group.first == 'All') { if (group.isEmpty) {
episodes = await dbHelper.getRecentNewRssItem(); episodes = await dbHelper.getRecentNewRssItem();
} else { } else {
episodes = await dbHelper.getGroupNewRssItem(group); episodes = await dbHelper.getGroupNewRssItem(group);
@ -768,7 +783,7 @@ class _RecentUpdateState extends State<_RecentUpdate>
super.initState(); super.initState();
_loadMore = false; _loadMore = false;
_groupName = 'All'; _groupName = 'All';
_group = ['All']; _group = [];
_scroll = false; _scroll = false;
} }
@ -820,24 +835,28 @@ class _RecentUpdateState extends State<_RecentUpdate>
} }
return true; return true;
}, },
child: CustomScrollView( child: RefreshIndicator(
key: PageStorageKey<String>('update'), key: _refreshIndicatorKey,
physics: const AlwaysScrollableScrollPhysics(), color: context.accentColor,
slivers: <Widget>[ onRefresh: () async {
SliverToBoxAdapter( await _updateRssItem();
child: Container( },
height: 40, child: CustomScrollView(
color: context.primaryColor, key: PageStorageKey<String>('update'),
child: Material( physics:
color: Colors.transparent, const AlwaysScrollableScrollPhysics(),
child: Row( slivers: <Widget>[
children: <Widget>[ SliverToBoxAdapter(
Consumer<GroupList>( child: Container(
builder: (context, groupList, height: 40,
child) => color: context.primaryColor,
Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: child: Row(
children: <Widget>[
Consumer<GroupList>(
builder: (context, groupList,
child) =>
PopupMenuButton<String>( PopupMenuButton<String>(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: borderRadius:
@ -903,7 +922,7 @@ class _RecentUpdateState extends State<_RecentUpdate>
if (value == 'All') { if (value == 'All') {
setState(() { setState(() {
_groupName = 'All'; _groupName = 'All';
_group = ['All']; _group = [];
}); });
} else { } else {
for (var group for (var group
@ -923,129 +942,128 @@ class _RecentUpdateState extends State<_RecentUpdate>
}, },
), ),
), ),
), Spacer(),
Spacer(), FutureBuilder<int>(
FutureBuilder<int>( future: _getUpdateCounts(
future: _group),
_getUpdateCounts(_group), initialData: 0,
initialData: 0, builder:
builder: (context, snapshot) { (context, snapshot) {
return snapshot.data != 0 return snapshot.data != 0
? Material( ? Material(
color: Colors color: Colors
.transparent, .transparent,
child: IconButton( child: IconButton(
tooltip: s tooltip: s
.addNewEpisodeTooltip, .addNewEpisodeTooltip,
icon: SizedBox( icon: SizedBox(
height: 15, height:
width: 20, 15,
child: CustomPaint( width: 20,
painter: AddToPlaylistPainter( child: CustomPaint(
context painter: AddToPlaylistPainter(
.textTheme.bodyText1.color, context
Colors .textTheme.bodyText1.color,
.red))), Colors
onPressed: .red))),
() async { onPressed:
await audio () async {
.addNewEpisode( await audio
_group); .addNewEpisode(
if (mounted) { _group);
setState( if (mounted) {
() {}); setState(
} () {});
Fluttertoast }
.showToast( Fluttertoast
msg: _groupName == .showToast(
'All' msg: _groupName ==
? s.addNewEpisodeAll(snapshot 'All'
.data) ? s.addNewEpisodeAll(snapshot
: s.addEpisodeGroup( .data)
_groupName, : s.addEpisodeGroup(
snapshot.data), _groupName,
gravity: snapshot.data),
ToastGravity gravity:
.BOTTOM, ToastGravity
); .BOTTOM,
}), );
) }),
: Material( )
color: Colors : Material(
.transparent, color: Colors
child: IconButton( .transparent,
tooltip: s child: IconButton(
.addNewEpisodeTooltip, tooltip: s
icon: SizedBox( .addNewEpisodeTooltip,
height: 15, icon: SizedBox(
width: 20, height: 15,
child: width: 20,
CustomPaint( child: CustomPaint(
painter: painter: AddToPlaylistPainter(
AddToPlaylistPainter( context
context .textColor,
.textColor, context
context .textColor,
.textColor, ))),
))), onPressed: () {}),
onPressed: );
() {}), }),
); Material(
}), color: Colors.transparent,
Material( child: IconButton(
color: Colors.transparent, tooltip:
child: IconButton( s.hideListenedSetting,
tooltip: icon: SizedBox(
s.hideListenedSetting, width: 30,
icon: SizedBox( height: 15,
width: 30, child: HideListened(
height: 15, hideListened:
child: HideListened( _hideListened ??
hideListened: false,
_hideListened ?? ),
false,
), ),
onPressed: () {
setState(() =>
_hideListened =
!_hideListened);
},
), ),
onPressed: () {
setState(() =>
_hideListened =
!_hideListened);
},
), ),
), Material(
Material( color: Colors.transparent,
color: Colors.transparent, child: LayoutButton(
child: LayoutButton( layout: _layout,
layout: _layout, onPressed: (layout) =>
onPressed: (layout) => setState(() {
setState(() { _layout = layout;
_layout = layout; }),
}), ),
), ),
), ],
], ),
), )),
)),
),
EpisodeGrid(
episodes: snapshot.data,
layout: _layout,
initNum: _scroll ? 0 : 12,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return _loadMore
? Container(
height: 2,
child:
LinearProgressIndicator())
: Center();
},
childCount: 1,
), ),
), EpisodeGrid(
])) episodes: snapshot.data,
layout: _layout,
initNum: _scroll ? 0 : 12,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return _loadMore
? Container(
height: 2,
child:
LinearProgressIndicator())
: Center();
},
childCount: 1,
),
),
]),
))
: Center(); : Center();
}, },
); );

View File

@ -361,11 +361,8 @@ class _ScrollPodcastsState extends State<ScrollPodcasts>
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
color: context.scaffoldBackgroundColor, color: context.scaffoldBackgroundColor,
child: TabBar( child: TabBar(
labelPadding: EdgeInsets.only( labelPadding:
top: 5.0, EdgeInsets.fromLTRB(6.0, 5.0, 6.0, 10.0),
bottom: 10.0,
left: 6.0,
right: 6.0),
indicator: CircleTabIndicator( indicator: CircleTabIndicator(
color: context.accentColor, radius: 3), color: context.accentColor, radius: 3),
isScrollable: true, isScrollable: true,
@ -439,17 +436,30 @@ class _ScrollPodcastsState extends State<ScrollPodcasts>
.podcasts .podcasts
.map<Widget>((podcastLocal) { .map<Widget>((podcastLocal) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).brightness == color: context.brightness ==
Brightness.light Brightness.light
? Theme.of(context).primaryColor ? context.primaryColor
: Colors.black12), : Colors.black12),
margin: EdgeInsets.symmetric(horizontal: 5.0), margin:
key: ObjectKey(podcastLocal.title), EdgeInsets.symmetric(horizontal: 5.0),
child: PodcastPreview( key: ObjectKey(podcastLocal.title),
podcastLocal: podcastLocal, child: Material(
), color: Colors.transparent,
); child: InkWell(
onTap: () {
Navigator.push(
context,
SlideLeftRoute(
page: PodcastDetail(
podcastLocal: podcastLocal,
)),
);
},
child: PodcastPreview(
podcastLocal: podcastLocal,
),
)));
}).toList(), }).toList(),
), ),
), ),
@ -513,27 +523,11 @@ class PodcastPreview extends StatelessWidget {
Expanded( Expanded(
flex: 1, flex: 1,
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Material( child: Padding(
color: Colors.transparent, padding: const EdgeInsets.only(right: 8.0),
child: Selector<AudioPlayerNotifier, bool>( child: Icon(Icons.arrow_forward),
selector: (_, audio) => audio.playerRunning, )),
builder: (_, playerRunning, __) => IconButton(
icon: Icon(Icons.arrow_forward),
tooltip: context.s.homeGroupsSeeAll,
onPressed: () {
Navigator.push(
context,
SlideLeftRoute(
page: PodcastDetail(
podcastLocal: podcastLocal,
)),
);
},
),
),
),
),
), ),
], ],
), ),

View File

@ -6,7 +6,6 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -26,7 +25,6 @@ class PopupMenu extends StatefulWidget {
class _PopupMenuState extends State<PopupMenu> { class _PopupMenuState extends State<PopupMenu> {
Future<String> _getRefreshDate(BuildContext context) async { Future<String> _getRefreshDate(BuildContext context) async {
int refreshDate; int refreshDate;
final s = context.s;
var refreshstorage = KeyValueStorage('refreshdate'); var refreshstorage = KeyValueStorage('refreshdate');
var i = await refreshstorage.getInt(); var i = await refreshstorage.getInt();
if (i == 0) { if (i == 0) {
@ -36,20 +34,21 @@ class _PopupMenuState extends State<PopupMenu> {
} else { } else {
refreshDate = i; refreshDate = i;
} }
var date = DateTime.fromMillisecondsSinceEpoch(refreshDate); return refreshDate.toDate(context);
var difference = DateTime.now().difference(date); // var date = DateTime.fromMillisecondsSinceEpoch(refreshDate);
if (difference.inSeconds < 60) { // var difference = DateTime.now().difference(date);
return s.secondsAgo(difference.inSeconds); // if (difference.inSeconds < 60) {
} else if (difference.inMinutes < 60) { // return s.secondsAgo(difference.inSeconds);
return s.minsAgo(difference.inMinutes); // } else if (difference.inMinutes < 60) {
} else if (difference.inHours < 24) { // return s.minsAgo(difference.inMinutes);
return s.hoursAgo(difference.inHours); // } else if (difference.inHours < 24) {
} else if (difference.inDays < 7) { // return s.hoursAgo(difference.inHours);
return s.daysAgo(difference.inDays); // } else if (difference.inDays < 7) {
} else { // return s.daysAgo(difference.inDays);
return DateFormat.yMMMd() // } else {
.format(DateTime.fromMillisecondsSinceEpoch(refreshDate)); // return DateFormat.yMMMd()
} // .format(DateTime.fromMillisecondsSinceEpoch(refreshDate));
// }
} }
void _saveOmpl(String path) async { void _saveOmpl(String path) async {
@ -198,8 +197,7 @@ class _PopupMenuState extends State<PopupMenu> {
} else if (value == 2) { } else if (value == 2) {
_getFilePath(); _getFilePath();
} else if (value == 1) { } else if (value == 1) {
//_refreshAll(); refreshWorker.start([]);
refreshWorker.start();
} else if (value == 3) { } else if (value == 3) {
// setting.theme != 2 ? setting.setTheme(2) : setting.setTheme(1); // setting.theme != 2 ? setting.setTheme(2) : setting.setTheme(1);
} else if (value == 4) { } else if (value == 4) {

View File

@ -69,7 +69,7 @@ class _PlaylistPageState extends State<PlaylistPage> {
systemNavigationBarColor: Theme.of(context).primaryColor, systemNavigationBarColor: Theme.of(context).primaryColor,
), ),
child: Scaffold( child: Scaffold(
backgroundColor: Theme.of(context).primaryColor, backgroundColor: context.primaryColor,
appBar: AppBar( appBar: AppBar(
elevation: 0, elevation: 0,
backgroundColor: context.accentColor.withAlpha(70), backgroundColor: context.accentColor.withAlpha(70),
@ -264,9 +264,12 @@ class _PlaylistPageState extends State<PlaylistPage> {
), ),
), ),
Expanded( Expanded(
child: AnimatedSwitcher( child: Container(
duration: Duration(milliseconds: 300), color: context.primaryColor,
child: _loadList)), child: AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: _loadList),
)),
], ],
); );
}, },
@ -586,171 +589,192 @@ class __HistoryListState extends State<_HistoryList> {
final date = snapshot final date = snapshot
.data[index].playdate.millisecondsSinceEpoch; .data[index].playdate.millisecondsSinceEpoch;
final episode = snapshot.data[index].episode; final episode = snapshot.data[index].episode;
final c = episode.backgroudColor(context); final c = episode?.backgroudColor(context);
return SizedBox( return episode == null
height: 90.0, ? Center()
child: Column( : SizedBox(
mainAxisAlignment: MainAxisAlignment.spaceAround, height: 90.0,
children: [ child: Column(
Expanded( mainAxisAlignment:
child: Center( MainAxisAlignment.spaceAround,
child: ListTile( children: [
contentPadding: Expanded(
EdgeInsets.fromLTRB(24, 8, 20, 8), child: Center(
onTap: () => audio.episodeLoad(episode), child: ListTile(
leading: CircleAvatar( contentPadding: EdgeInsets.fromLTRB(
backgroundColor: c.withOpacity(0.5), 24, 8, 20, 8),
backgroundImage: episode.avatarImage), onTap: () =>
title: Padding( audio.episodeLoad(episode),
padding: leading: CircleAvatar(
EdgeInsets.symmetric(vertical: 5.0), backgroundColor:
child: Text( c?.withOpacity(0.5),
snapshot.data[index].title, backgroundImage:
maxLines: 1, episode.avatarImage),
overflow: TextOverflow.ellipsis, title: Padding(
), padding: EdgeInsets.symmetric(
), vertical: 5.0),
subtitle: Container( child: Text(
height: 35, snapshot.data[index].title,
child: Row( maxLines: 1,
mainAxisAlignment: overflow: TextOverflow.ellipsis,
MainAxisAlignment.start, ),
crossAxisAlignment: ),
CrossAxisAlignment.center, subtitle: Container(
children: <Widget>[ height: 35,
if (seekValue < 0.9) child: Row(
Padding( mainAxisAlignment:
padding: MainAxisAlignment.start,
const EdgeInsets.symmetric( crossAxisAlignment:
vertical: 5.0), CrossAxisAlignment.center,
child: Material( children: <Widget>[
color: Colors.transparent, if (seekValue < 0.9)
child: InkWell( Padding(
onTap: () async { padding: const EdgeInsets
audio.episodeLoad(episode,
startPosition:
(seconds * 1000)
.toInt());
},
child: Stack(children: [
ShaderMask(
shaderCallback:
(bounds) {
return LinearGradient(
begin: Alignment
.centerLeft,
colors: <Color>[
Colors.cyan[600]
.withOpacity(
0.8),
Colors.white70
],
stops: [
seekValue,
seekValue
],
tileMode:
TileMode.mirror,
).createShader(
bounds);
},
child: Container(
height: 25,
alignment:
Alignment.center,
padding: EdgeInsets
.symmetric( .symmetric(
horizontal: vertical: 5.0),
20), child: Material(
decoration: color:
BoxDecoration( Colors.transparent,
borderRadius: child: InkWell(
BorderRadius.all( onTap: () async {
Radius.circular( audio.episodeLoad(
20.0)), episode,
color: context startPosition:
.accentColor, (seconds *
), 1000)
child: Text( .toInt());
seconds.toTime, },
style: TextStyle( child: Stack(
color: Colors children: [
.white), ShaderMask(
), shaderCallback:
(bounds) {
return LinearGradient(
begin: Alignment
.centerLeft,
colors: <
Color>[
Colors
.cyan[600]
.withOpacity(0.8),
Colors
.white70
],
stops: [
seekValue,
seekValue
],
tileMode:
TileMode
.mirror,
).createShader(
bounds);
},
child:
Container(
height: 25,
alignment:
Alignment
.center,
padding: EdgeInsets.symmetric(
horizontal:
20),
decoration:
BoxDecoration(
borderRadius:
BorderRadius.all(
Radius.circular(20.0)),
color: context
.accentColor,
),
child: Text(
seconds
.toTime,
style: TextStyle(
color:
Colors.white),
),
),
),
]),
), ),
), ),
]), ),
SizedBox(
child: Selector<
AudioPlayerNotifier,
Tuple2<
List<EpisodeBrief>,
bool>>(
selector: (_, audio) =>
Tuple2(
audio.queue
.playlist,
audio
.queueUpdate),
builder: (_, data, __) {
return data.item1
.contains(
episode)
? IconButton(
icon: Icon(
Icons
.playlist_add_check,
color: context
.accentColor),
onPressed:
() async {
audio.delFromPlaylist(
episode);
Fluttertoast
.showToast(
msg: s
.toastRemovePlaylist,
gravity:
ToastGravity
.BOTTOM,
);
})
: IconButton(
icon: Icon(
Icons
.playlist_add,
color: Colors
.grey[
700]),
onPressed:
() async {
audio.addToPlaylist(
episode);
Fluttertoast
.showToast(
msg: s
.toastAddPlaylist,
gravity:
ToastGravity
.BOTTOM,
);
});
},
),
), ),
), Spacer(),
), Text(
SizedBox( date.toDate(context),
child: Selector< style: TextStyle(
AudioPlayerNotifier, fontSize: 15,
Tuple2<List<EpisodeBrief>, ),
bool>>( ),
selector: (_, audio) => Tuple2( ],
audio.queue.playlist,
audio.queueUpdate),
builder: (_, data, __) {
return data.item1
.contains(episode)
? IconButton(
icon: Icon(
Icons
.playlist_add_check,
color: context
.accentColor),
onPressed: () async {
audio
.delFromPlaylist(
episode);
Fluttertoast
.showToast(
msg: s
.toastRemovePlaylist,
gravity:
ToastGravity
.BOTTOM,
);
})
: IconButton(
icon: Icon(
Icons
.playlist_add,
color: Colors
.grey[700]),
onPressed: () async {
audio.addToPlaylist(
episode);
Fluttertoast
.showToast(
msg: s
.toastAddPlaylist,
gravity:
ToastGravity
.BOTTOM,
);
});
},
), ),
), ),
Spacer(), ),
Text(
date.toDate(context),
style: TextStyle(
fontSize: 15,
),
),
],
), ),
), ),
), Divider(height: 1)
],
), ),
), );
Divider(height: 1)
],
),
);
} }
}), }),
) )

View File

@ -38,7 +38,8 @@ class DBHelper {
description TEXT, add_date INTEGER, imagePath TEXT, provider TEXT, link TEXT, description TEXT, add_date INTEGER, imagePath TEXT, provider TEXT, link TEXT,
background_image TEXT DEFAULT '', hosts TEXT DEFAULT '',update_count INTEGER DEFAULT 0, background_image TEXT DEFAULT '', hosts TEXT DEFAULT '',update_count INTEGER DEFAULT 0,
episode_count INTEGER DEFAULT 0, skip_seconds INTEGER DEFAULT 0, episode_count INTEGER DEFAULT 0, skip_seconds INTEGER DEFAULT 0,
auto_download INTEGER DEFAULT 0, skip_seconds_end INTEGER DEFAULT 0)"""); auto_download INTEGER DEFAULT 0, skip_seconds_end INTEGER DEFAULT 0,
never_update INTEGER DEFAULT 0)""");
await db await db
.execute("""CREATE TABLE Episodes(id INTEGER PRIMARY KEY,title TEXT, .execute("""CREATE TABLE Episodes(id INTEGER PRIMARY KEY,title TEXT,
enclosure_url TEXT UNIQUE, enclosure_length INTEGER, pubDate TEXT, enclosure_url TEXT UNIQUE, enclosure_length INTEGER, pubDate TEXT,
@ -62,27 +63,41 @@ class DBHelper {
"ALTER TABLE PodcastLocal ADD auto_download INTEGER DEFAULT 0"); "ALTER TABLE PodcastLocal ADD auto_download INTEGER DEFAULT 0");
await db.execute( await db.execute(
"ALTER TABLE PodcastLocal ADD skip_seconds_end INTEGER DEFAULT 0 "); "ALTER TABLE PodcastLocal ADD skip_seconds_end INTEGER DEFAULT 0 ");
await db.execute(
"ALTER TABLE PodcastLocal ADD never_update INTEGER DEFAULT 0 ");
} else if (oldVersion == 2) { } else if (oldVersion == 2) {
await db.execute( await db.execute(
"ALTER TABLE PodcastLocal ADD auto_download INTEGER DEFAULT 0"); "ALTER TABLE PodcastLocal ADD auto_download INTEGER DEFAULT 0");
await db.execute( await db.execute(
"ALTER TABLE PodcastLocal ADD skip_seconds_end INTEGER DEFAULT 0 "); "ALTER TABLE PodcastLocal ADD skip_seconds_end INTEGER DEFAULT 0 ");
await db.execute(
"ALTER TABLE PodcastLocal ADD never_update INTEGER DEFAULT 0 ");
} else if (oldVersion == 3) { } else if (oldVersion == 3) {
await db.execute( await db.execute(
"ALTER TABLE PodcastLocal ADD skip_seconds_end INTEGER DEFAULT 0 "); "ALTER TABLE PodcastLocal ADD skip_seconds_end INTEGER DEFAULT 0");
await db.execute(
"ALTER TABLE PodcastLocal ADD never_update INTEGER DEFAULT 0 ");
} }
} }
Future<List<PodcastLocal>> getPodcastLocal(List<String> podcasts) async { Future<List<PodcastLocal>> getPodcastLocal(List<String> podcasts,
{bool updateOnly = false}) async {
var dbClient = await database; var dbClient = await database;
var podcastLocal = <PodcastLocal>[]; var podcastLocal = <PodcastLocal>[];
for (var s in podcasts) { for (var s in podcasts) {
List<Map> list; List<Map> list;
list = await dbClient.rawQuery( if (updateOnly) {
"""SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath , provider, list = await dbClient.rawQuery(
"""SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath , provider,
link ,update_count, episode_count FROM PodcastLocal WHERE id = ? AND
never_update = 0""", [s]);
} else {
list = await dbClient.rawQuery(
"""SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath , provider,
link ,update_count, episode_count FROM PodcastLocal WHERE id = ?""", link ,update_count, episode_count FROM PodcastLocal WHERE id = ?""",
[s]); [s]);
}
if (list.length > 0) { if (list.length > 0) {
podcastLocal.add(PodcastLocal( podcastLocal.add(PodcastLocal(
list.first['title'], list.first['title'],
@ -101,10 +116,21 @@ class DBHelper {
return podcastLocal; return podcastLocal;
} }
Future<List<PodcastLocal>> getPodcastLocalAll() async { Future<List<PodcastLocal>> getPodcastLocalAll(
{bool updateOnly = false}) async {
var dbClient = await database; var dbClient = await database;
List<Map> list = await dbClient.rawQuery(
'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath, provider, link FROM PodcastLocal ORDER BY add_date DESC'); List<Map> list;
if (updateOnly) {
list = await dbClient.rawQuery(
"""SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath,
provider, link FROM PodcastLocal WHERE never_update = 0 ORDER BY
add_date DESC""");
} else {
list = await dbClient.rawQuery(
"""SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath,
provider, link FROM PodcastLocal ORDER BY add_date DESC""");
}
var podcastLocal = <PodcastLocal>[]; var podcastLocal = <PodcastLocal>[];
@ -131,6 +157,21 @@ class DBHelper {
return 0; return 0;
} }
Future<bool> getNeverUpdate(String id) async {
var dbClient = await database;
List<Map> list = await dbClient
.rawQuery('SELECT never_update FROM PodcastLocal WHERE id = ?', [id]);
if (list.isNotEmpty) return list.first['never_update'] == 1;
return false;
}
Future<int> saveNeverUpdate(String id, {bool boo}) async {
var dbClient = await database;
return await dbClient.rawUpdate(
"UPDATE PodcastLocal SET never_update = ? WHERE id = ?",
[boo ? 1 : 0, id]);
}
Future<int> getPodcastUpdateCounts(String id) async { Future<int> getPodcastUpdateCounts(String id) async {
var dbClient = await database; var dbClient = await database;
List<Map> list = await dbClient.rawQuery( List<Map> list = await dbClient.rawQuery(

View File

@ -58,6 +58,11 @@ class _PodcastSettingState extends State<PodcastSetting> {
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
Future<void> _setNeverUpdate(bool boo) async {
await _dbHelper.saveNeverUpdate(widget.podcastLocal.id, boo: boo);
if (mounted) setState(() {});
}
Future<void> _saveSkipSecondsStart(int seconds) async { Future<void> _saveSkipSecondsStart(int seconds) async {
await _dbHelper.saveSkipSecondsStart(widget.podcastLocal.id, seconds); await _dbHelper.saveSkipSecondsStart(widget.podcastLocal.id, seconds);
} }
@ -70,6 +75,10 @@ class _PodcastSettingState extends State<PodcastSetting> {
return await _dbHelper.getAutoDownload(id); return await _dbHelper.getAutoDownload(id);
} }
Future<bool> _getNeverUpdate(String id) async {
return await _dbHelper.getNeverUpdate(id);
}
Future<int> _getSkipSecondStart(String id) async { Future<int> _getSkipSecondStart(String id) async {
return await _dbHelper.getSkipSecondsStart(id); return await _dbHelper.getSkipSecondsStart(id);
} }
@ -217,6 +226,21 @@ class _PodcastSettingState extends State<PodcastSetting> {
), ),
); );
}), }),
FutureBuilder<bool>(
future: _getNeverUpdate(widget.podcastLocal.id),
initialData: false,
builder: (context, snapshot) {
return ListTile(
onTap: () => _setNeverUpdate(!snapshot.data),
leading: Icon(Icons.lock),
title: Text('Never update'),
trailing: Transform.scale(
scale: 0.9,
child:
Switch(value: snapshot.data, onChanged: _setNeverUpdate),
),
);
}),
FutureBuilder<int>( FutureBuilder<int>(
future: _getSkipSecondStart(widget.podcastLocal.id), future: _getSkipSecondStart(widget.podcastLocal.id),
initialData: 0, initialData: 0,
@ -246,42 +270,42 @@ class _PodcastSettingState extends State<PodcastSetting> {
}, },
onConfirm: () async { onConfirm: () async {
await _saveSkipSecondsStart(_secondsStart); await _saveSkipSecondsStart(_secondsStart);
setState(() => _showStartTimePicker = false); if (mounted) setState(() => _showStartTimePicker = false);
}, },
onChange: (value) => _secondsStart = value.inSeconds), onChange: (value) => _secondsStart = value.inSeconds),
FutureBuilder<int>( // FutureBuilder<int>(
future: _getSkipSecondEnd(widget.podcastLocal.id), // future: _getSkipSecondEnd(widget.podcastLocal.id),
initialData: 0, // initialData: 0,
builder: (context, snapshot) => ListTile( // builder: (context, snapshot) => ListTile(
onTap: () { // onTap: () {
_secondsEnd = 0; // _secondsEnd = 0;
setState(() { // setState(() {
_removeConfirm = false; // _removeConfirm = false;
_markConfirm = false; // _markConfirm = false;
_showStartTimePicker = false; // _showStartTimePicker = false;
_showEndTimePicker = !_showEndTimePicker; // _showEndTimePicker = !_showEndTimePicker;
}); // });
}, // },
leading: Icon(Icons.fast_rewind), // leading: Icon(Icons.fast_rewind),
title: Text(s.skipSecondsAtEnd), // title: Text(s.skipSecondsAtEnd),
trailing: Padding( // trailing: Padding(
padding: const EdgeInsets.only(right: 10.0), // padding: const EdgeInsets.only(right: 10.0),
child: Text(snapshot.data.toTime), // child: Text(snapshot.data.toTime),
), // ),
), // ),
), // ),
if (_showEndTimePicker) // if (_showEndTimePicker)
_TimePicker( // _TimePicker(
onCancel: () { // onCancel: () {
_secondsEnd = 0; // _secondsEnd = 0;
setState(() => _showEndTimePicker = false); // setState(() => _showEndTimePicker = false);
}, // },
onConfirm: () async { // onConfirm: () async {
await _saveSkipSecondsEnd(_secondsEnd); // await _saveSkipSecondsEnd(_secondsEnd);
setState(() => _showEndTimePicker = false); // setState(() => _showEndTimePicker = false);
}, // },
onChange: (value) => _secondsEnd = value.inSeconds, // onChange: (value) => _secondsEnd = value.inSeconds,
), // ),
ListTile( ListTile(
onTap: () { onTap: () {
if (_coverStatus != RefreshCoverStatus.start) { if (_coverStatus != RefreshCoverStatus.start) {
@ -419,7 +443,6 @@ class _TimePicker extends StatelessWidget {
children: [ children: [
SizedBox(height: 10), SizedBox(height: 10),
DurationPicker( DurationPicker(
key: key,
onChange: onChange, onChange: onChange,
), ),
Row( Row(

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'dart:io'; import 'dart:io';
import 'package:device_info/device_info.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -9,6 +10,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart'; import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import 'package:confetti/confetti.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -299,23 +301,37 @@ class _DataBackupState extends State<DataBackup> {
final loginInfo = snapshot.data; final loginInfo = snapshot.data;
if (loginInfo.isNotEmpty) { if (loginInfo.isNotEmpty) {
return ListTile( return ListTile(
contentPadding: contentPadding: const EdgeInsets.only(
const EdgeInsets.only(left: 70.0, right: 20), left: 70.0, right: 20, top: 10, bottom: 10),
onTap: _syncNow, onTap: _syncNow,
title: Text(s.syncNow), title: Text(s.syncNow),
trailing: IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => _GpodderInfo()));
},
icon: Icon(LineIcons.info_circle_solid),
),
subtitle: FutureBuilder<List<int>>( subtitle: FutureBuilder<List<int>>(
future: _getSyncStatus(), future: _getSyncStatus(),
initialData: [0, 0], initialData: [0, 0],
builder: (context, snapshot) { builder: (context, snapshot) {
final dateTime = snapshot.data[0]; final dateTime = snapshot.data[0];
final status = snapshot.data[1]; final status = snapshot.data[1];
return Wrap( return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'${s.lastUpdate}: ${dateTime.toDate(context)}'), '${s.lastUpdate}: ${dateTime.toDate(context)}'),
SizedBox(width: 8), SizedBox(width: 8),
Text('${s.status}: '), Row(
_syncStauts(status), children: [
Text('${s.status}: '),
_syncStauts(status),
],
),
], ],
); );
}), }),
@ -323,6 +339,7 @@ class _DataBackupState extends State<DataBackup> {
} }
return Center(); return Center();
}), }),
// ListTile( // ListTile(
// onTap: () async { // onTap: () async {
// final subscribeWorker = context.read<GroupList>(); // final subscribeWorker = context.read<GroupList>();
@ -402,7 +419,7 @@ class _DataBackupState extends State<DataBackup> {
], ],
), ),
), ),
Divider(), Divider(height: 1),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),
@ -568,13 +585,21 @@ class __LoginGpodderState extends State<_LoginGpodder> {
var _username = ''; var _username = '';
var _password = ''; var _password = '';
LoginStatus _loginStatus; LoginStatus _loginStatus;
ConfettiController _controller;
@override @override
void initState() { void initState() {
_loginStatus = LoginStatus.none; _loginStatus = LoginStatus.none;
_controller = ConfettiController(duration: Duration(seconds: 3));
super.initState(); super.initState();
} }
@override
void dispose() {
_controller.dispose();
super.dispose();
}
final GlobalKey<FormFieldState<String>> _passwordFieldKey = final GlobalKey<FormFieldState<String>> _passwordFieldKey =
GlobalKey<FormFieldState<String>>(); GlobalKey<FormFieldState<String>>();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
@ -601,6 +626,7 @@ class __LoginGpodderState extends State<_LoginGpodder> {
if (mounted) { if (mounted) {
setState(() { setState(() {
_loginStatus = LoginStatus.complete; _loginStatus = LoginStatus.complete;
_controller.play();
}); });
} }
} }
@ -635,9 +661,11 @@ class __LoginGpodderState extends State<_LoginGpodder> {
var rssLink = rssExp.stringMatch(rss.xmlUrl); var rssLink = rssExp.stringMatch(rss.xmlUrl);
if (rssLink != null) { if (rssLink != null) {
final dbHelper = DBHelper(); final dbHelper = DBHelper();
final exist = dbHelper.checkPodcast(rssLink); final exist = await dbHelper.checkPodcast(rssLink);
if (exist == '') { if (exist == '') {
var item = SubscribeItem(rssLink, rss.text, group: 'Home'); var item = SubscribeItem(
rssLink, rss.text == '' ? rssLink : rss.text,
group: 'Home');
await subscribeWorker.setSubscribeItem(item, syncGpodder: false); await subscribeWorker.setSubscribeItem(item, syncGpodder: false);
await Future.delayed(Duration(milliseconds: 200)); await Future.delayed(Duration(milliseconds: 200));
} }
@ -757,14 +785,37 @@ class __LoginGpodderState extends State<_LoginGpodder> {
_loginStatus == LoginStatus.complete _loginStatus == LoginStatus.complete
? SliverList( ? SliverList(
delegate: SliverChildListDelegate([ delegate: SliverChildListDelegate([
Padding( Stack(
padding: const EdgeInsets.fromLTRB(40.0, 50, 40, 100), children: [
child: Text( Padding(
s.gpodderLoginDes, padding:
textAlign: TextAlign.center, const EdgeInsets.fromLTRB(40.0, 50, 40, 100),
style: child: Text(
context.textTheme.subtitle1.copyWith(height: 2), s.gpodderLoginDes,
), textAlign: TextAlign.center,
style: context.textTheme.subtitle1
.copyWith(height: 2),
),
),
Align(
alignment: Alignment.center,
child: ConfettiWidget(
confettiController: _controller,
blastDirectionality:
BlastDirectionality.explosive,
emissionFrequency: 0.05,
maximumSize: Size(20, 10),
shouldLoop: false,
colors: const [
Colors.green,
Colors.blue,
Colors.pink,
Colors.orange,
Colors.purple
],
),
),
],
), ),
Center( Center(
child: OutlineButton( child: OutlineButton(
@ -773,7 +824,7 @@ class __LoginGpodderState extends State<_LoginGpodder> {
}, },
highlightedBorderColor: context.accentColor, highlightedBorderColor: context.accentColor,
child: Text(s.back)), child: Text(s.back)),
) ),
]), ]),
) )
: Form( : Form(
@ -922,3 +973,152 @@ class _PasswordFieldState extends State<PasswordField> {
); );
} }
} }
class _GpodderInfo extends StatefulWidget {
_GpodderInfo({Key key}) : super(key: key);
@override
__GpodderInfoState createState() => __GpodderInfoState();
}
class __GpodderInfoState extends State<_GpodderInfo> {
final _gpodder = Gpodder();
var _syncing = false;
Future<List<String>> _getLoginInfo() async {
final storage = KeyValueStorage(gpodderApiKey);
final androidInfo = await DeviceInfoPlugin().androidInfo;
final deviceInfo = await storage.getStringList();
deviceInfo.add("Tsacdop on ${androidInfo.model}");
return deviceInfo;
}
Future<void> _fullSync() async {
if (mounted) {
setState(() {
_syncing = true;
});
}
final uploadStatus = await _gpodder.uploadSubscriptions();
if (uploadStatus == 200) {
var subscribeWorker = context.read<GroupList>();
var rssExp = RegExp(r'^(https?):\/\/(.*)');
final opml = await _gpodder.getAllPodcast();
if (opml != '') {
Map<String, List<OmplOutline>> data = PodcastsBackup.parseOMPL(opml);
for (var entry in data.entries) {
var list = entry.value.reversed;
for (var rss in list) {
var rssLink = rssExp.stringMatch(rss.xmlUrl);
if (rssLink != null) {
final dbHelper = DBHelper();
final exist = await dbHelper.checkPodcast(rssLink);
if (exist == '') {
var item = SubscribeItem(
rssLink, rss.text == '' ? rssLink : rss.text,
group: 'Home');
await subscribeWorker.setSubscribeItem(item,
syncGpodder: false);
await Future.delayed(Duration(milliseconds: 200));
}
}
}
}
}
}
//await _syncNow();
if (mounted) {
setState(() {
_syncing = false;
});
}
}
@override
Widget build(BuildContext context) {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.dark,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
child: Scaffold(
resizeToAvoidBottomInset: true,
body: SafeArea(
top: false,
child: CustomScrollView(
slivers: [
SliverAppBar(
brightness: Brightness.dark,
iconTheme: IconThemeData(
color: Colors.white,
),
elevation: 0,
backgroundColor: context.accentColor,
expandedHeight: 200,
flexibleSpace: Container(
height: 200,
width: double.infinity,
color: context.accentColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
CircleAvatar(
minRadius: 50,
backgroundColor: context.primaryColor.withOpacity(0.3),
child: SizedBox(
height: 80,
width: 80,
child: Image.asset('assets/gpodder.png')),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(s.intergateWith('gpodder.net'),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold)),
),
],
),
),
),
SliverList(
delegate: SliverChildListDelegate([
FutureBuilder<List<String>>(
future: _getLoginInfo(),
initialData: [],
builder: (context, snapshot) {
final deviceId =
snapshot.data.isNotEmpty ? snapshot.data[1] : '';
final deviceName =
snapshot.data.isNotEmpty ? snapshot.data[3] : '';
return Column(
children: [
ListTile(
title: Text('Divice id'),
subtitle: Text(deviceId),
),
ListTile(
title: Text('Divice name'),
subtitle: Text(deviceName),
),
],
);
}),
ListTile(
onTap: _fullSync,
// contentPadding:
// const EdgeInsets.only(left: 70.0, right: 20),
title: Text('Full sync'),
subtitle: Text('If sync have error')),
]),
),
],
),
),
),
);
}
}

View File

@ -218,7 +218,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
volumeGainStorage.saveInt(volumeGain); volumeGainStorage.saveInt(volumeGain);
} }
Future _initAudioData() async { Future<void> _initAudioData() async {
var index = await playerHeightStorage.getInt(defaultValue: 0); var index = await playerHeightStorage.getInt(defaultValue: 0);
_playerHeight = PlayerHeight.values[index]; _playerHeight = PlayerHeight.values[index];
_currentSpeed = await speedStorage.getDoubel(defaultValue: 1.0); _currentSpeed = await speedStorage.getDoubel(defaultValue: 1.0);
@ -250,8 +250,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
void addListener(VoidCallback listener) { void addListener(VoidCallback listener) {
super.addListener(listener); super.addListener(listener);
_initAudioData(); _initAudioData();
// _queueUpdate = false;
// _getAutoSleepTimer();
AudioService.connect(); AudioService.connect();
var running = AudioService.running; var running = AudioService.running;
if (running) {} if (running) {}
@ -265,7 +263,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
if (_lastPostion > 0 && _queue.playlist.length > 0) { if (_lastPostion > 0 && _queue.playlist.length > 0) {
final episode = _queue.playlist.first; final episode = _queue.playlist.first;
final duration = episode.duration * 1000; final duration = episode.duration * 1000;
final seekValue = duration != 0 ? _lastPostion / duration : 1; final seekValue = duration != 0 ? _lastPostion / duration : 1.0;
final history = PlayHistory( final history = PlayHistory(
episode.title, episode.enclosureUrl, _lastPostion ~/ 1000, seekValue); episode.title, episode.enclosureUrl, _lastPostion ~/ 1000, seekValue);
await dbHelper.saveHistory(history); await dbHelper.saveHistory(history);
@ -319,7 +317,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
} }
} }
_startAudioService(int position, String url) async { Future<void> _startAudioService(int position, String url) async {
_stopOnComplete = false; _stopOnComplete = false;
_sleepTimerMode = SleepTimerMode.undefined; _sleepTimerMode = SleepTimerMode.undefined;
_switchValue = 0; _switchValue = 0;
@ -439,27 +437,26 @@ class AudioPlayerNotifier extends ChangeNotifier {
}); });
AudioService.customEventStream.distinct().listen((event) async { AudioService.customEventStream.distinct().listen((event) async {
if (event is String && _episode.title == event) { if (event is String &&
_queue.playlist.isNotEmpty &&
_queue.playlist.first.title == event) {
_queue.delFromPlaylist(_episode); _queue.delFromPlaylist(_episode);
_lastPostion = 0; _lastPostion = 0;
notifyListeners(); notifyListeners();
await positionStorage.saveInt(_lastPostion); await positionStorage.saveInt(_lastPostion);
if (_lastPostion == 0) { final history = PlayHistory(_episode.title, _episode.enclosureUrl,
_backgroundAudioPosition ~/ 1000, _seekSliderValue);
await dbHelper.saveHistory(history);
}
if (event is Map && event['playerRunning'] == false && _playerRunning) {
_playerRunning = false;
notifyListeners();
if (_lastPostion > 0) {
final history = PlayHistory(_episode.title, _episode.enclosureUrl, final history = PlayHistory(_episode.title, _episode.enclosureUrl,
_backgroundAudioPosition ~/ 1000, _seekSliderValue); _lastPostion ~/ 1000, _seekSliderValue);
await dbHelper.saveHistory(history); await dbHelper.saveHistory(history);
} }
} _episode = null;
if (event is Map && event['playerRunning'] == false) {
if (_playerRunning) {
_playerRunning = false;
notifyListeners();
if (_lastPostion > 0) {
final history = PlayHistory(_episode.title, _episode.enclosureUrl,
_lastPostion ~/ 1000, _seekSliderValue);
await dbHelper.saveHistory(history);
}
}
} }
}); });
@ -531,7 +528,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
Future<void> addNewEpisode(List<String> group) async { Future<void> addNewEpisode(List<String> group) async {
var newEpisodes = <EpisodeBrief>[]; var newEpisodes = <EpisodeBrief>[];
if (group.first == 'All') { if (group.isEmpty) {
newEpisodes = await dbHelper.getRecentNewRssItem(); newEpisodes = await dbHelper.getRecentNewRssItem();
} else { } else {
newEpisodes = await dbHelper.getGroupNewRssItem(group); newEpisodes = await dbHelper.getGroupNewRssItem(group);
@ -541,14 +538,14 @@ class AudioPlayerNotifier extends ChangeNotifier {
await addToPlaylist(episode); await addToPlaylist(episode);
} }
} }
if (group.first == 'All') { if (group.isEmpty) {
await dbHelper.removeAllNewMark(); await dbHelper.removeAllNewMark();
} else { } else {
await dbHelper.removeGroupNewMark(group); await dbHelper.removeGroupNewMark(group);
} }
} }
updateMediaItem(EpisodeBrief episode) async { Future<void> updateMediaItem(EpisodeBrief episode) async {
if (episode.enclosureUrl == episode.mediaId) { if (episode.enclosureUrl == episode.mediaId) {
var index = _queue.playlist var index = _queue.playlist
.indexWhere((item) => item.enclosureUrl == episode.enclosureUrl); .indexWhere((item) => item.enclosureUrl == episode.enclosureUrl);
@ -867,7 +864,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
await AudioServiceBackground.setQueue(_queue); await AudioServiceBackground.setQueue(_queue);
if (_queue.length == 0 || _stopAtEnd) { if (_queue.length == 0 || _stopAtEnd) {
_skipState = null; _skipState = null;
onStop(); await Future.delayed(Duration(milliseconds: 200));
await onStop();
} else { } else {
await AudioServiceBackground.setQueue(_queue); await AudioServiceBackground.setQueue(_queue);
await AudioServiceBackground.setMediaItem(mediaItem); await AudioServiceBackground.setMediaItem(mediaItem);
@ -878,13 +876,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
mediaItem.copyWith(duration: duration)); mediaItem.copyWith(duration: duration));
} }
_skipState = null; _skipState = null;
// Resume playback if we were playing
// if (_playing) {
//onPlay();
_playFromStart(); _playFromStart();
// } else {
// _setState(state: BasicPlaybackState.paused);
// }
} }
} }
@ -922,10 +914,11 @@ class AudioPlayerTask extends BackgroundAudioTask {
_session.setActive(true); _session.setActive(true);
if (mediaItem.extras['skipSecondsStart'] > 0 || if (mediaItem.extras['skipSecondsStart'] > 0 ||
mediaItem.extras['skipSecondsEnd'] > 0) { mediaItem.extras['skipSecondsEnd'] > 0) {
//_audioPlayer.seek(Duration(seconds: mediaItem.extras['skip'])); _audioPlayer
_audioPlayer.setClip( .seek(Duration(seconds: mediaItem.extras['skipSecondsStart']));
start: Duration(seconds: mediaItem.extras['skipSecondsStart']), // await _audioPlayer.setClip(
end: Duration(seconds: mediaItem.extras['skipSecondsEnd'])); // start: Duration(seconds: mediaItem.extras['skipSecondsStart']),
// );
} }
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting || if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting ||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none) { _audioPlayer.playbackEvent.state != AudioPlaybackState.none) {

View File

@ -152,14 +152,14 @@ class SubscribeItem {
///Podcast group, default Home. ///Podcast group, default Home.
String group; String group;
///sync to gpodder SubscribeItem(
bool syncWithGpodder; this.url,
SubscribeItem(this.url, this.title, this.title, {
{this.subscribeState = SubscribeState.none, this.subscribeState = SubscribeState.none,
this.id = '', this.id = '',
this.imgUrl = '', this.imgUrl = '',
this.group = '', this.group = '',
this.syncWithGpodder = true}); });
} }
class GroupList extends ChangeNotifier { class GroupList extends ChangeNotifier {
@ -219,7 +219,7 @@ class GroupList extends ChangeNotifier {
} }
Future _start() async { Future _start() async {
if (_created == false) { if (!_created) {
await _createIsolate(); await _createIsolate();
_created = true; _created = true;
listen(); listen();
@ -229,7 +229,6 @@ class GroupList extends ChangeNotifier {
_subscribeItem.title, _subscribeItem.title,
_subscribeItem.imgUrl, _subscribeItem.imgUrl,
_subscribeItem.group, _subscribeItem.group,
_subscribeItem.syncWithGpodder
]); ]);
} }
} }
@ -250,7 +249,6 @@ class GroupList extends ChangeNotifier {
_subscribeItem.title, _subscribeItem.title,
_subscribeItem.imgUrl, _subscribeItem.imgUrl,
_subscribeItem.group, _subscribeItem.group,
_subscribeItem.syncWithGpodder
]); ]);
} else if (message is List) { } else if (message is List) {
_setCurrentSubscribeItem(SubscribeItem( _setCurrentSubscribeItem(SubscribeItem(
@ -717,7 +715,7 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
subReceivePort.distinct().listen((message) { subReceivePort.distinct().listen((message) {
if (message is List<dynamic>) { if (message is List<dynamic>) {
items.add(SubscribeItem(message[0], message[1], items.add(SubscribeItem(message[0], message[1],
imgUrl: message[2], group: message[3], syncWithGpodder: message[4])); imgUrl: message[2], group: message[3]));
if (!_running) { if (!_running) {
_subscribe(items.first); _subscribe(items.first);
_running = true; _running = true;

View File

@ -34,8 +34,12 @@ class RefreshWorker extends ChangeNotifier {
refreshIsolateEntryPoint, receivePort.sendPort); refreshIsolateEntryPoint, receivePort.sendPort);
} }
void _listen() { void _listen(List<String> podcasts) {
receivePort.distinct().listen((message) { receivePort.distinct().listen((message) {
if (message is SendPort) {
refreshSendPort = message;
refreshSendPort.send(podcasts);
}
if (message is List) { if (message is List) {
_currentRefreshItem = _currentRefreshItem =
RefreshItem(message[0], RefreshState.values[message[1]]); RefreshItem(message[0], RefreshState.values[message[1]]);
@ -51,11 +55,11 @@ class RefreshWorker extends ChangeNotifier {
}); });
} }
Future<void> start() async { Future<void> start(List<String> podcasts) async {
if (!_created) { if (!_created) {
_complete = false; _complete = false;
_createIsolate(); await _createIsolate();
_listen(); _listen(podcasts);
_created = true; _created = true;
} }
} }
@ -68,14 +72,30 @@ class RefreshWorker extends ChangeNotifier {
} }
Future<void> refreshIsolateEntryPoint(SendPort sendPort) async { Future<void> refreshIsolateEntryPoint(SendPort sendPort) async {
var refreshstorage = KeyValueStorage(refreshdateKey); var refreshReceivePort = ReceivePort();
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch); sendPort.send(refreshReceivePort.sendPort);
var dbHelper = DBHelper(); var _dbHelper = DBHelper();
var podcastList = await dbHelper.getPodcastLocalAll();
for (var podcastLocal in podcastList) { Future<void> _refreshAll(List<String> podcasts) async {
sendPort.send([podcastLocal.title, 1]); var podcastList;
var updateCount = await dbHelper.updatePodcastRss(podcastLocal); if (podcasts.isEmpty) {
developer.log('Refresh ${podcastLocal.title}$updateCount'); var refreshstorage = KeyValueStorage(refreshdateKey);
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
podcastList = await _dbHelper.getPodcastLocalAll(updateOnly: true);
} else {
podcastList = await _dbHelper.getPodcastLocal(podcasts, updateOnly: true);
}
for (var podcastLocal in podcastList) {
sendPort.send([podcastLocal.title, 1]);
var updateCount = await _dbHelper.updatePodcastRss(podcastLocal);
developer.log('Refresh ${podcastLocal.title}$updateCount');
}
sendPort.send("done");
} }
sendPort.send("done");
refreshReceivePort.distinct().listen((message) {
if (message is List<dynamic>) {
_refreshAll(message);
}
});
} }

View File

@ -21,7 +21,7 @@ void callbackDispatcher() {
if (Platform.isAndroid) { if (Platform.isAndroid) {
Workmanager.executeTask((task, inputData) async { Workmanager.executeTask((task, inputData) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
var podcastList = await dbHelper.getPodcastLocalAll(); var podcastList = await dbHelper.getPodcastLocalAll(updateOnly: false);
//lastWork is a indicator for if the app was opened since last backgroundwork //lastWork is a indicator for if the app was opened since last backgroundwork
//if the app wes opend,then the old marked new episode would be marked not new. //if the app wes opend,then the old marked new episode would be marked not new.
var lastWorkStorage = KeyValueStorage(lastWorkKey); var lastWorkStorage = KeyValueStorage(lastWorkKey);

View File

@ -1,7 +1,7 @@
name: tsacdop name: tsacdop
description: An open source podacasts player. description: An open source podacasts player.
version: 0.4.17+34 version: 0.4.18+35
environment: environment:
sdk: ">=2.6.0 <3.0.0" sdk: ">=2.6.0 <3.0.0"
@ -15,7 +15,8 @@ dependencies:
audio_session: ^0.0.7 audio_session: ^0.0.7
cached_network_image: ^2.3.2+1 cached_network_image: ^2.3.2+1
color_thief_flutter: ^1.0.2 color_thief_flutter: ^1.0.2
cookie_jar: ^1.0.0 confetti: ^0.5.4+1
cookie_jar: ^1.0.1
cupertino_icons: ^1.0.0 cupertino_icons: ^1.0.0
connectivity: ^0.4.9 connectivity: ^0.4.9
device_info: ^0.4.2+7 device_info: ^0.4.2+7
@ -25,7 +26,7 @@ dependencies:
effective_dart: ^1.2.4 effective_dart: ^1.2.4
equatable: ^1.2.5 equatable: ^1.2.5
feature_discovery: ^0.10.0 feature_discovery: ^0.10.0
file_picker: ^2.0.0 file_picker: ^2.0.1+2
flutter_html: ^0.11.1 flutter_html: ^0.11.1
flutter_downloader: ^1.5.0 flutter_downloader: ^1.5.0
fluttertoast: ^4.0.0 fluttertoast: ^4.0.0
@ -36,18 +37,18 @@ dependencies:
fl_chart: ^0.11.1 fl_chart: ^0.11.1
marquee: ^1.3.1 marquee: ^1.3.1
google_fonts: ^1.1.0 google_fonts: ^1.1.0
image: ^2.1.14 image: ^2.1.17
intl: ^0.16.1 intl: ^0.16.1
json_serializable: ^3.4.1 json_serializable: ^3.5.0
json_annotation: ^3.0.1 json_annotation: ^3.1.0
path_provider: ^1.6.16 path_provider: ^1.6.18
permission_handler: ^5.0.1 permission_handler: ^5.0.1
provider: ^4.3.2 provider: ^4.3.2
rxdart: ^0.24.1 rxdart: ^0.24.1
sqflite: ^1.3.1 sqflite: ^1.3.1
shared_preferences: ^0.5.10 shared_preferences: ^0.5.12
tuple: ^1.0.3 tuple: ^1.0.3
url_launcher: ^5.6.0 url_launcher: ^5.7.1
uuid: ^2.2.2 uuid: ^2.2.2
xml: ^4.2.0 xml: ^4.2.0
workmanager: ^0.2.3 workmanager: ^0.2.3