feat: update setting pages to material you

This commit is contained in:
xijieyin 2022-06-04 01:33:34 +08:00
parent e97a493135
commit 92dd3dd34e
21 changed files with 2096 additions and 2282 deletions

View File

@ -87,8 +87,8 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
final audio = context.watch<AudioPlayerNotifier>();
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor: context.priamryContainer,
systemNavigationBarColor: context.priamryContainer,
statusBarColor: widget.episodeItem!.cardColor(context),
systemNavigationBarColor: widget.episodeItem!.cardColor(context),
systemNavigationBarContrastEnforced: false,
systemNavigationBarIconBrightness: context.iconBrightness,
statusBarBrightness: context.brightness,
@ -104,7 +104,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
}
},
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
backgroundColor: context.onPrimary,
body: SafeArea(
child: Stack(
children: <Widget>[
@ -116,7 +116,8 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
headerSliverBuilder: (context, innerBoxScrolled) {
return <Widget>[
SliverAppBar(
backgroundColor: context.priamryContainer,
backgroundColor:
widget.episodeItem!.cardColor(context),
floating: true,
pinned: true,
title: _showTitle
@ -296,8 +297,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
final height = kMinPlayerHeight[data.item2!.index];
return Container(
alignment: Alignment.bottomCenter,
padding:
EdgeInsets.only(bottom: data.item1 ? height : 0),
padding: EdgeInsets.only(bottom: data.item1 ? height : 0),
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
height: _showMenu ? 50 : 0,
@ -310,13 +310,16 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
),
),
);
}),
},
),
Selector<AudioPlayerNotifier, EpisodeBrief?>(
selector: (_, audio) => audio.episode,
builder: (_, data, __) => Container(
child: PlayerWidget(
playerKey: _playerKey,
isPlayingPage: data == widget.episodeItem))),
isPlayingPage: data == widget.episodeItem),
),
),
],
),
),

View File

@ -28,7 +28,7 @@ class MenuBarState extends State<MenuBar> {
return Container(
height: 50.0,
decoration: BoxDecoration(
color: context.priamryContainer,
color: widget.episodeItem!.cardColor(context),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
@ -48,7 +48,6 @@ class MenuBarState extends State<MenuBar> {
child: Container(
height: 30.0,
width: 30.0,
color: context.priamryContainer,
child: widget.hide!
? Center()
: CircleAvatar(

View File

@ -7,100 +7,12 @@ import '../widgets/custom_widget.dart';
const String version = '0.6.0';
class AboutApp extends StatefulWidget {
@override
_AboutAppState createState() => _AboutAppState();
}
class _AboutAppState extends State<AboutApp> {
ScrollController? _scrollController;
late bool _scroll;
@override
void initState() {
super.initState();
_scroll = false;
_scrollController = ScrollController()
..addListener(() {
if (_scrollController!.offset > 0 && !_scroll && mounted) {
setState(() => _scroll = true);
}
if (_scrollController!.offset <= 0 && _scroll && mounted) {
setState(() => _scroll = false);
}
});
}
Widget _listItem(
BuildContext context, String text, IconData icons, String url) =>
InkWell(
onTap: () => url.launchUrl,
child: Container(
height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
border: Border(
bottom: Divider.createBorderSide(context),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(icons, color: Theme.of(context).accentColor),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
),
Text(text),
],
),
),
);
Widget _translatorInfo(BuildContext context,
{required String name, String? flag}) =>
Container(
height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
border: Border(
bottom: Divider.createBorderSide(context),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(LineIcons.user, color: Theme.of(context).accentColor),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
),
Expanded(
child: Text(
name,
maxLines: 1,
overflow: TextOverflow.fade,
)),
if (flag != null)
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Image(
image: AssetImage('assets/$flag.png'),
height: 20,
width: 30,
fit: BoxFit.cover,
),
),
],
),
);
class AboutApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
OverlayEntry _createOverlayEntry(TapDownDetails detail) {
// RenderBox renderBox = context.findRenderObject();
var offset = detail.globalPosition;
final offset = detail.globalPosition;
return OverlayEntry(
builder: (constext) => Positioned(
left: offset.dx - 5,
@ -118,23 +30,21 @@ class _AboutAppState extends State<AboutApp> {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
statusBarColor: context.onPrimary,
statusBarIconBrightness: context.iconBrightness,
systemNavigationBarColor: context.onPrimary,
systemNavigationBarIconBrightness: context.iconBrightness,
),
child: SafeArea(
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
backgroundColor: context.onPrimary,
appBar: AppBar(
backgroundColor: context.onPrimary,
title: Text(s.homeToprightMenuAbout),
scrolledUnderElevation: 1,
leading: CustomBackButton(),
elevation: _scroll ? 1 : 0,
),
body: SafeArea(
child: ScrollConfiguration(
behavior: NoGrowBehavior(),
child: SingleChildScrollView(
controller: _scrollController,
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Padding(
padding: const EdgeInsets.all(20.0),
@ -170,12 +80,10 @@ class _AboutAppState extends State<AboutApp> {
children: [
TextButton(
onPressed: () =>
'https://tsacdop.stonegate.me/#/privacy'
.launchUrl,
'https://tsacdop.stonegate.me/#/privacy'.launchUrl,
style: TextButton.styleFrom(
primary: context.accentColor,
textStyle:
TextStyle(fontWeight: FontWeight.bold)),
textStyle: TextStyle(fontWeight: FontWeight.bold)),
child: Text(
s.privacyPolicy,
),
@ -185,8 +93,7 @@ class _AboutAppState extends State<AboutApp> {
height: 4,
width: 4,
decoration: BoxDecoration(
color: context.accentColor,
shape: BoxShape.circle),
color: context.accentColor, shape: BoxShape.circle),
),
TextButton(
onPressed: () =>
@ -194,8 +101,7 @@ class _AboutAppState extends State<AboutApp> {
.launchUrl,
style: TextButton.styleFrom(
primary: context.accentColor,
textStyle:
TextStyle(fontWeight: FontWeight.bold)),
textStyle: TextStyle(fontWeight: FontWeight.bold)),
child: Text(s.changelog,
style: TextStyle(color: context.accentColor)),
),
@ -209,10 +115,7 @@ class _AboutAppState extends State<AboutApp> {
children: <Widget>[
_listItem(context, 'Twitter @tsacdop',
LineIcons.twitter, 'https://twitter.com/tsacdop'),
_listItem(
context,
'GitHub',
LineIcons.alternateGithub,
_listItem(context, 'GitHub', LineIcons.alternateGithub,
'https://github.com/stonega/tsacdop'),
_listItem(context, 'Telegram', LineIcons.telegram,
'https://t.me/joinchat/Bk3LkRpTHy40QYC78PK7Qg'),
@ -226,23 +129,29 @@ class _AboutAppState extends State<AboutApp> {
'https://www.buymeacoffee.com/stonegate'
.launchUrl,
style: ElevatedButton.styleFrom(
primary: context.accentColor),
primary: Color(0xffffdd00),
elevation: 0,
enableFeedback: false,
),
child: Container(
height: 30.0,
padding:
EdgeInsets.symmetric(horizontal: 4.0),
padding: EdgeInsets.symmetric(horizontal: 4.0),
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Buy Me A Coffee',
Text(
'Buy Me A Coffee',
style: TextStyle(
fontWeight: FontWeight.bold)),
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
SizedBox(width: 10),
Image(
image: AssetImage(
'assets/buymeacoffee.png'),
image:
AssetImage('assets/buymeacoffee.png'),
height: 20,
fit: BoxFit.fitHeight,
),
@ -270,7 +179,7 @@ class _AboutAppState extends State<AboutApp> {
Text(
s.translators,
style: TextStyle(
color: Theme.of(context).accentColor,
color: context.accentColor,
fontWeight: FontWeight.bold),
),
SizedBox(width: 2),
@ -336,7 +245,73 @@ class _AboutAppState extends State<AboutApp> {
),
),
),
);
}
Widget _listItem(
BuildContext context, String text, IconData icons, String url) =>
InkWell(
onTap: () => url.launchUrl,
child: Container(
height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
border: Border(
bottom: Divider.createBorderSide(context),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(icons, color: context.accentColor),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
),
Text(text),
],
),
),
);
Widget _translatorInfo(BuildContext context,
{required String name, String? flag}) =>
Container(
height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
border: Border(
bottom: Divider.createBorderSide(context),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(LineIcons.user, color: context.accentColor),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
),
Expanded(
child: Text(
name,
maxLines: 1,
overflow: TextOverflow.fade,
),
),
if (flag != null)
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Image(
image: AssetImage('assets/$flag.png'),
height: 20,
width: 30,
fit: BoxFit.cover,
),
),
],
),
);
}
}

View File

@ -120,8 +120,6 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
children: <Widget>[
SafeArea(
bottom: false,
child: StretchingOverscrollIndicator(
axisDirection: AxisDirection.down,
child: NestedScrollView(
innerScrollPositionKeyBuilder: () {
return Key('tab${_controller!.index}');
@ -175,7 +173,8 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
textStyle: TextStyle(fontSize: 25)),
),
),
featureDiscoveryOverlay(context,
featureDiscoveryOverlay(
context,
featureId: menuFeature,
tapTarget: Icon(Icons.more_vert),
backgroundColor: Colors.cyan[500],
@ -186,7 +185,8 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
padding:
const EdgeInsets.only(right: 5.0),
child: PopupMenu(),
)),
),
),
],
),
),
@ -232,11 +232,17 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
controller: _controller,
children: <Widget>[
NestedScrollViewInnerScrollPositionKeyWidget(
Key('tab0'), _RecentUpdate()),
Key('tab0'),
_RecentUpdate(),
),
NestedScrollViewInnerScrollPositionKeyWidget(
Key('tab1'), _MyFavorite()),
Key('tab1'),
_MyFavorite(),
),
NestedScrollViewInnerScrollPositionKeyWidget(
Key('tab2'), _MyDownload()),
Key('tab2'),
_MyDownload(),
),
],
),
),
@ -252,8 +258,9 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
),
),
),
Container(
child: PlayerWidget(playerKey: _playerKey),
),
Container(child: PlayerWidget(playerKey: _playerKey)),
],
),
),
@ -345,6 +352,7 @@ class __PlaylistButtonState extends State<_PlaylistButton> {
borderRadius: BorderRadius.all(Radius.circular(10))),
elevation: 1,
icon: Icon(Icons.playlist_play),
color: context.priamryContainer,
tooltip: s.menu,
itemBuilder: (context) => [
MyPopupMenuItem(
@ -1249,8 +1257,7 @@ class _MyDownloadState extends State<_MyDownload>
),
),
onPressed: () {
setState(
() => _hideListened = !_hideListened!);
setState(() => _hideListened = !_hideListened!);
},
),
),
@ -1276,8 +1283,7 @@ class _MyDownloadState extends State<_MyDownload>
Icon(LineIcons.download,
size: 80, color: Colors.grey[500]),
Padding(
padding:
EdgeInsets.symmetric(vertical: 10)),
padding: EdgeInsets.symmetric(vertical: 10)),
Text(
s.noEpisodeDownload,
style: TextStyle(color: Colors.grey[500]),
@ -1295,7 +1301,8 @@ class _MyDownloadState extends State<_MyDownload>
],
),
);
}),
},
),
);
}

