diff --git a/assets/avatar_backup.png b/assets/avatar_backup.png new file mode 100644 index 0000000..a18c360 Binary files /dev/null and b/assets/avatar_backup.png differ diff --git a/lib/episodes/episode_detail.dart b/lib/episodes/episode_detail.dart index 128b09c..aa1f94a 100644 --- a/lib/episodes/episode_detail.dart +++ b/lib/episodes/episode_detail.dart @@ -41,42 +41,8 @@ class _EpisodeDetailState extends State { /// Show page title. bool _showTitle; - - /// Load shownote. - bool _loaddes; bool _showMenu; String path; - String _description; - - Future _getSDescription(String url) async { - 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'); - } - } - } - - if (mounted) { - setState(() { - _loaddes = true; - }); - } - } Future _getPosition(EpisodeBrief episode) async { var dbHelper = DBHelper(); @@ -104,33 +70,9 @@ class _EpisodeDetailState extends State { } else if (_showTitle) setState(() => _showTitle = false); } - _markListened(EpisodeBrief episode) async { - var dbHelper = DBHelper(); - var marked = await dbHelper.checkMarked(episode); - if (!marked) { - final history = PlayHistory(episode.title, episode.enclosureUrl, 0, 1); - await dbHelper.saveHistory(history); - } - } - - int _getTimeStamp(String url) { - final time = url.substring(7); - 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; - } - @override void initState() { super.initState(); - _loaddes = false; _showMenu = true; _showTitle = false; //_getSDescription(widget.episodeItem.enclosureUrl); @@ -569,8 +511,8 @@ class __MenuBarState extends State<_MenuBar> { ? Center() : CircleAvatar( radius: 15, - backgroundImage: FileImage( - File("${widget.episodeItem.imagePath}"))), + backgroundImage: + widget.episodeItem.avatarImage), ), ), ), diff --git a/lib/home/audioplayer.dart b/lib/home/audioplayer.dart index fe66abd..100cc85 100644 --- a/lib/home/audioplayer.dart +++ b/lib/home/audioplayer.dart @@ -59,7 +59,7 @@ class PlayerWidget extends StatelessWidget { selector: (_, audio) => Tuple2(audio.episode?.primaryColor, audio.seekSliderValue), builder: (_, data, __) { - var c = context.brightness == Brightness.light + final c = context.brightness == Brightness.light ? data.item1.colorizedark() : data.item1.colorizeLight(); return SizedBox( @@ -127,9 +127,10 @@ class PlayerWidget extends StatelessWidget { ), Expanded( flex: 2, - child: Selector>( + child: Selector>( selector: (_, audio) => - Tuple2(audio.buffering, audio.playing), + Tuple3(audio.buffering, audio.playing, audio.episode), builder: (_, data, __) { return Row( mainAxisAlignment: MainAxisAlignment.center, @@ -142,12 +143,15 @@ class PlayerWidget extends StatelessWidget { padding: const EdgeInsets.symmetric( vertical: 10.0), child: SizedBox( - height: 30.0, - width: 30.0, - child: CircleAvatar( - backgroundImage: FileImage(File( - "${audio.episode.imagePath}")), - )), + height: 30.0, + width: 30.0, + child: CircleAvatar( + backgroundColor: data.item3 + .backgroudColor(context), + backgroundImage: + data.item3.avatarImage, + ), + ), ), Container( height: 40.0, @@ -161,9 +165,8 @@ class PlayerWidget extends StatelessWidget { onTap: data.item2 ? () => audio.pauseAduio() : null, - child: ImageRotate( - title: audio.episode?.title, - path: audio.episode?.imagePath), + child: + ImageRotate(episodeItem: data.item3), ) : InkWell( onTap: data.item2 @@ -176,12 +179,15 @@ class PlayerWidget extends StatelessWidget { padding: EdgeInsets.symmetric( vertical: 10.0), child: SizedBox( - height: 30.0, - width: 30.0, - child: CircleAvatar( - backgroundImage: FileImage(File( - "${audio.episode.imagePath}")), - )), + height: 30.0, + width: 30.0, + child: CircleAvatar( + backgroundColor: data.item3 + .backgroudColor(context), + backgroundImage: + data.item3.avatarImage, + ), + ), ), Container( height: 40.0, @@ -384,71 +390,6 @@ class LastPosition extends StatelessWidget { } } -class ImageRotate extends StatefulWidget { - final String title; - final String path; - ImageRotate({this.title, this.path, Key key}) : super(key: key); - @override - _ImageRotateState createState() => _ImageRotateState(); -} - -class _ImageRotateState extends State - with SingleTickerProviderStateMixin { - Animation _animation; - AnimationController _controller; - double _value; - @override - void initState() { - super.initState(); - _value = 0; - _controller = AnimationController( - vsync: this, - duration: Duration(milliseconds: 2000), - ); - _animation = Tween(begin: 0.0, end: 1.0).animate(_controller) - ..addListener(() { - if (mounted) { - setState(() { - _value = _animation.value; - }); - } - }); - _controller.forward(); - _controller.addStatusListener((status) { - if (status == AnimationStatus.completed) { - _controller.reset(); - } else if (status == AnimationStatus.dismissed) { - _controller.forward(); - } - }); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Transform.rotate( - angle: 2 * math.pi * _value, - child: Container( - padding: EdgeInsets.symmetric(vertical: 10.0), - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(15.0)), - child: Container( - height: 30.0, - width: 30.0, - color: Colors.white, - child: Image.file(File("${widget.path}")), - ), - ), - ), - ); - } -} - class PlaylistWidget extends StatefulWidget { const PlaylistWidget({Key key}) : super(key: key); @@ -500,7 +441,6 @@ class _PlaylistWidgetState extends State { miniPlaylistKey.currentState.removeItem( index, (context, animation) => Center()); - miniPlaylistKey.currentState.insertItem(0); }, child: Container( height: 60, @@ -586,7 +526,7 @@ class _PlaylistWidgetState extends State { ), ], ), - Divider(height: 2), + Divider(height: 1), ], ), ), @@ -638,7 +578,10 @@ class _PlaylistWidgetState extends State { onTap: () { Navigator.push( context, - SlideLeftRoute(page: PlaylistPage()), + SlideLeftRoute( + page: PlaylistPage( + initPage: InitPage.playlist, + )), ); }, child: SizedBox( @@ -1382,8 +1325,8 @@ class _ControlPanelState extends State height: 30.0, width: 30.0, child: CircleAvatar( - backgroundImage: FileImage(File( - "${data.item1.imagePath}")), + backgroundImage: + data.item1.avatarImage, ), ), SizedBox(width: 5), diff --git a/lib/home/download_list.dart b/lib/home/download_list.dart index 760ea1e..3217fd7 100644 --- a/lib/home/download_list.dart +++ b/lib/home/download_list.dart @@ -118,9 +118,7 @@ class _DownloadListState extends State { ), ), leading: CircleAvatar( - backgroundImage: FileImage( - File("${tasks[index].episode.imagePath}")), - ), + backgroundImage: tasks[index].episode.avatarImage), trailing: _downloadButton(tasks[index], context), ); }, diff --git a/lib/home/home.dart b/lib/home/home.dart index 3186779..d4bc94e 100644 --- a/lib/home/home.dart +++ b/lib/home/home.dart @@ -614,10 +614,9 @@ class PlaylistButtonState extends State { alignment: Alignment.center, children: [ CircleAvatar( - radius: 20, - backgroundImage: FileImage(File( - "${data.item2.playlist.first.imagePath}")), - ), + radius: 20, + backgroundImage: data + .item2.playlist.first.avatarImage), Container( height: 40.0, width: 40.0, @@ -678,12 +677,42 @@ class PlaylistButtonState extends State { ), ), ), + PopupMenuItem( + value: 2, + child: Container( + padding: EdgeInsets.only(left: 10), + child: Row( + children: [ + Icon(Icons.history), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + ), + Text(s.settingsHistory), + ], + ), + ), + ), ], onSelected: (value) { if (value == 0) { Navigator.push( - context, MaterialPageRoute(builder: (context) => PlaylistPage())); - } else if (value == 1) {} + context, + MaterialPageRoute( + builder: (context) => PlaylistPage( + initPage: InitPage.playlist, + ), + ), + ); + } else if (value == 2) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PlaylistPage( + initPage: InitPage.history, + ), + ), + ); + } }, ); } diff --git a/lib/home/home_groups.dart b/lib/home/home_groups.dart index 7adad2d..a8f8eac 100644 --- a/lib/home/home_groups.dart +++ b/lib/home/home_groups.dart @@ -314,12 +314,8 @@ class _ScrollPodcastsState extends State { tabs: groups[_groupIndex] .podcasts .map((podcastLocal) { - var color = (Theme.of(context).brightness == - Brightness.light) - ? podcastLocal.primaryColor - .colorizedark() - : podcastLocal.primaryColor - .colorizeLight(); + final color = + podcastLocal.backgroudColor(context); return Tab( child: ClipRRect( borderRadius: BorderRadius.all( @@ -331,11 +327,10 @@ class _ScrollPodcastsState extends State { maxHeight: 50, maxWidth: 50, child: CircleAvatar( - backgroundColor: - color.withOpacity(0.5), - backgroundImage: FileImage(File( - "${podcastLocal.imagePath}")), - ), + backgroundColor: + color.withOpacity(0.5), + backgroundImage: + podcastLocal.avatarImage), ), FutureBuilder( future: getPodcastUpdateCounts( @@ -418,9 +413,7 @@ class PodcastPreview extends StatelessWidget { @override Widget build(BuildContext context) { - var _c = (Theme.of(context).brightness == Brightness.light) - ? podcastLocal.primaryColor.colorizedark() - : podcastLocal.primaryColor.colorizeLight(); + final c = podcastLocal.backgroudColor(context); return Column( children: [ Expanded( @@ -454,7 +447,7 @@ class PodcastPreview extends StatelessWidget { child: Text(podcastLocal.title, maxLines: 1, overflow: TextOverflow.visible, - style: TextStyle(fontWeight: FontWeight.bold, color: _c)), + style: TextStyle(fontWeight: FontWeight.bold, color: c)), ), Expanded( flex: 1, @@ -595,9 +588,7 @@ class ShowEpisode extends StatelessWidget { ), delegate: SliverChildBuilderDelegate( (context, index) { - var _c = (Theme.of(context).brightness == Brightness.light) - ? podcastLocal.primaryColor.colorizedark() - : podcastLocal.primaryColor.colorizeLight(); + final c = podcastLocal.backgroudColor(context); return Selector>>( selector: (_, audio) => Tuple2( @@ -809,8 +800,8 @@ class ShowEpisode extends StatelessWidget { height: _width / 18, width: _width / 18, child: CircleAvatar( - backgroundImage: FileImage(File( - "${podcastLocal.imagePath}")), + backgroundImage: + podcastLocal.avatarImage, ), ), ), @@ -882,7 +873,7 @@ class ShowEpisode extends StatelessWidget { overflow: TextOverflow.visible, style: TextStyle( fontSize: _width / 35, - color: _c, + color: c, fontStyle: FontStyle.italic, ), ), diff --git a/lib/home/playlist.dart b/lib/home/playlist.dart index a7b7d1c..ed6f597 100644 --- a/lib/home/playlist.dart +++ b/lib/home/playlist.dart @@ -16,7 +16,11 @@ import '../type/playlist.dart'; import '../util/custom_widget.dart'; import '../util/extension_helper.dart'; +enum InitPage { playlist, history } + class PlaylistPage extends StatefulWidget { + final InitPage initPage; + PlaylistPage({this.initPage, Key key}) : super(key: key); @override _PlaylistPageState createState() => _PlaylistPageState(); } @@ -47,7 +51,12 @@ class _PlaylistPageState extends State { @override void initState() { super.initState(); - _loadList = _ReorderablePlaylist(); + if (widget.initPage == InitPage.playlist) { + _loadList = _ReorderablePlaylist(); + } else { + _loadHistory = true; + _loadList = _HistoryList(); + } } @override @@ -213,41 +222,15 @@ class _PlaylistPageState extends State { ) : BoxDecoration(color: Colors.transparent), child: data.item2 - // ? _topHeight < 90 - // ? Row( - // mainAxisAlignment: - // MainAxisAlignment.center, - // crossAxisAlignment: - // CrossAxisAlignment.center, - // children: [ - // CircleAvatar( - // radius: 12, - // backgroundImage: FileImage(File( - // "${episodes.first.imagePath}")), - // ), - // Padding( - // padding: EdgeInsets.symmetric( - // horizontal: 15), - // child: SizedBox( - // width: 20, - // height: 15, - // child: WaveLoader( - // color: context.accentColor, - // )), - // ), - // ], - // ) ? Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ CircleAvatar( - radius: 15, - //backgroundColor: _c.withOpacity(0.5), - backgroundImage: FileImage( - File("${data.item4.imagePath}")), - ), + radius: 15, + backgroundImage: + data.item4.avatarImage), Container( width: 150, alignment: Alignment.center, @@ -380,11 +363,9 @@ class __DismissibleContainerState extends State<_DismissibleContainer> { @override Widget build(BuildContext context) { - var audio = Provider.of(context, listen: false); + final audio = Provider.of(context, listen: false); final s = context.s; - var c = (Theme.of(context).brightness == Brightness.light) - ? widget.episode.primaryColor.colorizedark() - : widget.episode.primaryColor.colorizeLight(); + final c = widget.episode.backgroudColor(context); return AnimatedContainer( duration: Duration(milliseconds: 300), alignment: Alignment.center, @@ -475,10 +456,8 @@ class __DismissibleContainerState extends State<_DismissibleContainer> { children: [ Icon(Icons.unfold_more, color: c), CircleAvatar( - backgroundColor: c.withOpacity(0.5), - backgroundImage: - FileImage(File(widget.episode.imagePath)), - ), + backgroundColor: c.withOpacity(0.5), + backgroundImage: widget.episode.avatarImage), ], ), subtitle: Container( @@ -595,10 +574,8 @@ class __HistoryListState extends State<_HistoryList> { EdgeInsets.fromLTRB(24, 8, 20, 8), onTap: () => audio.episodeLoad(episode), leading: CircleAvatar( - backgroundColor: c.withOpacity(0.5), - backgroundImage: - FileImage(File(episode.imagePath)), - ), + backgroundColor: c.withOpacity(0.5), + backgroundImage: episode.avatarImage), title: Padding( padding: EdgeInsets.symmetric(vertical: 5.0), @@ -741,7 +718,7 @@ class __HistoryListState extends State<_HistoryList> { ), ), ), - Divider(height: 2) + Divider(height: 1) ], ), ); diff --git a/lib/local_storage/sqflite_localpodcast.dart b/lib/local_storage/sqflite_localpodcast.dart index f41358f..d147dfb 100644 --- a/lib/local_storage/sqflite_localpodcast.dart +++ b/lib/local_storage/sqflite_localpodcast.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:developer' as developer; +import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; @@ -1170,6 +1171,12 @@ class DBHelper { list.first['is_new'], mediaId: list.first['media_id'], skipSeconds: list.first['skip_seconds']); + if (episode.enclosureUrl != episode.mediaId && + !File(episode.mediaId.substring(7)).existsSync()) { + final url = episode.enclosureUrl; + await delDownloaded(url); + episode = episode.copyWith(mediaId: url); + } return episode; } } @@ -1198,6 +1205,12 @@ class DBHelper { list.first['is_new'], mediaId: list.first['media_id'], skipSeconds: list.first['skip_seconds']); + if (episode.enclosureUrl != episode.mediaId && + !File(episode.mediaId.substring(7)).existsSync()) { + final url = episode.enclosureUrl; + await delDownloaded(url); + episode = episode.copyWith(mediaId: url); + } return episode; } } diff --git a/lib/podcasts/podcast_group.dart b/lib/podcasts/podcast_group.dart index 9110ede..d26af7b 100644 --- a/lib/podcasts/podcast_group.dart +++ b/lib/podcasts/podcast_group.dart @@ -161,9 +161,7 @@ class __PodcastCardState extends State<_PodcastCard> @override Widget build(BuildContext context) { - final c = (Theme.of(context).brightness == Brightness.light) - ? widget.podcastLocal.primaryColor.colorizedark() - : widget.podcastLocal.primaryColor.colorizeLight(); + final c = widget.podcastLocal.backgroudColor(context); final s = context.s; var width = context.width; var groupList = context.watch(); diff --git a/lib/settings/downloads_manage.dart b/lib/settings/downloads_manage.dart index e844898..e3a58ad 100644 --- a/lib/settings/downloads_manage.dart +++ b/lib/settings/downloads_manage.dart @@ -320,9 +320,9 @@ class _DownloadsManageState extends State { } }, leading: CircleAvatar( - backgroundImage: FileImage(File( - "${_episodes[index].imagePath}")), - ), + backgroundImage: + _episodes[index] + .avatarImage), title: Text( _episodes[index].title, maxLines: 1, diff --git a/lib/settings/layouts.dart b/lib/settings/layouts.dart index 24b4bc5..2ea76e4 100644 --- a/lib/settings/layouts.dart +++ b/lib/settings/layouts.dart @@ -190,7 +190,7 @@ class _LayoutSettingState extends State { title: Text(s.settingsPopupMenu), subtitle: Text(s.settingsPopupMenuDes), ), - Divider(height: 2), + Divider(height: 1), Padding( padding: EdgeInsets.all(10.0), ), @@ -226,7 +226,7 @@ class _LayoutSettingState extends State { audio.setPlayerHeight = PlayerHeight.values[index]), ), ), - Divider(height: 2), + Divider(height: 1), Padding( padding: EdgeInsets.all(10.0), ), @@ -258,7 +258,7 @@ class _LayoutSettingState extends State { text: s.settingsDefaultGridDownload, key: downloadLayoutKey), ]), - Divider(height: 2), + Divider(height: 1), ], ), )), diff --git a/lib/settings/play_setting.dart b/lib/settings/play_setting.dart index f43d0ef..f9fcd54 100644 --- a/lib/settings/play_setting.dart +++ b/lib/settings/play_setting.dart @@ -247,7 +247,7 @@ class PlaySetting extends StatelessWidget { ), ), ), - Divider(height: 2), + Divider(height: 1), ], ), Padding( @@ -415,7 +415,7 @@ class PlaySetting extends StatelessWidget { trailing: context.width > 360 ? _scheduleWidget(context) : null), - Divider(height: 2) + Divider(height: 1) ], ), SizedBox(height: 20) diff --git a/lib/settings/storage.dart b/lib/settings/storage.dart index 6c9bacd..2b2a4e7 100644 --- a/lib/settings/storage.dart +++ b/lib/settings/storage.dart @@ -145,7 +145,7 @@ class _StorageSettingState extends State ); }, ), - Divider(height: 2), + Divider(height: 1), FutureBuilder( future: _getAutoDownloadNetwork(), initialData: false, @@ -172,7 +172,7 @@ class _StorageSettingState extends State ), ); }), - Divider(height: 2), + Divider(height: 1), ], ), ]), @@ -207,7 +207,7 @@ class _StorageSettingState extends State title: Text(s.download), subtitle: Text(s.settingsManageDownloadDes), ), - Divider(height: 2), + Divider(height: 1), FutureBuilder( future: _getAutoDeleteDays(), initialData: 30, @@ -238,7 +238,7 @@ class _StorageSettingState extends State ); }, ), - Divider(height: 2), + Divider(height: 1), ListTile( contentPadding: EdgeInsets.only(left: 70.0, right: 25), // leading: Icon(Icons.colorize), @@ -281,7 +281,7 @@ class _StorageSettingState extends State }), ), ), - Divider(height: 2), + Divider(height: 1), ], ), ], diff --git a/lib/settings/syncing.dart b/lib/settings/syncing.dart index 82b99ce..5a3ccdf 100644 --- a/lib/settings/syncing.dart +++ b/lib/settings/syncing.dart @@ -110,7 +110,7 @@ class SyncingSetting extends StatelessWidget { value: e, child: Text(s.hoursCount(e))); }).toList()), ), - Divider(height: 2), + Divider(height: 1), ], ), ], diff --git a/lib/settings/theme.dart b/lib/settings/theme.dart index e725939..7f51d92 100644 --- a/lib/settings/theme.dart +++ b/lib/settings/theme.dart @@ -168,7 +168,7 @@ class ThemeSetting extends StatelessWidget { fontWeight: FontWeight.bold, color: context.accentColor)) ])), - content: ColorPicker( + content: _ColorPicker( onColorChanged: (value) => settings.setAccentColor = value, ), @@ -183,7 +183,7 @@ class ThemeSetting extends StatelessWidget { shape: BoxShape.circle, color: context.accentColor), ), ), - Divider(height: 2), + Divider(height: 1), ], ), ], @@ -193,14 +193,14 @@ class ThemeSetting extends StatelessWidget { } } -class ColorPicker extends StatefulWidget { +class _ColorPicker extends StatefulWidget { final ValueChanged onColorChanged; - ColorPicker({Key key, this.onColorChanged}) : super(key: key); + _ColorPicker({Key key, this.onColorChanged}) : super(key: key); @override - _ColorPickerState createState() => _ColorPickerState(); + __ColorPickerState createState() => __ColorPickerState(); } -class _ColorPickerState extends State +class __ColorPickerState extends State<_ColorPicker> with SingleTickerProviderStateMixin { TabController _controller; int _index; @@ -284,9 +284,9 @@ class _ColorPickerState extends State behavior: NoGrowBehavior(), child: GridView.count( primary: false, - padding: const EdgeInsets.all(10), - crossAxisSpacing: 10, - mainAxisSpacing: 10, + padding: const EdgeInsets.fromLTRB(2, 10, 2, 10), + crossAxisSpacing: 4, + mainAxisSpacing: 4, crossAxisCount: 3, children: [ _colorCircle(color.shade100), diff --git a/lib/type/episodebrief.dart b/lib/type/episodebrief.dart index 31a7a61..f72f6bd 100644 --- a/lib/type/episodebrief.dart +++ b/lib/type/episodebrief.dart @@ -1,6 +1,10 @@ +import 'dart:io'; + import 'package:equatable/equatable.dart'; import 'package:audio_service/audio_service.dart'; +import 'package:flutter/material.dart'; +import '../util/extension_helper.dart'; class EpisodeBrief extends Equatable { final String title; @@ -49,6 +53,29 @@ class EpisodeBrief extends Equatable { extras: {'skip': skipSeconds}); } + ImageProvider get avatarImage { + return File(imagePath).existsSync() + ? FileImage(File(imagePath)) + : const AssetImage('assets/avatar_backup.png'); + } + + Color backgroudColor(BuildContext context) { + return context.brightness == Brightness.light + ? primaryColor.colorizedark() + : primaryColor.colorizeLight(); + } + + EpisodeBrief copyWith({ + String mediaId, + }) => + EpisodeBrief(title, enclosureUrl, enclosureLength, pubDate, feedTitle, + primaryColor, duration, explicit, imagePath, isNew, + mediaId: mediaId ?? this.mediaId, + downloaded: downloaded, + skipSeconds: skipSeconds, + description: description, + downloadDate: downloadDate); + @override List get props => [enclosureUrl, title]; } diff --git a/lib/type/podcastlocal.dart b/lib/type/podcastlocal.dart index b2b56c4..d07ed31 100644 --- a/lib/type/podcastlocal.dart +++ b/lib/type/podcastlocal.dart @@ -1,4 +1,9 @@ +import 'dart:io'; + import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +import '../util/extension_helper.dart'; class PodcastLocal extends Equatable { final String title; @@ -29,6 +34,18 @@ class PodcastLocal extends Equatable { _episodeCount = episodeCount ?? 0, _upateCount = upateCount ?? 0; + ImageProvider get avatarImage { + return File(imagePath).existsSync() + ? FileImage(File(imagePath)) + : const AssetImage('assets/avatar_backup.png'); + } + + Color backgroudColor(BuildContext context) { + return context.brightness == Brightness.light + ? primaryColor.colorizedark() + : primaryColor.colorizeLight(); + } + @override List get props => [id, rssUrl]; } diff --git a/lib/util/audiopanel.dart b/lib/util/audiopanel.dart index fbd3bc6..a00ae1c 100644 --- a/lib/util/audiopanel.dart +++ b/lib/util/audiopanel.dart @@ -122,6 +122,10 @@ class AudioPanelState extends State with TickerProviderStateMixin { : Container( decoration: BoxDecoration( color: context.primaryColor, + // borderRadius: BorderRadius.only( + // topLeft: Radius.circular(20.0), + // topRight: Radius.circular(20.0)), + boxShadow: [ BoxShadow( offset: Offset(0, -1), diff --git a/lib/util/custom_dropdown.dart b/lib/util/custom_dropdown.dart index 553746f..bd28ac0 100644 --- a/lib/util/custom_dropdown.dart +++ b/lib/util/custom_dropdown.dart @@ -1113,6 +1113,7 @@ class MyDropdownButton extends StatefulWidget { class _MyDropdownButtonState extends State> // ignore: prefer_mixin with + // ignore: prefer_mixin WidgetsBindingObserver { int _selectedIndex; _DropdownRoute _dropdownRoute; diff --git a/lib/util/custom_time_picker.dart b/lib/util/custom_time_picker.dart index eb8aec3..cfe84ba 100644 --- a/lib/util/custom_time_picker.dart +++ b/lib/util/custom_time_picker.dart @@ -105,14 +105,12 @@ class _TimePickerHeader extends StatelessWidget { @override Widget build(BuildContext context) { assert(debugCheckHasMediaQuery(context)); - final ThemeData themeData = Theme.of(context); - final TimeOfDayFormat timeOfDayFormat = - MaterialLocalizations.of(context).timeOfDayFormat( + final themeData = Theme.of(context); + final timeOfDayFormat = MaterialLocalizations.of(context).timeOfDayFormat( alwaysUse24HourFormat: MediaQuery.of(context).alwaysUse24HourFormat, ); - final _TimePickerFragmentContext fragmentContext = - _TimePickerFragmentContext( + final fragmentContext = _TimePickerFragmentContext( selectedTime: selectedTime, mode: mode, onTimeChange: onChanged, @@ -241,26 +239,26 @@ class _HourMinuteControl extends StatelessWidget { @override Widget build(BuildContext context) { - final ThemeData themeData = Theme.of(context); - final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context); - final bool isDark = themeData.colorScheme.brightness == Brightness.dark; - final Color textColor = timePickerTheme.hourMinuteTextColor ?? - MaterialStateColor.resolveWith((Set states) { + final themeData = Theme.of(context); + final timePickerTheme = TimePickerTheme.of(context); + final isDark = themeData.colorScheme.brightness == Brightness.dark; + final textColor = timePickerTheme.hourMinuteTextColor ?? + MaterialStateColor.resolveWith((states) { return states.contains(MaterialState.selected) ? themeData.accentColor : themeData.colorScheme.onSurface; }); - final Color backgroundColor = timePickerTheme.hourMinuteColor ?? - MaterialStateColor.resolveWith((Set states) { + final backgroundColor = timePickerTheme.hourMinuteColor ?? + MaterialStateColor.resolveWith((states) { return states.contains(MaterialState.selected) ? themeData.accentColor.withOpacity(isDark ? 0.24 : 0.12) : themeData.colorScheme.onSurface.withOpacity(0.12); }); - final TextStyle style = + final style = timePickerTheme.hourMinuteTextStyle ?? themeData.textTheme.headline2; - final ShapeBorder shape = timePickerTheme.hourMinuteShape ?? _kDefaultShape; + final shape = timePickerTheme.hourMinuteShape ?? _kDefaultShape; - final Set states = isSelected + final states = isSelected ? {MaterialState.selected} : {}; return Container( @@ -298,38 +296,36 @@ class _HourControl extends StatelessWidget { @override Widget build(BuildContext context) { assert(debugCheckHasMediaQuery(context)); - final bool alwaysUse24HourFormat = - MediaQuery.of(context).alwaysUse24HourFormat; - final MaterialLocalizations localizations = - MaterialLocalizations.of(context); - final String formattedHour = localizations.formatHour( + final alwaysUse24HourFormat = MediaQuery.of(context).alwaysUse24HourFormat; + final localizations = MaterialLocalizations.of(context); + final formattedHour = localizations.formatHour( fragmentContext.selectedTime, alwaysUse24HourFormat: alwaysUse24HourFormat, ); TimeOfDay hoursFromSelected(int hoursToAdd) { if (fragmentContext.use24HourDials) { - final int selectedHour = fragmentContext.selectedTime.hour; + final selectedHour = fragmentContext.selectedTime.hour; return fragmentContext.selectedTime.replacing( hour: (selectedHour + hoursToAdd) % TimeOfDay.hoursPerDay, ); } else { // Cycle 1 through 12 without changing day period. - final int periodOffset = fragmentContext.selectedTime.periodOffset; - final int hours = fragmentContext.selectedTime.hourOfPeriod; + final periodOffset = fragmentContext.selectedTime.periodOffset; + final hours = fragmentContext.selectedTime.hourOfPeriod; return fragmentContext.selectedTime.replacing( hour: periodOffset + (hours + hoursToAdd) % TimeOfDay.hoursPerPeriod, ); } } - final TimeOfDay nextHour = hoursFromSelected(1); - final String formattedNextHour = localizations.formatHour( + final nextHour = hoursFromSelected(1); + final formattedNextHour = localizations.formatHour( nextHour, alwaysUse24HourFormat: alwaysUse24HourFormat, ); - final TimeOfDay previousHour = hoursFromSelected(-1); - final String formattedPreviousHour = localizations.formatHour( + final previousHour = hoursFromSelected(-1); + final formattedPreviousHour = localizations.formatHour( previousHour, alwaysUse24HourFormat: alwaysUse24HourFormat, ); @@ -381,11 +377,11 @@ class _StringFragment extends StatelessWidget { @override Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); - final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context); - final TextStyle hourMinuteStyle = + final theme = Theme.of(context); + final timePickerTheme = TimePickerTheme.of(context); + final hourMinuteStyle = timePickerTheme.hourMinuteTextStyle ?? theme.textTheme.headline2; - final Color textColor = + final textColor = timePickerTheme.hourMinuteTextColor ?? theme.colorScheme.onSurface; return ExcludeSemantics( @@ -417,21 +413,19 @@ class _MinuteControl extends StatelessWidget { @override Widget build(BuildContext context) { - final MaterialLocalizations localizations = - MaterialLocalizations.of(context); - final String formattedMinute = + final localizations = MaterialLocalizations.of(context); + final formattedMinute = localizations.formatMinute(fragmentContext.selectedTime); - final TimeOfDay nextMinute = fragmentContext.selectedTime.replacing( + final nextMinute = fragmentContext.selectedTime.replacing( minute: (fragmentContext.selectedTime.minute + 1) % TimeOfDay.minutesPerHour, ); - final String formattedNextMinute = localizations.formatMinute(nextMinute); - final TimeOfDay previousMinute = fragmentContext.selectedTime.replacing( + final formattedNextMinute = localizations.formatMinute(nextMinute); + final previousMinute = fragmentContext.selectedTime.replacing( minute: (fragmentContext.selectedTime.minute - 1) % TimeOfDay.minutesPerHour, ); - final String formattedPreviousMinute = - localizations.formatMinute(previousMinute); + final formattedPreviousMinute = localizations.formatMinute(previousMinute); return Semantics( excludeSemantics: true, @@ -470,9 +464,9 @@ class _DayPeriodControl extends StatelessWidget { final ValueChanged onChanged; void _togglePeriod() { - final int newHour = + final newHour = (selectedTime.hour + TimeOfDay.hoursPerPeriod) % TimeOfDay.hoursPerDay; - final TimeOfDay newTime = selectedTime.replacing(hour: newHour); + final newTime = selectedTime.replacing(hour: newHour); onChanged(newTime); } @@ -516,19 +510,18 @@ class _DayPeriodControl extends StatelessWidget { @override Widget build(BuildContext context) { - final MaterialLocalizations materialLocalizations = - MaterialLocalizations.of(context); - final ColorScheme colorScheme = Theme.of(context).colorScheme; - final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context); - final bool isDark = colorScheme.brightness == Brightness.dark; - final Color textColor = timePickerTheme.dayPeriodTextColor ?? - MaterialStateColor.resolveWith((Set states) { + final materialLocalizations = MaterialLocalizations.of(context); + final colorScheme = Theme.of(context).colorScheme; + final timePickerTheme = TimePickerTheme.of(context); + final isDark = colorScheme.brightness == Brightness.dark; + final textColor = timePickerTheme.dayPeriodTextColor ?? + MaterialStateColor.resolveWith((states) { return states.contains(MaterialState.selected) ? colorScheme.primary : colorScheme.onSurface.withOpacity(0.60); }); - final Color backgroundColor = timePickerTheme.dayPeriodColor ?? - MaterialStateColor.resolveWith((Set states) { + final backgroundColor = timePickerTheme.dayPeriodColor ?? + MaterialStateColor.resolveWith((states) { // The unselected day period should match the overall picker dialog // color. Making it transparent enables that without being redundant // and allows the optional elevation overlay for dark mode to be @@ -537,25 +530,25 @@ class _DayPeriodControl extends StatelessWidget { ? colorScheme.primary.withOpacity(isDark ? 0.24 : 0.12) : Colors.transparent; }); - final bool amSelected = selectedTime.period == DayPeriod.am; - final Set amStates = amSelected + final amSelected = selectedTime.period == DayPeriod.am; + final amStates = amSelected ? {MaterialState.selected} : {}; - final bool pmSelected = !amSelected; - final Set pmStates = pmSelected + final pmSelected = !amSelected; + final pmStates = pmSelected ? {MaterialState.selected} : {}; - final TextStyle textStyle = timePickerTheme.dayPeriodTextStyle ?? + final textStyle = timePickerTheme.dayPeriodTextStyle ?? Theme.of(context).textTheme.subtitle1; - final TextStyle amStyle = textStyle.copyWith( + final amStyle = textStyle.copyWith( color: MaterialStateProperty.resolveAs(textColor, amStates), ); - final TextStyle pmStyle = textStyle.copyWith( + final pmStyle = textStyle.copyWith( color: MaterialStateProperty.resolveAs(textColor, pmStates), ); - OutlinedBorder shape = timePickerTheme.dayPeriodShape ?? + var shape = timePickerTheme.dayPeriodShape ?? const RoundedRectangleBorder(borderRadius: _kDefaultBorderRadius); - final BorderSide borderSide = timePickerTheme.dayPeriodBorderSide ?? + final borderSide = timePickerTheme.dayPeriodBorderSide ?? BorderSide( color: Color.alphaBlend( colorScheme.onBackground.withOpacity(0.38), colorScheme.surface), @@ -565,7 +558,7 @@ class _DayPeriodControl extends StatelessWidget { side: borderSide, ); - final double buttonTextScaleFactor = + final buttonTextScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 2.0); final Widget amButton = Material( @@ -605,7 +598,7 @@ class _DayPeriodControl extends StatelessWidget { Widget result; switch (orientation) { case Orientation.portrait: - const double width = 52.0; + const width = 52.0; result = _DayPeriodInputPadding( minSize: const Size(width, kMinInteractiveDimension * 2), orientation: orientation, @@ -737,10 +730,10 @@ class _RenderInputPadding extends RenderShiftedBox { void performLayout() { if (child != null) { child.layout(constraints, parentUsesSize: true); - final double width = math.max(child.size.width, minSize.width); - final double height = math.max(child.size.height, minSize.height); + final width = math.max(child.size.width, minSize.width); + final height = math.max(child.size.height, minSize.height); size = constraints.constrain(Size(width, height)); - final BoxParentData childParentData = child.parentData as BoxParentData; + final childParentData = child.parentData as BoxParentData; childParentData.offset = Alignment.center.alongOffset(size - child.size as Offset); } else { @@ -761,7 +754,7 @@ class _RenderInputPadding extends RenderShiftedBox { return false; } - Offset newPosition = child.size.center(Offset.zero); + var newPosition = child.size.center(Offset.zero); switch (orientation) { case Orientation.portrait: if (position.dy > newPosition.dy) { @@ -782,7 +775,7 @@ class _RenderInputPadding extends RenderShiftedBox { return result.addWithRawTransform( transform: MatrixUtils.forceToPoint(newPosition), position: newPosition, - hitTest: (BoxHitTestResult result, Offset position) { + hitTest: (result, position) { assert(position == newPosition); return child.hitTest(result, position: newPosition); }, @@ -832,12 +825,12 @@ class _DialPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { - final double radius = size.shortestSide / 2.0; - final Offset center = Offset(size.width / 2.0, size.height / 2.0); - final Offset centerPoint = center; + final radius = size.shortestSide / 2.0; + final center = Offset(size.width / 2.0, size.height / 2.0); + final centerPoint = center; canvas.drawCircle(centerPoint, radius, Paint()..color = backgroundColor); - final double labelRadius = radius - _labelPadding; + final labelRadius = radius - _labelPadding; Offset getOffsetForTheta(double theta) { return center + Offset(labelRadius * math.cos(theta), -labelRadius * math.sin(theta)); @@ -845,12 +838,12 @@ class _DialPainter extends CustomPainter { void paintLabels(List<_TappableLabel> labels) { if (labels == null) return; - final double labelThetaIncrement = -_kTwoPi / labels.length; - double labelTheta = math.pi / 2.0; + final labelThetaIncrement = -_kTwoPi / labels.length; + var labelTheta = math.pi / 2.0; - for (final _TappableLabel label in labels) { - final TextPainter labelPainter = label.painter; - final Offset labelOffset = + for (final label in labels) { + final labelPainter = label.painter; + final labelOffset = Offset(-labelPainter.width / 2.0, -labelPainter.height / 2.0); labelPainter.paint(canvas, getOffsetForTheta(labelTheta) + labelOffset); labelTheta += labelThetaIncrement; @@ -859,9 +852,9 @@ class _DialPainter extends CustomPainter { paintLabels(primaryLabels); - final Paint selectorPaint = Paint()..color = accentColor; - final Offset focusedPoint = getOffsetForTheta(theta); - const double focusedRadius = _labelPadding - 4.0; + final selectorPaint = Paint()..color = accentColor; + final focusedPoint = getOffsetForTheta(theta); + const focusedRadius = _labelPadding - 4.0; canvas.drawCircle(centerPoint, 4.0, selectorPaint); canvas.drawCircle(focusedPoint, focusedRadius, selectorPaint); selectorPaint.strokeWidth = 2.0; @@ -871,13 +864,13 @@ class _DialPainter extends CustomPainter { // This checks that the selector's theta is between two labels. A remainder // between 0.1 and 0.45 indicates that the selector is roughly not above any // labels. The values were derived by manually testing the dial. - final double labelThetaIncrement = -_kTwoPi / primaryLabels.length; + final labelThetaIncrement = -_kTwoPi / primaryLabels.length; if (theta % labelThetaIncrement > 0.1 && theta % labelThetaIncrement < 0.45) { canvas.drawCircle(focusedPoint, 2.0, selectorPaint..color = dotColor); } - final Rect focusedRect = Rect.fromCircle( + final focusedRect = Rect.fromCircle( center: focusedPoint, radius: focusedRadius, ); @@ -972,8 +965,8 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { } void _animateTo(double targetTheta) { - final double currentTheta = _theta.value; - double beginTheta = + final currentTheta = _theta.value; + var beginTheta = _nearest(targetTheta, currentTheta, currentTheta + _kTwoPi); beginTheta = _nearest(targetTheta, beginTheta, currentTheta - _kTwoPi); _thetaTween @@ -985,17 +978,17 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { } double _getThetaForTime(TimeOfDay time) { - final int hoursFactor = widget.use24HourDials + final hoursFactor = widget.use24HourDials ? TimeOfDay.hoursPerDay : TimeOfDay.hoursPerPeriod; - final double fraction = widget.mode == _TimePickerMode.hour + final fraction = widget.mode == _TimePickerMode.hour ? (time.hour / hoursFactor) % hoursFactor : (time.minute / TimeOfDay.minutesPerHour) % TimeOfDay.minutesPerHour; return (math.pi / 2.0 - fraction * _kTwoPi) % _kTwoPi; } TimeOfDay _getTimeForTheta(double theta, {bool roundMinutes = false}) { - final double fraction = (0.25 - (theta % _kTwoPi) / _kTwoPi) % 1.0; + final fraction = (0.25 - (theta % _kTwoPi) / _kTwoPi) % 1.0; if (widget.mode == _TimePickerMode.hour) { int newHour; if (widget.use24HourDials) { @@ -1008,7 +1001,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { } return widget.selectedTime.replacing(hour: newHour); } else { - int minute = (fraction * TimeOfDay.minutesPerHour).round() % + var minute = (fraction * TimeOfDay.minutesPerHour).round() % TimeOfDay.minutesPerHour; if (roundMinutes) { // Round the minutes to nearest 5 minute interval. @@ -1019,8 +1012,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { } TimeOfDay _notifyOnChangedIfNeeded({bool roundMinutes = false}) { - final TimeOfDay current = - _getTimeForTheta(_theta.value, roundMinutes: roundMinutes); + final current = _getTimeForTheta(_theta.value, roundMinutes: roundMinutes); if (widget.onChanged == null) return current; if (current != widget.selectedTime) widget.onChanged(current); return current; @@ -1028,9 +1020,8 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { void _updateThetaForPan({bool roundMinutes = false}) { setState(() { - final Offset offset = _position - _center; - double angle = - (math.atan2(offset.dx, offset.dy) - math.pi / 2.0) % _kTwoPi; + final offset = _position - _center; + var angle = (math.atan2(offset.dx, offset.dy) - math.pi / 2.0) % _kTwoPi; if (roundMinutes) { angle = _getThetaForTime( _getTimeForTheta(angle, roundMinutes: roundMinutes)); @@ -1047,7 +1038,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { void _handlePanStart(DragStartDetails details) { assert(!_dragging); _dragging = true; - final RenderBox box = context.findRenderObject() as RenderBox; + final box = context.findRenderObject() as RenderBox; _position = box.globalToLocal(details.globalPosition); _center = box.size.center(Offset.zero); _updateThetaForPan(); @@ -1074,11 +1065,11 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { } void _handleTapUp(TapUpDetails details) { - final RenderBox box = context.findRenderObject() as RenderBox; + final box = context.findRenderObject() as RenderBox; _position = box.globalToLocal(details.globalPosition); _center = box.size.center(Offset.zero); _updateThetaForPan(roundMinutes: true); - final TimeOfDay newTime = _notifyOnChangedIfNeeded(roundMinutes: true); + final newTime = _notifyOnChangedIfNeeded(roundMinutes: true); if (widget.mode == _TimePickerMode.hour) { if (widget.use24HourDials) { _announceToAccessibility( @@ -1115,7 +1106,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { minute: widget.selectedTime.minute); } } - final double angle = _getThetaForTime(time); + final angle = _getThetaForTime(time); _thetaTween ..begin = angle ..end = angle; @@ -1124,11 +1115,11 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { void _selectMinute(int minute) { _announceToAccessibility(context, localizations.formatDecimal(minute)); - final TimeOfDay time = TimeOfDay( + final time = TimeOfDay( hour: widget.selectedTime.hour, minute: minute, ); - final double angle = _getThetaForTime(time); + final angle = _getThetaForTime(time); _thetaTween ..begin = angle ..end = angle; @@ -1167,8 +1158,8 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { _TappableLabel _buildTappableLabel(TextTheme textTheme, Color color, int value, String label, VoidCallback onTap) { - final TextStyle style = textTheme.subtitle1.copyWith(color: color); - final double labelScaleFactor = + final style = textTheme.subtitle1.copyWith(color: color); + final labelScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 2.0); return _TappableLabel( value: value, @@ -1212,7 +1203,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { ]; List<_TappableLabel> _buildMinutes(TextTheme textTheme, Color color) { - const List _minuteMarkerValues = [ + const _minuteMarkerValues = [ TimeOfDay(hour: 0, minute: 0), TimeOfDay(hour: 0, minute: 5), TimeOfDay(hour: 0, minute: 10), @@ -1243,15 +1234,14 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); - final TimePickerThemeData pickerTheme = TimePickerTheme.of(context); - final Color backgroundColor = pickerTheme.dialBackgroundColor ?? + final theme = Theme.of(context); + final pickerTheme = TimePickerTheme.of(context); + final backgroundColor = pickerTheme.dialBackgroundColor ?? themeData.colorScheme.onBackground.withOpacity(0.12); - final Color accentColor = - pickerTheme.dialHandColor ?? themeData.accentColor; - final Color primaryLabelColor = MaterialStateProperty.resolveAs( + final accentColor = pickerTheme.dialHandColor ?? themeData.accentColor; + final primaryLabelColor = MaterialStateProperty.resolveAs( pickerTheme.dialTextColor, {}); - final Color secondaryLabelColor = MaterialStateProperty.resolveAs( + final secondaryLabelColor = MaterialStateProperty.resolveAs( pickerTheme.dialTextColor, {MaterialState.selected}); List<_TappableLabel> primaryLabels; List<_TappableLabel> secondaryLabels; @@ -1339,7 +1329,7 @@ class _TimePickerInputState extends State<_TimePickerInput> { return null; } - int newHour = int.tryParse(value); + var newHour = int.tryParse(value); if (newHour == null) { return null; } @@ -1366,7 +1356,7 @@ class _TimePickerInputState extends State<_TimePickerInput> { return null; } - final int newMinute = int.tryParse(value); + final newMinute = int.tryParse(value); if (newMinute == null) { return null; } @@ -1378,7 +1368,7 @@ class _TimePickerInputState extends State<_TimePickerInput> { } void _handleHourSavedSubmitted(String value) { - final int newHour = _parseHour(value); + final newHour = _parseHour(value); if (newHour != null) { _selectedTime = TimeOfDay(hour: newHour, minute: _selectedTime.minute); widget.onChanged(_selectedTime); @@ -1386,7 +1376,7 @@ class _TimePickerInputState extends State<_TimePickerInput> { } void _handleHourChanged(String value) { - final int newHour = _parseHour(value); + final newHour = _parseHour(value); if (newHour != null && value.length == 2) { // If a valid hour is typed, move focus to the minute TextField. FocusScope.of(context).nextFocus(); @@ -1394,7 +1384,7 @@ class _TimePickerInputState extends State<_TimePickerInput> { } void _handleMinuteSavedSubmitted(String value) { - final int newMinute = _parseMinute(value); + final newMinute = _parseMinute(value); if (newMinute != null) { _selectedTime = TimeOfDay(hour: _selectedTime.hour, minute: int.parse(value)); @@ -1408,7 +1398,7 @@ class _TimePickerInputState extends State<_TimePickerInput> { } String _validateHour(String value) { - final int newHour = _parseHour(value); + final newHour = _parseHour(value); setState(() { hourHasError = newHour == null; }); @@ -1419,7 +1409,7 @@ class _TimePickerInputState extends State<_TimePickerInput> { } String _validateMinute(String value) { - final int newMinute = _parseMinute(value); + final newMinute = _parseMinute(value); setState(() { minuteHasError = newMinute == null; }); @@ -1432,14 +1422,13 @@ class _TimePickerInputState extends State<_TimePickerInput> { @override Widget build(BuildContext context) { assert(debugCheckHasMediaQuery(context)); - final MediaQueryData media = MediaQuery.of(context); - final TimeOfDayFormat timeOfDayFormat = MaterialLocalizations.of(context) + final media = MediaQuery.of(context); + final timeOfDayFormat = MaterialLocalizations.of(context) .timeOfDayFormat(alwaysUse24HourFormat: media.alwaysUse24HourFormat); - final bool use24HourDials = hourFormat(of: timeOfDayFormat) != HourFormat.h; - final ThemeData theme = Theme.of(context); - final TextStyle hourMinuteStyle = - TimePickerTheme.of(context).hourMinuteTextStyle ?? - theme.textTheme.headline2; + final use24HourDials = hourFormat(of: timeOfDayFormat) != HourFormat.h; + final theme = Theme.of(context); + final hourMinuteStyle = TimePickerTheme.of(context).hourMinuteTextStyle ?? + theme.textTheme.headline2; return Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0), @@ -1588,10 +1577,8 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> { } String get _formattedValue { - final bool alwaysUse24HourFormat = - MediaQuery.of(context).alwaysUse24HourFormat; - final MaterialLocalizations localizations = - MaterialLocalizations.of(context); + final alwaysUse24HourFormat = MediaQuery.of(context).alwaysUse24HourFormat; + final localizations = MaterialLocalizations.of(context); return !widget.isHour ? localizations.formatMinute(widget.selectedTime) : localizations.formatHour( @@ -1602,18 +1589,17 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> { @override Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); - final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context); - final ColorScheme colorScheme = theme.colorScheme; + final theme = Theme.of(context); + final timePickerTheme = TimePickerTheme.of(context); + final colorScheme = theme.colorScheme; - final InputDecorationTheme inputDecorationTheme = - timePickerTheme.inputDecorationTheme; + final inputDecorationTheme = timePickerTheme.inputDecorationTheme; InputDecoration inputDecoration; if (inputDecorationTheme != null) { inputDecoration = const InputDecoration().applyDefaults(inputDecorationTheme); } else { - final Color unfocusedFillColor = timePickerTheme.hourMinuteColor ?? + final unfocusedFillColor = timePickerTheme.hourMinuteColor ?? colorScheme.onSurface.withOpacity(0.12); inputDecoration = InputDecoration( contentPadding: EdgeInsets.zero, @@ -1807,9 +1793,8 @@ class _TimePickerDialogState extends State<_TimePickerDialog> { void _announceInitialTimeOnce() { if (_announcedInitialTime) return; - final MediaQueryData media = MediaQuery.of(context); - final MaterialLocalizations localizations = - MaterialLocalizations.of(context); + final media = MediaQuery.of(context); + final localizations = MaterialLocalizations.of(context); _announceToAccessibility( context, localizations.formatTimeOfDay(widget.initialTime, @@ -1837,7 +1822,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> { void _handleOk() { if (_entryMode == TimePickerEntryMode.input) { - final FormState form = _formKey.currentState; + final form = _formKey.currentState; if (!form.validate()) { setState(() { _autoValidate = true; @@ -1850,12 +1835,12 @@ class _TimePickerDialogState extends State<_TimePickerDialog> { } Size _dialogSize(BuildContext context) { - final Orientation orientation = MediaQuery.of(context).orientation; - final ThemeData theme = Theme.of(context); + final orientation = MediaQuery.of(context).orientation; + final theme = Theme.of(context); // Constrain the textScaleFactor to prevent layout issues. Since only some // parts of the time picker scale up with textScaleFactor, we cap the factor // to 1.1 as that provides enough space to reasonably fit all the content. - final double textScaleFactor = + final textScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 1.1); double timePickerWidth; @@ -1890,14 +1875,13 @@ class _TimePickerDialogState extends State<_TimePickerDialog> { @override Widget build(BuildContext context) { assert(debugCheckHasMediaQuery(context)); - final MediaQueryData media = MediaQuery.of(context); - final TimeOfDayFormat timeOfDayFormat = localizations.timeOfDayFormat( + final media = MediaQuery.of(context); + final timeOfDayFormat = localizations.timeOfDayFormat( alwaysUse24HourFormat: media.alwaysUse24HourFormat); - final bool use24HourDials = hourFormat(of: timeOfDayFormat) != HourFormat.h; - final ThemeData theme = Theme.of(context); - final ShapeBorder shape = - TimePickerTheme.of(context).shape ?? _kDefaultShape; - final Orientation orientation = media.orientation; + final use24HourDials = hourFormat(of: timeOfDayFormat) != HourFormat.h; + final theme = Theme.of(context); + final shape = TimePickerTheme.of(context).shape ?? _kDefaultShape; + final orientation = media.orientation; final Widget actions = Row( children: [ @@ -1924,7 +1908,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> { onPressed: _handleCancel, child: Text( widget.cancelText ?? localizations.cancelButtonLabel, - style: TextStyle(color: Theme.of(context).accentColor), + style: TextStyle(color: Colors.grey[600]), ), ), FlatButton( @@ -2029,7 +2013,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> { break; } - final Size dialogSize = _dialogSize(context); + final dialogSize = _dialogSize(context); return Dialog( shape: shape, elevation: 2, @@ -2166,7 +2150,7 @@ Future showCustomTimePicker({ ? Color.fromRGBO(113, 113, 113, 1) : Color.fromRGBO(15, 15, 15, 1), ), - child: Builder(builder: (BuildContext context) { + child: Builder(builder: (context) { return builder == null ? dialog : builder(context, dialog); }), //routeSettings: routeSettings, diff --git a/lib/util/custom_widget.dart b/lib/util/custom_widget.dart index 31d3599..5f051c2 100644 --- a/lib/util/custom_widget.dart +++ b/lib/util/custom_widget.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'dart:math' as math; import 'package:flutter/material.dart'; +import 'package:tsacdop/type/episodebrief.dart'; import 'episodegrid.dart'; import 'extension_helper.dart'; @@ -587,9 +588,8 @@ class _LineLoaderState extends State } class ImageRotate extends StatefulWidget { - final String title; - final String path; - ImageRotate({this.title, this.path, Key key}) : super(key: key); + final EpisodeBrief episodeItem; + ImageRotate({this.episodeItem, Key key}) : super(key: key); @override _ImageRotateState createState() => _ImageRotateState(); } @@ -599,6 +599,7 @@ class _ImageRotateState extends State Animation _animation; AnimationController _controller; double _value; + @override void initState() { super.initState(); @@ -635,16 +636,14 @@ class _ImageRotateState extends State Widget build(BuildContext context) { return Transform.rotate( angle: 2 * math.pi * _value, - child: Container( - padding: EdgeInsets.all(10.0), - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(15.0)), - child: Container( - height: 30.0, - width: 30.0, - color: Colors.white, - child: Image.file(File("${widget.path}")), - ), + child: Padding( + padding: EdgeInsets.symmetric(vertical: 10.0), + child: SizedBox( + width: 30, + height: 30, + child: CircleAvatar( + backgroundColor: widget.episodeItem.backgroudColor(context), + backgroundImage: widget.episodeItem.avatarImage), ), ), ); diff --git a/lib/util/episodegrid.dart b/lib/util/episodegrid.dart index aa16476..55dc545 100644 --- a/lib/util/episodegrid.dart +++ b/lib/util/episodegrid.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'dart:ui'; import 'package:auto_animated/auto_animated.dart'; @@ -134,8 +133,7 @@ class EpisodeGrid extends StatelessWidget { ? Center() : CircleAvatar( backgroundColor: color.withOpacity(0.5), - backgroundImage: FileImage(File("${episode.imagePath}")), - ), + backgroundImage: episode.avatarImage), ); Widget _downloadIndicater(BuildContext context, @@ -227,9 +225,7 @@ class EpisodeGrid extends StatelessWidget { crossAxisSpacing: 6.0, ), itemBuilder: (context, index, animation) { - var _c = (Theme.of(context).brightness == Brightness.light) - ? episodes[index].primaryColor.colorizedark() - : episodes[index].primaryColor.colorizeLight(); + final c = episodes[index].backgroudColor(context); scrollController.addListener(() {}); return FadeTransition( @@ -463,11 +459,11 @@ class EpisodeGrid extends StatelessWidget { layout != Layout.one ? _circleImage(context, episode: episodes[index], - color: _c, + color: c, boo: boo) : _pubDate(context, episode: episodes[index], - color: _c), + color: c), Spacer(), // _listenIndicater(context, // episode: episodes[index], @@ -477,7 +473,7 @@ class EpisodeGrid extends StatelessWidget { episode: episodes[index], isDownloaded: isDownloaded), _numberIndicater(context, - index: index, color: _c) + index: index, color: c) ], ), ), @@ -491,7 +487,7 @@ class EpisodeGrid extends StatelessWidget { children: [ _circleImage(context, episode: episodes[index], - color: _c, + color: c, boo: boo), SizedBox( width: 5, @@ -513,7 +509,7 @@ class EpisodeGrid extends StatelessWidget { if (layout != Layout.one) _pubDate(context, episode: episodes[index], - color: _c), + color: c), Spacer(), if (layout != Layout.three && episodes[index].duration != 0) @@ -600,9 +596,7 @@ class OpenContainerWrapper extends StatelessWidget { builder: (_, data, __) => OpenContainer( playerRunning: data.item1, playerHeight: kMinPlayerHeight[data.item2.index], - flightWidget: CircleAvatar( - backgroundImage: FileImage(File("${episode.imagePath}")), - ), + flightWidget: CircleAvatar(backgroundImage: episode.avatarImage), transitionDuration: Duration(milliseconds: 400), beginColor: Theme.of(context).primaryColor, endColor: Theme.of(context).primaryColor, diff --git a/lib/util/extension_helper.dart b/lib/util/extension_helper.dart index 92b8564..1cb407b 100644 --- a/lib/util/extension_helper.dart +++ b/lib/util/extension_helper.dart @@ -73,28 +73,26 @@ extension StringExtension on String { } Color colorizedark() { - Color _c; + Color c; var color = json.decode(this); if (color[0] > 200 && color[1] > 200 && color[2] > 200) { - _c = - Color.fromRGBO((255 - color[0]), 255 - color[1], 255 - color[2], 1.0); + c = Color.fromRGBO((255 - color[0]), 255 - color[1], 255 - color[2], 1.0); } else { - _c = Color.fromRGBO(color[0], color[1] > 200 ? 190 : color[1], + c = Color.fromRGBO(color[0], color[1] > 200 ? 190 : color[1], color[2] > 200 ? 190 : color[2], 1); } - return _c; + return c; } Color colorizeLight() { - Color _c; + Color c; var color = json.decode(this); if (color[0] < 50 && color[1] < 50 && color[2] < 50) { - _c = - Color.fromRGBO((255 - color[0]), 255 - color[1], 255 - color[2], 1.0); + c = Color.fromRGBO((255 - color[0]), 255 - color[1], 255 - color[2], 1.0); } else { - _c = Color.fromRGBO(color[0] < 50 ? 100 : color[0], + c = Color.fromRGBO(color[0] < 50 ? 100 : color[0], color[1] < 50 ? 100 : color[1], color[2] < 50 ? 100 : color[2], 1.0); } - return _c; + return c; } }