Popup menu setting
Auto download on work Add rewind when using headset Fixed audio auto play when notification come
This commit is contained in:
parent
1a497a78ed
commit
935566b304
|
@ -49,7 +49,7 @@ android {
|
|||
applicationId "com.stonegate.tsacdop"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 29
|
||||
versionCode 16
|
||||
versionCode 17
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
|
|
@ -55,10 +55,12 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
setState(() {
|
||||
_showMenu = true;
|
||||
});
|
||||
} else
|
||||
} else if (_controller.offset <
|
||||
_controller.position.maxScrollExtent * 0.8) {
|
||||
setState(() {
|
||||
_showMenu = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_launchUrl(String url) async {
|
||||
|
@ -113,7 +115,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
PopupMenuButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10))),
|
||||
elevation: 2,
|
||||
elevation: 1,
|
||||
tooltip: 'Menu',
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
|
@ -152,6 +154,8 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
),
|
||||
|
@ -323,8 +327,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
'Still no shownote received\n for this episode.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: context.textTheme
|
||||
.bodyText1.color
|
||||
color: context.textColor
|
||||
.withOpacity(0.5))),
|
||||
],
|
||||
),
|
||||
|
@ -381,7 +384,7 @@ class MenuBar extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _MenuBarState extends State<MenuBar> {
|
||||
bool _liked;
|
||||
bool _liked = false;
|
||||
|
||||
Future<PlayHistory> getPosition(EpisodeBrief episode) async {
|
||||
var dbHelper = DBHelper();
|
||||
|
@ -401,7 +404,6 @@ class _MenuBarState extends State<MenuBar> {
|
|||
if (result == 1 && mounted)
|
||||
setState(() {
|
||||
_liked = false;
|
||||
// _like = 0;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
@ -526,12 +528,10 @@ class _MenuBarState extends State<MenuBar> {
|
|||
},
|
||||
),
|
||||
DownloadButton(episode: widget.episodeItem),
|
||||
Selector<AudioPlayerNotifier, List<String>>(
|
||||
selector: (_, audio) => audio.queue.playlist
|
||||
.map((e) => e.enclosureUrl)
|
||||
.toList(),
|
||||
Selector<AudioPlayerNotifier, List<EpisodeBrief>>(
|
||||
selector: (_, audio) => audio.queue.playlist,
|
||||
builder: (_, data, __) {
|
||||
return data.contains(widget.episodeItem.enclosureUrl)
|
||||
return data.contains(widget.episodeItem)
|
||||
? _buttonOnMenu(
|
||||
Icon(Icons.playlist_add_check,
|
||||
color: Theme.of(context).accentColor), () {
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'dart:ui';
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
@ -13,6 +12,7 @@ import '../state/download_state.dart';
|
|||
import '../state/audiostate.dart';
|
||||
import '../state/settingstate.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../util/general_dialog.dart';
|
||||
|
||||
class DownloadButton extends StatefulWidget {
|
||||
final EpisodeBrief episode;
|
||||
|
@ -107,54 +107,31 @@ class _DownloadButtonState extends State<DownloadButton> {
|
|||
|
||||
Future<bool> _useDataConfirem() async {
|
||||
bool ifUseData = false;
|
||||
await showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
barrierColor: Colors.black54,
|
||||
transitionDuration: const Duration(milliseconds: 200),
|
||||
pageBuilder: (BuildContext context, Animation animaiton,
|
||||
Animation secondaryAnimation) =>
|
||||
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarColor:
|
||||
Theme.of(context).brightness == Brightness.light
|
||||
? Color.fromRGBO(113, 113, 113, 1)
|
||||
: Color.fromRGBO(15, 15, 15, 1),
|
||||
await generalDialog(
|
||||
context,
|
||||
title: Text('Cellular data warn'),
|
||||
content: Text('Are you sure you want to use cellular data to download?'),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'CANCEL',
|
||||
style: TextStyle(color: Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
child: AlertDialog(
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10.0))),
|
||||
titlePadding:
|
||||
EdgeInsets.only(top: 20, left: 20, right: 100, bottom: 20),
|
||||
title: Text('Cellular data warn'),
|
||||
content:
|
||||
Text('Are you sure you want to use cellular data to download?'),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'CANCEL',
|
||||
style: TextStyle(color: Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
ifUseData = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'CONFIRM',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
ifUseData = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'CONFIRM',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
return ifUseData;
|
||||
}
|
||||
|
|
|
@ -8,9 +8,13 @@ import 'intl/messages_all.dart';
|
|||
// Made by Localizely
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars
|
||||
|
||||
class S {
|
||||
S();
|
||||
|
||||
static S current;
|
||||
|
||||
static const AppLocalizationDelegate delegate =
|
||||
AppLocalizationDelegate();
|
||||
|
||||
|
@ -19,7 +23,9 @@ class S {
|
|||
final localeName = Intl.canonicalizedLocale(name);
|
||||
return initializeMessages(localeName).then((_) {
|
||||
Intl.defaultLocale = localeName;
|
||||
return S();
|
||||
S.current = S();
|
||||
|
||||
return S.current;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import 'package:line_icons/line_icons.dart';
|
|||
|
||||
import '../util/context_extension.dart';
|
||||
|
||||
const String version = '0.3.3';
|
||||
const String version = '0.3.4';
|
||||
|
||||
class AboutApp extends StatelessWidget {
|
||||
_launchUrl(String url) async {
|
||||
|
|
|
@ -238,7 +238,7 @@ class _SearchListState extends State<SearchList> {
|
|||
Future<List> _getList(String searchText, int nextOffset) async {
|
||||
String apiKey = environment['apiKey'];
|
||||
String url = "https://listen-api.listennotes.com/api/v2/search?q=" +
|
||||
searchText +
|
||||
Uri.encodeComponent(searchText) +
|
||||
"&sort_by_date=0&type=podcast&offset=$nextOffset";
|
||||
Response response = await Dio().get(url,
|
||||
options: Options(headers: {
|
||||
|
|
|
@ -1050,168 +1050,183 @@ class _MyFavoriteState extends State<_MyFavorite>
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getLikedRssItem(_top, _sortBy),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) print(snapshot.error);
|
||||
return (snapshot.hasData)
|
||||
? snapshot.data.length == 0
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(top: 150),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Icon(LineIcons.heartbeat_solid,
|
||||
size: 80, color: Colors.grey[500]),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 10)),
|
||||
Text(
|
||||
'No episode collected yet',
|
||||
style: TextStyle(color: Colors.grey[500]),
|
||||
return Selector<AudioPlayerNotifier, bool>(
|
||||
selector: (_, audio) => audio.episodeState,
|
||||
builder: (context, episodeState, child) {
|
||||
return FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getLikedRssItem(_top, _sortBy),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) print(snapshot.error);
|
||||
return (snapshot.hasData)
|
||||
? snapshot.data.length == 0
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(top: 150),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Icon(LineIcons.heartbeat_solid,
|
||||
size: 80, color: Colors.grey[500]),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10)),
|
||||
Text(
|
||||
'No episode collected yet',
|
||||
style: TextStyle(color: Colors.grey[500]),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
: NotificationListener<ScrollNotification>(
|
||||
onNotification: (ScrollNotification scrollInfo) {
|
||||
if (scrollInfo.metrics.pixels ==
|
||||
scrollInfo.metrics.maxScrollExtent &&
|
||||
snapshot.data.length == _top) _loadMoreEpisode();
|
||||
return true;
|
||||
},
|
||||
child: CustomScrollView(
|
||||
key: PageStorageKey<String>('favorite'),
|
||||
slivers: <Widget>[
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
height: 40,
|
||||
color: context.primaryColor,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: PopupMenuButton<int>(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10))),
|
||||
elevation: 1,
|
||||
tooltip: 'Sort By',
|
||||
child: Container(
|
||||
height: 50,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 20),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text('Sory by'),
|
||||
Padding(
|
||||
: NotificationListener<ScrollNotification>(
|
||||
onNotification: (ScrollNotification scrollInfo) {
|
||||
if (scrollInfo.metrics.pixels ==
|
||||
scrollInfo.metrics.maxScrollExtent &&
|
||||
snapshot.data.length == _top)
|
||||
_loadMoreEpisode();
|
||||
return true;
|
||||
},
|
||||
child: CustomScrollView(
|
||||
key: PageStorageKey<String>('favorite'),
|
||||
slivers: <Widget>[
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
height: 40,
|
||||
color: context.primaryColor,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: PopupMenuButton<int>(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10))),
|
||||
elevation: 1,
|
||||
tooltip: 'Sort By',
|
||||
child: Container(
|
||||
height: 50,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 5),
|
||||
horizontal: 20),
|
||||
child: Row(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text('Sory by'),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.symmetric(
|
||||
horizontal: 5),
|
||||
),
|
||||
Icon(
|
||||
_sortBy == 0
|
||||
? LineIcons
|
||||
.cloud_download_alt_solid
|
||||
: LineIcons
|
||||
.heartbeat_solid,
|
||||
size: 18,
|
||||
)
|
||||
],
|
||||
)),
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 0,
|
||||
child: Text('Update Date'),
|
||||
),
|
||||
Icon(
|
||||
_sortBy == 0
|
||||
? LineIcons
|
||||
.cloud_download_alt_solid
|
||||
: LineIcons.heartbeat_solid,
|
||||
size: 18,
|
||||
PopupMenuItem(
|
||||
value: 1,
|
||||
child: Text('Like Date'),
|
||||
)
|
||||
],
|
||||
)),
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 0,
|
||||
child: Text('Update Date'),
|
||||
onSelected: (value) {
|
||||
if (value == 0)
|
||||
setState(() => _sortBy = 0);
|
||||
else if (value == 1)
|
||||
setState(() => _sortBy = 1);
|
||||
},
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (_layout == Layout.three)
|
||||
setState(() {
|
||||
_layout = Layout.one;
|
||||
});
|
||||
else if (_layout == Layout.two)
|
||||
setState(() {
|
||||
_layout = Layout.three;
|
||||
});
|
||||
else
|
||||
setState(() {
|
||||
_layout = Layout.two;
|
||||
});
|
||||
},
|
||||
icon: _layout == Layout.three
|
||||
? SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
0,
|
||||
context
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.color),
|
||||
),
|
||||
)
|
||||
: _layout == Layout.two
|
||||
? SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
1,
|
||||
context
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.color),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
4,
|
||||
context
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.color),
|
||||
),
|
||||
)),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 1,
|
||||
child: Text('Like Date'),
|
||||
)
|
||||
],
|
||||
onSelected: (value) {
|
||||
if (value == 0)
|
||||
setState(() => _sortBy = 0);
|
||||
else if (value == 1)
|
||||
setState(() => _sortBy = 1);
|
||||
},
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (_layout == Layout.three)
|
||||
setState(() {
|
||||
_layout = Layout.one;
|
||||
});
|
||||
else if (_layout == Layout.two)
|
||||
setState(() {
|
||||
_layout = Layout.three;
|
||||
});
|
||||
else
|
||||
setState(() {
|
||||
_layout = Layout.two;
|
||||
});
|
||||
},
|
||||
icon: _layout == Layout.three
|
||||
? SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
0,
|
||||
context.textTheme
|
||||
.bodyText1.color),
|
||||
),
|
||||
)
|
||||
: _layout == Layout.two
|
||||
? SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
1,
|
||||
context.textTheme
|
||||
.bodyText1.color),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
4,
|
||||
context.textTheme
|
||||
.bodyText1.color),
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
EpisodeGrid(
|
||||
episodes: snapshot.data,
|
||||
layout: _layout,
|
||||
initNum: 0,
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return _loadMore
|
||||
? Container(
|
||||
height: 2,
|
||||
child: LinearProgressIndicator())
|
||||
: Center();
|
||||
},
|
||||
childCount: 1,
|
||||
)),
|
||||
),
|
||||
EpisodeGrid(
|
||||
episodes: snapshot.data,
|
||||
layout: _layout,
|
||||
initNum: 0,
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return _loadMore
|
||||
? Container(
|
||||
height: 2,
|
||||
child: LinearProgressIndicator())
|
||||
: Center();
|
||||
},
|
||||
childCount: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Center();
|
||||
},
|
||||
);
|
||||
)
|
||||
: Center();
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'package:feature_discovery/feature_discovery.dart';
|
|||
import '../type/episodebrief.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../state/subscribe_podcast.dart';
|
||||
import '../state/download_state.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../state/audiostate.dart';
|
||||
import '../util/custompaint.dart';
|
||||
|
@ -19,6 +20,7 @@ import '../util/pageroute.dart';
|
|||
import '../util/colorize.dart';
|
||||
import '../util/context_extension.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../episodes/episodedetail.dart';
|
||||
import '../podcasts/podcastdetail.dart';
|
||||
import '../podcasts/podcastmanage.dart';
|
||||
|
@ -478,11 +480,61 @@ class ShowEpisode extends StatelessWidget {
|
|||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
Future<int> _isListened(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
return await dbHelper.isListened(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
Future<bool> _isLiked(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
return await dbHelper.isLiked(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
Future<List<int>> _getEpisodeMenu() async {
|
||||
KeyValueStorage popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
|
||||
List<int> list = await popupMenuStorage.getMenu();
|
||||
return list;
|
||||
}
|
||||
|
||||
Future<bool> _isDownloaded(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
return await dbHelper.isDownloaded(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
_markListened(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
bool marked = await dbHelper.checkMarked(episode);
|
||||
if (!marked) {
|
||||
final PlayHistory history =
|
||||
PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
await dbHelper.saveHistory(history);
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> _saveLiked(String url) async {
|
||||
var dbHelper = DBHelper();
|
||||
int result = await dbHelper.setLiked(url);
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<int> _setUnliked(String url) async {
|
||||
var dbHelper = DBHelper();
|
||||
int result = await dbHelper.setUniked(url);
|
||||
return result;
|
||||
}
|
||||
|
||||
_showPopupMenu(Offset offset, EpisodeBrief episode, BuildContext context,
|
||||
bool isPlaying, bool isInPlaylist) async {
|
||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||
double left = offset.dx;
|
||||
double top = offset.dy;
|
||||
bool isLiked, isDownload;
|
||||
int isListened;
|
||||
var downloader = Provider.of<DownloadState>(context, listen: false);
|
||||
List<int> menuList = await _getEpisodeMenu();
|
||||
if (menuList.contains(3)) isListened = await _isListened(episode);
|
||||
if (menuList.contains(2)) isLiked = await _isLiked(episode);
|
||||
if (menuList.contains(4)) isDownload = await _isDownloaded(episode);
|
||||
await showMenu<int>(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10))),
|
||||
|
@ -506,39 +558,134 @@ class ShowEpisode extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 1,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
LineIcons.clock_solid,
|
||||
color: Colors.red,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
!isInPlaylist ? Text('Later') : Text('Remove')
|
||||
],
|
||||
)),
|
||||
menuList.contains(1)
|
||||
? PopupMenuItem(
|
||||
value: 1,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
LineIcons.clock_solid,
|
||||
color: Colors.red,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
!isInPlaylist ? Text('Later') : Text('Remove')
|
||||
],
|
||||
))
|
||||
: null,
|
||||
menuList.contains(2)
|
||||
? PopupMenuItem(
|
||||
value: 2,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(LineIcons.heart, color: Colors.red, size: 21),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
isLiked
|
||||
? Text(
|
||||
'Unlike',
|
||||
)
|
||||
: Text('Like')
|
||||
],
|
||||
))
|
||||
: null,
|
||||
menuList.contains(3)
|
||||
? PopupMenuItem(
|
||||
value: 3,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
width: 23,
|
||||
height: 23,
|
||||
child: CustomPaint(
|
||||
painter:
|
||||
ListenedAllPainter(Colors.blue, stroke: 1.5)),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
isListened > 0.95
|
||||
? Text('Listened',
|
||||
style: TextStyle(
|
||||
color: context.textColor.withOpacity(0.5)))
|
||||
: Text('Mark\nListened')
|
||||
],
|
||||
))
|
||||
: null,
|
||||
menuList.contains(4)
|
||||
? PopupMenuItem(
|
||||
value: 4,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(LineIcons.download_solid, color: Colors.green),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
isDownload
|
||||
? Text('Downloaded',
|
||||
style: TextStyle(
|
||||
color: context.textColor.withOpacity(0.5)))
|
||||
: Text('Download')
|
||||
],
|
||||
))
|
||||
: null,
|
||||
],
|
||||
elevation: 5.0,
|
||||
).then((value) {
|
||||
if (value == 0) {
|
||||
if (!isPlaying) audio.episodeLoad(episode);
|
||||
} else if (value == 1) {
|
||||
if (!isInPlaylist) {
|
||||
audio.addToPlaylist(episode);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added to playlist',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
audio.delFromPlaylist(episode);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Removed from playlist',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
).then((value) async {
|
||||
switch (value) {
|
||||
case 0:
|
||||
if (!isPlaying) audio.episodeLoad(episode);
|
||||
break;
|
||||
case 1:
|
||||
if (!isInPlaylist) {
|
||||
audio.addToPlaylist(episode);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added to playlist',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
audio.delFromPlaylist(episode);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Removed from playlist',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (isLiked) {
|
||||
await _setUnliked(episode.enclosureUrl);
|
||||
audio.setEpisodeState = true;
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Unliked',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
await _saveLiked(episode.enclosureUrl);
|
||||
audio.setEpisodeState = true;
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Liked',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (isListened < 0.95) {
|
||||
await _markListened(episode);
|
||||
audio.setEpisodeState = true;
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Mark listened',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
if (!isDownload) downloader.startTask(episode);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ const String podcastLayoutKey = 'podcastLayoutKey';
|
|||
const String recentLayoutKey = 'recentLayoutKey';
|
||||
const String favLayoutKey = 'favLayoutKey';
|
||||
const String downloadLayoutKey = 'downloadLayoutKey';
|
||||
const String autoDownloadNetworkKey = 'autoDownloadNetwork';
|
||||
const String episodePopupMenuKey = 'episodePopupMenuKey';
|
||||
|
||||
class KeyValueStorage {
|
||||
final String key;
|
||||
|
@ -88,4 +90,18 @@ class KeyValueStorage {
|
|||
}
|
||||
return prefs.getString(key);
|
||||
}
|
||||
|
||||
saveMenu(List<int> list) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setStringList(key, list.map((e) => e.toString()).toList());
|
||||
}
|
||||
|
||||
Future<List<int>> getMenu() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getStringList(key) == null) {
|
||||
await prefs.setStringList(key, ['0', '1', '12', '13', '14']);
|
||||
}
|
||||
List<String> list = prefs.getStringList(key);
|
||||
return list.map((e) => int.parse(e)).toList();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -382,7 +382,6 @@ class DBHelper {
|
|||
String month = mmDd.stringMatch(pubDate);
|
||||
date = DateFormat('yyyy-MM-dd HH:mm', 'en_US')
|
||||
.parse(month + ' ' + time);
|
||||
print(month);
|
||||
print(date.toString());
|
||||
} else {
|
||||
date = DateTime.now();
|
||||
|
@ -832,36 +831,36 @@ class DBHelper {
|
|||
return episodes;
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> gettNewRssItem(String id) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
|
||||
E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds
|
||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE is_new = 1 AND downloaded != 'ND' AND P.id = ?ORDER BY E.milliseconds DESC """,
|
||||
[id],
|
||||
);
|
||||
for (int x = 0; x < list.length; x++) {
|
||||
episodes.add(EpisodeBrief(
|
||||
list[x]['title'],
|
||||
list[x]['enclosure_url'],
|
||||
list[x]['enclosure_length'],
|
||||
list[x]['milliseconds'],
|
||||
list[x]['feed_title'],
|
||||
list[x]['primaryColor'],
|
||||
list[x]['liked'],
|
||||
list[x]['downloaded'],
|
||||
list[x]['duration'],
|
||||
list[x]['explicit'],
|
||||
list[x]['imagePath'],
|
||||
list[x]['media_id'],
|
||||
list[x]['is_new'],
|
||||
list[x]['skip_seconds']));
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
//Future<List<EpisodeBrief>> getNewRssItem(String id) async {
|
||||
// var dbClient = await database;
|
||||
// List<EpisodeBrief> episodes = [];
|
||||
// List<Map> list = await dbClient.rawQuery(
|
||||
// """SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
// E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
|
||||
// E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new, P.skip_seconds
|
||||
// FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
// WHERE is_new = 1 AND downloaded != 'ND' AND P.id = ?ORDER BY E.milliseconds DESC """,
|
||||
// [id],
|
||||
// );
|
||||
// for (int x = 0; x < list.length; x++) {
|
||||
// episodes.add(EpisodeBrief(
|
||||
// list[x]['title'],
|
||||
// list[x]['enclosure_url'],
|
||||
// list[x]['enclosure_length'],
|
||||
// list[x]['milliseconds'],
|
||||
// list[x]['feed_title'],
|
||||
// list[x]['primaryColor'],
|
||||
// list[x]['liked'],
|
||||
// list[x]['downloaded'],
|
||||
// list[x]['duration'],
|
||||
// list[x]['explicit'],
|
||||
// list[x]['imagePath'],
|
||||
// list[x]['media_id'],
|
||||
// list[x]['is_new'],
|
||||
// list[x]['skip_seconds']));
|
||||
// }
|
||||
// return episodes;
|
||||
//}
|
||||
|
||||
Future<int> removeAllNewMark() async {
|
||||
var dbClient = await database;
|
||||
|
@ -994,6 +993,13 @@ class DBHelper {
|
|||
return list.first['liked'] == 0 ? false : true;
|
||||
}
|
||||
|
||||
Future<bool> isDownloaded(String url) async {
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery("SELECT downloaded FROM Episodes WHERE enclosure_url = ?", [url]);
|
||||
return list.first['downloaded'] == 'ND' ? false: true;
|
||||
}
|
||||
|
||||
Future<int> saveDownloaded(String url, String id) async {
|
||||
var dbClient = await database;
|
||||
int milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:connectivity/connectivity.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:tsacdop/state/download_state.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
|
@ -56,6 +58,26 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
msg: 'Updated $result Episodes',
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
bool autoDownload = await dbHelper.getAutoDownload(podcastLocal.id);
|
||||
if (autoDownload) {
|
||||
var result = await Connectivity().checkConnectivity();
|
||||
KeyValueStorage autoDownloadStorage =
|
||||
KeyValueStorage(autoDownloadNetworkKey);
|
||||
int autoDownloadNetwork = await autoDownloadStorage.getInt();
|
||||
if (autoDownloadNetwork == 1) {
|
||||
List<EpisodeBrief> episodes =
|
||||
await dbHelper.getNewEpisodes(podcastLocal.id);
|
||||
episodes.forEach((episode) {
|
||||
DownloadState().startTask(episode, showNotification: false);
|
||||
});
|
||||
} else if (result == ConnectivityResult.wifi) {
|
||||
List<EpisodeBrief> episodes =
|
||||
await dbHelper.getNewEpisodes(podcastLocal.id);
|
||||
episodes.forEach((episode) {
|
||||
DownloadState().startTask(episode, showNotification: false);
|
||||
});
|
||||
}
|
||||
}
|
||||
// Provider.of<GroupList>(context, listen: false)
|
||||
// .updatePodcast(podcastLocal.id);
|
||||
} else {
|
||||
|
|
|
@ -5,14 +5,12 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../state/podcast_group.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../podcasts/podcastdetail.dart';
|
||||
import '../util/pageroute.dart';
|
||||
import '../util/colorize.dart';
|
||||
import '../util/duraiton_picker.dart';
|
||||
import '../util/context_extension.dart';
|
||||
|
@ -98,8 +96,11 @@ class _PodcastCardState extends State<PodcastCard>
|
|||
}
|
||||
|
||||
_setAutoDownload(String id, bool boo) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
await dbHelper.saveAutoDownload(id, boo);
|
||||
bool permission = await _checkPermmison();
|
||||
if (permission) {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
await dbHelper.saveAutoDownload(id, boo);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _getAutoDownload(String id) async {
|
||||
|
@ -107,6 +108,21 @@ class _PodcastCardState extends State<PodcastCard>
|
|||
return await dbHelper.getAutoDownload(id);
|
||||
}
|
||||
|
||||
Future<bool> _checkPermmison() async {
|
||||
PermissionStatus permission = await Permission.storage.status;
|
||||
if (permission != PermissionStatus.granted) {
|
||||
Map<Permission, PermissionStatus> permissions =
|
||||
await [Permission.storage].request();
|
||||
if (permissions[Permission.storage] == PermissionStatus.granted) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
String _stringForSeconds(double seconds) {
|
||||
if (seconds == null) return null;
|
||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
|
@ -344,13 +360,27 @@ class _PodcastCardState extends State<PodcastCard>
|
|||
initialData: false,
|
||||
builder: (context, snapshot) {
|
||||
return _buttonOnMenu(
|
||||
icon: Icon(
|
||||
LineIcons.cloud_download_alt_solid,
|
||||
size: _value == 0 ? 1 : 20 * _value,
|
||||
color: snapshot.data
|
||||
? context.accentColor
|
||||
: null),
|
||||
tooltip: 'AutoDownload',
|
||||
icon: Container(
|
||||
child: Icon(Icons.done_all,
|
||||
size: _value * 15,
|
||||
color: snapshot.data
|
||||
? Colors.white
|
||||
: null),
|
||||
height: _value == 0 ? 1 : 18 * _value,
|
||||
width: _value == 0 ? 1 : 18 * _value,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 1,
|
||||
color: snapshot.data
|
||||
? context.accentColor
|
||||
: context.textTheme.subtitle1
|
||||
.color),
|
||||
shape: BoxShape.circle,
|
||||
color: snapshot.data
|
||||
? context.accentColor
|
||||
: null),
|
||||
),
|
||||
tooltip: 'Auto Download',
|
||||
onTap: () {
|
||||
_setAutoDownload(widget.podcastLocal.id,
|
||||
!snapshot.data);
|
||||
|
|
|
@ -58,7 +58,6 @@ class _DownloadsManageState extends State<DownloadsManage> {
|
|||
_delSelectedEpisodes() async {
|
||||
setState(() => _clearing = true);
|
||||
await Future.forEach(_selectedList, (EpisodeBrief episode) async {
|
||||
print(episode.downloaded);
|
||||
await FlutterDownloader.remove(
|
||||
taskId: episode.downloaded, shouldDeleteContent: true);
|
||||
var dbHelper = DBHelper();
|
||||
|
@ -212,7 +211,6 @@ class _DownloadsManageState extends State<DownloadsManage> {
|
|||
value: _selectedList
|
||||
.contains(_episodes[index]),
|
||||
onChanged: (bool boo) {
|
||||
print(boo);
|
||||
if (boo) {
|
||||
setState(() => _selectedList
|
||||
.add(_episodes[index]));
|
||||
|
|
|
@ -117,7 +117,6 @@ class _PlayedHistoryState extends State<PlayedHistory>
|
|||
systemNavigationBarColor: Theme.of(context).primaryColor,
|
||||
systemNavigationBarIconBrightness:
|
||||
Theme.of(context).accentColorBrightness,
|
||||
//statusBarColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
|
|
|
@ -5,6 +5,7 @@ import '../util/context_extension.dart';
|
|||
import '../util/episodegrid.dart';
|
||||
import '../util/custompaint.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import 'popup_menu.dart';
|
||||
|
||||
class LayoutSetting extends StatefulWidget {
|
||||
const LayoutSetting({Key key}) : super(key: key);
|
||||
|
@ -23,7 +24,7 @@ class _LayoutSettingState extends State<LayoutSetting> {
|
|||
Widget _gridOptions(BuildContext context,
|
||||
{String key, Layout layout, Layout option, double scale}) =>
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0, left: 20.0),
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
KeyValueStorage storage = KeyValueStorage(key);
|
||||
|
@ -34,7 +35,9 @@ class _LayoutSettingState extends State<LayoutSetting> {
|
|||
child: Container(
|
||||
height: 30,
|
||||
width: 50,
|
||||
color: layout == option ? context.accentColor : Colors.transparent,
|
||||
color: layout == option
|
||||
? context.accentColor
|
||||
: context.primaryColorDark,
|
||||
alignment: Alignment.center,
|
||||
child: SizedBox(
|
||||
height: 10,
|
||||
|
@ -80,6 +83,32 @@ class _LayoutSettingState extends State<LayoutSetting> {
|
|||
});
|
||||
}
|
||||
|
||||
Widget _setDefaultGridView(BuildContext context, {String text, String key}) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
|
||||
child: context.width > 360
|
||||
? Row(
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
),
|
||||
Spacer(),
|
||||
_setDefaultGrid(context, key: key),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
),
|
||||
_setDefaultGrid(context, key: key),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
|
@ -106,7 +135,7 @@ class _LayoutSettingState extends State<LayoutSetting> {
|
|||
height: 30.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 80),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('Default grid view',
|
||||
child: Text('Episode popup menu',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
|
@ -118,44 +147,42 @@ class _LayoutSettingState extends State<LayoutSetting> {
|
|||
scrollDirection: Axis.vertical,
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
contentPadding:
|
||||
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
|
||||
// leading: Icon(Icons.colorize),
|
||||
title: Text(
|
||||
'Podcast page',
|
||||
),
|
||||
subtitle:
|
||||
_setDefaultGrid(context, key: podcastLayoutKey),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding:
|
||||
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
|
||||
// leading: Icon(Icons.colorize),
|
||||
title: Text(
|
||||
'Recent tab in homepage',
|
||||
),
|
||||
subtitle:
|
||||
_setDefaultGrid(context, key: recentLayoutKey),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding:
|
||||
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
|
||||
// leading: Icon(Icons.colorize),
|
||||
title: Text(
|
||||
'Favorite tab in homepage',
|
||||
),
|
||||
subtitle: _setDefaultGrid(context, key: favLayoutKey),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding:
|
||||
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
|
||||
// leading: Icon(Icons.colorize),
|
||||
title: Text(
|
||||
'Download tab in homepage',
|
||||
),
|
||||
subtitle: _setDefaultGrid(context, key: downloadLayoutKey),
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PopupMenuSetting())),
|
||||
contentPadding: EdgeInsets.only(left: 80.0, right: 20),
|
||||
title: Text('Episode popup menu'),
|
||||
subtitle: Text('Change the menu when long tap episode'),
|
||||
),
|
||||
Divider(height: 2),
|
||||
Container(
|
||||
height: 30.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 80),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('Default grid view',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(
|
||||
color: Theme.of(context).accentColor)),
|
||||
),
|
||||
ListView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
scrollDirection: Axis.vertical,
|
||||
children: <Widget>[
|
||||
_setDefaultGridView(context,
|
||||
text: 'Podcast page', key: podcastLayoutKey),
|
||||
_setDefaultGridView(context,
|
||||
text: 'Recent tab', key: recentLayoutKey),
|
||||
_setDefaultGridView(context,
|
||||
text: 'Favorite tab', key: favLayoutKey),
|
||||
_setDefaultGridView(context,
|
||||
text: 'Downlaod tab', key: downloadLayoutKey),
|
||||
Divider(height: 2),
|
||||
]),
|
||||
Divider(height: 2)
|
||||
]),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:flare_flutter/flare_actor.dart';
|
||||
|
||||
import '../util/context_extension.dart';
|
||||
import '../util/custompaint.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
|
||||
class PopupMenuSetting extends StatefulWidget {
|
||||
const PopupMenuSetting({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_PopupMenuSettingState createState() => _PopupMenuSettingState();
|
||||
}
|
||||
|
||||
class _PopupMenuSettingState extends State<PopupMenuSetting> {
|
||||
Future<List<int>> _getEpisodeMenu() async {
|
||||
KeyValueStorage popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
|
||||
List<int> list = await popupMenuStorage.getMenu();
|
||||
return list;
|
||||
}
|
||||
|
||||
_saveEpisodeMene(List<int> list) async {
|
||||
KeyValueStorage popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
|
||||
await popupMenuStorage.saveMenu(list);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Widget _popupMenuItem(List<int> menu, int e,
|
||||
{Widget icon,
|
||||
String text,
|
||||
String description = '',
|
||||
bool enable = false}) {
|
||||
return Padding(
|
||||
key: ObjectKey(text),
|
||||
padding: EdgeInsets.only(left: 60.0, right: 20),
|
||||
child: ListTile(
|
||||
leading: icon,
|
||||
title: Text(text),
|
||||
subtitle: Text(description),
|
||||
trailing: Checkbox(
|
||||
value: e < 10,
|
||||
onChanged: e == 0
|
||||
? null
|
||||
: (bool boo) {
|
||||
if (boo && e >= 10) {
|
||||
int index = menu.indexOf(e);
|
||||
menu.remove(e);
|
||||
menu.insert(index, e - 10);
|
||||
_saveEpisodeMene(menu);
|
||||
} else if (e < 10) {
|
||||
int index = menu.indexOf(e);
|
||||
menu.remove(e);
|
||||
menu.insert(index, e + 10);
|
||||
_saveEpisodeMene(menu);
|
||||
}
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
|
||||
systemNavigationBarColor: context.primaryColor,
|
||||
systemNavigationBarIconBrightness:
|
||||
Theme.of(context).accentColorBrightness,
|
||||
),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: context.primaryColor,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
color: context.primaryColor,
|
||||
height: 200,
|
||||
// color: Colors.red,
|
||||
child: FlareActor(
|
||||
'assets/longtap.flr',
|
||||
alignment: Alignment.center,
|
||||
animation: 'longtap',
|
||||
fit: BoxFit.cover,
|
||||
)),
|
||||
Divider(height: 2),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
),
|
||||
Container(
|
||||
height: 30.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 80),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('Episode popup menu',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(color: Theme.of(context).accentColor)),
|
||||
),
|
||||
FutureBuilder<List<int>>(
|
||||
future: _getEpisodeMenu(),
|
||||
initialData: [0, 1, 12, 13, 14],
|
||||
builder: (context, snapshot) {
|
||||
List<int> menu = snapshot.data;
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
children: menu.map<Widget>((int e) {
|
||||
int i = e % 10;
|
||||
switch (i) {
|
||||
case 0:
|
||||
return _popupMenuItem(menu, e,
|
||||
icon: Icon(
|
||||
LineIcons.play_circle_solid,
|
||||
color: context.accentColor,
|
||||
),
|
||||
text: 'Play',
|
||||
description: 'Play the episode');
|
||||
break;
|
||||
case 1:
|
||||
return _popupMenuItem(menu, e,
|
||||
icon: Icon(
|
||||
LineIcons.clock_solid,
|
||||
color: Colors.cyan,
|
||||
),
|
||||
text: 'Later',
|
||||
description: 'Add episode to playlist');
|
||||
break;
|
||||
case 2:
|
||||
return _popupMenuItem(menu, e,
|
||||
icon: Icon(
|
||||
LineIcons.heart,
|
||||
color: Colors.red,
|
||||
size: 21
|
||||
),
|
||||
text: 'Like',
|
||||
description: 'Add episode to favorite');
|
||||
break;
|
||||
case 3:
|
||||
return _popupMenuItem(menu, e,
|
||||
icon: SizedBox(
|
||||
width: 23,
|
||||
height: 23,
|
||||
child: CustomPaint(
|
||||
painter: ListenedAllPainter(Colors.blue,
|
||||
stroke: 1.5)),
|
||||
),
|
||||
text: 'Mark Listened',
|
||||
description: 'Mark episode as listened');
|
||||
break;
|
||||
|
||||
case 4:
|
||||
return _popupMenuItem(menu, e,
|
||||
icon: Icon(
|
||||
LineIcons.download_solid,
|
||||
color: Colors.green,
|
||||
),
|
||||
text: 'Download',
|
||||
description: 'Download episode');
|
||||
break;
|
||||
default:
|
||||
return Text('Text');
|
||||
break;
|
||||
}
|
||||
}).toList(),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ class _StorageSettingState extends State<StorageSetting>
|
|||
Animation<double> _animation;
|
||||
_getCacheMax() async {
|
||||
int cache = await cacheStorage.getInt();
|
||||
int value = cache == 0 ? 500 : cache ~/ (1024 * 1024);
|
||||
int value = cache == 0 ? 200 : cache ~/ (1024 * 1024);
|
||||
if (value > 100) {
|
||||
_controller = AnimationController(
|
||||
vsync: this, duration: Duration(milliseconds: value * 2));
|
||||
|
@ -38,6 +38,17 @@ class _StorageSettingState extends State<StorageSetting>
|
|||
}
|
||||
}
|
||||
|
||||
Future<bool> _getAutoDownloadNetwork() async {
|
||||
KeyValueStorage storage = KeyValueStorage(autoDownloadNetworkKey);
|
||||
int value = await storage.getInt();
|
||||
return value != 0;
|
||||
}
|
||||
|
||||
_setAudtDownloadNetwork(bool boo) async {
|
||||
KeyValueStorage storage = KeyValueStorage(autoDownloadNetworkKey);
|
||||
await storage.saveInt(boo ? 1 : 0);
|
||||
}
|
||||
|
||||
double _value;
|
||||
|
||||
@override
|
||||
|
@ -96,29 +107,50 @@ class _StorageSettingState extends State<StorageSetting>
|
|||
shrinkWrap: true,
|
||||
scrollDirection: Axis.vertical,
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DownloadsManage())),
|
||||
contentPadding: EdgeInsets.only(
|
||||
left: 80.0, right: 25, bottom: 10, top: 10),
|
||||
title: Text('Ask before using cellular data'),
|
||||
subtitle: Text(
|
||||
'Ask to confirm when using cellular data to download episodes.'),
|
||||
trailing: Selector<SettingState, bool>(
|
||||
selector: (_, settings) =>
|
||||
settings.downloadUsingData,
|
||||
builder: (_, data, __) {
|
||||
return Switch(
|
||||
Selector<SettingState, bool>(
|
||||
selector: (_, settings) => settings.downloadUsingData,
|
||||
builder: (_, data, __) {
|
||||
return ListTile(
|
||||
onTap: () => settings.downloadUsingData = !data,
|
||||
contentPadding: EdgeInsets.only(
|
||||
left: 80.0, right: 25, bottom: 10, top: 10),
|
||||
title: Text('Ask before using cellular data'),
|
||||
subtitle: Text(
|
||||
'Ask to confirm when using cellular data to download episodes.'),
|
||||
trailing: Switch(
|
||||
value: data,
|
||||
onChanged: (value) =>
|
||||
settings.downloadUsingData = value,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Divider(height: 2),
|
||||
FutureBuilder<bool>(
|
||||
future: _getAutoDownloadNetwork(),
|
||||
initialData: false,
|
||||
builder: (context, snapshot) {
|
||||
return ListTile(
|
||||
onTap: () async {
|
||||
_setAudtDownloadNetwork(!snapshot.data);
|
||||
setState(() {});
|
||||
},
|
||||
contentPadding: EdgeInsets.only(
|
||||
left: 80.0, right: 25, bottom: 10, top: 10),
|
||||
title:
|
||||
Text('Auto download using cellular data'),
|
||||
subtitle: Text(
|
||||
'You can set podcast auto download in group manage page.'),
|
||||
trailing: Switch(
|
||||
value: snapshot.data,
|
||||
onChanged: (value) async {
|
||||
await _setAudtDownloadNetwork(value);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
Divider(height: 2),
|
||||
],
|
||||
),
|
||||
]),
|
||||
|
@ -151,7 +183,7 @@ class _StorageSettingState extends State<StorageSetting>
|
|||
builder: (context) => DownloadsManage())),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 80.0),
|
||||
title: Text('Downloads'),
|
||||
subtitle: Text('Manage doanloaded audio files'),
|
||||
subtitle: Text('Manage downloaded audio files'),
|
||||
),
|
||||
Divider(height: 2),
|
||||
ListTile(
|
||||
|
@ -175,8 +207,10 @@ class _StorageSettingState extends State<StorageSetting>
|
|||
left: 60.0, right: 20.0, bottom: 10.0),
|
||||
child: SliderTheme(
|
||||
data: Theme.of(context).sliderTheme.copyWith(
|
||||
showValueIndicator: ShowValueIndicator.always,
|
||||
),
|
||||
showValueIndicator: ShowValueIndicator.always,
|
||||
trackHeight: 2,
|
||||
thumbShape:
|
||||
RoundSliderThumbShape(enabledThumbRadius: 6)),
|
||||
child: Slider(
|
||||
label: '${_value ~/ 100 * 100} Mb',
|
||||
activeColor: context.accentColor,
|
||||
|
|
|
@ -155,6 +155,8 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
int _currentPosition;
|
||||
double _currentSpeed = 1;
|
||||
BehaviorSubject<List<MediaItem>> queueSubject;
|
||||
//Update episode card when setting changed
|
||||
bool _episodeState = false;
|
||||
|
||||
BasicPlaybackState get audioState => _audioState;
|
||||
|
||||
|
@ -176,6 +178,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
int get timeLeft => _timeLeft;
|
||||
double get switchValue => _switchValue;
|
||||
double get currentSpeed => _currentSpeed;
|
||||
bool get episodeState => _episodeState;
|
||||
|
||||
set setSwitchValue(double value) {
|
||||
_switchValue = value;
|
||||
|
@ -193,6 +196,11 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
set setEpisodeState(bool boo) {
|
||||
_episodeState = !_episodeState;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future _getAutoPlay() async {
|
||||
int i = await autoPlayStorage.getInt();
|
||||
_autoPlay = i == 0 ? true : false;
|
||||
|
@ -315,7 +323,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
queueSubject.addStream(
|
||||
AudioService.queueStream.distinct().where((event) => event != null));
|
||||
queueSubject.stream.listen((event) {
|
||||
print(event.length);
|
||||
if (event.length == _queue.playlist.length - 1 &&
|
||||
_audioState == BasicPlaybackState.skippingToNext) {
|
||||
if (event.length == 0 || _stopOnComplete) {
|
||||
|
@ -506,7 +513,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
}
|
||||
|
||||
sliderSeek(double val) async {
|
||||
print(val.toString());
|
||||
if (_audioState != BasicPlaybackState.connecting &&
|
||||
_audioState != BasicPlaybackState.none) {
|
||||
_noSlide = false;
|
||||
|
@ -633,6 +639,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
AudioPlayer _audioPlayer = AudioPlayer();
|
||||
Completer _completer = Completer();
|
||||
BasicPlaybackState _skipState;
|
||||
bool _lostFocus;
|
||||
bool _playing;
|
||||
bool _stopAtEnd;
|
||||
int cacheMax;
|
||||
|
@ -662,7 +669,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
@override
|
||||
Future<void> onStart() async {
|
||||
_stopAtEnd = false;
|
||||
|
||||
_lostFocus = false;
|
||||
var playerStateSubscription = _audioPlayer.playbackStateStream
|
||||
.where((state) => state == AudioPlaybackState.completed)
|
||||
.listen((state) {
|
||||
|
@ -792,9 +799,12 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
@override
|
||||
void onPause() {
|
||||
if (_skipState == null) {
|
||||
if (_playing == null) {}
|
||||
_playing = false;
|
||||
_audioPlayer.pause();
|
||||
if (_playing == null) {
|
||||
} else if (_audioPlayer.playbackEvent.state ==
|
||||
AudioPlaybackState.playing) {
|
||||
_playing = false;
|
||||
_audioPlayer.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -857,32 +867,45 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
milliseconds: AudioServiceBackground.state.position + 30 * 1000));
|
||||
}
|
||||
|
||||
@override
|
||||
void onRewind() {
|
||||
_audioPlayer.seek(Duration(
|
||||
milliseconds: AudioServiceBackground.state.position - 10 * 1000));
|
||||
}
|
||||
|
||||
@override
|
||||
void onAudioFocusLost() {
|
||||
if (_skipState == null) {
|
||||
if (_playing == null ||
|
||||
_audioPlayer.playbackState == AudioPlaybackState.none ||
|
||||
_audioPlayer.playbackState == AudioPlaybackState.connecting) {}
|
||||
_playing = false;
|
||||
_audioPlayer.pause();
|
||||
if (_playing == null) {
|
||||
} else if (_audioPlayer.playbackEvent.state ==
|
||||
AudioPlaybackState.playing) {
|
||||
_playing = false;
|
||||
_lostFocus = true;
|
||||
_audioPlayer.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onAudioBecomingNoisy() {
|
||||
if (_skipState == null) {
|
||||
if (_playing == null) {}
|
||||
_playing = false;
|
||||
_audioPlayer.pause();
|
||||
if (_playing == null) {
|
||||
} else if (_audioPlayer.playbackEvent.state ==
|
||||
AudioPlaybackState.playing) {
|
||||
_playing = false;
|
||||
_audioPlayer.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onAudioFocusGained() {
|
||||
if (_skipState == null) {
|
||||
if (_playing == null) {}
|
||||
_playing = true;
|
||||
_audioPlayer.play();
|
||||
if (_lostFocus) {
|
||||
_lostFocus = false;
|
||||
_playing = true;
|
||||
_audioPlayer.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,8 @@ class DownloadState extends ChangeNotifier {
|
|||
final completeTask = await FlutterDownloader.loadTasksWithRawQuery(
|
||||
query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'");
|
||||
String filePath = 'file://' +
|
||||
path.join(completeTask.first.savedDir, completeTask.first.filename);
|
||||
path.join(completeTask.first.savedDir,
|
||||
Uri.encodeComponent(completeTask.first.filename));
|
||||
dbHelper.saveMediaId(
|
||||
episodeTask.episode.enclosureUrl, filePath, episodeTask.taskId);
|
||||
EpisodeBrief episode =
|
||||
|
@ -124,7 +125,7 @@ class DownloadState extends ChangeNotifier {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
Future startTask(EpisodeBrief episode) async {
|
||||
Future startTask(EpisodeBrief episode, {bool showNotification = true}) async {
|
||||
final dir = await getExternalStorageDirectory();
|
||||
String localPath = path.join(dir.path, episode.feedTitle);
|
||||
final saveDir = Directory(localPath);
|
||||
|
@ -137,10 +138,7 @@ class DownloadState extends ChangeNotifier {
|
|||
now.month.toString() +
|
||||
now.day.toString() +
|
||||
now.second.toString();
|
||||
String title = episode.title.trim().substring(0, 1) == '#'
|
||||
? episode.title.trim().substring(1)
|
||||
: episode.title.trim();
|
||||
String fileName = title +
|
||||
String fileName = episode.title +
|
||||
datePlus +
|
||||
'.' +
|
||||
episode.enclosureUrl.split('/').last.split('.').last;
|
||||
|
@ -148,12 +146,12 @@ class DownloadState extends ChangeNotifier {
|
|||
fileName: fileName,
|
||||
url: episode.enclosureUrl,
|
||||
savedDir: localPath,
|
||||
showNotification: true,
|
||||
showNotification: showNotification,
|
||||
openFileFromNotification: false,
|
||||
);
|
||||
_episodeTasks.add(EpisodeTask(episode, taskId));
|
||||
var dbHelper = DBHelper();
|
||||
await dbHelper.saveDownloaded(taskId, episode.enclosureUrl);
|
||||
await dbHelper.saveDownloaded(episode.enclosureUrl, taskId);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,13 @@ import 'dart:isolate';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_isolate/flutter_isolate.dart';
|
||||
import 'package:tsacdop/local_storage/key_value_storage.dart';
|
||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||
import 'package:connectivity/connectivity.dart';
|
||||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import 'download_state.dart';
|
||||
|
||||
enum RefreshState { none, fetch, error }
|
||||
|
||||
|
@ -68,13 +71,32 @@ class RefreshWorker extends ChangeNotifier {
|
|||
|
||||
Future<void> refreshIsolateEntryPoint(SendPort sendPort) async {
|
||||
KeyValueStorage refreshstorage = KeyValueStorage(refreshdateKey);
|
||||
KeyValueStorage autoDownloadStorage = KeyValueStorage(autoDownloadNetworkKey);
|
||||
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
|
||||
var dbHelper = DBHelper();
|
||||
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
|
||||
//int i = 0;
|
||||
await Future.forEach<PodcastLocal>(podcastList, (podcastLocal) async {
|
||||
sendPort.send([podcastLocal.title, 1]);
|
||||
await dbHelper.updatePodcastRss(podcastLocal);
|
||||
int updateCount = await dbHelper.updatePodcastRss(podcastLocal);
|
||||
bool autoDownload = await dbHelper.getAutoDownload(podcastLocal.id);
|
||||
if (autoDownload && updateCount > 0) {
|
||||
var result = await Connectivity().checkConnectivity();
|
||||
int autoDownloadNetwork = await autoDownloadStorage.getInt();
|
||||
if (autoDownloadNetwork == 1) {
|
||||
List<EpisodeBrief> episodes =
|
||||
await dbHelper.getNewEpisodes(podcastLocal.id);
|
||||
episodes.forEach((episode) {
|
||||
DownloadState().startTask(episode, showNotification: false);
|
||||
});
|
||||
} else if (result == ConnectivityResult.wifi) {
|
||||
List<EpisodeBrief> episodes =
|
||||
await dbHelper.getNewEpisodes(podcastLocal.id);
|
||||
episodes.forEach((episode) {
|
||||
DownloadState().startTask(episode, showNotification: false);
|
||||
});
|
||||
}
|
||||
}
|
||||
print('Refresh ' + podcastLocal.title);
|
||||
});
|
||||
// KeyValueStorage refreshcountstorage = KeyValueStorage('refreshcount');
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'dart:io';
|
||||
import 'package:connectivity/connectivity.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
|
@ -24,11 +24,23 @@ void callbackDispatcher() {
|
|||
await dbHelper.updatePodcastRss(podcastLocal, removeMark: lastWork);
|
||||
bool autoDownload = await dbHelper.getAutoDownload(podcastLocal.id);
|
||||
if (autoDownload && updateCount > 0) {
|
||||
List<EpisodeBrief> episodes =
|
||||
await dbHelper.getNewEpisodes(podcastLocal.id);
|
||||
episodes.forEach((episode) {
|
||||
DownloadState().startTask(episode);
|
||||
});
|
||||
var result = await Connectivity().checkConnectivity();
|
||||
KeyValueStorage autoDownloadStorage =
|
||||
KeyValueStorage(autoDownloadNetworkKey);
|
||||
int autoDownloadNetwork = await autoDownloadStorage.getInt();
|
||||
if (autoDownloadNetwork == 1) {
|
||||
List<EpisodeBrief> episodes =
|
||||
await dbHelper.getNewEpisodes(podcastLocal.id);
|
||||
episodes.forEach((episode) {
|
||||
DownloadState().startTask(episode, showNotification: false);
|
||||
});
|
||||
} else if (result == ConnectivityResult.wifi) {
|
||||
List<EpisodeBrief> episodes =
|
||||
await dbHelper.getNewEpisodes(podcastLocal.id);
|
||||
episodes.forEach((episode) {
|
||||
DownloadState().startTask(episode, showNotification: false);
|
||||
});
|
||||
}
|
||||
}
|
||||
print('Refresh ' + podcastLocal.title);
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@ extension ContextExtension on BuildContext{
|
|||
Color get accentColor => Theme.of(this).accentColor;
|
||||
Color get scaffoldBackgroundColor => Theme.of(this).scaffoldBackgroundColor;
|
||||
Color get primaryColorDark => Theme.of(this).primaryColorDark;
|
||||
Color get textColor => Theme.of(this).textTheme.bodyText1.color;
|
||||
Brightness get brightness => Theme.of(this).brightness;
|
||||
double get width => MediaQuery.of(this).size.width;
|
||||
double get height => MediaQuery.of(this).size.width;
|
||||
|
|
|
@ -11,9 +11,11 @@ import 'package:auto_animated/auto_animated.dart';
|
|||
import 'open_container.dart';
|
||||
|
||||
import '../state/audiostate.dart';
|
||||
import '../state/download_state.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../episodes/episodedetail.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import 'colorize.dart';
|
||||
import 'context_extension.dart';
|
||||
import 'custompaint.dart';
|
||||
|
@ -51,6 +53,39 @@ class EpisodeGrid extends StatelessWidget {
|
|||
return await dbHelper.isLiked(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
Future<List<int>> _getEpisodeMenu() async {
|
||||
KeyValueStorage popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
|
||||
List<int> list = await popupMenuStorage.getMenu();
|
||||
return list;
|
||||
}
|
||||
|
||||
Future<bool> _isDownloaded(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
return await dbHelper.isDownloaded(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
_markListened(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
bool marked = await dbHelper.checkMarked(episode);
|
||||
if (!marked) {
|
||||
final PlayHistory history =
|
||||
PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
await dbHelper.saveHistory(history);
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> _saveLiked(String url) async {
|
||||
var dbHelper = DBHelper();
|
||||
int result = await dbHelper.setLiked(url);
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<int> _setUnliked(String url) async {
|
||||
var dbHelper = DBHelper();
|
||||
int result = await dbHelper.setUniked(url);
|
||||
return result;
|
||||
}
|
||||
|
||||
String _stringForSeconds(double seconds) {
|
||||
if (seconds == null) return null;
|
||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
|
@ -171,15 +206,23 @@ class EpisodeGrid extends StatelessWidget {
|
|||
color: color,
|
||||
fontStyle: FontStyle.italic),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double _width = MediaQuery.of(context).size.width;
|
||||
double _width = context.width;
|
||||
Offset _offset;
|
||||
_showPopupMenu(Offset offset, EpisodeBrief episode, BuildContext context,
|
||||
bool isPlaying, bool isInPlaylist) async {
|
||||
{bool isPlaying, bool isInPlaylist}) async {
|
||||
bool isLiked, isDownload;
|
||||
int isListened;
|
||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||
var downloader = Provider.of<DownloadState>(context, listen: false);
|
||||
double left = offset.dx;
|
||||
double top = offset.dy;
|
||||
List<int> menuList = await _getEpisodeMenu();
|
||||
if (menuList.contains(3)) isListened = await _isListened(episode);
|
||||
if (menuList.contains(2)) isLiked = await _isLiked(episode);
|
||||
if (menuList.contains(4)) isDownload = await _isDownloaded(episode);
|
||||
await showMenu<int>(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10))),
|
||||
|
@ -203,39 +246,134 @@ class EpisodeGrid extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 1,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
LineIcons.clock_solid,
|
||||
color: Colors.red,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
!isInPlaylist ? Text('Later') : Text('Remove')
|
||||
],
|
||||
)),
|
||||
menuList.contains(1)
|
||||
? PopupMenuItem(
|
||||
value: 1,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
LineIcons.clock_solid,
|
||||
color: Colors.cyan,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
!isInPlaylist ? Text('Later') : Text('Remove')
|
||||
],
|
||||
))
|
||||
: null,
|
||||
menuList.contains(2)
|
||||
? PopupMenuItem(
|
||||
value: 2,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(LineIcons.heart, color: Colors.red, size: 21),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
isLiked
|
||||
? Text(
|
||||
'Unlike',
|
||||
)
|
||||
: Text('Like')
|
||||
],
|
||||
))
|
||||
: null,
|
||||
menuList.contains(3)
|
||||
? PopupMenuItem(
|
||||
value: 3,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
width: 23,
|
||||
height: 23,
|
||||
child: CustomPaint(
|
||||
painter:
|
||||
ListenedAllPainter(Colors.blue, stroke: 1.5)),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
isListened > 0.95
|
||||
? Text('Listened',
|
||||
style: TextStyle(
|
||||
color: context.textColor.withOpacity(0.5)))
|
||||
: Text('Mark\nListened')
|
||||
],
|
||||
))
|
||||
: null,
|
||||
menuList.contains(4)
|
||||
? PopupMenuItem(
|
||||
value: 4,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(LineIcons.download_solid, color: Colors.green),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
isDownload
|
||||
? Text('Downloaded',
|
||||
style: TextStyle(
|
||||
color: context.textColor.withOpacity(0.5)))
|
||||
: Text('Download')
|
||||
],
|
||||
))
|
||||
: null,
|
||||
],
|
||||
elevation: 5.0,
|
||||
).then((value) {
|
||||
if (value == 0) {
|
||||
if (!isPlaying) audio.episodeLoad(episode);
|
||||
} else if (value == 1) {
|
||||
if (!isInPlaylist) {
|
||||
audio.addToPlaylist(episode);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added to playlist',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
audio.delFromPlaylist(episode);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Removed from playlist',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
).then((value) async {
|
||||
switch (value) {
|
||||
case 0:
|
||||
if (!isPlaying) audio.episodeLoad(episode);
|
||||
break;
|
||||
case 1:
|
||||
if (!isInPlaylist) {
|
||||
audio.addToPlaylist(episode);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added to playlist',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
audio.delFromPlaylist(episode);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Removed from playlist',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (isLiked) {
|
||||
await _setUnliked(episode.enclosureUrl);
|
||||
audio.setEpisodeState = true;
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Unliked',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
await _saveLiked(episode.enclosureUrl);
|
||||
audio.setEpisodeState = true;
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Liked',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (isListened < 0.95) {
|
||||
await _markListened(episode);
|
||||
audio.setEpisodeState = true;
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Mark listened',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
if (!isDownload) downloader.startTask(episode);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -273,9 +411,11 @@ class EpisodeGrid extends StatelessWidget {
|
|||
opacity: Tween<double>(begin: index < initNum ? 0 : 1, end: 1)
|
||||
.animate(animation),
|
||||
child: Selector<AudioPlayerNotifier,
|
||||
Tuple2<EpisodeBrief, List<String>>>(
|
||||
selector: (_, audio) => Tuple2(audio?.episode,
|
||||
audio.queue.playlist.map((e) => e.enclosureUrl).toList()),
|
||||
Tuple3<EpisodeBrief, List<String>, bool>>(
|
||||
selector: (_, audio) => Tuple3(
|
||||
audio?.episode,
|
||||
audio.queue.playlist.map((e) => e.enclosureUrl).toList(),
|
||||
audio.episodeState),
|
||||
builder: (_, data, __) => OpenContainerWrapper(
|
||||
episode: episodes[index],
|
||||
closedBuilder: (context, action, boo) => FutureBuilder<int>(
|
||||
|
@ -310,12 +450,13 @@ class EpisodeGrid extends StatelessWidget {
|
|||
details.globalPosition.dx,
|
||||
details.globalPosition.dy),
|
||||
onLongPress: () => _showPopupMenu(
|
||||
_offset,
|
||||
episodes[index],
|
||||
context,
|
||||
data.item1 == episodes[index],
|
||||
data.item2
|
||||
.contains(episodes[index].enclosureUrl)),
|
||||
_offset,
|
||||
episodes[index],
|
||||
context,
|
||||
isPlaying: data.item1 == episodes[index],
|
||||
isInPlaylist: data.item2
|
||||
.contains(episodes[index].enclosureUrl),
|
||||
),
|
||||
onTap: action,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
name: tsacdop
|
||||
description: An easy-use podacasts player.
|
||||
|
||||
version: 0.3.3
|
||||
version: 0.3.4
|
||||
|
||||
environment:
|
||||
sdk: ">=2.6.0 <3.0.0"
|
||||
|
|
Loading…
Reference in New Issue