View File

@ -118,11 +118,13 @@ class _ScrollPodcastsState extends State<ScrollPodcasts>
);
} else {
if (mounted) {
setState(() {
setState(
() {
(_groupIndex < groups.length - 1)
? _groupIndex++
: _groupIndex = 0;
});
},
);
}
}
}
@ -134,13 +136,13 @@ class _ScrollPodcastsState extends State<ScrollPodcasts>
child: Row(
children: <Widget>[
Padding(
padding:
EdgeInsets.symmetric(horizontal: 15.0),
padding: EdgeInsets.symmetric(horizontal: 15.0),
child: Text(
groups[_groupIndex]!.name!,
style: context.textTheme.bodyText1!
.copyWith(color: context.accentColor),
)),
),
),
Spacer(),
Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
@ -154,7 +156,8 @@ class _ScrollPodcastsState extends State<ScrollPodcasts>
.read<SettingState>()
.openAllPodcastDefalt!
? PodcastList()
: PodcastManage()),
: PodcastManage(),
),
);
}
},
@ -443,12 +446,14 @@ class _ScrollPodcastsState extends State<ScrollPodcasts>
width: 10,
decoration: BoxDecoration(
color: Colors.red,
border: Border.all(color: context.primaryColor, width: 2),
border:
Border.all(color: context.primaryColor, width: 2),
shape: BoxShape.circle),
),
)
: Center();
});
},
);
}
class PodcastPreview extends StatefulWidget {
@ -463,12 +468,6 @@ class PodcastPreview extends StatefulWidget {
class _PodcastPreviewState extends State<PodcastPreview> {
Future? _getRssItem;
Future<List<EpisodeBrief>> _getRssItemTop(PodcastLocal podcastLocal) async {
final dbHelper = DBHelper();
final episodes = await dbHelper.getRssItemTop(podcastLocal.id);
return episodes;
}
@override
void initState() {
super.initState();
@ -510,10 +509,12 @@ class _PodcastPreviewState extends State<PodcastPreview> {
children: <Widget>[
Expanded(
flex: 4,
child: Text(widget.podcastLocal!.title!,
child: Text(
widget.podcastLocal!.title!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.bold, color: c)),
style: TextStyle(fontWeight: FontWeight.bold, color: c),
),
),
Expanded(
flex: 1,
@ -531,6 +532,12 @@ class _PodcastPreviewState extends State<PodcastPreview> {
],
);
}
Future<List<EpisodeBrief>> _getRssItemTop(PodcastLocal podcastLocal) async {
final dbHelper = DBHelper();
final episodes = await dbHelper.getRssItemTop(podcastLocal.id);
return episodes;
}
}
class ShowEpisode extends StatelessWidget {
@ -539,121 +546,6 @@ class ShowEpisode extends StatelessWidget {
final DBHelper _dbHelper = DBHelper();
ShowEpisode({Key? key, this.episodes, this.podcastLocal}) : super(key: key);
Future<tuple.Tuple5<int, bool, bool, bool, List<int>>> _initData(
EpisodeBrief episode) async {
final menuList = await _getEpisodeMenu();
final tapToOpen = await _getTapToOpenPopupMenu();
final listened = await _isListened(episode);
final liked = await _isLiked(episode);
final downloaded = await _isDownloaded(episode);
return tuple.Tuple5(listened, liked, downloaded, tapToOpen, menuList);
}
Future<int> _isListened(EpisodeBrief episode) async {
return await _dbHelper.isListened(episode.enclosureUrl);
}
Future<bool> _isLiked(EpisodeBrief episode) async {
return await _dbHelper.isLiked(episode.enclosureUrl);
}
Future<List<int>> _getEpisodeMenu() async {
final popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
final list = await popupMenuStorage.getMenu();
return list;
}
Future<bool> _isDownloaded(EpisodeBrief episode) async {
return await _dbHelper.isDownloaded(episode.enclosureUrl);
}
Future<bool> _getTapToOpenPopupMenu() async {
final tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey);
final boo = await tapToOpenPopupMenuStorage.getInt(defaultValue: 0);
return boo == 1;
}
Future<void> _markListened(EpisodeBrief episode) async {
var marked = await _dbHelper.checkMarked(episode);
if (!marked) {
final history = PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
await _dbHelper.saveHistory(history);
}
}
Future<void> _saveLiked(String url) async {
await _dbHelper.setLiked(url);
}
Future<void> _setUnliked(String url) async {
await _dbHelper.setUniked(url);
}
Future<void> _requestDownload(BuildContext context,
{EpisodeBrief? episode}) async {
final permissionReady = await _checkPermmison();
final downloadUsingData = await KeyValueStorage(downloadUsingDataKey)
.getBool(defaultValue: true, reverse: true);
final result = await Connectivity().checkConnectivity();
final usingData = result == ConnectivityResult.mobile;
var dataConfirm = true;
if (permissionReady) {
if (downloadUsingData && usingData) {
dataConfirm = await _useDataConfirm(context);
}
if (dataConfirm) {
Provider.of<DownloadState>(context, listen: false).startTask(episode!);
}
}
}
Future<bool> _checkPermmison() async {
var permission = await Permission.storage.status;
if (permission != PermissionStatus.granted) {
var permissions = await [Permission.storage].request();
if (permissions[Permission.storage] == PermissionStatus.granted) {
return true;
} else {
return false;
}
} else {
return true;
}
}
Future<bool> _useDataConfirm(BuildContext context) async {
var ifUseData = false;
final s = context.s;
await generalDialog(
context,
title: Text(s.cellularConfirm),
content: Text(s.cellularConfirmDes),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
s.cancel,
style: TextStyle(color: Colors.grey[600]),
),
),
TextButton(
onPressed: () {
ifUseData = true;
Navigator.of(context).pop();
},
child: Text(
s.confirm,
style: TextStyle(color: Colors.red),
),
)
],
);
return ifUseData;
}
@override
Widget build(BuildContext context) {
final width = context.width;
@ -669,7 +561,7 @@ class ShowEpisode extends StatelessWidget {
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 1.5,
crossAxisCount: 2,
mainAxisSpacing: 6.0,
mainAxisSpacing: 8.0,
crossAxisSpacing: 6.0,
),
delegate: SliverChildBuilderDelegate(
@ -691,19 +583,17 @@ class ShowEpisode extends StatelessWidget {
final isDownloaded = snapshot.data!.item3;
final tapToOpen = snapshot.data!.item4;
final menuList = snapshot.data!.item5;
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: context.background,
),
return Align(
alignment: Alignment.center,
child: FocusedMenuHolder(
blurSize: 0.0,
menuItemExtent: 45,
menuBoxDecoration: BoxDecoration(
color: Colors.transparent,
borderRadius:
BorderRadius.all(Radius.circular(15.0))),
color: context.priamryContainer,
borderRadius: BorderRadius.all(
Radius.circular(15.0),
),
),
duration: Duration(milliseconds: 100),
tapMode:
tapToOpen ? TapMode.onTap : TapMode.onLongPress,
@ -716,10 +606,7 @@ class ShowEpisode extends StatelessWidget {
menuOffset: 6,
menuItems: <FocusedMenuItem>[
FocusedMenuItem(
backgroundColor:
context.brightness == Brightness.light
? context.primaryColor
: context.dialogBackgroundColor,
backgroundColor: context.priamryContainer,
title: Text(data.item1 != episodes![index] ||
!data.item3
? s.play
@ -736,10 +623,7 @@ class ShowEpisode extends StatelessWidget {
}),
if (menuList.contains(1))
FocusedMenuItem(
backgroundColor:
context.brightness == Brightness.light
? context.primaryColor
: context.dialogBackgroundColor,
backgroundColor: context.priamryContainer,
title: data.item2.contains(
episodes![index].enclosureUrl)
? Text(s.remove)
@ -766,10 +650,7 @@ class ShowEpisode extends StatelessWidget {
}),
if (menuList.contains(2))
FocusedMenuItem(
backgroundColor:
context.brightness == Brightness.light
? context.primaryColor
: context.dialogBackgroundColor,
backgroundColor: context.priamryContainer,
title:
isLiked ? Text(s.unlike) : Text(s.like),
trailingIcon: Icon(LineIcons.heart,
@ -795,10 +676,7 @@ class ShowEpisode extends StatelessWidget {
}),
if (menuList.contains(3))
FocusedMenuItem(
backgroundColor:
context.brightness == Brightness.light
? context.primaryColor
: context.dialogBackgroundColor,
backgroundColor: context.priamryContainer,
title: isListened > 0
? Text(s.listened,
style: TextStyle(
@ -828,10 +706,7 @@ class ShowEpisode extends StatelessWidget {
}),
if (menuList.contains(4))
FocusedMenuItem(
backgroundColor:
context.brightness == Brightness.light
? context.primaryColor
: context.dialogBackgroundColor,
backgroundColor: context.priamryContainer,
title: isDownloaded
? Text(s.downloaded,
style: TextStyle(
@ -850,10 +725,7 @@ class ShowEpisode extends StatelessWidget {
}),
if (menuList.contains(5))
FocusedMenuItem(
backgroundColor:
context.brightness == Brightness.light
? context.primaryColor
: context.dialogBackgroundColor,
backgroundColor: context.priamryContainer,
title: Text(s.playNext),
trailingIcon: Icon(
LineIcons.lightningBolt,
@ -865,7 +737,8 @@ class ShowEpisode extends StatelessWidget {
msg: s.playNextDes,
gravity: ToastGravity.BOTTOM,
);
}),
},
),
],
onPressed: () => Navigator.push(
context,
@ -878,6 +751,10 @@ class ShowEpisode extends StatelessWidget {
),
child: Container(
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: podcastLocal!.cardColor(context),
borderRadius: BorderRadius.circular(15.0),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
@ -999,18 +876,18 @@ class ShowEpisode extends StatelessWidget {
),
if (episodes![index].enclosureLength !=
null &&
episodes![index].enclosureLength !=
0)
episodes![index].enclosureLength != 0)
Container(
alignment: Alignment.center,
child: Text(
'${episodes![index].enclosureLength! ~/ 1000000}MB',
style: TextStyle(
fontSize: width / 35),
style:
TextStyle(fontSize: width / 35),
),
),
],
)),
),
),
],
),
),
@ -1027,6 +904,121 @@ class ShowEpisode extends StatelessWidget {
],
);
}
Future<tuple.Tuple5<int, bool, bool, bool, List<int>>> _initData(
EpisodeBrief episode) async {
final menuList = await _getEpisodeMenu();
final tapToOpen = await _getTapToOpenPopupMenu();
final listened = await _isListened(episode);
final liked = await _isLiked(episode);
final downloaded = await _isDownloaded(episode);
return tuple.Tuple5(listened, liked, downloaded, tapToOpen, menuList);
}
Future<int> _isListened(EpisodeBrief episode) async {
return await _dbHelper.isListened(episode.enclosureUrl);
}
Future<bool> _isLiked(EpisodeBrief episode) async {
return await _dbHelper.isLiked(episode.enclosureUrl);
}
Future<List<int>> _getEpisodeMenu() async {
final popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
final list = await popupMenuStorage.getMenu();
return list;
}
Future<bool> _isDownloaded(EpisodeBrief episode) async {
return await _dbHelper.isDownloaded(episode.enclosureUrl);
}
Future<bool> _getTapToOpenPopupMenu() async {
final tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey);
final boo = await tapToOpenPopupMenuStorage.getInt(defaultValue: 0);
return boo == 1;
}
Future<void> _markListened(EpisodeBrief episode) async {
var marked = await _dbHelper.checkMarked(episode);
if (!marked) {
final history = PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
await _dbHelper.saveHistory(history);
}
}
Future<void> _saveLiked(String url) async {
await _dbHelper.setLiked(url);
}
Future<void> _setUnliked(String url) async {
await _dbHelper.setUniked(url);
}
Future<void> _requestDownload(BuildContext context,
{EpisodeBrief? episode}) async {
final permissionReady = await _checkPermmison();
final downloadUsingData = await KeyValueStorage(downloadUsingDataKey)
.getBool(defaultValue: true, reverse: true);
final result = await Connectivity().checkConnectivity();
final usingData = result == ConnectivityResult.mobile;
var dataConfirm = true;
if (permissionReady) {
if (downloadUsingData && usingData) {
dataConfirm = await _useDataConfirm(context);
}
if (dataConfirm) {
Provider.of<DownloadState>(context, listen: false).startTask(episode!);
}
}
}
Future<bool> _checkPermmison() async {
var permission = await Permission.storage.status;
if (permission != PermissionStatus.granted) {
var permissions = await [Permission.storage].request();
if (permissions[Permission.storage] == PermissionStatus.granted) {
return true;
} else {
return false;
}
} else {
return true;
}
}
Future<bool> _useDataConfirm(BuildContext context) async {
var ifUseData = false;
final s = context.s;
await generalDialog(
context,
title: Text(s.cellularConfirm),
content: Text(s.cellularConfirmDes),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
s.cancel,
style: TextStyle(color: Colors.grey[600]),
),
),
TextButton(
onPressed: () {
ifUseData = true;
Navigator.of(context).pop();
},
child: Text(
s.confirm,
style: TextStyle(color: Colors.red),
),
)
],
);
return ifUseData;
}
}
//Circle Indicator

View File

@ -673,10 +673,10 @@ class _PodcastDetailState extends State<PodcastDetail> {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor: color,
statusBarIconBrightness: Brightness.dark,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
systemNavigationBarColor: context.primaryColor,
systemNavigationBarIconBrightness: context.iconBrightness,
),
child: WillPopScope(
onWillPop: () {

View File

@ -33,18 +33,233 @@ class DataBackup extends StatefulWidget {
class _DataBackupState extends State<DataBackup> {
final _gpodder = Gpodder();
var _syncing = false;
// var _syncing = false;
@override
Widget build(BuildContext context) {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: context.iconBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness: context.brightness,
),
child: Scaffold(
backgroundColor: context.onPrimary,
appBar: AppBar(
elevation: 0,
title: Text(s.settingsBackup),
leading: CustomBackButton(),
backgroundColor: context.primaryColor,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 20,
),
Container(
height: 30.0,
padding: EdgeInsets.fromLTRB(70, 0, 70, 0),
alignment: Alignment.centerLeft,
child: Text(s.subscribe,
style: context.textTheme.bodyText1!
.copyWith(color: context.accentColor)),
),
Padding(
padding:
EdgeInsets.only(left: 70.0, right: 20, top: 10, bottom: 10),
child: Text(s.subscribeExportDes),
),
Padding(
padding: EdgeInsets.only(left: 70.0, right: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
ButtonTheme(
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: BorderSide(color: Colors.green[700]!),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
),
),
child: Row(
children: [
Icon(
LineIcons.save,
color: Colors.green[700],
size: context.textTheme.headline6!.fontSize,
),
SizedBox(width: 10),
Text(s.save,
style: TextStyle(color: Colors.green[700])),
],
),
onPressed: () async {
final file = await _exportOmpl(context);
await _saveFile(file);
}),
),
SizedBox(width: 10),
ButtonTheme(
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: BorderSide(color: Colors.blue[700]!),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
),
),
child: Row(
children: [
Icon(
Icons.share,
size: context.textTheme.headline6!.fontSize,
color: Colors.blue[700],
),
SizedBox(width: 10),
Text(s.share,
style: TextStyle(color: Colors.blue[700])),
],
),
onPressed: () async {
var file = await _exportOmpl(context);
await _shareFile(file);
}),
)
],
),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Divider(height: 1),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(
s.settings,
style: context.textTheme.bodyText1!
.copyWith(color: context.accentColor),
),
),
Padding(
padding:
EdgeInsets.only(left: 70.0, right: 20, top: 10, bottom: 10),
child: Text(s.settingsExportDes),
),
Padding(
padding: EdgeInsets.only(left: 70.0, right: 10),
child: Wrap(children: [
ButtonTheme(
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: BorderSide(color: Colors.green[700]!),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
LineIcons.save,
color: Colors.green[700],
size: context.textTheme.headline6!.fontSize,
),
SizedBox(width: 10),
Text(s.save,
style: TextStyle(color: Colors.green[700])),
],
),
onPressed: () async {
var file = await _exportSetting(context);
await _saveFile(file);
}),
),
SizedBox(width: 10),
ButtonTheme(
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: BorderSide(color: Colors.blue[700]!),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.share,
size: context.textTheme.headline6!.fontSize,
color: Colors.blue[700],
),
SizedBox(width: 10),
Text(s.share,
style: TextStyle(color: Colors.blue[700])),
],
),
onPressed: () async {
var file = await _exportSetting(context);
await _shareFile(file);
}),
),
SizedBox(width: 10),
ButtonTheme(
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: BorderSide(color: Colors.red[700]!),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
LineIcons.paperclip,
size: context.textTheme.headline6!.fontSize,
color: Colors.red[700],
),
SizedBox(width: 10),
Text(s.import,
style: TextStyle(color: Colors.red[700])),
],
),
onPressed: () {
_getFilePath(context);
},
),
),
]),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Divider(height: 1),
)
],
),
),
);
}
Future<File> _exportOmpl(BuildContext context) async {
var groups = context.read<GroupList>().groups;
var opml = PodcastsBackup(groups).omplBuilder();
var tempdir = await getTemporaryDirectory();
var now = DateTime.now();
var datePlus = now.year.toString() +
final groups = context.read<GroupList>().groups;
final opml = PodcastsBackup(groups).omplBuilder();
final tempdir = await getTemporaryDirectory();
final now = DateTime.now();
final datePlus = now.year.toString() +
now.month.toString() +
now.day.toString() +
now.second.toString();
var file = File(path.join(tempdir.path, 'tsacdop_opml_$datePlus.xml'));
final file = File(path.join(tempdir.path, 'tsacdop_opml_$datePlus.xml'));
await file.writeAsString(opml.toXmlString());
return file;
}
@ -147,27 +362,6 @@ class _DataBackupState extends State<DataBackup> {
return await storage.getStringList();
}
Future<void> _syncNow() async {
if (mounted) {
setState(() {
_syncing = true;
});
}
final gpodder = Gpodder();
final status = await gpodder.getChanges();
if (status == 200) {
final groupList = context.read<GroupList>();
await gpodder.updateChange();
await groupList.gpodderSyncNow();
}
if (mounted) {
setState(() {
_syncing = false;
});
}
}
Future<List<int?>> _getSyncStatus() async {
var dateTimeStorage = KeyValueStorage(gpodderSyncDateTimeKey);
var statusStorage = KeyValueStorage(gpodderSyncStatusKey);
@ -175,353 +369,6 @@ class _DataBackupState extends State<DataBackup> {
final statusIndex = await statusStorage.getInt();
return [syncDateTime, statusIndex];
}
@override
Widget build(BuildContext context) {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: context.iconBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness: context.brightness,
),
child: Scaffold(
appBar: AppBar(
elevation: 0,
title: Text(s.settingsBackup),
leading: CustomBackButton(),
backgroundColor: context.primaryColor,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(10.0),
),
FutureBuilder<List<String?>?>(
future: _getLoginInfo(),
initialData: [],
builder: (context, snapshot) {
final loginInfo = snapshot.data!;
return Container(
height: 160,
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Stack(
children: [
Hero(
tag: 'gpodder.net',
child: CircleAvatar(
minRadius: 40,
backgroundColor: context.primaryColor,
child: SizedBox(
height: 60,
width: 60,
child: Image.asset('assets/gpodder.png')),
),
),
if (_syncing)
Positioned(
left: context.width / 2 - 40,
child: SizedBox(
height: 80,
width: 80,
child: CircularProgressIndicator(
strokeWidth: 1,
),
),
),
if (_syncing)
Positioned(
bottom: 39,
left: context.width / 2 - 12,
child: _OpenEye()),
if (_syncing)
Positioned(
bottom: 39,
left: context.width / 2 + 3,
child: _OpenEye()),
],
),
Text(
loginInfo.isEmpty
? s.intergateWith('gpodder.net')
: s.loggedInAs(loginInfo.first!),
style: TextStyle(color: Colors.purple[700])),
ButtonTheme(
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
side: BorderSide(color: Colors.purple[700]!)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
LineIcons.user,
color: Colors.purple[700],
size: context.textTheme.headline6!.fontSize,
),
SizedBox(width: 10),
Text(loginInfo.isEmpty ? s.login : s.logout,
style:
TextStyle(color: Colors.purple[700])),
],
),
onPressed: () {
if (loginInfo.isEmpty) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => _LoginGpodder(),
fullscreenDialog: true));
} else {
_logout();
}
},
),
),
],
),
);
}),
FutureBuilder<List<String?>?>(
future: _getLoginInfo(),
initialData: [],
builder: (context, snapshot) {
final loginInfo = snapshot.data!;
if (loginInfo.isNotEmpty) {
return ListTile(
contentPadding: const EdgeInsets.only(
left: 70.0, right: 20, top: 10, bottom: 10),
onTap: _syncNow,
title: Text(s.syncNow),
trailing: IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => _GpodderInfo()));
},
icon: Icon(LineIcons.infoCircle),
),
subtitle: FutureBuilder<List<int?>>(
future: _getSyncStatus(),
initialData: [0, 0],
builder: (context, snapshot) {
final dateTime = snapshot.data![0]!;
final status = snapshot.data![1];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${s.lastUpdate}: ${dateTime.toDate(context)}'),
SizedBox(width: 8),
Row(
children: [
Text('${s.status}: '),
_syncStauts(status),
],
),
],
);
}),
);
}
return Center();
}),
// ListTile(
// onTap: () async {
// final subscribeWorker = context.read<GroupList>();
// await subscribeWorker.cancelWork();
// subscribeWorker.setWorkManager();
// },
// title: Text('reset'),
// ),
Divider(height: 1),
Container(
height: 30.0,
padding: EdgeInsets.fromLTRB(70, 0, 70, 0),
alignment: Alignment.centerLeft,
child: Text(s.subscribe,
style: context.textTheme.bodyText1!
.copyWith(color: context.accentColor)),
),
Padding(
padding:
EdgeInsets.only(left: 70.0, right: 20, top: 10, bottom: 10),
child: Text(s.subscribeExportDes),
),
Padding(
padding: EdgeInsets.only(left: 70.0, right: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
ButtonTheme(
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
side: BorderSide(color: Colors.green[700]!)),
),
child: Row(
children: [
Icon(
LineIcons.save,
color: Colors.green[700],
size: context.textTheme.headline6!.fontSize,
),
SizedBox(width: 10),
Text(s.save,
style: TextStyle(color: Colors.green[700])),
],
),
onPressed: () async {
var file = await _exportOmpl(context);
await _saveFile(file);
}),
),
SizedBox(width: 10),
ButtonTheme(
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
side: BorderSide(color: Colors.blue[700]!)),
),
child: Row(
children: [
Icon(
Icons.share,
size: context.textTheme.headline6!.fontSize,
color: Colors.blue[700],
),
SizedBox(width: 10),
Text(s.share,
style: TextStyle(color: Colors.blue[700])),
],
),
onPressed: () async {
var file = await _exportOmpl(context);
await _shareFile(file);
}),
)
],
),
),
Divider(height: 1),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.settings,
style: context.textTheme.bodyText1!
.copyWith(color: Theme.of(context).accentColor)),
),
Padding(
padding:
EdgeInsets.only(left: 70.0, right: 20, top: 10, bottom: 10),
child: Text(s.settingsExportDes),
),
Padding(
padding: EdgeInsets.only(left: 70.0, right: 10),
child: Wrap(children: [
ButtonTheme(
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
side: BorderSide(color: Colors.green[700]!)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
LineIcons.save,
color: Colors.green[700],
size: context.textTheme.headline6!.fontSize,
),
SizedBox(width: 10),
Text(s.save,
style: TextStyle(color: Colors.green[700])),
],
),
onPressed: () async {
var file = await _exportSetting(context);
await _saveFile(file);
}),
),
SizedBox(width: 10),
ButtonTheme(
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
side: BorderSide(color: Colors.blue[700]!)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.share,
size: context.textTheme.headline6!.fontSize,
color: Colors.blue[700],
),
SizedBox(width: 10),
Text(s.share,
style: TextStyle(color: Colors.blue[700])),
],
),
onPressed: () async {
var file = await _exportSetting(context);
await _shareFile(file);
}),
),
SizedBox(width: 10),
ButtonTheme(
height: 32,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
side: BorderSide(color: Colors.red[700]!)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
LineIcons.paperclip,
size: context.textTheme.headline6!.fontSize,
color: Colors.red[700],
),
SizedBox(width: 10),
Text(s.import,
style: TextStyle(color: Colors.red[700])),
],
),
onPressed: () {
_getFilePath(context);
},
),
),
]),
),
Divider(height: 1)
],
),
),
);
}
}
class _OpenEye extends StatefulWidget {
@ -710,7 +557,6 @@ class __LoginGpodderState extends State<_LoginGpodder> {
context.s.settingsSyncing,
style: TextStyle(color: Colors.white),
);
break;
case LoginStatus.start:
return SizedBox(
height: 20,
@ -725,7 +571,6 @@ class __LoginGpodderState extends State<_LoginGpodder> {
context.s.login,
style: TextStyle(color: Colors.white),
);
break;
}
}

View File

@ -25,86 +25,6 @@ class PlayedHistory extends StatefulWidget {
class _PlayedHistoryState extends State<PlayedHistory>
with SingleTickerProviderStateMixin {
/// Get play history.
Future<List<PlayHistory>> _getPlayHistory(int top) async {
var dbHelper = DBHelper();
List<PlayHistory> playHistory;
playHistory = await dbHelper.getPlayHistory(top);
for (var record in playHistory) {
await record.getEpisode();
}
return playHistory;
}
bool _loadMore = false;
Future<void> _loadMoreData() async {
if (mounted) {
setState(() {
_loadMore = true;
});
}
await Future.delayed(Duration(milliseconds: 500));
if (mounted) {
setState(() {
_top = _top + 10;
_loadMore = false;
});
}
}
int _top = 10;
Future<List<SubHistory>> getSubHistory() async {
var dbHelper = DBHelper();
return await dbHelper.getSubHistory();
}
TabController? _controller;
List<int> list = const [0, 1, 2, 3, 4, 5, 6];
Future<List<FlSpot>> getData() async {
var dbHelper = DBHelper();
var stats = <FlSpot>[];
for (var day in list) {
var mins = await dbHelper.listenMins(7 - day);
stats.add(FlSpot(day.toDouble(), mins));
}
return stats;
}
Future recoverSub(BuildContext context, String url) async {
Fluttertoast.showToast(
msg: context.s.toastPodcastRecovering,
gravity: ToastGravity.BOTTOM,
);
var subscribeWorker = context.watch<GroupList>();
try {
var options = BaseOptions(
connectTimeout: 10000,
receiveTimeout: 10000,
);
var response = await Dio(options).get(url);
var p = RssFeed.parse(response.data);
var podcast = OnlinePodcast(
rss: url,
title: p.title,
publisher: p.author,
description: p.description,
image: p.itunes!.image!.href);
var item = SubscribeItem(podcast.rss, podcast.title,
imgUrl: podcast.image, group: 'Home');
subscribeWorker.setSubscribeItem(item);
} catch (e) {
developer.log(e.toString(), name: 'Recover podcast error');
Fluttertoast.showToast(
msg: context.s.toastRecoverFailed,
gravity: ToastGravity.BOTTOM,
);
}
}
@override
void initState() {
super.initState();
@ -122,13 +42,9 @@ class _PlayedHistoryState extends State<PlayedHistory>
Widget build(BuildContext context) {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: context.brightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness: context.iconBrightness,
),
value: context.overlay,
child: Scaffold(
backgroundColor: context.primaryColor,
backgroundColor: context.onPrimary,
body: SafeArea(
child: NestedScrollView(
headerSliverBuilder: (context, innerBoxScrolled) {
@ -389,6 +305,86 @@ class _PlayedHistoryState extends State<PlayedHistory>
),
);
}
/// Get play history.
Future<List<PlayHistory>> _getPlayHistory(int top) async {
var dbHelper = DBHelper();
List<PlayHistory> playHistory;
playHistory = await dbHelper.getPlayHistory(top);
for (var record in playHistory) {
await record.getEpisode();
}
return playHistory;
}
bool _loadMore = false;
Future<void> _loadMoreData() async {
if (mounted) {
setState(() {
_loadMore = true;
});
}
await Future.delayed(Duration(milliseconds: 500));
if (mounted) {
setState(() {
_top = _top + 10;
_loadMore = false;
});
}
}
int _top = 10;
Future<List<SubHistory>> getSubHistory() async {
var dbHelper = DBHelper();
return await dbHelper.getSubHistory();
}
TabController? _controller;
List<int> list = const [0, 1, 2, 3, 4, 5, 6];
Future<List<FlSpot>> getData() async {
var dbHelper = DBHelper();
var stats = <FlSpot>[];
for (var day in list) {
var mins = await dbHelper.listenMins(7 - day);
stats.add(FlSpot(day.toDouble(), mins));
}
return stats;
}
Future recoverSub(BuildContext context, String url) async {
Fluttertoast.showToast(
msg: context.s.toastPodcastRecovering,
gravity: ToastGravity.BOTTOM,
);
var subscribeWorker = context.watch<GroupList>();
try {
var options = BaseOptions(
connectTimeout: 10000,
receiveTimeout: 10000,
);
var response = await Dio(options).get(url);
var p = RssFeed.parse(response.data);
var podcast = OnlinePodcast(
rss: url,
title: p.title,
publisher: p.author,
description: p.description,
image: p.itunes!.image!.href);
var item = SubscribeItem(podcast.rss, podcast.title,
imgUrl: podcast.image, group: 'Home');
subscribeWorker.setSubscribeItem(item);
} catch (e) {
developer.log(e.toString(), name: 'Recover podcast error');
Fluttertoast.showToast(
msg: context.s.toastRecoverFailed,
gravity: ToastGravity.BOTTOM,
);
}
}
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {

View File

@ -21,6 +21,246 @@ class LayoutSetting extends StatefulWidget {
}
class _LayoutSettingState extends State<LayoutSetting> {
@override
Widget build(BuildContext context) {
final s = context.s;
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: context.overlay,
child: Scaffold(
backgroundColor: context.onPrimary,
appBar: AppBar(
title: Text(s.settingsLayout),
leading: CustomBackButton(),
elevation: 0,
backgroundColor: context.primaryColor,
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: const EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.settingsPopupMenu,
style: context.textTheme.bodyText1!
.copyWith(color: context.accentColor)),
),
ListTile(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PopupMenuSetting())),
contentPadding: EdgeInsets.only(left: 70.0, right: 20),
title: Text(s.settingsPopupMenu),
subtitle: Text(s.settingsPopupMenuDes),
),
Divider(height: 1),
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(
s.player,
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: context.accentColor),
),
),
ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
title: Text(s.settingsPlayerHeight),
subtitle: Text(s.settingsPlayerHeightDes),
trailing: Selector<AudioPlayerNotifier, PlayerHeight?>(
selector: (_, audio) => audio.playerHeight,
builder: (_, data, __) => MyDropdownButton(
hint: Text(_getHeightString(data)),
underline: Center(),
elevation: 1,
value: data!.index,
items: <int>[0, 1, 2].map<DropdownMenuItem<int>>((e) {
return DropdownMenuItem<int>(
value: e,
child: Text(
_getHeightString(PlayerHeight.values[e])));
}).toList(),
onChanged: (dynamic index) =>
audio.setPlayerHeight = PlayerHeight.values[index]),
),
),
if (environment['apiKey'] != '') Divider(height: 1),
if (environment['apiKey'] != '')
Padding(
padding: EdgeInsets.all(10.0),
),
if (environment['apiKey'] != '')
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.search,
style: context.textTheme.bodyText1!
.copyWith(color: context.accentColor)),
),
if (environment['apiKey'] != '')
FutureBuilder<bool>(
future: _getHideDiscovery(),
initialData: false,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
onTap: () => _saveHideDiscovery(!snapshot.data!),
title: Text(s.hidePodcastDiscovery),
subtitle: Text(s.hidePodcastDiscoveryDes),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: snapshot.data!,
onChanged: _saveHideDiscovery),
),
),
),
if (environment['apiKey'] != '')
FutureBuilder(
future: _getSearchEngine(),
initialData: SearchEngine.listenNotes,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
title: Text(s.defaultSearchEngine),
subtitle: Text(s.defaultSearchEngineDes),
trailing: MyDropdownButton(
hint: Text(''),
underline: Center(),
elevation: 1,
value: snapshot.data,
items: [
DropdownMenuItem<SearchEngine>(
value: SearchEngine.podcastIndex,
child: Text('Podcastindex')),
DropdownMenuItem<SearchEngine>(
value: SearchEngine.listenNotes,
child: Text('ListenNotes')),
],
onChanged: (dynamic value) =>
_saveSearchEngine(value)),
),
),
Divider(height: 1),
SizedBox(height: 20),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text('Default page',
style: context.textTheme.bodyText1!
.copyWith(color: context.accentColor)),
),
Selector<SettingState, bool?>(
selector: (_, setting) => setting.openPlaylistDefault,
builder: (_, data, __) {
return ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
onTap: () => context
.read<SettingState>()
.openPlaylistDefault = !data!,
title: Text('Open playlist page by default'),
subtitle: Text(
'Open playlist page instead of homepage by default'),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: data!,
onChanged: (boo) => context
.read<SettingState>()
.openPlaylistDefault = boo),
),
);
},
),
Selector<SettingState, bool?>(
selector: (_, setting) => setting.openAllPodcastDefalt,
builder: (_, data, __) {
return ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
onTap: () => context
.read<SettingState>()
.openAllPodcastDefault = !data!,
title: Text('Open all podcasts page by default'),
subtitle: Text(
'Open all podcasts page instead of group page by default'),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: data!,
onChanged: (boo) => context
.read<SettingState>()
.openAllPodcastDefault = boo),
),
);
},
),
Divider(height: 1),
SizedBox(height: 20),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(
s.settingsDefaultGrid,
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: context.accentColor),
),
),
ListView(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
FutureBuilder<bool>(
future: _hideListened(),
initialData: false,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.only(left: 70, right: 10),
onTap: () => _saveHideListened(!snapshot.data!),
title: Text('Hide listened'),
subtitle: Text('Hide listened episodes by default'),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: snapshot.data!,
onChanged: _saveHideListened),
),
),
),
_setDefaultGridView(context,
text: s.settingsDefaultGridPodcast,
key: podcastLayoutKey),
_setDefaultGridView(context,
text: s.settingsDefaultGridRecent,
key: recentLayoutKey),
_setDefaultGridView(context,
text: s.settingsDefaultGridFavorite,
key: favLayoutKey),
_setDefaultGridView(context,
text: s.settingsDefaultGridDownload,
key: downloadLayoutKey),
]),
Divider(height: 1),
],
),
)),
);
}
final _hideDiscoveyStorage = KeyValueStorage(hidePodcastDiscoveryKey);
Future<Layout> _getLayout(String key) async {
final keyValueStorage = KeyValueStorage(key);
@ -38,8 +278,8 @@ class _LayoutSettingState extends State<LayoutSetting> {
}
Future<bool> _hideListened() async {
var hideListenedStorage = KeyValueStorage(hideListenedKey);
var hideListened = await hideListenedStorage.getBool(defaultValue: false);
final hideListenedStorage = KeyValueStorage(hideListenedKey);
final hideListened = await hideListenedStorage.getBool(defaultValue: false);
return hideListened;
}
@ -181,244 +421,4 @@ class _LayoutSettingState extends State<LayoutSetting> {
),
);
}
@override
Widget build(BuildContext context) {
final s = context.s;
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: context.primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
child: Scaffold(
appBar: AppBar(
title: Text(s.settingsLayout),
leading: CustomBackButton(),
elevation: 0,
backgroundColor: context.primaryColor,
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: const EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.settingsPopupMenu,
style: context.textTheme.bodyText1!
.copyWith(color: context.accentColor)),
),
ListTile(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PopupMenuSetting())),
contentPadding: EdgeInsets.only(left: 70.0, right: 20),
title: Text(s.settingsPopupMenu),
subtitle: Text(s.settingsPopupMenuDes),
),
Divider(height: 1),
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.player,
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Theme.of(context).accentColor)),
),
ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
title: Text(s.settingsPlayerHeight),
subtitle: Text(s.settingsPlayerHeightDes),
trailing: Selector<AudioPlayerNotifier, PlayerHeight?>(
selector: (_, audio) => audio.playerHeight,
builder: (_, data, __) => MyDropdownButton(
hint: Text(_getHeightString(data)),
underline: Center(),
elevation: 1,
value: data!.index,
items: <int>[0, 1, 2].map<DropdownMenuItem<int>>((e) {
return DropdownMenuItem<int>(
value: e,
child: Text(
_getHeightString(PlayerHeight.values[e])));
}).toList(),
onChanged: (dynamic index) =>
audio.setPlayerHeight = PlayerHeight.values[index]),
),
),
if (environment['apiKey'] != '') Divider(height: 1),
if (environment['apiKey'] != '')
Padding(
padding: EdgeInsets.all(10.0),
),
if (environment['apiKey'] != '')
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.search,
style: context.textTheme.bodyText1!
.copyWith(color: context.accentColor)),
),
if (environment['apiKey'] != '')
FutureBuilder<bool>(
future: _getHideDiscovery(),
initialData: false,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
onTap: () => _saveHideDiscovery(!snapshot.data!),
title: Text(s.hidePodcastDiscovery),
subtitle: Text(s.hidePodcastDiscoveryDes),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: snapshot.data!,
onChanged: _saveHideDiscovery),
),
),
),
if (environment['apiKey'] != '')
FutureBuilder(
future: _getSearchEngine(),
initialData: SearchEngine.listenNotes,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
title: Text(s.defaultSearchEngine),
subtitle: Text(s.defaultSearchEngineDes),
trailing: MyDropdownButton(
hint: Text(''),
underline: Center(),
elevation: 1,
value: snapshot.data,
items: [
DropdownMenuItem<SearchEngine>(
value: SearchEngine.podcastIndex,
child: Text('Podcastindex')),
DropdownMenuItem<SearchEngine>(
value: SearchEngine.listenNotes,
child: Text('ListenNotes')),
],
onChanged: (dynamic value) =>
_saveSearchEngine(value)),
),
),
Divider(height: 1),
SizedBox(height: 20),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text('Default page',
style: context.textTheme.bodyText1!
.copyWith(color: context.accentColor)),
),
Selector<SettingState, bool?>(
selector: (_, setting) => setting.openPlaylistDefault,
builder: (_, data, __) {
return ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
onTap: () => context
.read<SettingState>()
.openPlaylistDefault = !data!,
title: Text('Open playlist page by default'),
subtitle: Text(
'Open playlist page instead of homepage by default'),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: data!,
onChanged: (boo) => context
.read<SettingState>()
.openPlaylistDefault = boo),
),
);
},
),
Selector<SettingState, bool?>(
selector: (_, setting) => setting.openAllPodcastDefalt,
builder: (_, data, __) {
return ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
onTap: () => context
.read<SettingState>()
.openAllPodcastDefault = !data!,
title: Text('Open all podcasts page by default'),
subtitle: Text(
'Open all podcasts page instead of group page by default'),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: data!,
onChanged: (boo) => context
.read<SettingState>()
.openAllPodcastDefault = boo),
),
);
},
),
Divider(height: 1),
SizedBox(height: 20),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.settingsDefaultGrid,
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Theme.of(context).accentColor)),
),
ListView(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
FutureBuilder<bool>(
future: _hideListened(),
initialData: false,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.only(left: 70, right: 10),
onTap: () => _saveHideListened(!snapshot.data!),
title: Text('Hide listened'),
subtitle: Text('Hide listened episodes by default'),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: snapshot.data!,
onChanged: _saveHideListened),
),
),
),
_setDefaultGridView(context,
text: s.settingsDefaultGridPodcast,
key: podcastLayoutKey),
_setDefaultGridView(context,
text: s.settingsDefaultGridRecent,
key: recentLayoutKey),
_setDefaultGridView(context,
text: s.settingsDefaultGridFavorite,
key: favLayoutKey),
_setDefaultGridView(context,
text: s.settingsDefaultGridDownload,
key: downloadLayoutKey),
]),
Divider(height: 1),
],
),
)),
);
}
}

View File

@ -1,29 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';
import '../util/extension_helper.dart';
import '../widgets/custom_widget.dart';
import 'licenses.dart';
class Libries extends StatelessWidget {
_launchUrl(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
value: context.overlay,
child: Scaffold(
appBar: AppBar(
title: Text(context.s.settingsLibraries),
@ -50,14 +36,14 @@ class Libries extends StatelessWidget {
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Theme.of(context).accentColor)),
.copyWith(color: context.accentColor)),
),
Column(
children: google.map<Widget>(
(e) {
return ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 80),
onTap: () => _launchUrl(e.link),
onTap: () => e.link.launchUrl,
title: Text(e.name),
subtitle: Text(e.license),
);
@ -72,14 +58,14 @@ class Libries extends StatelessWidget {
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Theme.of(context).accentColor)),
.copyWith(color: context.accentColor)),
),
Column(
children: fonts.map<Widget>(
(e) {
return ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 80),
onTap: () => _launchUrl(e.link),
onTap: () => e.link.launchUrl,
title: Text(e.name),
subtitle: Text(e.license),
);
@ -94,14 +80,14 @@ class Libries extends StatelessWidget {
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Theme.of(context).accentColor)),
.copyWith(color: context.accentColor)),
),
Container(
child: Column(
children: plugins.map<Widget>(
(e) {
return ListTile(
onTap: () => _launchUrl(e.link),
onTap: () => e.link.launchUrl,
contentPadding:
EdgeInsets.symmetric(horizontal: 80),
title: Text(e.name),

View File

@ -38,198 +38,15 @@ class PlaySetting extends StatefulWidget {
}
class _PlaySettingState extends State<PlaySetting> {
String _volumeEffect(BuildContext context, int? i) {
final s = context.s;
if (i == 2000) {
return s.playerHeightShort;
} else if (i == 3000) {
return s.playerHeightMed;
}
return s.playerHeightTall;
}
Future<bool> _getMarkListenedSkip() async {
final storage = KeyValueStorage(markListenedAfterSkipKey);
return storage.getBool(defaultValue: false);
}
Future<void> _saveMarkListenedSkip(bool boo) async {
final storage = KeyValueStorage(markListenedAfterSkipKey);
await storage.saveBool(boo);
if (mounted) setState(() {});
}
Widget _modeWidget(BuildContext context) {
var settings = Provider.of<SettingState>(context, listen: false);
return Selector<SettingState, Tuple2<int?, int?>>(
selector: (_, settings) =>
Tuple2(settings.autoSleepTimerMode, settings.defaultSleepTimer),
builder: (_, data, __) => Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () => settings.setAutoSleepTimerMode = 0,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5), topLeft: Radius.circular(5)),
child: Material(
color: Colors.transparent,
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
decoration: BoxDecoration(
color: data.item1 == 0
? context.accentColor
: context.primaryColorDark,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5),
topLeft: Radius.circular(5)),
),
padding: const EdgeInsets.all(8.0),
child: Text(context.s.endOfEpisode,
style: TextStyle(
color: data.item1 == 0 ? Colors.white : null)),
),
),
),
InkWell(
onTap: () => settings.setAutoSleepTimerMode = 1,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(5),
topRight: Radius.circular(5)),
child: Material(
color: Colors.transparent,
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
decoration: BoxDecoration(
color: data.item1 == 1
? context.accentColor
: context.primaryColorDark,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(5),
topRight: Radius.circular(5)),
),
padding: const EdgeInsets.all(8.0),
child: Text(context.s.minsCount(data.item2!),
style: TextStyle(
color: data.item1 == 1 ? Colors.white : null)),
),
),
),
],
),
),
);
}
Widget _scheduleWidget(BuildContext context) {
var settings = Provider.of<SettingState>(context, listen: false);
final s = context.s;
return Selector<SettingState, Tuple2<int?, int?>>(
selector: (_, settings) =>
Tuple2(settings.autoSleepTimerStart, settings.autoSleepTimerEnd),
builder: (_, data, __) => Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () async {
var startTime = data.item1!;
final timeOfDay = await showCustomTimePicker(
context: context,
cancelText: s.cancel,
confirmText: s.confirm,
helpText: '',
initialTime: TimeOfDay(
hour: startTime ~/ 60, minute: startTime % 60));
if (timeOfDay != null) {
startTime = timeOfDay.hour * 60 + timeOfDay.minute;
if (startTime != data.item2) {
settings.setAutoSleepTimerStart = startTime;
} else {
Fluttertoast.showToast(
msg: s.toastTimeEqualEnd,
gravity: ToastGravity.BOTTOM,
);
}
}
},
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5), topLeft: Radius.circular(5)),
child: Material(
color: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: context.primaryColorDark,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5),
topLeft: Radius.circular(5)),
),
padding: const EdgeInsets.all(8.0),
child: Text(s.from(data.item1!.toTime)),
),
),
),
InkWell(
onTap: () async {
var endTime = data.item2!;
final timeOfDay = await showCustomTimePicker(
context: context,
cancelText: s.cancel,
confirmText: s.confirm,
helpText: '',
initialTime:
TimeOfDay(hour: endTime ~/ 60, minute: endTime % 60));
if (timeOfDay != null) {
endTime = timeOfDay.hour * 60 + timeOfDay.minute;
if (endTime != data.item1) {
settings.setAutoSleepTimerEnd = endTime;
} else {
Fluttertoast.showToast(
msg: s.toastTimeEqualStart,
gravity: ToastGravity.BOTTOM,
);
}
}
},
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(5),
topRight: Radius.circular(5)),
child: Material(
color: Colors.transparent,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(5),
topRight: Radius.circular(5))),
child: Text(s.to(data.item2!.toTime),
style: TextStyle(color: Colors.white)),
),
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
var settings = context.watch<SettingState>();
var audio = context.watch<AudioPlayerNotifier>();
final settings = context.watch<SettingState>();
final audio = context.watch<AudioPlayerNotifier>();
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: context.brightness,
systemNavigationBarColor: context.primaryColor,
systemNavigationBarIconBrightness: context.iconBrightness,
),
value: context.overlay,
child: Scaffold(
backgroundColor: context.onPrimary,
appBar: AppBar(
title: Text(s.play),
leading: CustomBackButton(),
@ -463,6 +280,186 @@ class _PlaySettingState extends State<PlaySetting> {
),
);
}
String _volumeEffect(BuildContext context, int? i) {
final s = context.s;
if (i == 2000) {
return s.playerHeightShort;
} else if (i == 3000) {
return s.playerHeightMed;
}
return s.playerHeightTall;
}
Future<bool> _getMarkListenedSkip() async {
final storage = KeyValueStorage(markListenedAfterSkipKey);
return storage.getBool(defaultValue: false);
}
Future<void> _saveMarkListenedSkip(bool boo) async {
final storage = KeyValueStorage(markListenedAfterSkipKey);
await storage.saveBool(boo);
if (mounted) setState(() {});
}
Widget _modeWidget(BuildContext context) {
var settings = Provider.of<SettingState>(context, listen: false);
return Selector<SettingState, Tuple2<int?, int?>>(
selector: (_, settings) =>
Tuple2(settings.autoSleepTimerMode, settings.defaultSleepTimer),
builder: (_, data, __) => Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () => settings.setAutoSleepTimerMode = 0,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5), topLeft: Radius.circular(5)),
child: Material(
color: Colors.transparent,
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
decoration: BoxDecoration(
color: data.item1 == 0
? context.accentColor
: context.primaryColorDark,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5),
topLeft: Radius.circular(5)),
),
padding: const EdgeInsets.all(8.0),
child: Text(context.s.endOfEpisode,
style: TextStyle(
color: data.item1 == 0 ? Colors.white : null)),
),
),
),
InkWell(
onTap: () => settings.setAutoSleepTimerMode = 1,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(5),
topRight: Radius.circular(5)),
child: Material(
color: Colors.transparent,
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
decoration: BoxDecoration(
color: data.item1 == 1
? context.accentColor
: context.primaryColorDark,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(5),
topRight: Radius.circular(5)),
),
padding: const EdgeInsets.all(8.0),
child: Text(context.s.minsCount(data.item2!),
style: TextStyle(
color: data.item1 == 1 ? Colors.white : null)),
),
),
),
],
),
),
);
}
Widget _scheduleWidget(BuildContext context) {
var settings = Provider.of<SettingState>(context, listen: false);
final s = context.s;
return Selector<SettingState, Tuple2<int?, int?>>(
selector: (_, settings) =>
Tuple2(settings.autoSleepTimerStart, settings.autoSleepTimerEnd),
builder: (_, data, __) => Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () async {
var startTime = data.item1!;
final timeOfDay = await showCustomTimePicker(
context: context,
cancelText: s.cancel,
confirmText: s.confirm,
helpText: '',
initialTime: TimeOfDay(
hour: startTime ~/ 60, minute: startTime % 60));
if (timeOfDay != null) {
startTime = timeOfDay.hour * 60 + timeOfDay.minute;
if (startTime != data.item2) {
settings.setAutoSleepTimerStart = startTime;
} else {
Fluttertoast.showToast(
msg: s.toastTimeEqualEnd,
gravity: ToastGravity.BOTTOM,
);
}
}
},
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5), topLeft: Radius.circular(5)),
child: Material(
color: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: context.primaryColorDark,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5),
topLeft: Radius.circular(5)),
),
padding: const EdgeInsets.all(8.0),
child: Text(s.from(data.item1!.toTime)),
),
),
),
InkWell(
onTap: () async {
var endTime = data.item2!;
final timeOfDay = await showCustomTimePicker(
context: context,
cancelText: s.cancel,
confirmText: s.confirm,
helpText: '',
initialTime:
TimeOfDay(hour: endTime ~/ 60, minute: endTime % 60));
if (timeOfDay != null) {
endTime = timeOfDay.hour * 60 + timeOfDay.minute;
if (endTime != data.item1) {
settings.setAutoSleepTimerEnd = endTime;
} else {
Fluttertoast.showToast(
msg: s.toastTimeEqualStart,
gravity: ToastGravity.BOTTOM,
);
}
}
},
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(5),
topRight: Radius.circular(5)),
child: Material(
color: Colors.transparent,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(5),
topRight: Radius.circular(5))),
child: Text(s.to(data.item2!.toTime),
style: TextStyle(color: Colors.white)),
),
),
),
],
),
),
);
}
}
class _NotificationLayout extends StatefulWidget {

View File

@ -71,20 +71,19 @@ class _SettingsState extends State<Settings> {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
statusBarIconBrightness: context.brightness,
systemNavigationBarColor: context.onPrimary,
systemNavigationBarIconBrightness: context.iconBrightness,
),
child: Scaffold(
backgroundColor: context.onPrimary,
appBar: AppBar(
title: Text(s.settings),
leading: CustomBackButton(),
elevation: _showTitle ? 1 : 0,
backgroundColor: context.primaryColor,
backgroundColor: context.onPrimary,
),
body: SafeArea(
child: SingleChildScrollView(
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
controller: _controller,
child: Column(
@ -142,10 +141,8 @@ class _SettingsState extends State<Settings> {
subtitle: Text(s.settingsSyncingDes)),
Divider(height: 1),
ListTile(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StorageSetting())),
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => StorageSetting())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.save, color: Colors.green[700]),
title: Text(s.settingStorage),
@ -218,9 +215,7 @@ class _SettingsState extends State<Settings> {
'https://github.com/stonega/tsacdop/issues'),
_feedbackItem(LineIcons.telegram, s.feedbackTelegram,
'https://t.me/joinchat/Bk3LkRpTHy40QYC78PK7Qg'),
_feedbackItem(
LineIcons.envelopeOpenText,
s.feedbackEmail,
_feedbackItem(LineIcons.envelopeOpenText, s.feedbackEmail,
'mailto:<tsacdop.app@gmail.com>?subject=Tsacdop Feedback'),
_feedbackItem(LineIcons.googlePlay, s.feedbackPlay,
'https://play.google.com/store/apps/details?id=com.stonegate.tsacdop'),
@ -261,8 +256,7 @@ class _SettingsState extends State<Settings> {
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
SlideIntro(goto: Goto.settings))),
builder: (context) => SlideIntro(goto: Goto.settings))),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.columns, color: Colors.blueGrey),
title: Text(s.settingsAppIntro),
@ -275,7 +269,6 @@ class _SettingsState extends State<Settings> {
),
),
),
),
);
}
}

View File

@ -23,75 +23,6 @@ class _StorageSettingState extends State<StorageSetting>
AnimationController? _controller;
late Animation<double> _animation;
List<String>? _dirs;
Future<void> _getCacheMax() async {
var cache =
await cacheStorage.getInt(defaultValue: (200 * 1024 * 1024).toInt());
if (cache == 0) {
await cacheStorage.saveInt((200 * 1024 * 1024).toInt());
cache = 200 * 1024 * 1024;
}
var value = cache ~/ (1024 * 1024);
if (value > 100) {
_controller = AnimationController(
vsync: this, duration: Duration(milliseconds: value * 2));
_animation = Tween<double>(begin: 100, end: value.toDouble()).animate(
CurvedAnimation(curve: Curves.easeOutQuart, parent: _controller!))
..addListener(() {
setState(() => _value = _animation.value);
});
_controller!.forward();
}
}
Future<bool> _getAutoDownloadNetwork() async {
var storage = KeyValueStorage(autoDownloadNetworkKey);
var value = await storage.getBool(defaultValue: false);
return value;
}
Future<int?> _getAutoDeleteDays() async {
var storage = KeyValueStorage(autoDeleteKey);
var days = await storage.getInt();
if (days == 0) {
storage.saveInt(30);
return 30;
}
return days;
}
Future<int?> _getDownloadPasition() async {
final storage = KeyValueStorage(downloadPositionKey);
final index = await storage.getInt();
final externalDirs = await getExternalStorageDirectories();
_dirs = [for (var dir in externalDirs!) dir.path];
return index;
}
Future<bool> _getDelteAfterPlayed() async {
final storage = KeyValueStorage(deleteAfterPlayedKey);
return await storage.getBool(defaultValue: false);
}
Future<void> _setAutoDeleteDays(int days) async {
var storage = KeyValueStorage(autoDeleteKey);
await storage.saveInt(days);
setState(() {});
}
Future<void> _setAudtDownloadNetwork(bool boo) async {
var storage = KeyValueStorage(autoDownloadNetworkKey);
await storage.saveBool(boo);
}
Future<void> _setDownloadPosition(int? index) async {
final storage = KeyValueStorage(downloadPositionKey);
await storage.saveInt(index!);
}
Future<void> _setDeleteAfterPlayed(bool? boo) async {
final storage = KeyValueStorage(deleteAfterPlayedKey);
await storage.saveBool(boo);
}
late double _value;
@ -113,13 +44,9 @@ class _StorageSettingState extends State<StorageSetting>
final s = context.s;
var settings = Provider.of<SettingState>(context, listen: false);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
value: context.overlay,
child: Scaffold(
backgroundColor: context.onPrimary,
appBar: AppBar(
title: Text(s.settingStorage),
leading: CustomBackButton(),
@ -345,4 +272,74 @@ class _StorageSettingState extends State<StorageSetting>
),
);
}
Future<void> _getCacheMax() async {
var cache =
await cacheStorage.getInt(defaultValue: (200 * 1024 * 1024).toInt());
if (cache == 0) {
await cacheStorage.saveInt((200 * 1024 * 1024).toInt());
cache = 200 * 1024 * 1024;
}
var value = cache ~/ (1024 * 1024);
if (value > 100) {
_controller = AnimationController(
vsync: this, duration: Duration(milliseconds: value * 2));
_animation = Tween<double>(begin: 100, end: value.toDouble()).animate(
CurvedAnimation(curve: Curves.easeOutQuart, parent: _controller!))
..addListener(() {
setState(() => _value = _animation.value);
});
_controller!.forward();
}
}
Future<bool> _getAutoDownloadNetwork() async {
var storage = KeyValueStorage(autoDownloadNetworkKey);
var value = await storage.getBool(defaultValue: false);
return value;
}
Future<int?> _getAutoDeleteDays() async {
var storage = KeyValueStorage(autoDeleteKey);
var days = await storage.getInt();
if (days == 0) {
storage.saveInt(30);
return 30;
}
return days;
}
Future<int?> _getDownloadPasition() async {
final storage = KeyValueStorage(downloadPositionKey);
final index = await storage.getInt();
final externalDirs = await getExternalStorageDirectories();
_dirs = [for (var dir in externalDirs!) dir.path];
return index;
}
Future<bool> _getDelteAfterPlayed() async {
final storage = KeyValueStorage(deleteAfterPlayedKey);
return await storage.getBool(defaultValue: false);
}
Future<void> _setAutoDeleteDays(int days) async {
var storage = KeyValueStorage(autoDeleteKey);
await storage.saveInt(days);
setState(() {});
}
Future<void> _setAudtDownloadNetwork(bool boo) async {
var storage = KeyValueStorage(autoDownloadNetworkKey);
await storage.saveBool(boo);
}
Future<void> _setDownloadPosition(int? index) async {
final storage = KeyValueStorage(downloadPositionKey);
await storage.saveInt(index!);
}
Future<void> _setDeleteAfterPlayed(bool? boo) async {
final storage = KeyValueStorage(deleteAfterPlayedKey);
await storage.saveBool(boo);
}
}

View File

@ -14,12 +14,7 @@ class SyncingSetting extends StatelessWidget {
final s = context.s;
var settings = Provider.of<SettingState>(context, listen: false);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
value: context.overlay,
child: Scaffold(
appBar: AppBar(
title: Text(s.settingsSyncing),

View File

@ -13,18 +13,14 @@ class ThemeSetting extends StatelessWidget {
final s = context.s;
var settings = Provider.of<SettingState>(context, listen: false);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
value: context.overlay,
child: Scaffold(
backgroundColor: context.onPrimary,
appBar: AppBar(
title: Text(s.settingsAppearance),
leading: CustomBackButton(),
elevation: 0,
backgroundColor: Theme.of(context).primaryColor,
backgroundColor: context.onPrimary,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
@ -37,18 +33,20 @@ class ThemeSetting extends StatelessWidget {
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.settingsInterface,
child: Text(
s.settingsInterface,
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Theme.of(context).accentColor)),
.copyWith(color: context.accentColor),
),
),
ListTile(
onTap: () => showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: MaterialLocalizations.of(context)
.modalBarrierDismissLabel,
barrierLabel:
MaterialLocalizations.of(context).modalBarrierDismissLabel,
barrierColor: Colors.black54,
transitionDuration: const Duration(milliseconds: 200),
pageBuilder: (context, animaiton, secondaryAnimation) =>
@ -68,8 +66,7 @@ class ThemeSetting extends StatelessWidget {
),
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(10.0))),
borderRadius: BorderRadius.all(Radius.circular(10.0))),
title: Text(s.settingsTheme),
content: SingleChildScrollView(
scrollDirection: Axis.vertical,
@ -78,8 +75,7 @@ class ThemeSetting extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
ClipRRect(
borderRadius:
BorderRadius.all(Radius.circular(5)),
borderRadius: BorderRadius.all(Radius.circular(5)),
child: Material(
color: Colors.transparent,
child: RadioListTile(
@ -93,8 +89,7 @@ class ThemeSetting extends StatelessWidget {
),
),
ClipRRect(
borderRadius:
BorderRadius.all(Radius.circular(5)),
borderRadius: BorderRadius.all(Radius.circular(5)),
child: Material(
color: Colors.transparent,
child: RadioListTile(
@ -108,8 +103,7 @@ class ThemeSetting extends StatelessWidget {
),
),
ClipRRect(
borderRadius:
BorderRadius.all(Radius.circular(5)),
borderRadius: BorderRadius.all(Radius.circular(5)),
child: Material(
color: Colors.transparent,
child: RadioListTile(
@ -126,7 +120,8 @@ class ThemeSetting extends StatelessWidget {
),
),
),
)),
),
),
contentPadding: EdgeInsets.symmetric(horizontal: 70.0),
// leading: Icon(Icons.colorize),
title: Text(s.settingsTheme),
@ -324,7 +319,8 @@ class __ColorPickerState extends State<_ColorPicker>
key: UniqueKey(),
controller: _controller,
children: Colors.primaries
.map<Widget>((color) => ScrollConfiguration(
.map<Widget>(
(color) => ScrollConfiguration(
behavior: NoGrowBehavior(),
child: GridView.count(
primary: false,
@ -351,8 +347,7 @@ class __ColorPickerState extends State<_ColorPicker>
: color == Colors.orange
? _accentList(Colors.orangeAccent)
: color == Colors.amber
? _accentList(
Colors.amberAccent)
? _accentList(Colors.amberAccent)
: color == Colors.yellow
? _accentList(
Colors.yellowAccent)
@ -360,8 +355,7 @@ class __ColorPickerState extends State<_ColorPicker>
? _accentList(
Colors.limeAccent)
: color ==
Colors
.lightGreen
Colors.lightGreen
? _accentList(Colors
.lightGreenAccent)
: color ==
@ -377,7 +371,8 @@ class __ColorPickerState extends State<_ColorPicker>
: color ==
Colors
.cyan
? _accentList(Colors
? _accentList(
Colors
.cyanAccent)
: color ==
Colors.lightBlue
@ -393,7 +388,8 @@ class __ColorPickerState extends State<_ColorPicker>
: []
],
),
))
),
)
.toList(),
),
),

View File

@ -150,6 +150,7 @@ class SettingState extends ChangeNotifier {
color: Colors.grey[100],
elevation: 0,
titleTextStyle: TextStyle(color: Colors.black),
scrolledUnderElevation: 1,
iconTheme: IconThemeData(color: Colors.black),
systemOverlayStyle: SystemUiOverlayStyle.dark),
textTheme: TextTheme(
@ -259,6 +260,7 @@ class SettingState extends ChangeNotifier {
appBarTheme: AppBarTheme(
color: Colors.grey[900],
elevation: 0,
scrolledUnderElevation: 0,
systemOverlayStyle: SystemUiOverlayStyle.light),
buttonTheme: ButtonThemeData(height: 32),
dialogBackgroundColor: _realDark! ? Colors.grey[900] : null,

View File

@ -80,6 +80,14 @@ class EpisodeBrief extends Equatable {
: primaryColor!.colorizeLight();
}
Color cardColor(BuildContext context) {
final schema = ColorScheme.fromSeed(
seedColor: primaryColor!.colorizedark(),
brightness: context.brightness,
);
return schema.primaryContainer;
}
EpisodeBrief copyWith({
String? mediaId,
}) =>

View File

@ -42,12 +42,13 @@ class PodcastLocal extends Equatable {
this.description = '',
this.updateCount = 0,
this.episodeCount = 0,
}) : assert(rssUrl != null);
});
ImageProvider get avatarImage {
return (File(imagePath!).existsSync()
? FileImage(File(imagePath!))
: const AssetImage('assets/avatar_backup.png')) as ImageProvider<Object>;
: const AssetImage('assets/avatar_backup.png'))
as ImageProvider<Object>;
}
Color backgroudColor(BuildContext context) {
@ -56,9 +57,26 @@ class PodcastLocal extends Equatable {
: primaryColor!.colorizeLight();
}
Color cardColor(BuildContext context) {
final schema = ColorScheme.fromSeed(
seedColor: primaryColor!.colorizedark(),
brightness: context.brightness,
);
return schema.primaryContainer;
}
PodcastLocal copyWith({int? updateCount, int? episodeCount}) {
return PodcastLocal(title, imageUrl, rssUrl, primaryColor, author, id,
imagePath, provider, link, funding,
return PodcastLocal(
title,
imageUrl,
rssUrl,
primaryColor,
author,
id,
imagePath,
provider,
link,
funding,
description: description,
updateCount: updateCount ?? 0,
episodeCount: episodeCount ?? 0,

View File

@ -2,8 +2,8 @@ import 'dart:convert';
import 'dart:developer' as developer;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:material_color_utilities/material_color_utilities.dart';
import 'package:intl/intl.dart';
import 'package:url_launcher/url_launcher_string.dart';
import '../generated/l10n.dart';
@ -16,6 +16,7 @@ extension ContextExtension on BuildContext {
Color get onPrimary => Theme.of(this).colorScheme.onPrimary;
Color get background => Theme.of(this).colorScheme.background;
Color get tertiary => colorScheme.tertiary;
Color get tertiaryContainer => colorScheme.tertiaryContainer;
Color get onTertiary => colorScheme.onTertiary;
Color get secondary => colorScheme.secondary;
Color get onsecondary => colorScheme.onSecondary;
@ -30,6 +31,12 @@ extension ContextExtension on BuildContext {
double get height => MediaQuery.of(this).size.height;
double get paddingTop => MediaQuery.of(this).padding.top;
TextTheme get textTheme => Theme.of(this).textTheme;
SystemUiOverlayStyle get overlay => SystemUiOverlayStyle(
statusBarColor: onPrimary,
statusBarIconBrightness: iconBrightness,
systemNavigationBarColor: onPrimary,
systemNavigationBarIconBrightness: iconBrightness,
);
S get s => S.of(this)!;
}

View File

@ -36,17 +36,15 @@ Widget featureDiscoveryOverlay(BuildContext context,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(description),
FlatButton(
color: buttonColor,
padding: EdgeInsets.zero,
TextButton(
style: TextButton.styleFrom(primary: buttonColor),
child: Text(s.understood,
style: context.textTheme.button!.copyWith(color: Colors.white)),
onPressed: () async =>
FeatureDiscovery.completeCurrentStep(context),
),
FlatButton(
color: buttonColor,
padding: EdgeInsets.zero,
TextButton(
style: TextButton.styleFrom(primary: buttonColor),
child: Text(s.dismiss,
style: context.textTheme.button!.copyWith(color: Colors.white)),
onPressed: () => FeatureDiscovery.dismissAll(context),

View File

@ -4,7 +4,7 @@ description: An open source podcast player.
version: 0.6.0+48
publish_to: "none"
environment:
sdk: '>=2.12.0 <3.0.0'
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
@ -55,13 +55,13 @@ dependencies:
just_audio: ^0.9.23
flutter_downloader:
git:
url: https://github.com/stonega/flutter_downloader.git
url: https://github.com/tsacdop/flutter_downloader.git
focused_menu:
git:
url: https://github.com/stonega/focused_menu.git
url: https://github.com/tsacdop/focused_menu.git
webfeed:
git:
url: https://github.com/stonega/webfeed.git
url: https://github.com/tsacdop/webfeed.git
equatable: ^2.0.0
path_provider: ^2.0.1
http_parser: ^4.0.0
@ -74,7 +74,7 @@ dependencies:
dependency_overrides:
linkify:
git:
url: https://github.com/stonega/linkify.git
url: https://github.com/tsacdop/linkify.git
dev_dependencies:
flutter_test: