diff --git a/lib/episodes/episode_detail.dart b/lib/episodes/episode_detail.dart index 3a93161..e8408cf 100644 --- a/lib/episodes/episode_detail.dart +++ b/lib/episodes/episode_detail.dart @@ -3,23 +3,20 @@ import 'dart:developer' as developer; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_html/flutter_html.dart'; -import 'package:fluttertoast/fluttertoast.dart'; -import 'package:intl/intl.dart'; -import 'package:linkify/linkify.dart'; import 'package:provider/provider.dart'; +import 'package:tsacdop/episodes/menu_bar.dart'; +import 'package:tsacdop/episodes/shownote.dart'; +import 'package:tsacdop/util/helpers.dart'; import 'package:tuple/tuple.dart'; import '../home/audioplayer.dart'; import '../local_storage/sqflite_localpodcast.dart'; import '../state/audio_state.dart'; -import '../state/setting_state.dart'; import '../type/episodebrief.dart'; import '../type/play_histroy.dart'; import '../util/extension_helper.dart'; import '../widgets/audiopanel.dart'; import '../widgets/custom_widget.dart'; -import 'episode_download.dart'; class EpisodeDetail extends StatefulWidget { final EpisodeBrief? episodeItem; @@ -48,23 +45,23 @@ class _EpisodeDetailState extends State { return await dbHelper.getPosition(episode); } - ScrollController? _controller; + late ScrollController _controller; _scrollListener() { - if (_controller!.position.userScrollDirection == ScrollDirection.reverse) { + if (_controller.position.userScrollDirection == ScrollDirection.reverse) { if (_showMenu && mounted) { setState(() { _showMenu = false; }); } } - if (_controller!.position.userScrollDirection == ScrollDirection.forward) { + if (_controller.position.userScrollDirection == ScrollDirection.forward) { if (!_showMenu && mounted) { setState(() { _showMenu = true; }); } } - if (_controller!.offset > context.textTheme.headline5!.fontSize!) { + if (_controller.offset > context.textTheme.headline5!.fontSize!) { if (!_showTitle) setState(() => _showTitle = true); } else if (_showTitle) setState(() => _showTitle = false); } @@ -75,623 +72,256 @@ class _EpisodeDetailState extends State { _showMenu = true; _showTitle = false; _controller = ScrollController(); - _controller!.addListener(_scrollListener); + _controller.addListener(_scrollListener); } @override void dispose() { - _controller!.dispose(); + _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - SystemChrome.setSystemUIOverlayStyle( - SystemUiOverlayStyle( - statusBarColor: Colors.transparent, - systemNavigationBarColor: Colors.transparent, + final s = context.s; + final audio = context.watch(); + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: context.priamryContainer, + systemNavigationBarColor: context.priamryContainer, systemNavigationBarContrastEnforced: false, systemNavigationBarIconBrightness: context.iconBrightness, statusBarBrightness: context.brightness, statusBarIconBrightness: context.iconBrightness), - ); - final s = context.s!; - final audio = context.watch(); - return WillPopScope( - onWillPop: () async { - if (_playerKey.currentState != null && - _playerKey.currentState!.initSize! > 100) { - _playerKey.currentState!.backToMini(); - return false; - } else { - return true; - } - }, - child: Scaffold( - backgroundColor: Theme.of(context).primaryColor, - body: SafeArea( - child: Stack( - children: [ - ScrollConfiguration( - behavior: NoGrowBehavior(), - child: NestedScrollView( - scrollDirection: Axis.vertical, - controller: _controller, - headerSliverBuilder: (context, innerBoxScrolled) { - return [ - SliverAppBar( - floating: true, - pinned: true, - title: _showTitle - ? Text( - widget.episodeItem?.title ?? '', - maxLines: 1, - overflow: TextOverflow.ellipsis, - ) - : Text( - widget.episodeItem!.feedTitle!, - maxLines: 1, - style: TextStyle( - fontSize: 15, - color: context.textColor!.withOpacity(0.7)), + child: WillPopScope( + onWillPop: () async { + if (_playerKey.currentState != null && + _playerKey.currentState!.initSize! > 100) { + _playerKey.currentState!.backToMini(); + return false; + } else { + return true; + } + }, + child: Scaffold( + backgroundColor: Theme.of(context).primaryColor, + body: SafeArea( + child: Stack( + children: [ + StretchingOverscrollIndicator( + axisDirection: AxisDirection.down, + child: NestedScrollView( + scrollDirection: Axis.vertical, + controller: _controller, + headerSliverBuilder: (context, innerBoxScrolled) { + return [ + SliverAppBar( + backgroundColor: context.priamryContainer, + floating: true, + pinned: true, + title: _showTitle + ? Text( + widget.episodeItem?.title ?? '', + maxLines: 1, + overflow: TextOverflow.ellipsis, + ) + : Text( + widget.episodeItem!.feedTitle!, + maxLines: 1, + style: TextStyle( + fontSize: 15, + color: + context.textColor.withOpacity(0.7)), + ), + leading: CustomBackButton(), + elevation: 0, + ), + ]; + }, + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + widget.episodeItem!.title!, + textAlign: TextAlign.left, + style: + Theme.of(context).textTheme.headlineSmall, ), - leading: CustomBackButton(), - elevation: _showTitle ? 1 : 0, - ), - ]; - }, - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 20.0), - child: Align( - alignment: Alignment.centerLeft, - child: Text( - widget.episodeItem!.title!, - textAlign: TextAlign.left, - style: Theme.of(context).textTheme.headline5, ), ), - ), - Padding( - padding: EdgeInsets.fromLTRB(20, 10, 20, 10), - child: Row( - children: [ - Text( - s.published(DateFormat.yMMMd().format( - DateTime.fromMillisecondsSinceEpoch( - widget.episodeItem!.pubDate!))), - style: TextStyle(color: context.accentColor)), - SizedBox(width: 10), - if (widget.episodeItem!.explicit == 1) - Text('E', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.red)), - Spacer(), - ], + Padding( + padding: const EdgeInsets.fromLTRB(20, 10, 20, 10), + child: Row( + children: [ + Text( + s.published(formateDate( + widget.episodeItem!.pubDate!)), + style: + TextStyle(color: context.accentColor)), + SizedBox(width: 10), + if (widget.episodeItem!.explicit == 1) + Text('E', + style: TextStyle( + fontWeight: FontWeight.bold, + color: context.error)), + Spacer(), + ], + ), ), - ), - Padding( - padding: - EdgeInsets.symmetric(horizontal: 20, vertical: 5), - child: Row( - children: [ - if (widget.episodeItem!.duration != 0) - Container( + Padding( + padding: EdgeInsets.symmetric( + horizontal: 20, vertical: 5), + child: Row( + children: [ + if (widget.episodeItem!.duration != 0) + Container( + decoration: BoxDecoration( + color: context.secondary, + borderRadius: BorderRadius.all( + Radius.circular(16.0))), + height: 30.0, + margin: EdgeInsets.only(right: 12.0), + padding: EdgeInsets.symmetric( + horizontal: 10.0), + alignment: Alignment.center, + child: Text( + s.minsCount( + widget.episodeItem!.duration! ~/ 60, + ), + style: + TextStyle(color: context.onPrimary), + )), + if (widget.episodeItem!.enclosureLength != + null && + widget.episodeItem!.enclosureLength != 0) + Container( decoration: BoxDecoration( - color: Colors.cyan[300], + color: context.tertiary, borderRadius: BorderRadius.all( Radius.circular(16.0))), - height: 28.0, - margin: EdgeInsets.only(right: 10.0), + height: 30.0, + margin: EdgeInsets.only(right: 12.0), padding: EdgeInsets.symmetric(horizontal: 10.0), alignment: Alignment.center, child: Text( - s.minsCount( - widget.episodeItem!.duration! ~/ 60, - ), - style: TextStyle(color: Colors.black), - )), - if (widget.episodeItem!.enclosureLength != null && - widget.episodeItem!.enclosureLength != 0) - Container( - decoration: BoxDecoration( - color: Colors.lightBlue[300], - borderRadius: BorderRadius.all( - Radius.circular(16.0))), - height: 28.0, - margin: EdgeInsets.only(right: 10.0), - padding: - EdgeInsets.symmetric(horizontal: 10.0), - alignment: Alignment.center, - child: Text( - '${widget.episodeItem!.enclosureLength! ~/ 1000000}MB', - style: TextStyle(color: Colors.black), + '${widget.episodeItem!.enclosureLength! ~/ 1000000}MB', + style: + TextStyle(color: context.onPrimary), + ), ), - ), - FutureBuilder( - future: _getPosition(widget.episodeItem!), - builder: (context, snapshot) { - if (snapshot.hasError) { - developer.log(snapshot.error as String); - } - if (snapshot.hasData && - snapshot.data!.seekValue! < 0.9 && - snapshot.data!.seconds! > 10) { - return ButtonTheme( - height: 28, - padding: - EdgeInsets.symmetric(horizontal: 0), - child: OutlinedButton( - style: OutlinedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular( - 100.0), - side: BorderSide( - color: - context.accentColor)), - ), - onPressed: () => audio.episodeLoad( - widget.episodeItem, - startPosition: - (snapshot.data!.seconds! * - 1000) - .toInt()), - child: Row( - children: [ - SizedBox( - width: 20, - height: 20, - child: CustomPaint( - painter: ListenedPainter( - context.textColor, - stroke: 2.0), + FutureBuilder( + future: _getPosition(widget.episodeItem!), + builder: (context, snapshot) { + if (snapshot.hasError) { + developer.log(snapshot.error as String); + } + if (snapshot.hasData && + snapshot.data!.seekValue! < 0.9 && + snapshot.data!.seconds! > 10) { + return ButtonTheme( + height: 28, + padding: EdgeInsets.symmetric( + horizontal: 0), + child: OutlinedButton( + style: OutlinedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular( + 100.0), + side: BorderSide( + color: + context.accentColor)), + ), + onPressed: () => audio.episodeLoad( + widget.episodeItem, + startPosition: + (snapshot.data!.seconds! * + 1000) + .toInt()), + child: Row( + children: [ + SizedBox( + width: 20, + height: 20, + child: CustomPaint( + painter: ListenedPainter( + context.textColor, + stroke: 2.0), + ), ), - ), - SizedBox(width: 5), - Text( - snapshot.data!.seconds!.toTime, - ), - ], + SizedBox(width: 5), + Text( + snapshot + .data!.seconds!.toTime, + ), + ], + ), ), - ), - ); - } else { - return Center(); - } - }), - ], + ); + } else { + return Center(); + } + }), + ], + ), ), - ), - ShowNote(episode: widget.episodeItem), - Selector>( - selector: (_, audio) => - Tuple2(audio.playerRunning, audio.playerHeight), - builder: (_, data, __) { - var height = kMinPlayerHeight[data.item2!.index]; - return SizedBox( - height: data.item1 ? height : 0, - ); - }), - ], + ShowNote(episode: widget.episodeItem), + Selector>( + selector: (_, audio) => Tuple2( + audio.playerRunning, audio.playerHeight), + builder: (_, data, __) { + final height = + kMinPlayerHeight[data.item2!.index]; + return SizedBox( + height: data.item1 ? height : 0, + ); + }), + ], + ), ), ), ), - ), - Selector>( - selector: (_, audio) => - Tuple2(audio.playerRunning, audio.playerHeight), - builder: (_, data, __) { - var height = kMinPlayerHeight[data.item2!.index]; - return Container( - alignment: Alignment.bottomCenter, - padding: EdgeInsets.only(bottom: data.item1 ? height : 0), - child: AnimatedContainer( - duration: Duration(milliseconds: 400), - height: _showMenu ? 50 : 0, - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: _MenuBar( - episodeItem: widget.episodeItem, - heroTag: widget.heroTag, - hide: widget.hide), - ), - ), - ); - }), - Selector( - selector: (_, audio) => audio.episode, - builder: (_, data, __) => Container( - child: PlayerWidget( - playerKey: _playerKey, - isPlayingPage: data == widget.episodeItem))), - ], - ), - ), - ), - ); - } -} - -class _MenuBar extends StatefulWidget { - final EpisodeBrief? episodeItem; - final String? heroTag; - final bool? hide; - _MenuBar({this.episodeItem, this.heroTag, this.hide, Key? key}) - : super(key: key); - @override - __MenuBarState createState() => __MenuBarState(); -} - -class __MenuBarState extends State<_MenuBar> { - Future _isListened(EpisodeBrief episode) async { - var dbHelper = DBHelper(); - return await dbHelper.isListened(episode.enclosureUrl); - } - - Future _saveLiked(String url) async { - var dbHelper = DBHelper(); - await dbHelper.setLiked(url); - if (mounted) setState(() {}); - } - - Future _setUnliked(String url) async { - var dbHelper = DBHelper(); - await dbHelper.setUniked(url); - if (mounted) setState(() {}); - } - - Future _markListened(EpisodeBrief episode) async { - var dbHelper = DBHelper(); - final history = PlayHistory(episode.title, episode.enclosureUrl, 0, 1); - await dbHelper.saveHistory(history); - if (mounted) setState(() {}); - } - - Future _markNotListened(String url) async { - var dbHelper = DBHelper(); - await dbHelper.markNotListened(url); - if (mounted) setState(() {}); - } - - Future _isLiked(EpisodeBrief episode) async { - var dbHelper = DBHelper(); - return await dbHelper.isLiked(episode.enclosureUrl); - } - - Widget _buttonOnMenu({Widget? child, VoidCallback? onTap}) => Material( - color: Colors.transparent, - child: InkWell( - onTap: onTap, - child: SizedBox( - height: 50, - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 15.0), child: child), - ), - ), - ); - - OverlayEntry _createOverlayEntry() { - RenderBox renderBox = context.findRenderObject() as RenderBox; - var offset = renderBox.localToGlobal(Offset.zero); - return OverlayEntry( - builder: (constext) => Positioned( - left: offset.dx + 50, - top: offset.dy - 60, - child: Container( - width: 70, - height: 100, - //color: Colors.grey[200], - child: HeartOpen(width: 50, height: 80)), - ), - ); - } - - @override - Widget build(BuildContext context) { - var audio = Provider.of(context, listen: false); - final s = context.s; - return Container( - height: 50.0, - decoration: BoxDecoration( - color: context.scaffoldBackgroundColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Hero( - tag: widget.episodeItem!.enclosureUrl + widget.heroTag!, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 10.0), - child: Container( - height: 30.0, - width: 30.0, - color: context.scaffoldBackgroundColor, - child: widget.hide! - ? Center() - : CircleAvatar( - radius: 15, - backgroundImage: - widget.episodeItem!.avatarImage), - ), - ), - ), - FutureBuilder( - future: _isLiked(widget.episodeItem!), - initialData: false, - builder: (context, snapshot) { - return (!snapshot.data!) - ? _buttonOnMenu( - child: Icon( - Icons.favorite_border, - color: Colors.grey[700], - ), - onTap: () async { - await _saveLiked( - widget.episodeItem!.enclosureUrl); - OverlayEntry _overlayEntry; - _overlayEntry = _createOverlayEntry(); - Overlay.of(context)!.insert(_overlayEntry); - await Future.delayed(Duration(seconds: 2)); - _overlayEntry.remove(); - }) - : _buttonOnMenu( - child: Icon( - Icons.favorite, - color: Colors.red, - ), - onTap: () => _setUnliked( - widget.episodeItem!.enclosureUrl)); - }, - ), - DownloadButton(episode: widget.episodeItem), - Selector>( - selector: (_, audio) => audio.queue.episodes, + Selector>( + selector: (_, audio) => + Tuple2(audio.playerRunning, audio.playerHeight), builder: (_, data, __) { - final inPlaylist = data.contains(widget.episodeItem); - return inPlaylist - ? _buttonOnMenu( - child: Icon(Icons.playlist_add_check, - color: context.accentColor), - onTap: () { - audio.delFromPlaylist(widget.episodeItem!); - Fluttertoast.showToast( - msg: s!.toastRemovePlaylist, - gravity: ToastGravity.BOTTOM, - ); - }) - : _buttonOnMenu( - child: Icon(Icons.playlist_add, - color: Colors.grey[700]), - onTap: () { - audio.addToPlaylist(widget.episodeItem!); - Fluttertoast.showToast( - msg: s!.toastAddPlaylist, - gravity: ToastGravity.BOTTOM, - ); - }); - }, - ), - FutureBuilder( - future: _isListened(widget.episodeItem!), - initialData: 0, - builder: (context, snapshot) { - return snapshot.data == 0 - ? _buttonOnMenu( - child: Padding( - padding: EdgeInsets.symmetric(vertical: 12), - child: CustomPaint( - size: Size(25, 20), - painter: ListenedAllPainter(Colors.grey[700], - stroke: 2.0), - ), - ), - onTap: () { - _markListened(widget.episodeItem!); - Fluttertoast.showToast( - msg: s!.markListened, - gravity: ToastGravity.BOTTOM, - ); - }) - : _buttonOnMenu( - child: Padding( - padding: EdgeInsets.symmetric(vertical: 12), - child: CustomPaint( - size: Size(25, 20), - painter: ListenedAllPainter( - context.accentColor, - stroke: 2.0), - ), - ), - onTap: () { - _markNotListened( - widget.episodeItem!.enclosureUrl); - Fluttertoast.showToast( - msg: s!.markNotListened, - gravity: ToastGravity.BOTTOM, - ); - }); - }, - ), - ], - ), + final height = kMinPlayerHeight[data.item2!.index]; + return Container( + alignment: Alignment.bottomCenter, + padding: + EdgeInsets.only(bottom: data.item1 ? height : 0), + child: AnimatedContainer( + duration: Duration(milliseconds: 400), + height: _showMenu ? 50 : 0, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: MenuBar( + episodeItem: widget.episodeItem, + heroTag: widget.heroTag, + hide: widget.hide), + ), + ), + ); + }), + Selector( + selector: (_, audio) => audio.episode, + builder: (_, data, __) => Container( + child: PlayerWidget( + playerKey: _playerKey, + isPlayingPage: data == widget.episodeItem))), + ], ), ), - Selector>( - selector: (_, audio) => Tuple2(audio.episode, audio.playerRunning), - builder: (_, data, __) { - return (widget.episodeItem == data.item1 && data.item2) - ? Padding( - padding: EdgeInsets.only(right: 30), - child: SizedBox( - width: 20, - height: 15, - child: WaveLoader(color: context.accentColor))) - : Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - audio.episodeLoad(widget.episodeItem); - }, - child: Container( - alignment: Alignment.center, - height: 50.0, - padding: EdgeInsets.symmetric(horizontal: 20.0), - child: Row( - children: [ - Text(s!.play.toUpperCase(), - style: TextStyle( - color: context.accentColor, - fontSize: 15, - fontWeight: FontWeight.bold, - )), - Icon( - Icons.play_arrow, - color: context.accentColor, - ), - ], - ), - ), - ), - ); - }, - ), - ], + ), ), ); } } - -class ShowNote extends StatelessWidget { - final EpisodeBrief? episode; - const ShowNote({this.episode, Key? key}) : super(key: key); - - int? _getTimeStamp(String url) { - final time = url.substring(3).trim(); - final data = time.split(':'); - var seconds; - if (data.length == 3) { - seconds = int.tryParse(data[0])! * 3600 + - int.tryParse(data[1])! * 60 + - int.tryParse(data[2])!; - } else if (data.length == 2) { - seconds = int.tryParse(data[0])! * 60 + int.tryParse(data[1])!; - } - return seconds; - } - - Future _getSDescription(String url) async { - var description; - var dbHelper = DBHelper(); - description = (await dbHelper.getDescription(url))! - .replaceAll(RegExp(r'\s?

(
)?

\s?'), '') - .replaceAll('\r', '') - .trim(); - if (!description.contains('<')) { - final linkList = linkify(description, - options: LinkifyOptions(humanize: false), - linkifiers: [UrlLinkifier(), EmailLinkifier()]); - for (var element in linkList) { - if (element is UrlElement) { - description = description.replaceAll(element.url, - '${element.text}'); - } - if (element is EmailElement) { - final address = element.emailAddress; - description = description.replaceAll(address, - '$address'); - } - } - await dbHelper.saveEpisodeDes(url, description: description); - } - return description; - } - - @override - Widget build(BuildContext context) { - var audio = context.watch(); - final s = context.s; - return FutureBuilder( - future: _getSDescription(episode!.enclosureUrl), - builder: (context, snapshot) { - if (snapshot.hasData) { - var description = snapshot.data; - if (description == null) return Center(); - return description.length > 0 - ? Selector( - selector: (_, audio) => audio.episode, - builder: (_, playEpisode, __) { - if (playEpisode == episode && - !description!.contains('#t=')) { - final linkList = linkify(description, - options: LinkifyOptions(humanize: false), - linkifiers: [TimeStampLinkifier()]); - for (var element in linkList) { - if (element is TimeStampElement) { - final time = element.timeStamp; - description = description!.replaceFirst(time!, - '$time'); - } - } - } - return Selector( - selector: (_, settings) => settings.showNoteFontStyle, - builder: (_, data, __) => Html( - style: { - 'a': Style( - color: context.accentColor, - ), - }, - data: description, - onLinkTap: (url, _, __, ___) { - if (url!.substring(0, 3) == '#t=') { - final seconds = _getTimeStamp(url); - if (playEpisode == episode) { - audio.seekTo(seconds! * 1000); - } - } else { - url.launchUrl; - } - }, - ), - ); - }) - : Container( - height: context.width, - alignment: Alignment.center, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image( - image: AssetImage('assets/shownote.png'), - height: 100.0, - ), - Padding(padding: EdgeInsets.all(5.0)), - Text(s!.noShownote, - textAlign: TextAlign.center, - style: TextStyle( - color: context.textColor!.withOpacity(0.5))), - ], - ), - ); - } else { - return Center(); - } - }, - ); - } -} diff --git a/lib/episodes/episode_download.dart b/lib/episodes/episode_download.dart index 624a8c2..967aa94 100644 --- a/lib/episodes/episode_download.dart +++ b/lib/episodes/episode_download.dart @@ -45,7 +45,7 @@ class _DownloadButtonState extends State { void _deleteDownload(EpisodeBrief episode) async { Provider.of(context, listen: false).delTask(episode); Fluttertoast.showToast( - msg: context.s!.downloadRemovedToast, + msg: context.s.downloadRemovedToast, gravity: ToastGravity.BOTTOM, ); } @@ -78,22 +78,20 @@ class _DownloadButtonState extends State { Future _useDataConfirm() async { var ifUseData = false; - final s = context.s!; + final s = context.s; await generalDialog( context, title: Text(s.cellularConfirm), content: Text(s.cellularConfirmDes), actions: [ - FlatButton( - onPressed: () { - Navigator.of(context).pop(); - }, + TextButton( + onPressed: Navigator.of(context).pop, child: Text( s.cancel, style: TextStyle(color: Colors.grey[600]), ), ), - FlatButton( + TextButton( onPressed: () { ifUseData = true; Navigator.of(context).pop(); @@ -130,7 +128,7 @@ class _DownloadButtonState extends State { AnimatedContainer( duration: Duration(seconds: 1), decoration: BoxDecoration( - color: Theme.of(context).accentColor, + color: context.accentColor, borderRadius: BorderRadius.all(Radius.circular(15.0))), height: 20.0, width: (_task.status == DownloadTaskStatus.running) ? 50.0 : 0, @@ -222,7 +220,6 @@ class _DownloadButtonState extends State { ), ), ); - break; case 3: Provider.of(context, listen: false) .updateMediaItem(task.episode!); @@ -251,11 +248,9 @@ class _DownloadButtonState extends State { ), ), ); - break; case 4: return _buttonOnMenu(Icon(Icons.refresh, color: Colors.red), () => _retryDownload(task.episode!)); - break; default: return Center(); } diff --git a/lib/episodes/menu_bar.dart b/lib/episodes/menu_bar.dart new file mode 100644 index 0000000..479a4b2 --- /dev/null +++ b/lib/episodes/menu_bar.dart @@ -0,0 +1,271 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:tsacdop/local_storage/sqflite_localpodcast.dart'; +import 'package:tsacdop/type/play_histroy.dart'; +import 'package:tuple/tuple.dart'; +import 'package:provider/provider.dart'; +import 'package:tsacdop/episodes/episode_download.dart'; +import 'package:tsacdop/state/audio_state.dart'; +import 'package:tsacdop/type/episodebrief.dart'; +import 'package:tsacdop/util/extension_helper.dart'; +import 'package:tsacdop/widgets/custom_widget.dart'; + +class MenuBar extends StatefulWidget { + final EpisodeBrief? episodeItem; + final String? heroTag; + final bool? hide; + MenuBar({this.episodeItem, this.heroTag, this.hide, Key? key}) + : super(key: key); + @override + MenuBarState createState() => MenuBarState(); +} + +class MenuBarState extends State { + @override + Widget build(BuildContext context) { + final audio = Provider.of(context, listen: false); + final s = context.s; + return Container( + height: 50.0, + decoration: BoxDecoration( + color: context.priamryContainer, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Hero( + tag: widget.episodeItem!.enclosureUrl + widget.heroTag!, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.0), + child: Container( + height: 30.0, + width: 30.0, + color: context.priamryContainer, + child: widget.hide! + ? Center() + : CircleAvatar( + radius: 15, + backgroundImage: + widget.episodeItem!.avatarImage), + ), + ), + ), + FutureBuilder( + future: _isLiked(widget.episodeItem!), + initialData: false, + builder: (context, snapshot) { + return (!snapshot.data!) + ? _buttonOnMenu( + child: Icon( + Icons.favorite_border, + color: Colors.grey[700], + ), + onTap: () async { + await _saveLiked( + widget.episodeItem!.enclosureUrl); + OverlayEntry _overlayEntry; + _overlayEntry = _createOverlayEntry(); + Overlay.of(context)!.insert(_overlayEntry); + await Future.delayed(Duration(seconds: 2)); + _overlayEntry.remove(); + }) + : _buttonOnMenu( + child: Icon( + Icons.favorite, + color: Colors.red, + ), + onTap: () => _setUnliked( + widget.episodeItem!.enclosureUrl)); + }, + ), + DownloadButton(episode: widget.episodeItem), + Selector>( + selector: (_, audio) => audio.queue.episodes, + builder: (_, data, __) { + final inPlaylist = data.contains(widget.episodeItem); + return inPlaylist + ? _buttonOnMenu( + child: Icon(Icons.playlist_add_check, + color: context.accentColor), + onTap: () { + audio.delFromPlaylist(widget.episodeItem!); + Fluttertoast.showToast( + msg: s.toastRemovePlaylist, + gravity: ToastGravity.BOTTOM, + ); + }) + : _buttonOnMenu( + child: Icon(Icons.playlist_add, + color: Colors.grey[700]), + onTap: () { + audio.addToPlaylist(widget.episodeItem!); + Fluttertoast.showToast( + msg: s.toastAddPlaylist, + gravity: ToastGravity.BOTTOM, + ); + }); + }, + ), + FutureBuilder( + future: _isListened(widget.episodeItem!), + initialData: 0, + builder: (context, snapshot) { + return snapshot.data == 0 + ? _buttonOnMenu( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 12), + child: CustomPaint( + size: Size(25, 20), + painter: ListenedAllPainter(Colors.grey[700], + stroke: 2.0), + ), + ), + onTap: () { + _markListened(widget.episodeItem!); + Fluttertoast.showToast( + msg: s.markListened, + gravity: ToastGravity.BOTTOM, + ); + }) + : _buttonOnMenu( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 12), + child: CustomPaint( + size: Size(25, 20), + painter: ListenedAllPainter( + context.accentColor, + stroke: 2.0), + ), + ), + onTap: () { + _markNotListened( + widget.episodeItem!.enclosureUrl); + Fluttertoast.showToast( + msg: s.markNotListened, + gravity: ToastGravity.BOTTOM, + ); + }, + ); + }, + ), + ], + ), + ), + ), + Selector>( + selector: (_, audio) => Tuple2(audio.episode, audio.playerRunning), + builder: (_, data, __) { + return (widget.episodeItem == data.item1 && data.item2) + ? Padding( + padding: EdgeInsets.only(right: 30), + child: SizedBox( + width: 20, + height: 15, + child: WaveLoader(color: context.accentColor))) + : Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + audio.episodeLoad(widget.episodeItem); + }, + child: Container( + alignment: Alignment.center, + height: 50.0, + padding: EdgeInsets.symmetric(horizontal: 20.0), + child: Row( + children: [ + Text( + s.play.toUpperCase(), + style: TextStyle( + color: context.accentColor, + fontSize: 15, + fontWeight: FontWeight.bold, + ), + ), + Icon( + Icons.play_arrow, + color: context.accentColor, + ), + ], + ), + ), + ), + ); + }, + ), + ], + ), + ); + } + + Future _isListened(EpisodeBrief episode) async { + final dbHelper = DBHelper(); + return await dbHelper.isListened(episode.enclosureUrl); + } + + Future _saveLiked(String url) async { + final dbHelper = DBHelper(); + await dbHelper.setLiked(url); + if (mounted) setState(() {}); + } + + Future _setUnliked(String url) async { + final dbHelper = DBHelper(); + await dbHelper.setUniked(url); + if (mounted) setState(() {}); + } + + Future _markListened(EpisodeBrief episode) async { + final dbHelper = DBHelper(); + final history = PlayHistory(episode.title, episode.enclosureUrl, 0, 1); + await dbHelper.saveHistory(history); + if (mounted) setState(() {}); + } + + Future _markNotListened(String url) async { + final dbHelper = DBHelper(); + await dbHelper.markNotListened(url); + if (mounted) setState(() {}); + } + + Future _isLiked(EpisodeBrief episode) async { + final dbHelper = DBHelper(); + return await dbHelper.isLiked(episode.enclosureUrl); + } + + Widget _buttonOnMenu({Widget? child, VoidCallback? onTap}) => Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + child: SizedBox( + height: 50, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 15.0), child: child), + ), + ), + ); + + OverlayEntry _createOverlayEntry() { + RenderBox renderBox = context.findRenderObject() as RenderBox; + var offset = renderBox.localToGlobal(Offset.zero); + return OverlayEntry( + builder: (constext) => Positioned( + left: offset.dx + 50, + top: offset.dy - 60, + child: Container( + width: 70, + height: 100, + //color: Colors.grey[200], + child: HeartOpen(width: 50, height: 80)), + ), + ); + } +} diff --git a/lib/episodes/shownote.dart b/lib/episodes/shownote.dart new file mode 100644 index 0000000..413c136 --- /dev/null +++ b/lib/episodes/shownote.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:linkify/linkify.dart'; +import 'package:provider/provider.dart'; +import 'package:tsacdop/local_storage/sqflite_localpodcast.dart'; +import 'package:tsacdop/state/audio_state.dart'; +import 'package:tsacdop/state/setting_state.dart'; +import 'package:tsacdop/type/episodebrief.dart'; +import 'package:tsacdop/util/extension_helper.dart'; + +class ShowNote extends StatelessWidget { + final EpisodeBrief? episode; + const ShowNote({this.episode, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final audio = context.watch(); + final s = context.s; + return FutureBuilder( + future: _getSDescription(episode!.enclosureUrl), + builder: (context, snapshot) { + if (snapshot.hasData) { + var description = snapshot.data; + if (description == null) return Center(); + if (description.length > 0) { + return Selector( + selector: (_, audio) => audio.episode, + builder: (_, playEpisode, __) { + if (playEpisode == episode && !description!.contains('#t=')) { + final linkList = linkify(description, + options: LinkifyOptions(humanize: false), + linkifiers: [TimeStampLinkifier()]); + for (final element in linkList) { + if (element is TimeStampElement) { + final time = element.timeStamp; + description = description!.replaceFirst(time!, + '$time'); + } + } + } + return Selector( + selector: (_, settings) => settings.showNoteFontStyle, + builder: (_, data, __) => Html( + style: { + 'html': Style.fromTextStyle(data.copyWith(fontSize: 14)) + .copyWith( + padding: const EdgeInsets.symmetric(horizontal: 12), + ), + 'a': Style( + color: context.accentColor, + textDecoration: TextDecoration.none, + ), + }, + data: description, + onLinkTap: (url, _, __, ___) { + if (url!.substring(0, 3) == '#t=') { + final seconds = _getTimeStamp(url); + if (playEpisode == episode) { + audio.seekTo(seconds! * 1000); + } + } else { + url.launchUrl; + } + }, + ), + ); + }, + ); + } else { + return Container( + height: context.width, + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image( + image: AssetImage('assets/shownote.png'), + height: 100.0, + ), + Padding(padding: EdgeInsets.all(5.0)), + Text(s.noShownote, + textAlign: TextAlign.center, + style: + TextStyle(color: context.textColor.withOpacity(0.5))), + ], + ), + ); + } + } else { + return Center(); + } + }, + ); + } + + int? _getTimeStamp(String url) { + final time = url.substring(3).trim(); + final data = time.split(':'); + int? seconds; + if (data.length == 3) { + seconds = int.tryParse(data[0])! * 3600 + + int.tryParse(data[1])! * 60 + + int.tryParse(data[2])!; + } else if (data.length == 2) { + seconds = int.tryParse(data[0])! * 60 + int.tryParse(data[1])!; + } + return seconds; + } + + Future _getSDescription(String url) async { + final dbHelper = DBHelper(); + String description; + description = (await dbHelper.getDescription(url))! + .replaceAll(RegExp(r'\s?

(
)?

\s?'), '') + .replaceAll('\r', '') + .trim(); + if (!description.contains('<')) { + final linkList = linkify(description, + options: LinkifyOptions(humanize: false), + linkifiers: [UrlLinkifier(), EmailLinkifier()]); + for (var element in linkList) { + if (element is UrlElement) { + description = description.replaceAll(element.url!, + '${element.text}'); + } + if (element is EmailElement) { + final address = element.emailAddress; + description = description.replaceAll(address, + '$address'); + } + } + await dbHelper.saveEpisodeDes(url, description: description); + } + return description; + } +} diff --git a/lib/home/about.dart b/lib/home/about.dart index ef7daf5..8adbffa 100644 --- a/lib/home/about.dart +++ b/lib/home/about.dart @@ -57,7 +57,8 @@ class _AboutAppState extends State { ), ); - Widget _translatorInfo(BuildContext context, {required String name, String? flag}) => + Widget _translatorInfo(BuildContext context, + {required String name, String? flag}) => Container( height: 50.0, padding: EdgeInsets.symmetric(horizontal: 20.0), @@ -114,7 +115,7 @@ class _AboutAppState extends State { ); } - final s = context.s!; + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarIconBrightness: Theme.of(context).accentColorBrightness, @@ -208,7 +209,10 @@ class _AboutAppState extends State { children: [ _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'), @@ -281,7 +285,7 @@ class _AboutAppState extends State { name: 'Bruno Pinheiro', flag: 'pt'), _translatorInfo(context, name: 'Edoardo Maria Elidoro', flag: 'it'), - _translatorInfo(context, + _translatorInfo(context, name: 'Murat T. Akyuz', flag: 'tr'), ], ), diff --git a/lib/home/audioplayer.dart b/lib/home/audioplayer.dart index 3886629..e613019 100644 --- a/lib/home/audioplayer.dart +++ b/lib/home/audioplayer.dart @@ -11,6 +11,7 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:line_icons/line_icons.dart'; import 'package:marquee/marquee.dart'; import 'package:provider/provider.dart'; +import 'package:tsacdop/episodes/shownote.dart'; import 'package:tuple/tuple.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; @@ -499,8 +500,8 @@ class _PlaylistWidgetState extends State { children: [ Text( data.item1!.name == 'Queue' - ? context.s!.queue - : '${context.s!.homeMenuPlaylist}${'-${data.item1!.name}'}', + ? context.s.queue + : '${context.s.homeMenuPlaylist}${'-${data.item1!.name}'}', overflow: TextOverflow.fade, style: TextStyle( color: context.accentColor, @@ -826,7 +827,7 @@ class SleepModeState extends State padding: EdgeInsets.symmetric(horizontal: 20.0), child: Row( children: [ - Text(context.s!.sleepTimer, + Text(context.s.sleepTimer, style: TextStyle( color: context.accentColor, fontWeight: FontWeight.bold, @@ -935,10 +936,9 @@ class _ChaptersWidgetState extends State { padding: EdgeInsets.symmetric(horizontal: 0), child: OutlinedButton( style: OutlinedButton.styleFrom( - - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(100.0), - side: BorderSide(color: context.accentColor)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100.0), + side: BorderSide(color: context.accentColor)), ), // highlightedBorderColor: Colors.green[700], onPressed: () { @@ -1088,7 +1088,7 @@ class _ChaptersWidgetState extends State { child: Row( children: [ Text( - context.s!.homeToprightMenuAbout, + context.s.homeToprightMenuAbout, overflow: TextOverflow.fade, style: TextStyle( color: context.accentColor, @@ -1332,7 +1332,7 @@ class _ControlPanelState extends State .buffering || data.audioState == AudioProcessingState.loading - ? context.s!.buffering + ? context.s.buffering : '', style: TextStyle( color: context.accentColor), diff --git a/lib/home/home.dart b/lib/home/home.dart index 9aaedbd..32ac7ff 100644 --- a/lib/home/home.dart +++ b/lib/home/home.dart @@ -64,7 +64,7 @@ class _HomeState extends State with SingleTickerProviderStateMixin { _controller = TabController(length: 3, vsync: this); // FeatureDiscovery.hasPreviouslyCompleted(context, addFeature).then((value) { // if (!value) { - SchedulerBinding.instance!.addPostFrameCallback((_) { + SchedulerBinding.instance.addPostFrameCallback((_) { FeatureDiscovery.discoverFeatures( context, const { @@ -90,15 +90,15 @@ class _HomeState extends State with SingleTickerProviderStateMixin { double top = 0; @override Widget build(BuildContext context) { - var height = (context.width - 20) / 3 + 140; - var settings = Provider.of(context, listen: false); + final height = (context.width - 20) / 3 + 140; + final settings = Provider.of(context, listen: false); final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( - systemNavigationBarIconBrightness: - context.brightness, + systemNavigationBarIconBrightness: context.brightness, statusBarIconBrightness: context.iconBrightness, - systemNavigationBarColor: context.primaryColor, + systemNavigationBarColor: context.onPrimary, + statusBarColor: context.onPrimary, ), child: WillPopScope( onWillPop: () async { @@ -115,12 +115,13 @@ class _HomeState extends State with SingleTickerProviderStateMixin { }, child: Scaffold( key: _scaffoldKey, + backgroundColor: context.onPrimary, body: Stack( children: [ SafeArea( bottom: false, - child: ScrollConfiguration( - behavior: NoGrowBehavior(), + child: StretchingOverscrollIndicator( + axisDirection: AxisDirection.down, child: NestedScrollView( innerScrollPositionKeyBuilder: () { return Key('tab${_controller!.index}'); @@ -141,7 +142,7 @@ class _HomeState extends State with SingleTickerProviderStateMixin { context, featureId: addFeature, tapTarget: Icon(Icons.add_circle_outline), - title: s!.featureDiscoverySearch, + title: s.featureDiscoverySearch, backgroundColor: Colors.cyan[600], buttonColor: Colors.cyan[500], description: s.featureDiscoverySearchDes, @@ -240,14 +241,13 @@ class _HomeState extends State with SingleTickerProviderStateMixin { ), ), Selector( - selector: (_, audio) => - audio.playerRunning, - builder: (_, data, __) { - return Padding( - padding: - EdgeInsets.only(bottom: data ? 60.0 : 0), - ); - }), + selector: (_, audio) => audio.playerRunning, + builder: (_, data, __) { + return Padding( + padding: EdgeInsets.only(bottom: data ? 60.0 : 0), + ); + }, + ), ], ), ), @@ -274,9 +274,9 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { @override Widget build( BuildContext context, double shrinkOffset, bool overlapsContent) { - final s = context.s!; + final s = context.s; return Container( - color: context.scaffoldBackgroundColor, + color: context.background, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -335,7 +335,7 @@ class __PlaylistButtonState extends State<_PlaylistButton> { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; return Material( color: Colors.transparent, borderRadius: BorderRadius.circular(100), @@ -406,19 +406,23 @@ class __PlaylistButtonState extends State<_PlaylistButton> { Padding( padding: EdgeInsets.symmetric(vertical: 2), ), - Container( + SizedBox( height: 70, width: 140, child: Column( children: [ Text( (data.item3 ~/ 1000).toTime, + style: + TextStyle(color: context.textColor), ), Text( data.item2!.title!, maxLines: 2, textAlign: TextAlign.center, overflow: TextOverflow.fade, + style: + TextStyle(color: context.textColor), // style: TextStyle(color: Colors.white), ), ], @@ -443,7 +447,10 @@ class __PlaylistButtonState extends State<_PlaylistButton> { Padding( padding: EdgeInsets.symmetric(horizontal: 5.0), ), - Text(s.homeMenuPlaylist), + Text( + s.homeMenuPlaylist, + style: TextStyle(color: context.textColor), + ), ], ), ), @@ -475,7 +482,7 @@ class __PlaylistButtonState extends State<_PlaylistButton> { Navigator.push( context, MaterialPageRoute( - builder: (context) => PlaylistHome(), + builder: (_) => PlaylistHome(), ), ); } @@ -530,7 +537,7 @@ class _RecentUpdateState extends State<_RecentUpdate> refreshWorker.start(_group); await Future.delayed(Duration(seconds: 1)); Fluttertoast.showToast( - msg: context.s!.refreshStarted, + msg: context.s.refreshStarted, gravity: ToastGravity.BOTTOM, ); } @@ -593,14 +600,14 @@ class _RecentUpdateState extends State<_RecentUpdate> builder: (context, groupList, child) => PopupMenuButton( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), elevation: 1, - tooltip: context.s!.groupFilter, + tooltip: context.s.groupFilter, child: Container( padding: EdgeInsets.symmetric(horizontal: 20), height: 50, child: Row( mainAxisSize: MainAxisSize.min, children: [ - Text(_groupName == 'All' ? context.s!.all : _groupName!), + Text(_groupName == 'All' ? context.s.all : _groupName!), Padding( padding: EdgeInsets.symmetric(horizontal: 5), ), @@ -613,7 +620,7 @@ class _RecentUpdateState extends State<_RecentUpdate> itemBuilder: (context) => [ PopupMenuItem( child: Row(children: [ - Text(context.s!.all), + Text(context.s.all), Spacer(), if (_groupName == 'All') DotIndicator() ]), @@ -806,7 +813,7 @@ class _RecentUpdateState extends State<_RecentUpdate> Material( color: Colors.transparent, child: IconButton( - tooltip: context.s!.refresh, + tooltip: context.s.refresh, icon: Icon( LineIcons.alternateRedo, size: 16), diff --git a/lib/home/home_groups.dart b/lib/home/home_groups.dart index aa878c5..ec775e3 100644 --- a/lib/home/home_groups.dart +++ b/lib/home/home_groups.dart @@ -70,8 +70,355 @@ class _ScrollPodcastsState extends State super.dispose(); } + @override + Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + final s = context.s; + return Selector2, bool, bool>>( + selector: (_, groupList, refreshWorker) => tuple.Tuple3( + groupList.groups, groupList.created, refreshWorker.created), + builder: (_, data, __) { + final groups = data.item1; + final import = data.item2; + if (groups.isEmpty) { + return SizedBox( + height: (width - 20) / 3 + 140, + ); + } + if (groups[_groupIndex]!.podcastList.length == 0) { + return SizedBox( + height: (width - 20) / 3 + 140, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onVerticalDragEnd: (event) { + if (event.primaryVelocity! > 200) { + if (groups.length == 1) { + Fluttertoast.showToast( + msg: s.addSomeGroups, + gravity: ToastGravity.BOTTOM, + ); + } else { + if (mounted) { + setState(() { + (_groupIndex != 0) + ? _groupIndex-- + : _groupIndex = groups.length - 1; + }); + } + } + } else if (event.primaryVelocity! < -200) { + if (groups.length == 1) { + Fluttertoast.showToast( + msg: s.addSomeGroups, + gravity: ToastGravity.BOTTOM, + ); + } else { + if (mounted) { + setState(() { + (_groupIndex < groups.length - 1) + ? _groupIndex++ + : _groupIndex = 0; + }); + } + } + } + }, + child: Column( + children: [ + SizedBox( + height: 30, + child: Row( + children: [ + Padding( + 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), + child: InkWell( + onTap: () { + if (!import) { + Navigator.push( + context, + SlideLeftRoute( + page: context + .read() + .openAllPodcastDefalt! + ? PodcastList() + : PodcastManage()), + ); + } + }, + onLongPress: () { + if (!import) { + Navigator.push( + context, + SlideLeftRoute(page: PodcastList()), + ); + } + }, + borderRadius: BorderRadius.circular(5), + child: Padding( + padding: const EdgeInsets.all(5.0), + child: Text( + s.homeGroupsSeeAll, + style: context.textTheme.bodyText1! + .copyWith( + color: import + ? context.primaryColorDark + : context.accentColor), + ), + ), + ), + ), + ], + ), + ), + Container( + height: 70, + color: context.background, + child: Row( + children: [ + _circleContainer(context), + _circleContainer(context), + _circleContainer(context) + ], + )), + ], + )), + Container( + height: (width - 20) / 3 + 40, + color: context.onPrimary, + margin: EdgeInsets.symmetric(horizontal: 15), + child: Center( + child: _groupIndex == 0 + ? Text.rich(TextSpan( + style: context.textTheme.headline6! + .copyWith(height: 2), + children: [ + TextSpan( + text: 'Welcome to Tsacdop\n', + style: context.textTheme.headline6! + .copyWith(color: context.accentColor)), + TextSpan( + text: 'Get started\n', + style: context.textTheme.headline6! + .copyWith(color: context.accentColor)), + TextSpan(text: 'Tap '), + WidgetSpan( + child: Icon(Icons.add_circle_outline)), + TextSpan(text: ' to search podcasts') + ], + )) + : Text(s.noPodcastGroup, + style: TextStyle( + color: context.textTheme.bodyText2!.color! + .withOpacity(0.5)))), + ), + ], + ), + ); + } + return DefaultTabController( + length: groups[_groupIndex]!.podcasts.length, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onVerticalDragEnd: (event) async { + if (event.primaryVelocity! > 200) { + if (groups.length == 1) { + Fluttertoast.showToast( + msg: s.addSomeGroups, + gravity: ToastGravity.BOTTOM, + ); + } else { + if (mounted) { + setState(() => _slideTween = _getSlideTween(20)); + _controller.forward(); + await Future.delayed(Duration(milliseconds: 50)); + if (mounted) { + setState(() { + (_groupIndex != 0) + ? _groupIndex-- + : _groupIndex = groups.length - 1; + }); + } + } + } + } else if (event.primaryVelocity! < -200) { + if (groups.length == 1) { + Fluttertoast.showToast( + msg: s.addSomeGroups, + gravity: ToastGravity.BOTTOM, + ); + } else { + setState(() => _slideTween = _getSlideTween(-20)); + await Future.delayed(Duration(milliseconds: 50)); + _controller.forward(); + if (mounted) { + setState(() { + (_groupIndex < groups.length - 1) + ? _groupIndex++ + : _groupIndex = 0; + }); + } + } + } + }, + child: Column( + children: [ + SizedBox( + height: 30, + child: Row( + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 15.0), + child: Text( + groups[_groupIndex]!.name!, + style: context.textTheme.bodyText1! + .copyWith(color: context.accentColor), + )), + Spacer(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: InkWell( + onTap: () { + if (!import) { + Navigator.push( + context, + SlideLeftRoute( + page: context + .read() + .openAllPodcastDefalt! + ? PodcastList() + : PodcastManage()), + ); + } + }, + onLongPress: () { + if (!import) { + Navigator.push( + context, + SlideLeftRoute(page: PodcastList()), + ); + } + }, + borderRadius: BorderRadius.circular(5), + child: Padding( + padding: const EdgeInsets.all(5.0), + child: Text( + s.homeGroupsSeeAll, + style: context.textTheme.bodyText1! + .copyWith( + color: import + ? context.primaryColorDark + : context.accentColor), + ), + )), + ) + ], + ), + ), + Container( + height: 70, + width: width, + alignment: Alignment.centerLeft, + color: context.onPrimary, + child: TabBar( + labelPadding: EdgeInsets.fromLTRB(6.0, 5.0, 6.0, 10.0), + indicator: CircleTabIndicator( + color: context.accentColor, radius: 3), + isScrollable: true, + tabs: groups[_groupIndex]! + .podcasts + .map((podcastLocal) { + final color = podcastLocal.backgroudColor(context); + return Tab( + child: Transform.translate( + offset: Offset( + 0, _slideTween.animate(_controller).value), + child: LimitedBox( + maxHeight: 50, + maxWidth: 50, + child: CircleAvatar( + backgroundColor: color.withOpacity(0.5), + backgroundImage: podcastLocal.avatarImage, + child: _updateIndicator(podcastLocal)), + ), + ), + ); + }).toList(), + ), + ), + ], + ), + ), + Container( + height: (width - 20) / 3 + 40, + margin: const EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: context.background, + ), + child: ScrollConfiguration( + behavior: NoGrowBehavior(), + child: TabBarView( + children: groups[_groupIndex]!.podcasts.map( + (podcastLocal) { + return Container( + decoration: BoxDecoration( + color: context.brightness == Brightness.light + ? context.primaryColor + : Colors.black12), + margin: EdgeInsets.symmetric(horizontal: 5.0), + key: ObjectKey(podcastLocal.title), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Navigator.push( + context, + HidePlayerRoute( + PodcastDetail( + podcastLocal: podcastLocal, + ), + PodcastDetail( + podcastLocal: podcastLocal, + hide: true), + duration: Duration(milliseconds: 300), + )); + }, + child: PodcastPreview( + podcastLocal: podcastLocal, + ), + ), + ), + ); + }, + ).toList(), + ), + ), + ), + ], + ), + ); + }, + ); + } + Future _getPodcastUpdateCounts(String? id) async { - var dbHelper = DBHelper(); + final dbHelper = DBHelper(); return await dbHelper.getPodcastUpdateCounts(id); } @@ -102,387 +449,6 @@ class _ScrollPodcastsState extends State ) : Center(); }); - - @override - Widget build(BuildContext context) { - final width = MediaQuery.of(context).size.width; - final s = context.s; - return Selector2, bool, bool>>( - selector: (_, groupList, refreshWorker) => tuple.Tuple3( - groupList.groups, groupList.created, refreshWorker.created), - builder: (_, data, __) { - var groups = data.item1; - var import = data.item2; - return groups.isEmpty - ? SizedBox( - height: (width - 20) / 3 + 140, - ) - : groups[_groupIndex]!.podcastList.length == 0 - ? SizedBox( - height: (width - 20) / 3 + 140, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - GestureDetector( - onVerticalDragEnd: (event) { - if (event.primaryVelocity! > 200) { - if (groups.length == 1) { - Fluttertoast.showToast( - msg: s!.addSomeGroups, - gravity: ToastGravity.BOTTOM, - ); - } else { - if (mounted) { - setState(() { - (_groupIndex != 0) - ? _groupIndex-- - : _groupIndex = groups.length - 1; - }); - } - } - } else if (event.primaryVelocity! < -200) { - if (groups.length == 1) { - Fluttertoast.showToast( - msg: s!.addSomeGroups, - gravity: ToastGravity.BOTTOM, - ); - } else { - if (mounted) { - setState(() { - (_groupIndex < groups.length - 1) - ? _groupIndex++ - : _groupIndex = 0; - }); - } - } - } - }, - child: Column( - children: [ - SizedBox( - height: 30, - child: Row( - children: [ - Padding( - 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), - child: InkWell( - onTap: () { - if (!import) { - Navigator.push( - context, - SlideLeftRoute( - page: context - .read< - SettingState>() - .openAllPodcastDefalt! - ? PodcastList() - : PodcastManage()), - ); - } - }, - onLongPress: () { - if (!import) { - Navigator.push( - context, - SlideLeftRoute( - page: PodcastList()), - ); - } - }, - borderRadius: - BorderRadius.circular(5), - child: Padding( - padding: const EdgeInsets.all(5.0), - child: Text( - s!.homeGroupsSeeAll, - style: context - .textTheme.bodyText1! - .copyWith( - color: import - ? context - .primaryColorDark - : context - .accentColor), - ), - ), - ), - ), - ], - ), - ), - Container( - height: 70, - color: context.scaffoldBackgroundColor, - child: Row( - children: [ - _circleContainer(context), - _circleContainer(context), - _circleContainer(context) - ], - )), - ], - )), - Container( - height: (width - 20) / 3 + 40, - color: context.primaryColor, - margin: EdgeInsets.symmetric(horizontal: 15), - child: Center( - child: _groupIndex == 0 - ? Text.rich(TextSpan( - style: context.textTheme.headline6! - .copyWith(height: 2), - children: [ - TextSpan( - text: 'Welcome to Tsacdop\n', - style: context.textTheme.headline6! - .copyWith( - color: - context.accentColor)), - TextSpan( - text: 'Get started\n', - style: context.textTheme.headline6! - .copyWith( - color: - context.accentColor)), - TextSpan(text: 'Tap '), - WidgetSpan( - child: - Icon(Icons.add_circle_outline)), - TextSpan(text: ' to search podcasts') - ], - )) - : Text(s.noPodcastGroup, - style: TextStyle( - color: context - .textTheme.bodyText2!.color! - .withOpacity(0.5)))), - ), - ], - ), - ) - : DefaultTabController( - length: groups[_groupIndex]!.podcasts.length, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - GestureDetector( - onVerticalDragEnd: (event) async { - if (event.primaryVelocity! > 200) { - if (groups.length == 1) { - Fluttertoast.showToast( - msg: s!.addSomeGroups, - gravity: ToastGravity.BOTTOM, - ); - } else { - if (mounted) { - setState( - () => _slideTween = _getSlideTween(20)); - _controller.forward(); - await Future.delayed( - Duration(milliseconds: 50)); - if (mounted) { - setState(() { - (_groupIndex != 0) - ? _groupIndex-- - : _groupIndex = groups.length - 1; - }); - } - } - } - } else if (event.primaryVelocity! < -200) { - if (groups.length == 1) { - Fluttertoast.showToast( - msg: s!.addSomeGroups, - gravity: ToastGravity.BOTTOM, - ); - } else { - setState( - () => _slideTween = _getSlideTween(-20)); - await Future.delayed( - Duration(milliseconds: 50)); - _controller.forward(); - if (mounted) { - setState(() { - (_groupIndex < groups.length - 1) - ? _groupIndex++ - : _groupIndex = 0; - }); - } - } - } - }, - child: Column( - children: [ - SizedBox( - height: 30, - child: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 15.0), - child: Text( - groups[_groupIndex]!.name!, - style: context.textTheme.bodyText1! - .copyWith( - color: context.accentColor), - )), - Spacer(), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 15), - child: InkWell( - onTap: () { - if (!import) { - Navigator.push( - context, - SlideLeftRoute( - page: context - .read< - SettingState>() - .openAllPodcastDefalt! - ? PodcastList() - : PodcastManage()), - ); - } - }, - onLongPress: () { - if (!import) { - Navigator.push( - context, - SlideLeftRoute( - page: PodcastList()), - ); - } - }, - borderRadius: - BorderRadius.circular(5), - child: Padding( - padding: const EdgeInsets.all(5.0), - child: Text( - s!.homeGroupsSeeAll, - style: context - .textTheme.bodyText1! - .copyWith( - color: import - ? context - .primaryColorDark - : context - .accentColor), - ), - )), - ) - ], - ), - ), - Container( - height: 70, - width: width, - alignment: Alignment.centerLeft, - color: context.scaffoldBackgroundColor, - child: TabBar( - labelPadding: - EdgeInsets.fromLTRB(6.0, 5.0, 6.0, 10.0), - indicator: CircleTabIndicator( - color: context.accentColor, radius: 3), - isScrollable: true, - tabs: groups[_groupIndex]! - .podcasts - .map((podcastLocal) { - final color = - podcastLocal.backgroudColor(context); - return Tab( - child: Transform.translate( - offset: Offset( - 0, - _slideTween - .animate(_controller) - .value), - child: LimitedBox( - maxHeight: 50, - maxWidth: 50, - child: CircleAvatar( - backgroundColor: - color.withOpacity(0.5), - backgroundImage: - podcastLocal.avatarImage, - child: _updateIndicator( - podcastLocal)), - ), - ), - ); - }).toList(), - ), - ), - ], - ), - ), - Container( - height: (width - 20) / 3 + 40, - margin: const EdgeInsets.symmetric(horizontal: 10), - decoration: BoxDecoration( - color: context.scaffoldBackgroundColor, - ), - child: ScrollConfiguration( - behavior: NoGrowBehavior(), - child: TabBarView( - children: groups[_groupIndex]! - .podcasts - .map((podcastLocal) { - return Container( - decoration: BoxDecoration( - color: context.brightness == - Brightness.light - ? context.primaryColor - : Colors.black12), - margin: - EdgeInsets.symmetric(horizontal: 5.0), - key: ObjectKey(podcastLocal.title), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - Navigator.push( - context, - HidePlayerRoute( - PodcastDetail( - podcastLocal: podcastLocal, - ), - PodcastDetail( - podcastLocal: - podcastLocal, - hide: true), - duration: Duration( - milliseconds: 300), - )); - }, - child: PodcastPreview( - podcastLocal: podcastLocal, - ), - ))); - }).toList(), - ), - ), - ), - ], - ), - ); - }, - ); - } } class PodcastPreview extends StatefulWidget { @@ -498,8 +464,8 @@ class _PodcastPreviewState extends State { Future? _getRssItem; Future> _getRssItemTop(PodcastLocal podcastLocal) async { - var dbHelper = DBHelper(); - var episodes = await dbHelper.getRssItemTop(podcastLocal.id); + final dbHelper = DBHelper(); + final episodes = await dbHelper.getRssItemTop(podcastLocal.id); return episodes; } @@ -552,11 +518,12 @@ class _PodcastPreviewState extends State { Expanded( flex: 1, child: Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.only(right: 8.0), - child: Icon(Icons.arrow_forward), - )), + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Icon(Icons.arrow_forward), + ), + ), ), ], ), @@ -592,8 +559,8 @@ class ShowEpisode extends StatelessWidget { } Future> _getEpisodeMenu() async { - var popupMenuStorage = KeyValueStorage(episodePopupMenuKey); - var list = await popupMenuStorage.getMenu(); + final popupMenuStorage = KeyValueStorage(episodePopupMenuKey); + final list = await popupMenuStorage.getMenu(); return list; } @@ -603,7 +570,7 @@ class ShowEpisode extends StatelessWidget { Future _getTapToOpenPopupMenu() async { final tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey); - var boo = await tapToOpenPopupMenuStorage.getInt(defaultValue: 0); + final boo = await tapToOpenPopupMenuStorage.getInt(defaultValue: 0); return boo == 1; } @@ -657,13 +624,13 @@ class ShowEpisode extends StatelessWidget { Future _useDataConfirm(BuildContext context) async { var ifUseData = false; - final s = context.s!; + final s = context.s; await generalDialog( context, title: Text(s.cellularConfirm), content: Text(s.cellularConfirmDes), actions: [ - FlatButton( + TextButton( onPressed: () { Navigator.of(context).pop(); }, @@ -672,7 +639,7 @@ class ShowEpisode extends StatelessWidget { style: TextStyle(color: Colors.grey[600]), ), ), - FlatButton( + TextButton( onPressed: () { ifUseData = true; Navigator.of(context).pop(); @@ -709,362 +676,349 @@ class ShowEpisode extends StatelessWidget { (context, index) { final c = podcastLocal!.backgroudColor(context); return Selector, bool>>( - selector: (_, audio) => tuple.Tuple3( - audio.episode, - audio.queue.episodes - .map((e) => e!.enclosureUrl) - .toList(), - audio.playerRunning), - builder: (_, data, __) => FutureBuilder< - tuple.Tuple5>>( - future: _initData(episodes![index]), - initialData: tuple.Tuple5(0, false, false, false, []), - builder: (context, snapshot) { - final isListened = snapshot.data!.item1; - final isLiked = snapshot.data!.item2; - 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.scaffoldBackgroundColor, - ), - alignment: Alignment.center, - child: FocusedMenuHolder( - blurSize: 0.0, - menuItemExtent: 45, - menuBoxDecoration: BoxDecoration( - color: Colors.transparent, - borderRadius: - BorderRadius.all(Radius.circular(15.0))), - duration: Duration(milliseconds: 100), - tapMode: tapToOpen - ? TapMode.onTap - : TapMode.onLongPress, - animateMenuItems: false, - blurBackgroundColor: - context.brightness == Brightness.light - ? Colors.white38 - : Colors.black38, - bottomOffsetHeight: 10, - menuOffset: 6, - menuItems: [ - FocusedMenuItem( - backgroundColor: - context.brightness == Brightness.light - ? context.primaryColor - : context.dialogBackgroundColor, - title: Text( - data.item1 != episodes![index] || - !data.item3 - ? s!.play - : s!.playing), - trailingIcon: Icon( - LineIcons.playCircle, - color: context.accentColor, + tuple.Tuple3, bool>>( + selector: (_, audio) => tuple.Tuple3( + audio.episode, + audio.queue.episodes.map((e) => e!.enclosureUrl).toList(), + audio.playerRunning), + builder: (_, data, __) => FutureBuilder< + tuple.Tuple5>>( + future: _initData(episodes![index]), + initialData: tuple.Tuple5(0, false, false, false, []), + builder: (context, snapshot) { + final isListened = snapshot.data!.item1; + final isLiked = snapshot.data!.item2; + 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, + ), + alignment: Alignment.center, + child: FocusedMenuHolder( + blurSize: 0.0, + menuItemExtent: 45, + menuBoxDecoration: BoxDecoration( + color: Colors.transparent, + borderRadius: + BorderRadius.all(Radius.circular(15.0))), + duration: Duration(milliseconds: 100), + tapMode: + tapToOpen ? TapMode.onTap : TapMode.onLongPress, + animateMenuItems: false, + blurBackgroundColor: + context.brightness == Brightness.light + ? Colors.white38 + : Colors.black38, + bottomOffsetHeight: 10, + menuOffset: 6, + menuItems: [ + FocusedMenuItem( + backgroundColor: + context.brightness == Brightness.light + ? context.primaryColor + : context.dialogBackgroundColor, + title: Text(data.item1 != episodes![index] || + !data.item3 + ? s.play + : s.playing), + trailingIcon: Icon( + LineIcons.playCircle, + color: context.accentColor, + ), + onPressed: () { + if (data.item1 != episodes![index] || + !data.item3) { + audio.episodeLoad(episodes![index]); + } + }), + if (menuList.contains(1)) + FocusedMenuItem( + backgroundColor: + context.brightness == Brightness.light + ? context.primaryColor + : context.dialogBackgroundColor, + title: data.item2.contains( + episodes![index].enclosureUrl) + ? Text(s.remove) + : Text(s.later), + trailingIcon: Icon( + LineIcons.clock, + color: Colors.cyan, + ), + onPressed: () { + if (!data.item2.contains( + episodes![index].enclosureUrl)) { + audio.addToPlaylist(episodes![index]); + Fluttertoast.showToast( + msg: s.toastAddPlaylist, + gravity: ToastGravity.BOTTOM, + ); + } else { + audio.delFromPlaylist(episodes![index]); + Fluttertoast.showToast( + msg: s.toastRemovePlaylist, + gravity: ToastGravity.BOTTOM, + ); + } + }), + if (menuList.contains(2)) + FocusedMenuItem( + backgroundColor: + context.brightness == Brightness.light + ? context.primaryColor + : context.dialogBackgroundColor, + title: + isLiked ? Text(s.unlike) : Text(s.like), + trailingIcon: Icon(LineIcons.heart, + color: Colors.red, size: 21), + onPressed: () async { + if (isLiked) { + await _setUnliked( + episodes![index].enclosureUrl); + audio.setEpisodeState = true; + Fluttertoast.showToast( + msg: s.unliked, + gravity: ToastGravity.BOTTOM, + ); + } else { + await _saveLiked( + episodes![index].enclosureUrl); + audio.setEpisodeState = true; + Fluttertoast.showToast( + msg: s.liked, + gravity: ToastGravity.BOTTOM, + ); + } + }), + if (menuList.contains(3)) + FocusedMenuItem( + backgroundColor: + context.brightness == Brightness.light + ? context.primaryColor + : context.dialogBackgroundColor, + title: isListened > 0 + ? Text(s.listened, + style: TextStyle( + color: context.textColor + .withOpacity(0.5))) + : Text( + s.markListened, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + trailingIcon: SizedBox( + width: 23, + height: 23, + child: CustomPaint( + painter: ListenedAllPainter(Colors.blue, + stroke: 1.5)), + ), + onPressed: () async { + if (isListened < 1) { + await _markListened(episodes![index]); + audio.setEpisodeState = true; + Fluttertoast.showToast( + msg: s.markListened, + gravity: ToastGravity.BOTTOM, + ); + } + }), + if (menuList.contains(4)) + FocusedMenuItem( + backgroundColor: + context.brightness == Brightness.light + ? context.primaryColor + : context.dialogBackgroundColor, + title: isDownloaded + ? Text(s.downloaded, + style: TextStyle( + color: context.textColor + .withOpacity(0.5))) + : Text(s.download), + trailingIcon: Icon(LineIcons.download, + color: Colors.green), + onPressed: () { + if (!isDownloaded) { + _requestDownload(context, + episode: episodes![index]); + // downloader + // .startTask(episodes[index]); + } + }), + if (menuList.contains(5)) + FocusedMenuItem( + backgroundColor: + context.brightness == Brightness.light + ? context.primaryColor + : context.dialogBackgroundColor, + title: Text(s.playNext), + trailingIcon: Icon( + LineIcons.lightningBolt, + color: Colors.amber, + ), + onPressed: () { + audio.moveToTop(episodes![index]); + Fluttertoast.showToast( + msg: s.playNextDes, + gravity: ToastGravity.BOTTOM, + ); + }), + ], + onPressed: () => Navigator.push( + context, + ScaleRoute( + page: EpisodeDetail( + episodeItem: episodes![index], + heroTag: 'scroll', + //unique hero tag + )), + ), + child: Container( + padding: EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + flex: 2, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Hero( + tag: + '${episodes![index].enclosureUrl}scroll', + child: Container( + height: width / 18, + width: width / 18, + child: CircleAvatar( + backgroundImage: + podcastLocal!.avatarImage, + ), + ), + ), + Spacer(), + Selector< + AudioPlayerNotifier, + tuple + .Tuple2>( + selector: (_, audio) => tuple.Tuple2( + audio.episode, + audio.playerRunning), + builder: (_, data, __) { + return (episodes![index] + .enclosureUrl == + data.item1 + ?.enclosureUrl && + data.item2) + ? Container( + height: 20, + width: 20, + margin: + EdgeInsets.symmetric( + horizontal: 2), + decoration: BoxDecoration( + shape: BoxShape.circle, + ), + child: WaveLoader( + color: context + .accentColor)) + : Center(); + }), + episodes![index].isNew == 1 + ? Text( + 'New', + style: TextStyle( + color: Colors.red, + fontStyle: FontStyle.italic), + ) + : Center(), + ], + ), + ), + Expanded( + flex: 5, + child: Container( + padding: EdgeInsets.only(top: 2.0), + alignment: Alignment.topLeft, + child: Text( + episodes![index].title!, + style: TextStyle( + //fontSize: _width / 32, + ), + maxLines: 4, + overflow: TextOverflow.fade, ), - onPressed: () { - if (data.item1 != episodes![index] || - !data.item3) { - audio.episodeLoad(episodes![index]); - } - }), - if (menuList.contains(1)) - FocusedMenuItem( - backgroundColor: - context.brightness == Brightness.light - ? context.primaryColor - : context.dialogBackgroundColor, - title: data.item2.contains( - episodes![index].enclosureUrl) - ? Text(s.remove) - : Text(s.later), - trailingIcon: Icon( - LineIcons.clock, - color: Colors.cyan, - ), - onPressed: () { - if (!data.item2.contains( - episodes![index].enclosureUrl)) { - audio.addToPlaylist(episodes![index]); - Fluttertoast.showToast( - msg: s.toastAddPlaylist, - gravity: ToastGravity.BOTTOM, - ); - } else { - audio.delFromPlaylist( - episodes![index]); - Fluttertoast.showToast( - msg: s.toastRemovePlaylist, - gravity: ToastGravity.BOTTOM, - ); - } - }), - if (menuList.contains(2)) - FocusedMenuItem( - backgroundColor: - context.brightness == Brightness.light - ? context.primaryColor - : context.dialogBackgroundColor, - title: isLiked - ? Text(s.unlike) - : Text(s.like), - trailingIcon: Icon(LineIcons.heart, - color: Colors.red, size: 21), - onPressed: () async { - if (isLiked) { - await _setUnliked( - episodes![index].enclosureUrl); - audio.setEpisodeState = true; - Fluttertoast.showToast( - msg: s.unliked, - gravity: ToastGravity.BOTTOM, - ); - } else { - await _saveLiked( - episodes![index].enclosureUrl); - audio.setEpisodeState = true; - Fluttertoast.showToast( - msg: s.liked, - gravity: ToastGravity.BOTTOM, - ); - } - }), - if (menuList.contains(3)) - FocusedMenuItem( - backgroundColor: - context.brightness == Brightness.light - ? context.primaryColor - : context.dialogBackgroundColor, - title: isListened > 0 - ? Text(s.listened, + ), + ), + Expanded( + flex: 1, + child: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Text( + episodes![index] + .pubDate! + .toDate(context), + overflow: TextOverflow.visible, + style: TextStyle( + height: 1, + fontSize: width / 35, + color: c, + fontStyle: FontStyle.italic, + ), + ), + Spacer(), + if (episodes![index].duration != 0) + Align( + alignment: Alignment.center, + child: Text( + episodes![index].duration!.toTime, style: TextStyle( - color: context.textColor! - .withOpacity(0.5))) - : Text( - s.markListened, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - trailingIcon: SizedBox( - width: 23, - height: 23, - child: CustomPaint( - painter: ListenedAllPainter( - Colors.blue, - stroke: 1.5)), - ), - onPressed: () async { - if (isListened < 1) { - await _markListened(episodes![index]); - audio.setEpisodeState = true; - Fluttertoast.showToast( - msg: s.markListened, - gravity: ToastGravity.BOTTOM, - ); - } - }), - if (menuList.contains(4)) - FocusedMenuItem( - backgroundColor: - context.brightness == Brightness.light - ? context.primaryColor - : context.dialogBackgroundColor, - title: isDownloaded - ? Text(s.downloaded, - style: TextStyle( - color: context.textColor! - .withOpacity(0.5))) - : Text(s.download), - trailingIcon: Icon(LineIcons.download, - color: Colors.green), - onPressed: () { - if (!isDownloaded) { - _requestDownload(context, - episode: episodes![index]); - // downloader - // .startTask(episodes[index]); - } - }), - if (menuList.contains(5)) - FocusedMenuItem( - backgroundColor: - context.brightness == Brightness.light - ? context.primaryColor - : context.dialogBackgroundColor, - title: Text(s.playNext), - trailingIcon: Icon( - LineIcons.lightningBolt, - color: Colors.amber, - ), - onPressed: () { - audio.moveToTop(episodes![index]); - Fluttertoast.showToast( - msg: s.playNextDes, - gravity: ToastGravity.BOTTOM, - ); - }), - ], - onPressed: () => Navigator.push( - context, - ScaleRoute( - page: EpisodeDetail( - episodeItem: episodes![index], - heroTag: 'scroll', - //unique hero tag - )), - ), - child: Container( - padding: EdgeInsets.all(10.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - flex: 2, - child: Row( - mainAxisAlignment: - MainAxisAlignment.start, - children: [ - Hero( - tag: - '${episodes![index].enclosureUrl}scroll', - child: Container( - height: width / 18, - width: width / 18, - child: CircleAvatar( - backgroundImage: - podcastLocal!.avatarImage, + fontSize: width / 35, + // color: _c, + // fontStyle: FontStyle.italic, ), ), ), - Spacer(), - Selector< - AudioPlayerNotifier, - tuple.Tuple2>( - selector: (_, audio) => - tuple.Tuple2(audio.episode, - audio.playerRunning), - builder: (_, data, __) { - return (episodes![index] - .enclosureUrl == - data.item1 - ?.enclosureUrl && - data.item2) - ? Container( - height: 20, - width: 20, - margin: EdgeInsets - .symmetric( - horizontal: 2), - decoration: - BoxDecoration( - shape: - BoxShape.circle, - ), - child: WaveLoader( - color: context - .accentColor)) - : Center(); - }), - episodes![index].isNew == 1 - ? Text( - 'New', - style: TextStyle( - color: Colors.red, - fontStyle: - FontStyle.italic), - ) - : Center(), - ], - ), - ), - Expanded( - flex: 5, - child: Container( - padding: EdgeInsets.only(top: 2.0), - alignment: Alignment.topLeft, - child: Text( - episodes![index].title!, - style: TextStyle( - //fontSize: _width / 32, - ), - maxLines: 4, - overflow: TextOverflow.fade, - ), - ), - ), - Expanded( - flex: 1, - child: Row( - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Text( - episodes![index] - .pubDate! - .toDate(context), - overflow: TextOverflow.visible, - style: TextStyle( - height: 1, - fontSize: width / 35, - color: c, - fontStyle: FontStyle.italic, - ), - ), - Spacer(), - if (episodes![index].duration != 0) - Align( - alignment: Alignment.center, - child: Text( - episodes![index] - .duration! - .toTime, - style: TextStyle( - fontSize: width / 35, - // color: _c, - // fontStyle: FontStyle.italic, - ), - ), - ), - episodes![index].duration == 0 || - episodes![index] - .enclosureLength == - null || - episodes![index] - .enclosureLength == - 0 - ? Center() - : Text( - '|', - style: TextStyle( - fontSize: width / 35, - ), - ), - if (episodes![index] - .enclosureLength != - null && + episodes![index].duration == 0 || episodes![index] - .enclosureLength != - 0) - Container( - alignment: Alignment.center, - child: Text( - '${episodes![index].enclosureLength! ~/ 1000000}MB', - style: TextStyle( - fontSize: width / 35), + .enclosureLength == + null || + episodes![index] + .enclosureLength == + 0 + ? Center() + : Text( + '|', + style: TextStyle( + fontSize: width / 35, ), ), - ], - )), - ], - ), - ), + if (episodes![index].enclosureLength != + null && + episodes![index].enclosureLength != + 0) + Container( + alignment: Alignment.center, + child: Text( + '${episodes![index].enclosureLength! ~/ 1000000}MB', + style: TextStyle( + fontSize: width / 35), + ), + ), + ], + )), + ], ), - ); - })); + ), + ), + ); + }, + ), + ); }, childCount: math.min(episodes!.length, 2), ), diff --git a/lib/home/home_menu.dart b/lib/home/home_menu.dart index a5f20c9..be480d2 100644 --- a/lib/home/home_menu.dart +++ b/lib/home/home_menu.dart @@ -23,72 +23,10 @@ class PopupMenu extends StatefulWidget { } class _PopupMenuState extends State { - Future _getRefreshDate(BuildContext context) async { - int? refreshDate; - var refreshstorage = KeyValueStorage('refreshdate'); - var i = await refreshstorage.getInt(); - if (i == 0) { - var refreshstorage = KeyValueStorage('refreshdate'); - await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch); - refreshDate = DateTime.now().millisecondsSinceEpoch; - } else { - refreshDate = i; - } - return refreshDate.toDate(context); - } - - void _saveOmpl(String path) async { - var subscribeWorker = Provider.of(context, listen: false); - var rssExp = RegExp(r'^(https?):\/\/(.*)'); - final s = context.s; - var file = File(path); - try { - final opml = file.readAsStringSync(); - Map> data = PodcastsBackup.parseOPML(opml); - for (var entry in data.entries) { - var title = entry.key; - var list = entry.value.reversed; - for (var rss in list) { - var rssLink = rssExp.stringMatch(rss.xmlUrl!); - if (rssLink != null) { - var item = SubscribeItem(rssLink, rss.text, group: title); - await subscribeWorker.setSubscribeItem(item); - await Future.delayed(Duration(milliseconds: 200)); - } - } - } - } catch (e) { - developer.log(e.toString(), name: 'OMPL parse error'); - Fluttertoast.showToast( - msg: s!.toastFileError, - gravity: ToastGravity.TOP, - ); - } - } - - void _getFilePath() async { - final s = context.s; - try { - var filePickResult = - await FilePicker.platform.pickFiles(type: FileType.any); - if (filePickResult == null) { - return; - } - Fluttertoast.showToast( - msg: s!.toastReadFile, - gravity: ToastGravity.TOP, - ); - final filePath = filePickResult.files.first.path!; - _saveOmpl(filePath); - } on PlatformException catch (e) { - developer.log(e.toString(), name: 'Get OMPL file'); - } - } - @override Widget build(BuildContext context) { var refreshWorker = Provider.of(context, listen: false); - final s = context.s!; + final s = context.s; return Material( color: Colors.transparent, borderRadius: BorderRadius.circular(100), @@ -98,10 +36,11 @@ class _PopupMenuState extends State { width: 40, child: PopupMenuButton( icon: Icon(Icons.more_vert), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10)), + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), elevation: 1, tooltip: s.menu, + color: context.priamryContainer, itemBuilder: (context) => [ PopupMenuItem( value: 1, @@ -126,8 +65,8 @@ class _PopupMenuState extends State { if (snapshot.hasData) { return Text( snapshot.data!, - style: - TextStyle(color: Colors.red, fontSize: 12), + style: TextStyle( + color: Colors.red, fontSize: 12), ); } else { return Center(); @@ -141,7 +80,7 @@ class _PopupMenuState extends State { ), PopupMenuItem( value: 2, - child: Container( + child: Padding( padding: EdgeInsets.only(left: 10), child: Row( children: [ @@ -204,4 +143,66 @@ class _PopupMenuState extends State { ), ); } + + Future _getRefreshDate(BuildContext context) async { + int? refreshDate; + final refreshstorage = KeyValueStorage('refreshdate'); + final i = await refreshstorage.getInt(); + if (i == 0) { + final refreshstorage = KeyValueStorage('refreshdate'); + await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch); + refreshDate = DateTime.now().millisecondsSinceEpoch; + } else { + refreshDate = i; + } + return refreshDate.toDate(context); + } + + void _saveOmpl(String path) async { + final subscribeWorker = Provider.of(context, listen: false); + final rssExp = RegExp(r'^(https?):\/\/(.*)'); + final s = context.s; + final file = File(path); + try { + final opml = file.readAsStringSync(); + Map> data = PodcastsBackup.parseOPML(opml); + for (final entry in data.entries) { + var title = entry.key; + var list = entry.value.reversed; + for (var rss in list) { + var rssLink = rssExp.stringMatch(rss.xmlUrl!); + if (rssLink != null) { + var item = SubscribeItem(rssLink, rss.text, group: title); + await subscribeWorker.setSubscribeItem(item); + await Future.delayed(Duration(milliseconds: 200)); + } + } + } + } catch (e) { + developer.log(e.toString(), name: 'OMPL parse error'); + Fluttertoast.showToast( + msg: s.toastFileError, + gravity: ToastGravity.TOP, + ); + } + } + + void _getFilePath() async { + final s = context.s; + try { + var filePickResult = + await FilePicker.platform.pickFiles(type: FileType.any); + if (filePickResult == null) { + return; + } + Fluttertoast.showToast( + msg: s.toastReadFile, + gravity: ToastGravity.TOP, + ); + final filePath = filePickResult.files.first.path!; + _saveOmpl(filePath); + } on PlatformException catch (e) { + developer.log(e.toString(), name: 'Get OMPL file'); + } + } } diff --git a/lib/home/pocast_discovery.dart b/lib/home/pocast_discovery.dart index 6f974e2..e9ab269 100644 --- a/lib/home/pocast_discovery.dart +++ b/lib/home/pocast_discovery.dart @@ -112,7 +112,7 @@ class DiscoveryPageState extends State { // disabledTextColor: Colors.grey[500], // disabledBorderColor: Colors.grey[500], ), - child: Text(context.s!.subscribe), + child: Text(context.s.subscribe), onPressed: () {}), ), ), @@ -277,7 +277,7 @@ class DiscoveryPageState extends State { padding: EdgeInsets.fromLTRB(50, 20, 50, 20), child: Center( child: Text( - context.s!.searchHelper, + context.s.searchHelper, textAlign: TextAlign.center, style: context.textTheme.headline6! .copyWith(color: Colors.grey[400]), @@ -477,7 +477,7 @@ class __TopPodcastListState extends State<_TopPodcastList> { child: CircularProgressIndicator( strokeWidth: 2, )) - : Text(context.s!.loadMore), + : Text(context.s.loadMore), onPressed: () => _loading ? null : setState( diff --git a/lib/home/search_podcast.dart b/lib/home/search_podcast.dart index d437839..7662ab9 100644 --- a/lib/home/search_podcast.dart +++ b/lib/home/search_podcast.dart @@ -52,7 +52,7 @@ class MyHomePageDelegate extends SearchDelegate { Widget _invalidRss(BuildContext context) => Container( padding: EdgeInsets.only(top: 200), alignment: Alignment.topCenter, - child: Text(context.s!.searchInvalidRss, + child: Text(context.s.searchInvalidRss, style: context.textTheme.headline6!.copyWith(color: Colors.red)), ); @@ -86,7 +86,7 @@ class MyHomePageDelegate extends SearchDelegate { return false; }, child: IconButton( - tooltip: context.s!.back, + tooltip: context.s.back, splashRadius: 20, icon: Icon(_getIconData(Theme.of(context).platform)), onPressed: () { @@ -111,7 +111,7 @@ class MyHomePageDelegate extends SearchDelegate { return [ if (query.isNotEmpty) IconButton( - tooltip: context.s!.clear, + tooltip: context.s.clear, splashRadius: 20, icon: const Icon(Icons.clear), onPressed: () { @@ -216,7 +216,7 @@ class _RssResultState extends State { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; var items = widget.rssFeed!.items!; return DefaultTabController( length: 2, @@ -339,7 +339,7 @@ class _RssResultState extends State { borderRadius: BorderRadius.all(Radius.circular(100))), ), - child: Text(context.s!.loadMore), + child: Text(context.s.loadMore), onPressed: () => setState( () => _loadItems += 10, ), @@ -560,7 +560,7 @@ class __ListenNotesSearchState extends State<_ListenNotesSearch> { : CircularProgressIndicator( strokeWidth: 2, )) - : Text(context.s!.loadMore), + : Text(context.s.loadMore), onPressed: () => _loading ? null : setState( @@ -722,7 +722,7 @@ class __PodcastIndexSearchState extends State<_PodcastIndexSearch> { child: CircularProgressIndicator( strokeWidth: 2, )) - : Text(context.s!.loadMore), + : Text(context.s.loadMore), onPressed: () => _loading ? null : setState( @@ -1011,7 +1011,7 @@ class _SearchResultDetailState extends State child: CircularProgressIndicator( strokeWidth: 2, )) - : Text(context.s!.loadMore), + : Text(context.s.loadMore), onPressed: () { if (widget.searchEngine == SearchEngine.listenNotes && @@ -1057,12 +1057,11 @@ class _SearchResultDetailState extends State foregroundColor: MaterialStateProperty.all( context.accentColor), overlayColor: MaterialStateProperty.all( - context.scaffoldBackgroundColor - .withOpacity(0.3)), + context.background.withOpacity(0.3)), padding: MaterialStateProperty.all( EdgeInsets.symmetric(horizontal: 2))), - child: Text(context.s!.play.toUpperCase()), + child: Text(context.s.play.toUpperCase()), onPressed: () { context.read().episodeLoad( content[index].toEpisode, @@ -1215,7 +1214,7 @@ class _SearchResultDetailState extends State ), Expanded( child: Container( - color: context.scaffoldBackgroundColor, + color: context.background, child: TabBarView(children: [ ListView( physics: _animation.value != widget.maxHeight @@ -1327,7 +1326,7 @@ class PodcastSlideup extends StatelessWidget { child: GestureDetector( onTap: searchState.clearSelect, child: Container( - color: context.scaffoldBackgroundColor.withOpacity(0.9), + color: context.background.withOpacity(0.9), ), ), ), diff --git a/lib/intro_slider/app_intro.dart b/lib/intro_slider/app_intro.dart index 6221cc2..ba78ea1 100644 --- a/lib/intro_slider/app_intro.dart +++ b/lib/intro_slider/app_intro.dart @@ -142,7 +142,7 @@ class _SlideIntroState extends State { height: 40, width: 80, child: Center( - child: Text(context.s!.next, + child: Text(context.s.next, style: TextStyle( color: Colors.black, fontWeight: @@ -164,7 +164,7 @@ class _SlideIntroState extends State { height: 40, width: 80, child: Center( - child: Text(context.s!.done, + child: Text(context.s.done, style: TextStyle( color: Colors.black, fontWeight: diff --git a/lib/intro_slider/fourthpage.dart b/lib/intro_slider/fourthpage.dart index 7f64e8a..668bd5f 100644 --- a/lib/intro_slider/fourthpage.dart +++ b/lib/intro_slider/fourthpage.dart @@ -24,7 +24,7 @@ class _FourthPageState extends State { alignment: Alignment.center, padding: EdgeInsets.fromLTRB(40, context.paddingTop + 20, 40, 20), child: Text( - context.s!.introFourthPage, + context.s.introFourthPage, style: TextStyle(fontSize: 30, color: Colors.white), ), ), diff --git a/lib/intro_slider/secondpage.dart b/lib/intro_slider/secondpage.dart index 407958b..7c4e8ab 100644 --- a/lib/intro_slider/secondpage.dart +++ b/lib/intro_slider/secondpage.dart @@ -24,7 +24,7 @@ class _SecondPageState extends State { alignment: Alignment.center, padding: EdgeInsets.fromLTRB(40, context.paddingTop + 20, 40, 20), child: Text( - context.s!.introSecondPage, + context.s.introSecondPage, style: TextStyle(fontSize: 30, color: Colors.white), ), ), diff --git a/lib/intro_slider/thirdpage.dart b/lib/intro_slider/thirdpage.dart index 6e94a7e..f9a58e0 100644 --- a/lib/intro_slider/thirdpage.dart +++ b/lib/intro_slider/thirdpage.dart @@ -24,7 +24,7 @@ class _ThirdPageState extends State { alignment: Alignment.center, padding: EdgeInsets.fromLTRB(40, context.paddingTop + 20, 40, 20), child: Text( - context.s!.introThirdPage, + context.s.introThirdPage, style: TextStyle(fontSize: 30, color: Colors.white), ), ), diff --git a/lib/playlists/playlist_home.dart b/lib/playlists/playlist_home.dart index cca7f93..28ddcfa 100644 --- a/lib/playlists/playlist_home.dart +++ b/lib/playlists/playlist_home.dart @@ -60,7 +60,7 @@ class _PlaylistHomeState extends State { Color? color}) { return OutlinedButton.icon( style: OutlinedButton.styleFrom( - side: BorderSide(color: context.scaffoldBackgroundColor), + side: BorderSide(color: context.background), primary: color, backgroundColor: isSelected ? context.primaryColorDark : Colors.transparent, @@ -73,7 +73,7 @@ class _PlaylistHomeState extends State { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( systemNavigationBarIconBrightness: @@ -100,7 +100,7 @@ class _PlaylistHomeState extends State { return Text(data?.title ?? '', maxLines: 1); }, ), - backgroundColor: context.scaffoldBackgroundColor, + backgroundColor: context.background, ), body: Column( children: [ @@ -994,7 +994,7 @@ class __NewPlaylistState extends State<_NewPlaylist> { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light, diff --git a/lib/playlists/playlist_page.dart b/lib/playlists/playlist_page.dart index 313c786..da43bb9 100644 --- a/lib/playlists/playlist_page.dart +++ b/lib/playlists/playlist_page.dart @@ -36,7 +36,7 @@ class _PlaylistDetailState extends State { leading: IconButton( splashRadius: 20, icon: Icon(Icons.close), - tooltip: context.s!.back, + tooltip: context.s.back, onPressed: () { Navigator.maybePop(context); }, @@ -110,7 +110,8 @@ class _PlaylistDetailState extends State { scrollDirection: Axis.vertical, children: episodes.map((episode) { return _PlaylistItem(episode, - key: ValueKey(episode!.enclosureUrl), onSelect: (episode) { + key: ValueKey(episode!.enclosureUrl), + onSelect: (episode) { _selectedEpisodes.add(episode); setState(() {}); }, onRemove: (episode) { @@ -341,8 +342,8 @@ class __PlaylistSettingState extends State<_PlaylistSetting> { onPressed: () => setState(() { _clearConfirm = false; }), - child: - Text(s!.cancel, style: TextStyle(color: Colors.grey[600])), + child: Text(s!.cancel, + style: TextStyle(color: Colors.grey[600])), ), FlatButton( splashColor: Colors.red.withAlpha(70), @@ -384,8 +385,8 @@ class __PlaylistSettingState extends State<_PlaylistSetting> { onPressed: () => setState(() { _removeConfirm = false; }), - child: - Text(s!.cancel, style: TextStyle(color: Colors.grey[600])), + child: Text(s!.cancel, + style: TextStyle(color: Colors.grey[600])), ), FlatButton( splashColor: Colors.red.withAlpha(70), diff --git a/lib/podcasts/podcast_detail.dart b/lib/podcasts/podcast_detail.dart index 56139b5..4b265ec 100644 --- a/lib/podcasts/podcast_detail.dart +++ b/lib/podcasts/podcast_detail.dart @@ -117,7 +117,7 @@ class _PodcastDetailState extends State { final result = await _dbHelper.updatePodcastRss(podcastLocal); if (result >= 0) { Fluttertoast.showToast( - msg: context.s!.updateEpisodesCount(result), + msg: context.s.updateEpisodesCount(result), gravity: ToastGravity.TOP, ); } @@ -148,7 +148,7 @@ class _PodcastDetailState extends State { } } else if (result != 0) { Fluttertoast.showToast( - msg: context.s!.updateFailed, + msg: context.s.updateFailed, gravity: ToastGravity.TOP, ); } @@ -359,7 +359,7 @@ class _PodcastDetailState extends State { Container( padding: EdgeInsets.fromLTRB(15, 10, 15, 10), alignment: Alignment.topLeft, - color: context.scaffoldBackgroundColor, + color: context.background, child: AboutPodcast(podcastLocal: widget.podcastLocal), ), ], @@ -389,7 +389,7 @@ class _PodcastDetailState extends State { ); Widget _actionBar(BuildContext context) { - final s = context.s!; + final s = context.s; return SizedBox( height: 30, child: Row( @@ -668,7 +668,7 @@ class _PodcastDetailState extends State { @override Widget build(BuildContext context) { final color = widget.podcastLocal!.primaryColor!.colorizedark(); - final s = context.s!; + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarIconBrightness: Brightness.dark, @@ -1138,7 +1138,7 @@ class _SearchEpisodeState extends State { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light, diff --git a/lib/podcasts/podcast_group.dart b/lib/podcasts/podcast_group.dart index aaaab2a..b8e6414 100644 --- a/lib/podcasts/podcast_group.dart +++ b/lib/podcasts/podcast_group.dart @@ -141,7 +141,10 @@ class __PodcastCardState extends State<_PodcastCard> }); } - Widget _buttonOnMenu({required Widget icon, VoidCallback? onTap, required String tooltip}) => + Widget _buttonOnMenu( + {required Widget icon, + VoidCallback? onTap, + required String tooltip}) => Material( color: Colors.transparent, child: InkWell( @@ -166,7 +169,7 @@ class __PodcastCardState extends State<_PodcastCard> @override Widget build(BuildContext context) { final c = widget.podcastLocal!.backgroudColor(context); - final s = context.s!; + final s = context.s; var groupList = context.watch(); _belongGroups = groupList.getPodcastGroup(widget.podcastLocal!.id); return Column( @@ -243,7 +246,7 @@ class __PodcastCardState extends State<_PodcastCard> : Container( child: Container( decoration: BoxDecoration( - color: context.scaffoldBackgroundColor, + color: context.background, ), // border: Border( // bottom: BorderSide( @@ -365,7 +368,8 @@ class __PodcastCardState extends State<_PodcastCard> tooltip: s.autoDownload, onTap: () async { await _setAutoDownload( - widget.podcastLocal!.id, !snapshot.data!); + widget.podcastLocal!.id, + !snapshot.data!); setState(() {}); }, ); @@ -504,7 +508,7 @@ class _RenameGroupState extends State { Widget build(BuildContext context) { var groupList = Provider.of(context, listen: false); List list = groupList.groups.map((e) => e!.name).toList(); - final s = context.s!; + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light, diff --git a/lib/podcasts/podcast_manage.dart b/lib/podcasts/podcast_manage.dart index 454d663..a9146fc 100644 --- a/lib/podcasts/podcast_manage.dart +++ b/lib/podcasts/podcast_manage.dart @@ -113,9 +113,10 @@ class _PodcastManageState extends State } } else { groupList.saveOrder(groupList.groups[_index!]); - groupList.drlFromOrderChanged(groupList.groups[_index!]!.name); + groupList + .drlFromOrderChanged(groupList.groups[_index!]!.name); Fluttertoast.showToast( - msg: context.s!.toastSettingSaved, + msg: context.s.toastSettingSaved, gravity: ToastGravity.BOTTOM, ); _controller.reverse(); @@ -155,7 +156,7 @@ class _PodcastManageState extends State @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarIconBrightness: Theme.of(context).accentColorBrightness, @@ -166,7 +167,7 @@ class _PodcastManageState extends State ), child: Scaffold( appBar: AppBar( - title: Text(context.s!.groups(2)), + title: Text(context.s.groups(2)), leading: CustomBackButton(), actions: [ featureDiscoveryOverlay( @@ -218,7 +219,7 @@ class _PodcastManageState extends State : Stack( children: [ Container( - color: context.scaffoldBackgroundColor, + color: context.background, child: CustomTabView( itemCount: _groups.length, tabBuilder: (context, index) => Tab( @@ -265,10 +266,8 @@ class _PodcastManageState extends State } }, child: Container( - color: context.scaffoldBackgroundColor - .withOpacity(0.8 * - math.min( - _menuController.value * 2, 1.0)), + color: context.background.withOpacity(0.8 * + math.min(_menuController.value * 2, 1.0)), ), ), ), @@ -332,7 +331,7 @@ class _PodcastManageState extends State padding: EdgeInsets.symmetric( horizontal: 5.0), ), - Text(context.s!.editGroupName, + Text(context.s.editGroupName, style: TextStyle( color: Colors.white)), ], @@ -366,7 +365,7 @@ class _PodcastManageState extends State Navigator.of(context) .pop(), child: Text( - context.s!.cancel, + context.s.cancel, style: TextStyle( color: Colors.grey[600]), @@ -385,7 +384,8 @@ class _PodcastManageState extends State _index = _index! - 1; }); groupList.delGroup( - _groups[_index! + 1]!); + _groups[ + _index! + 1]!); } else { groupList.delGroup( _groups[_index!]!); @@ -393,7 +393,7 @@ class _PodcastManageState extends State Navigator.of(context).pop(); }, child: Text( - context.s!.confirm, + context.s.confirm, style: TextStyle( color: Colors.red), ), @@ -441,7 +441,7 @@ class _PodcastManageState extends State class _OrderMenu extends StatelessWidget { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; return PopupMenuButton( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), elevation: 1, @@ -494,7 +494,7 @@ class _AddGroupState extends State { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; var groupList = Provider.of(context, listen: false); List list = groupList.groups.map((e) => e!.name).toList(); return AnnotatedRegion( diff --git a/lib/podcasts/podcast_settings.dart b/lib/podcasts/podcast_settings.dart index 6b95f89..79b1027 100644 --- a/lib/podcasts/podcast_settings.dart +++ b/lib/podcasts/podcast_settings.dart @@ -19,6 +19,7 @@ import '../widgets/custom_widget.dart'; import '../widgets/duraiton_picker.dart'; enum MarkStatus { start, complete, none } + enum RefreshCoverStatus { start, complete, error, none } class PodcastSetting extends StatefulWidget { @@ -200,7 +201,7 @@ class _PodcastSettingState extends State { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; final groupList = context.watch(); final textStyle = context.textTheme.bodyText2!; return Column( @@ -233,8 +234,8 @@ class _PodcastSettingState extends State { ), trailing: Transform.scale( scale: 0.8, - child: - Switch(value: snapshot.data!, onChanged: _setAutoDownload), + child: Switch( + value: snapshot.data!, onChanged: _setAutoDownload), ), ); }), @@ -464,7 +465,7 @@ class _TimePicker extends StatelessWidget { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; return Container( color: context.primaryColorDark, child: Column( diff --git a/lib/podcasts/podcastlist.dart b/lib/podcasts/podcastlist.dart index 07f4498..043d54b 100644 --- a/lib/podcasts/podcastlist.dart +++ b/lib/podcasts/podcastlist.dart @@ -51,7 +51,7 @@ class _AboutPodcastState extends State { @override Widget build(BuildContext context) { var _groupList = Provider.of(context, listen: false); - final s = context.s!; + final s = context.s; return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)), titlePadding: EdgeInsets.only( @@ -116,7 +116,7 @@ class _PodcastListState extends State { ), child: Scaffold( appBar: AppBar( - title: Text(context.s!.podcast(2)), + title: Text(context.s.podcast(2)), leading: CustomBackButton(), actions: [ Selector( diff --git a/lib/settings/data_backup.dart b/lib/settings/data_backup.dart index 686b2c4..49a9a37 100644 --- a/lib/settings/data_backup.dart +++ b/lib/settings/data_backup.dart @@ -79,7 +79,7 @@ class _DataBackupState extends State { } Future _importSetting(String path, BuildContext context) async { - final s = context.s!; + final s = context.s; var settings = context.read(); var file = File(path); try { @@ -178,7 +178,7 @@ class _DataBackupState extends State { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarIconBrightness: context.iconBrightness, @@ -634,14 +634,14 @@ class __LoginGpodderState extends State<_LoginGpodder> { } else { if (mounted) setState(() => _loginStatus = LoginStatus.error); Fluttertoast.showToast( - msg: context.s!.loginFailed, + msg: context.s.loginFailed, gravity: ToastGravity.BOTTOM, ); } } else { if (mounted) setState(() => _loginStatus = LoginStatus.error); Fluttertoast.showToast( - msg: context.s!.loginFailed, + msg: context.s.loginFailed, gravity: ToastGravity.BOTTOM, ); } @@ -680,11 +680,11 @@ class __LoginGpodderState extends State<_LoginGpodder> { String? _validateName(String? value) { if (value!.isEmpty) { - return context.s!.usernameRequired; + return context.s.usernameRequired; } final nameExp = RegExp(r'^[A-Za-z ]+$'); if (!nameExp.hasMatch(value)) { - return context.s!.invalidName; + return context.s.invalidName; } return null; } @@ -692,7 +692,7 @@ class __LoginGpodderState extends State<_LoginGpodder> { String? _validatePassword(String? value) { final passwordField = _passwordFieldKey.currentState!; if (passwordField.value == null || passwordField.value!.isEmpty) { - return context.s!.passwdRequired; + return context.s.passwdRequired; } return null; } @@ -701,13 +701,13 @@ class __LoginGpodderState extends State<_LoginGpodder> { switch (_loginStatus) { case LoginStatus.none: return Text( - context.s!.login, + context.s.login, style: TextStyle(color: Colors.white), ); break; case LoginStatus.syncing: return Text( - context.s!.settingsSyncing, + context.s.settingsSyncing, style: TextStyle(color: Colors.white), ); break; @@ -722,7 +722,7 @@ class __LoginGpodderState extends State<_LoginGpodder> { ); default: return Text( - context.s!.login, + context.s.login, style: TextStyle(color: Colors.white), ); break; @@ -731,7 +731,7 @@ class __LoginGpodderState extends State<_LoginGpodder> { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarIconBrightness: Brightness.dark, diff --git a/lib/settings/downloads_manage.dart b/lib/settings/downloads_manage.dart index 8b4f3af..07e4815 100644 --- a/lib/settings/downloads_manage.dart +++ b/lib/settings/downloads_manage.dart @@ -117,7 +117,7 @@ class _DownloadsManageState extends State { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarIconBrightness: Theme.of(context).accentColorBrightness, @@ -219,11 +219,9 @@ class _DownloadsManageState extends State { ), Icon( _mode == 0 - ? LineIcons - .hourglassStart + ? LineIcons.hourglassStart : _mode == 1 - ? LineIcons - .hourglassHalf + ? LineIcons.hourglassHalf : LineIcons.save, size: 18, ) @@ -304,7 +302,8 @@ class _DownloadsManageState extends State { future: _isListened(_episodes[index]), initialData: 0, builder: (context, snapshot) { - return (_onlyListened! && snapshot.data == 0) + return (_onlyListened! && + snapshot.data == 0) ? Center() : Column( children: [ diff --git a/lib/settings/history.dart b/lib/settings/history.dart index 08d9545..ea27850 100644 --- a/lib/settings/history.dart +++ b/lib/settings/history.dart @@ -76,7 +76,7 @@ class _PlayedHistoryState extends State Future recoverSub(BuildContext context, String url) async { Fluttertoast.showToast( - msg: context.s!.toastPodcastRecovering, + msg: context.s.toastPodcastRecovering, gravity: ToastGravity.BOTTOM, ); var subscribeWorker = context.watch(); @@ -99,7 +99,7 @@ class _PlayedHistoryState extends State } catch (e) { developer.log(e.toString(), name: 'Recover podcast error'); Fluttertoast.showToast( - msg: context.s!.toastRecoverFailed, + msg: context.s.toastRecoverFailed, gravity: ToastGravity.BOTTOM, ); } @@ -220,7 +220,7 @@ class _PlayedHistoryState extends State return Container( padding: const EdgeInsets.symmetric(vertical: 5), - color: context.scaffoldBackgroundColor, + color: context.background, child: Column( children: [ ListTile( @@ -234,7 +234,8 @@ class _PlayedHistoryState extends State DateFormat.yMd() .add_jm() .format(snapshot - .data![index].playdate!), + .data![index] + .playdate!), style: TextStyle( color: context.textColor! .withOpacity(0.8), @@ -318,7 +319,7 @@ class _PlayedHistoryState extends State itemBuilder: (context, index) { var _status = snapshot.data![index].status; return Container( - color: context.scaffoldBackgroundColor, + color: context.background, child: Column( children: [ ListTile( @@ -362,7 +363,8 @@ class _PlayedHistoryState extends State .alternativeTrashRestore), onPressed: () => recoverSub( context, - snapshot.data![index].rssUrl!), + snapshot + .data![index].rssUrl!), ), ) : null, @@ -476,11 +478,11 @@ class HistoryChart extends StatelessWidget { lineTouchData: LineTouchData( enabled: true, touchTooltipData: LineTouchTooltipData( - tooltipBgColor: context.scaffoldBackgroundColor, + tooltipBgColor: context.background, fitInsideHorizontally: true, getTooltipItems: (touchedBarSpots) { return touchedBarSpots.map((barSpot) { - return LineTooltipItem(context.s!.minsCount(barSpot.y.toInt()), + return LineTooltipItem(context.s.minsCount(barSpot.y.toInt()), context.textTheme.subtitle1!); }).toList(); }, diff --git a/lib/settings/languages.dart b/lib/settings/languages.dart index c49eb69..ff33c5a 100644 --- a/lib/settings/languages.dart +++ b/lib/settings/languages.dart @@ -41,7 +41,7 @@ class _LanguagesSettingState extends State { } } else { await localeStorage - .saveStringList([locale!.languageCode, locale.countryCode??'']); + .saveStringList([locale!.languageCode, locale.countryCode ?? '']); await S.load(locale); if (mounted) { setState(() {}); @@ -66,7 +66,7 @@ class _LanguagesSettingState extends State { @override Widget build(BuildContext context) { final textStyle = context.textTheme.bodyText2!; - final s = context.s!; + final s = context.s; return Column( children: [ ListTile( diff --git a/lib/settings/layouts.dart b/lib/settings/layouts.dart index 059a448..dd1273c 100644 --- a/lib/settings/layouts.dart +++ b/lib/settings/layouts.dart @@ -184,7 +184,7 @@ class _LayoutSettingState extends State { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; var audio = Provider.of(context, listen: false); return AnnotatedRegion( value: SystemUiOverlayStyle( diff --git a/lib/settings/libries.dart b/lib/settings/libries.dart index 9879cbb..3f5f9cb 100644 --- a/lib/settings/libries.dart +++ b/lib/settings/libries.dart @@ -26,7 +26,7 @@ class Libries extends StatelessWidget { ), child: Scaffold( appBar: AppBar( - title: Text(context.s!.settingsLibraries), + title: Text(context.s.settingsLibraries), leading: CustomBackButton(), elevation: 0, backgroundColor: Theme.of(context).primaryColor, @@ -68,7 +68,7 @@ class Libries extends StatelessWidget { height: 30.0, padding: EdgeInsets.symmetric(horizontal: 70), alignment: Alignment.centerLeft, - child: Text(context.s!.fonts, + child: Text(context.s.fonts, style: Theme.of(context) .textTheme .bodyText1! @@ -90,7 +90,7 @@ class Libries extends StatelessWidget { height: 30.0, padding: EdgeInsets.symmetric(horizontal: 70), alignment: Alignment.centerLeft, - child: Text(context.s!.plugins, + child: Text(context.s.plugins, style: Theme.of(context) .textTheme .bodyText1! diff --git a/lib/settings/play_setting.dart b/lib/settings/play_setting.dart index 6c4b8eb..aef2334 100644 --- a/lib/settings/play_setting.dart +++ b/lib/settings/play_setting.dart @@ -89,7 +89,7 @@ class _PlaySettingState extends State { topLeft: Radius.circular(5)), ), padding: const EdgeInsets.all(8.0), - child: Text(context.s!.endOfEpisode, + child: Text(context.s.endOfEpisode, style: TextStyle( color: data.item1 == 0 ? Colors.white : null)), ), @@ -113,7 +113,7 @@ class _PlaySettingState extends State { topRight: Radius.circular(5)), ), padding: const EdgeInsets.all(8.0), - child: Text(context.s!.minsCount(data.item2!), + child: Text(context.s.minsCount(data.item2!), style: TextStyle( color: data.item1 == 1 ? Colors.white : null)), ), @@ -224,7 +224,7 @@ class _PlaySettingState extends State { Widget build(BuildContext context) { var settings = context.watch(); var audio = context.watch(); - final s = context.s!; + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarIconBrightness: Theme.of(context).accentColorBrightness, @@ -298,7 +298,8 @@ class _PlaySettingState extends State { trailing: Transform.scale( scale: 0.9, child: Switch( - value: snapshot.data!, onChanged: _saveMarkListenedSkip), + value: snapshot.data!, + onChanged: _saveMarkListenedSkip), ), ), ), @@ -350,7 +351,8 @@ class _PlaySettingState extends State { displayItemCount: 5, isDense: true, value: data, - onChanged: (dynamic value) => settings.setRewindSeconds = value, + onChanged: (dynamic value) => + settings.setRewindSeconds = value, items: kSecondsToSelect.map>((e) { return DropdownMenuItem( value: e, child: Text(s.secCount(e))); @@ -524,8 +526,8 @@ class __NotificationLayoutState extends State<_NotificationLayout> { mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _notificationIcon( - Icon(Icons.pause_circle_filled), '${s!.play}| ${s.pause}'), + _notificationIcon(Icon(Icons.pause_circle_filled), + '${s!.play}| ${s.pause}'), _notificationIcon(Icon(Icons.fast_forward), s.fastForward), _notificationIcon(Icon(Icons.skip_next), s.skipToNext), _notificationIcon(Icon(Icons.close), s.stop), @@ -600,7 +602,7 @@ class __SpeedListState extends State<_SpeedList> { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; return ListTile( contentPadding: EdgeInsets.only(left: 70.0, right: 20, bottom: 10, top: 10), diff --git a/lib/settings/settting.dart b/lib/settings/settting.dart index 5b0c1f2..725f47d 100644 --- a/lib/settings/settting.dart +++ b/lib/settings/settting.dart @@ -68,7 +68,7 @@ class _SettingsState extends State { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarIconBrightness: Theme.of(context).accentColorBrightness, @@ -107,8 +107,7 @@ class _SettingsState extends State { onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => ThemeSetting())), contentPadding: EdgeInsets.symmetric(horizontal: 25.0), - leading: - Icon(LineIcons.adjust, color: context.accentColor), + leading: Icon(LineIcons.adjust, color: context.accentColor), title: Text(s.settingsAppearance), subtitle: Text(s.settingsAppearanceDes), ), @@ -117,8 +116,7 @@ class _SettingsState extends State { onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => LayoutSetting())), contentPadding: EdgeInsets.symmetric(horizontal: 25.0), - leading: Icon(LineIcons.stopCircle, - color: Colors.blueAccent), + leading: Icon(LineIcons.stopCircle, color: Colors.blueAccent), title: Text(s.settingsLayout), subtitle: Text(s.settingsLayoutDes), ), @@ -168,8 +166,7 @@ class _SettingsState extends State { title: s.settingsLanguages, child: LanguagesSetting()) .then((value) => setState(() {})), contentPadding: EdgeInsets.symmetric(horizontal: 25.0), - leading: Icon(LineIcons.language, - color: Colors.purpleAccent), + leading: Icon(LineIcons.language, color: Colors.purpleAccent), title: Text(s.settingsLanguages), subtitle: Text(s.settingsLanguagesDes), ), @@ -181,8 +178,8 @@ class _SettingsState extends State { MaterialPageRoute(builder: (context) => DataBackup())); }, contentPadding: EdgeInsets.symmetric(horizontal: 25.0), - leading: Icon(LineIcons.codeFile, - color: Colors.lightGreen[700]), + leading: + Icon(LineIcons.codeFile, color: Colors.lightGreen[700]), title: Text(s.settingsBackup), subtitle: Text(s.settingsBackupDes), ), @@ -204,8 +201,7 @@ class _SettingsState extends State { onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => Libries())), contentPadding: EdgeInsets.symmetric(horizontal: 25.0), - leading: Icon(LineIcons.bookOpen, - color: Colors.purple[700]), + leading: Icon(LineIcons.bookOpen, color: Colors.purple[700]), title: Text(s.settingsLibraries), subtitle: Text(s.settingsLibrariesDes), ), @@ -257,8 +253,7 @@ class _SettingsState extends State { ); }, contentPadding: EdgeInsets.symmetric(horizontal: 25.0), - leading: - Icon(LineIcons.capsules, color: Colors.pinkAccent), + leading: Icon(LineIcons.capsules, color: Colors.pinkAccent), title: Text(s.settingsDiscovery), ), Divider(height: 1), @@ -269,8 +264,7 @@ class _SettingsState extends State { builder: (context) => SlideIntro(goto: Goto.settings))), contentPadding: EdgeInsets.symmetric(horizontal: 25.0), - leading: - Icon(LineIcons.columns, color: Colors.blueGrey), + leading: Icon(LineIcons.columns, color: Colors.blueGrey), title: Text(s.settingsAppIntro), ), Divider(height: 1), diff --git a/lib/settings/storage.dart b/lib/settings/storage.dart index 7122aac..40904fb 100644 --- a/lib/settings/storage.dart +++ b/lib/settings/storage.dart @@ -110,7 +110,7 @@ class _StorageSettingState extends State @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; var settings = Provider.of(context, listen: false); return AnnotatedRegion( value: SystemUiOverlayStyle( diff --git a/lib/settings/syncing.dart b/lib/settings/syncing.dart index 395cd00..00a9543 100644 --- a/lib/settings/syncing.dart +++ b/lib/settings/syncing.dart @@ -11,7 +11,7 @@ import '../widgets/custom_widget.dart'; class SyncingSetting extends StatelessWidget { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; var settings = Provider.of(context, listen: false); return AnnotatedRegion( value: SystemUiOverlayStyle( diff --git a/lib/settings/theme.dart b/lib/settings/theme.dart index b41df7d..95559ce 100644 --- a/lib/settings/theme.dart +++ b/lib/settings/theme.dart @@ -10,7 +10,7 @@ import '../widgets/general_dialog.dart'; class ThemeSetting extends StatelessWidget { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; var settings = Provider.of(context, listen: false); return AnnotatedRegion( value: SystemUiOverlayStyle( diff --git a/lib/state/setting_state.dart b/lib/state/setting_state.dart index a419024..cdf0214 100644 --- a/lib/state/setting_state.dart +++ b/lib/state/setting_state.dart @@ -65,9 +65,10 @@ final showNotesFontStyles = [ height: 1.8, )), GoogleFonts.bitter( - textStyle: TextStyle( - height: 1.8, - )), + textStyle: TextStyle( + height: 1.8, + ), + ), ]; class SettingState extends ChangeNotifier { @@ -133,15 +134,13 @@ class SettingState extends ChangeNotifier { /// Load locale. Locale? get locale => _locale; - /// Spp thememode. default auto. + /// Set thememode. default auto. ThemeMode? _theme; ThemeMode? get theme => _theme; - ThemeData get lightTheme => ThemeData().copyWith( - colorScheme: ThemeData() - .colorScheme - .copyWith(brightness: Brightness.light, secondary: _accentSetColor), - brightness: Brightness.dark, + ThemeData get lightTheme => ThemeData.light().copyWith( + colorScheme: _colors(Brightness.light, _accentSetColor!), + brightness: Brightness.light, primaryColor: Colors.grey[100], primaryColorLight: Colors.white, primaryColorDark: Colors.grey[300], @@ -154,6 +153,10 @@ class SettingState extends ChangeNotifier { iconTheme: IconThemeData(color: Colors.black), systemOverlayStyle: SystemUiOverlayStyle.dark), textTheme: TextTheme( + headlineSmall: TextStyle( + fontSize: 20.0, + color: Colors.black, + fontWeight: FontWeight.normal), bodyLarge: TextStyle( fontSize: 17.0, color: Colors.black, @@ -162,6 +165,34 @@ class SettingState extends ChangeNotifier { fontSize: 15.0, color: Colors.black, fontWeight: FontWeight.normal), + bodySmall: TextStyle( + fontSize: 14.0, + color: Colors.black, + fontWeight: FontWeight.normal), + labelLarge: TextStyle( + fontSize: 16.0, + color: Colors.black, + fontWeight: FontWeight.normal), + labelMedium: TextStyle( + fontSize: 14.0, + color: Colors.black, + fontWeight: FontWeight.normal), + labelSmall: TextStyle( + fontSize: 12.0, + color: Colors.black, + fontWeight: FontWeight.normal), + titleLarge: TextStyle( + fontSize: 16.0, + color: Colors.black, + fontWeight: FontWeight.normal), + titleMedium: TextStyle( + fontSize: 14.0, + color: Colors.black, + fontWeight: FontWeight.normal), + titleSmall: TextStyle( + fontSize: 12.0, + color: Colors.black, + fontWeight: FontWeight.normal), ), tabBarTheme: TabBarTheme( labelColor: Colors.black, @@ -177,15 +208,18 @@ class SettingState extends ChangeNotifier { hoverColor: _accentSetColor!.withAlpha(70), splashColor: _accentSetColor!.withAlpha(70), ), + useMaterial3: true, ); ThemeData get darkTheme => ThemeData.dark().copyWith( - colorScheme: ThemeData.dark() - .colorScheme - .copyWith(brightness: Brightness.dark, secondary: _accentSetColor), - brightness: Brightness.light, + colorScheme: _colors(Brightness.dark, _accentSetColor!), + brightness: Brightness.dark, primaryColorDark: Colors.grey[800], textTheme: TextTheme( + headlineSmall: TextStyle( + fontSize: 20.0, + color: Colors.white, + fontWeight: FontWeight.normal), bodyLarge: TextStyle( fontSize: 17.0, color: Colors.white, @@ -194,9 +228,31 @@ class SettingState extends ChangeNotifier { fontSize: 15.0, color: Colors.white, fontWeight: FontWeight.normal), + labelLarge: TextStyle( + fontSize: 16.0, + color: Colors.white, + fontWeight: FontWeight.normal), + labelMedium: TextStyle( + fontSize: 14.0, + color: Colors.white, + fontWeight: FontWeight.normal), + labelSmall: TextStyle( + fontSize: 12.0, + color: Colors.white, + fontWeight: FontWeight.normal), + titleLarge: TextStyle( + fontSize: 16.0, + color: Colors.white, + fontWeight: FontWeight.normal), + titleMedium: TextStyle( + fontSize: 14.0, + color: Colors.white, + fontWeight: FontWeight.normal), + titleSmall: TextStyle( + fontSize: 12.0, + color: Colors.white, + fontWeight: FontWeight.normal), ), - scaffoldBackgroundColor: - _realDark! ? Colors.black87 : Color(0XFF212121), primaryColor: _realDark! ? Colors.black : Color(0XFF1B1B1B), popupMenuTheme: PopupMenuThemeData() .copyWith(color: _realDark! ? Colors.grey[900] : null), @@ -206,6 +262,7 @@ class SettingState extends ChangeNotifier { systemOverlayStyle: SystemUiOverlayStyle.light), buttonTheme: ButtonThemeData(height: 32), dialogBackgroundColor: _realDark! ? Colors.grey[900] : null, + useMaterial3: true, ); set setTheme(ThemeMode? mode) { @@ -214,6 +271,13 @@ class SettingState extends ChangeNotifier { notifyListeners(); } + ColorScheme _colors(Brightness brightness, Color targetColor) { + return ColorScheme.fromSeed( + seedColor: targetColor, + brightness: brightness, + ); + } + void setWorkManager(int? hour) { _updateInterval = hour; notifyListeners(); @@ -366,9 +430,9 @@ class SettingState extends ChangeNotifier { _saveRewindSeconds(); } - int? _showNotesFontIndex; - int? get showNotesFontIndex => _showNotesFontIndex; - TextStyle get showNoteFontStyle => showNotesFontStyles[_showNotesFontIndex!]; + late int _showNotesFontIndex; + int get showNotesFontIndex => _showNotesFontIndex; + TextStyle get showNoteFontStyle => showNotesFontStyles[_showNotesFontIndex]; set setShowNoteFontStyle(int index) { _showNotesFontIndex = index; notifyListeners(); diff --git a/lib/util/extension_helper.dart b/lib/util/extension_helper.dart index d200132..969fe87 100644 --- a/lib/util/extension_helper.dart +++ b/lib/util/extension_helper.dart @@ -3,39 +3,49 @@ import 'dart:developer' as developer; import 'package:flutter/material.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.dart'; +import 'package:url_launcher/url_launcher_string.dart'; import '../generated/l10n.dart'; extension ContextExtension on BuildContext { - Color get primaryColor => Theme.of(this).primaryColor; - Color get accentColor => Theme.of(this).colorScheme.secondary; - Color get scaffoldBackgroundColor => Theme.of(this).scaffoldBackgroundColor; + ColorScheme get colorScheme => Theme.of(this).colorScheme; + Color get accentColor => Theme.of(this).colorScheme.primary; + Color get primaryColor => Theme.of(this).colorScheme.onPrimary; + Color get priamryContainer => Theme.of(this).colorScheme.primaryContainer; + Color get onPrimary => Theme.of(this).colorScheme.onPrimary; + Color get background => Theme.of(this).colorScheme.background; + Color get tertiary => colorScheme.tertiary; + Color get onTertiary => colorScheme.onTertiary; + Color get secondary => colorScheme.secondary; + Color get onsecondary => colorScheme.onSecondary; + Color get error => colorScheme.error; Color get primaryColorDark => Theme.of(this).primaryColorDark; Color get textColor => textTheme.bodyLarge!.color!; Color get dialogBackgroundColor => Theme.of(this).dialogBackgroundColor; Brightness get brightness => Theme.of(this).brightness; - Brightness get iconBrightness => Theme.of(this).colorScheme.brightness; + Brightness get iconBrightness => + brightness == Brightness.dark ? Brightness.light : Brightness.dark; double get width => MediaQuery.of(this).size.width; double get height => MediaQuery.of(this).size.height; double get paddingTop => MediaQuery.of(this).padding.top; TextTheme get textTheme => Theme.of(this).textTheme; - S? get s => S.of(this); + S get s => S.of(this)!; } extension IntExtension on int { String toDate(BuildContext context) { final s = context.s; - var date = DateTime.fromMillisecondsSinceEpoch(this, isUtc: true); - var difference = DateTime.now().toUtc().difference(date); + final date = DateTime.fromMillisecondsSinceEpoch(this, isUtc: true); + final difference = DateTime.now().toUtc().difference(date); if (difference.inMinutes < 30) { - return s!.minsAgo(difference.inMinutes); + return s.minsAgo(difference.inMinutes); } else if (difference.inMinutes < 60) { - return s!.hoursAgo(0); + return s.hoursAgo(0); } else if (difference.inHours < 24) { - return s!.hoursAgo(difference.inHours); + return s.hoursAgo(difference.inHours); } else if (difference.inDays < 7) { - return s!.daysAgo(difference.inDays); + return s.daysAgo(difference.inDays); } else { return DateFormat.yMMMd().format( DateTime.fromMillisecondsSinceEpoch(this, isUtc: true).toLocal()); @@ -50,21 +60,21 @@ extension IntExtension on int { final s = context.s; var interval = Duration(milliseconds: this); if (interval.inHours <= 48) { - return s!.publishedDaily; + return s.publishedDaily; } else if (interval.inDays > 2 && interval.inDays <= 14) { - return s!.publishedWeekly; + return s.publishedWeekly; } else if (interval.inDays > 14 && interval.inDays < 60) { - return s!.publishedMonthly; + return s.publishedMonthly; } else { - return s!.publishedYearly; + return s.publishedYearly; } } } extension StringExtension on String { Future get launchUrl async { - if (await canLaunch(this)) { - await launch(this); + if (await canLaunchUrlString(this)) { + await launchUrlString(this); } else { developer.log('Could not launch $this'); Fluttertoast.showToast( diff --git a/lib/util/helpers.dart b/lib/util/helpers.dart index 83ad7be..a05cd85 100644 --- a/lib/util/helpers.dart +++ b/lib/util/helpers.dart @@ -2,6 +2,7 @@ import 'dart:ui' as ui; import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; Future getImageFromProvider(ImageProvider imageProvider) async { final ImageStream stream = imageProvider.resolve( @@ -17,3 +18,9 @@ Future getImageFromProvider(ImageProvider imageProvider) async { final image = await imageCompleter.future; return image; } + +String formateDate(int timeStamp) { + return DateFormat.yMMMd().format( + DateTime.fromMillisecondsSinceEpoch(timeStamp), + ); +} diff --git a/lib/widgets/audiopanel.dart b/lib/widgets/audiopanel.dart index 6b68c61..74dfaf0 100644 --- a/lib/widgets/audiopanel.dart +++ b/lib/widgets/audiopanel.dart @@ -94,8 +94,8 @@ class AudioPanelState extends State with TickerProviderStateMixin { child: GestureDetector( onTap: backToMini, child: Container( - color: context.scaffoldBackgroundColor.withOpacity(0.9 * - math.min(_animation.value / widget.maxHeight, 1)), + color: context.background.withOpacity( + 0.9 * math.min(_animation.value / widget.maxHeight, 1)), ), ), ) @@ -314,7 +314,7 @@ class __AudioPanelRouteState extends State<_AudioPanelRoute> { // child: // Container( // color: Theme.of(context) - // .scaffoldBackgroundColor + // .background // .withOpacity(0.8), // //), diff --git a/lib/widgets/custom_widget.dart b/lib/widgets/custom_widget.dart index 81e91b4..d7561b3 100644 --- a/lib/widgets/custom_widget.dart +++ b/lib/widgets/custom_widget.dart @@ -651,8 +651,7 @@ class _LineLoaderState extends State @override Widget build(BuildContext context) { - return CustomPaint( - painter: LinePainter(_fraction, context.accentColor)); + return CustomPaint(painter: LinePainter(_fraction, context.accentColor)); } } @@ -1115,8 +1114,7 @@ class LayoutButton extends StatelessWidget { height: 10, width: 30, child: CustomPaint( - painter: - LayoutPainter(4, context.textColor), + painter: LayoutPainter(4, context.textColor), ), ), ); @@ -1265,7 +1263,7 @@ class CustomBackButton extends StatelessWidget { return IconButton( splashRadius: 20, icon: const BackButtonIcon(), - tooltip: context.s!.back, + tooltip: context.s.back, onPressed: () { Navigator.maybePop(context); }, diff --git a/lib/widgets/episodegrid.dart b/lib/widgets/episodegrid.dart index 4d0ab21..f27aed8 100644 --- a/lib/widgets/episodegrid.dart +++ b/lib/widgets/episodegrid.dart @@ -132,7 +132,7 @@ class EpisodeGrid extends StatelessWidget { if (dataConfirm) { context.read().startTask(episode!); Fluttertoast.showToast( - msg: context.s!.downloadStart, + msg: context.s.downloadStart, gravity: ToastGravity.BOTTOM, ); } @@ -155,7 +155,7 @@ class EpisodeGrid extends StatelessWidget { Future _useDataConfirm(BuildContext context) async { var ifUseData = false; - final s = context.s!; + final s = context.s; await generalDialog( context, title: Text(s.cellularConfirm), @@ -205,7 +205,10 @@ class EpisodeGrid extends StatelessWidget { /// Circel avatar widget. Widget _circleImage(BuildContext context, - {EpisodeBrief? episode, Color? color, required bool boo, double? radius}) => + {EpisodeBrief? episode, + Color? color, + required bool boo, + double? radius}) => InkWell( onTap: () async { if (openPodcast) { @@ -287,7 +290,8 @@ class EpisodeGrid extends StatelessWidget { : Center(); /// Pubdate widget - Widget _pubDate(BuildContext context, {required EpisodeBrief episode, Color? color}) => + Widget _pubDate(BuildContext context, + {required EpisodeBrief episode, Color? color}) => Text( episode.pubDate!.toDate(context), overflow: TextOverflow.visible, @@ -299,7 +303,11 @@ class EpisodeGrid extends StatelessWidget { fontStyle: FontStyle.italic), ); Widget _episodeCard(BuildContext context, - {int? index, Color? color, bool? isLiked, bool? isDownloaded, bool? boo}) { + {int? index, + Color? color, + bool? isLiked, + bool? isDownloaded, + bool? boo}) { var width = context.width; if (layout == Layout.one) { return _layoutOneCard(context, @@ -323,7 +331,8 @@ class EpisodeGrid extends StatelessWidget { layout != Layout.one ? _circleImage(context, episode: episodes![index!], color: color, boo: boo!) - : _pubDate(context, episode: episodes![index!], color: color), + : _pubDate(context, + episode: episodes![index!], color: color), Spacer(), _isNewIndicator(episodes![index]), _downloadIndicater(context, @@ -403,7 +412,11 @@ class EpisodeGrid extends StatelessWidget { } Widget _layoutOneCard(BuildContext context, - {required int index, Color? color, required bool isLiked, bool? isDownloaded, required bool boo}) { + {required int index, + Color? color, + required bool isLiked, + bool? isDownloaded, + required bool boo}) { var width = context.width; return Padding( padding: EdgeInsets.symmetric(vertical: 8), @@ -443,7 +456,8 @@ class EpisodeGrid extends StatelessWidget { ), _isNewIndicator(episodes![index]), _downloadIndicater(context, - episode: episodes![index], isDownloaded: isDownloaded), + episode: episodes![index], + isDownloaded: isDownloaded), _numberIndicater(context, index: index, color: color) ], ), @@ -574,7 +588,7 @@ class EpisodeGrid extends StatelessWidget { ? context.brightness == Brightness.light ? Colors.grey[200] : Color.fromRGBO(50, 50, 50, 1) - : context.scaffoldBackgroundColor, + : context.background, boxShadow: [ BoxShadow( color: context.brightness == Brightness.light @@ -590,7 +604,8 @@ class EpisodeGrid extends StatelessWidget { color: Colors.transparent, child: InkWell( onTap: () { - if (!selectedList!.contains(episodes![index])) { + if (!selectedList! + .contains(episodes![index])) { _selectedList = selectedList; _selectedList!.add(episodes![index]); } else { @@ -609,7 +624,7 @@ class EpisodeGrid extends StatelessWidget { : context.brightness == Brightness.light ? context.primaryColor - : context.scaffoldBackgroundColor, + : context.background, width: 1.0, ), ), @@ -628,7 +643,7 @@ class EpisodeGrid extends StatelessWidget { border: Border.all( color: context.brightness == Brightness.light ? context.primaryColor - : context.scaffoldBackgroundColor, + : context.background, width: 1.0, ), ), @@ -687,8 +702,8 @@ class EpisodeGrid extends StatelessWidget { onPressed: () { if (!data.item2.contains( episodes![index].enclosureUrl)) { - audio - .addToPlaylist(episodes![index]); + audio.addToPlaylist( + episodes![index]); Fluttertoast.showToast( msg: s.toastAddPlaylist, gravity: ToastGravity.BOTTOM, @@ -787,8 +802,7 @@ class EpisodeGrid extends StatelessWidget { color: context.textColor! .withOpacity(0.5))) : Text(s.download), - trailingIcon: Icon( - LineIcons.download, + trailingIcon: Icon(LineIcons.download, color: Colors.green), onPressed: () async { if (!isDownloaded) { @@ -858,9 +872,9 @@ class OpenContainerWrapper extends StatelessWidget { beginColor: Theme.of(context).primaryColor, endColor: Theme.of(context).primaryColor, closedColor: Theme.of(context).brightness == Brightness.light - ? Theme.of(context).primaryColor - : Theme.of(context).scaffoldBackgroundColor, - openColor: Theme.of(context).scaffoldBackgroundColor, + ? context.primaryColor + : context.background, + openColor: context.background, openElevation: 0, closedElevation: 0, openShape: diff --git a/lib/widgets/feature_discovery.dart b/lib/widgets/feature_discovery.dart index 654240c..24ee0d1 100644 --- a/lib/widgets/feature_discovery.dart +++ b/lib/widgets/feature_discovery.dart @@ -22,7 +22,7 @@ Widget featureDiscoveryOverlay(BuildContext context, required Widget tapTarget, required String title, required String description}) { - final s = context.s!; + final s = context.s; return DescribedFeatureOverlay( featureId: featureId, tapTarget: tapTarget, diff --git a/lib/widgets/muiliselect_bar.dart b/lib/widgets/muiliselect_bar.dart index 168e613..f2e2d4c 100644 --- a/lib/widgets/muiliselect_bar.dart +++ b/lib/widgets/muiliselect_bar.dart @@ -140,7 +140,7 @@ class _MultiSelectMenuBarState extends State { Future _useDataConfirm() async { var ifUseData = false; - final s = context.s!; + final s = context.s; await generalDialog( context, title: Text(s.cellularConfirm), @@ -575,7 +575,7 @@ class __NewPlaylistState extends State<_NewPlaylist> { @override Widget build(BuildContext context) { - final s = context.s!; + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light, diff --git a/lib/widgets/settings_sheet.dart b/lib/widgets/settings_sheet.dart index b9e077b..ed205f4 100644 --- a/lib/widgets/settings_sheet.dart +++ b/lib/widgets/settings_sheet.dart @@ -58,7 +58,7 @@ class _SettingsSheetState extends State Navigator.pop(context); }, child: Container( - color: context.scaffoldBackgroundColor.withOpacity( + color: context.background.withOpacity( 0.8 * math.min(_animation.value / widget.height, 1.0)), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index b6da722..c84822e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,6 +68,8 @@ dependencies: collection: ^1.15.0-nullsafety.4 shared_preferences_android: ^2.0.12 path_provider_android: ^2.0.14 + material_color_utilities: ^0.1.4 + dynamic_color: ^1.4.0 dependency_overrides: linkify: