Effective dart.
This commit is contained in:
parent
913358ede4
commit
54120848bb
|
@ -9,7 +9,7 @@ Release date 2020/7/25
|
|||
* Filter in podcast detail page, you can also hide listened episodes.
|
||||
* Search result ui improved, you can see more info for result.
|
||||
* Update audio service to latest version.
|
||||
* Support fast forward seconds and rewind seconds costomize.
|
||||
* Support fast forward seconds and rewind seconds customize.
|
||||
* Add Franch language support(beta).
|
||||
* Add translators in about page.
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
[![GitHub Release][]][github release - recent]
|
||||
[![Github Downloads][]][github release - recent]
|
||||
[![Localizely][]][localizely - website]
|
||||
[![style: effective dart][]][effective dart pub]
|
||||
|
||||
## About
|
||||
|
||||
|
@ -140,3 +141,5 @@ For help getting started with Flutter, view our
|
|||
[Podcast Screenshot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893877702.png
|
||||
[Episode Screenshot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585896237809.png
|
||||
[Darkmode Screenshot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893920721.png
|
||||
[style: effective dart]: https://img.shields.io/badge/style-effective_dart-40c4ff.svg
|
||||
[effective dart pub]: https://pub.dev/packages/effective_dart
|
||||
|
|
|
@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:tsacdop/home/audioplayer.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
@ -12,12 +11,13 @@ import 'package:tuple/tuple.dart';
|
|||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import '../home/audioplayer.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../state/audio_state.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import 'episode_download.dart';
|
||||
|
||||
class EpisodeDetail extends StatefulWidget {
|
||||
|
@ -50,25 +50,28 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
_description = (await dbHelper.getDescription(url))
|
||||
.replaceAll(RegExp(r'\s?<p>(<br>)?</p>\s?'), '')
|
||||
.replaceAll('\r', '');
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_loaddes = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ScrollController _controller;
|
||||
_scrollListener() {
|
||||
if (_controller.offset > _controller.position.maxScrollExtent * 0.8) {
|
||||
if (!_showMenu)
|
||||
if (!_showMenu) {
|
||||
setState(() {
|
||||
_showMenu = true;
|
||||
});
|
||||
}
|
||||
} else if (_controller.offset <
|
||||
_controller.position.maxScrollExtent * 0.8) {
|
||||
if (_showMenu)
|
||||
if (_showMenu) {
|
||||
setState(() {
|
||||
_showMenu = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (_controller.offset > context.textTheme.headline5.fontSize) {
|
||||
if (!_showTitle) setState(() => _showTitle = true);
|
||||
|
@ -84,11 +87,10 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
}
|
||||
|
||||
_markListened(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
bool marked = await dbHelper.checkMarked(episode);
|
||||
var dbHelper = DBHelper();
|
||||
var marked = await dbHelper.checkMarked(episode);
|
||||
if (!marked) {
|
||||
final PlayHistory history =
|
||||
PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
final history = PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
await dbHelper.saveHistory(history);
|
||||
}
|
||||
}
|
||||
|
@ -164,7 +166,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
),
|
||||
),
|
||||
],
|
||||
onSelected: (int value) async {
|
||||
onSelected: (value) async {
|
||||
switch (value) {
|
||||
case 0:
|
||||
await _markListened(widget.episodeItem);
|
||||
|
@ -255,10 +257,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
((widget.episodeItem.enclosureLength) ~/
|
||||
1000000)
|
||||
.toString() +
|
||||
'MB',
|
||||
'${(widget.episodeItem.enclosureLength) ~/ 1000000}MB',
|
||||
style: textstyle),
|
||||
),
|
||||
],
|
||||
|
@ -397,15 +396,10 @@ class _MenuBarState extends State<MenuBar> {
|
|||
}
|
||||
|
||||
Future<bool> _isLiked(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
var dbHelper = DBHelper();
|
||||
return await dbHelper.isLiked(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
static String _stringForSeconds(double seconds) {
|
||||
if (seconds == null) return null;
|
||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
Widget _buttonOnMenu(Widget widget, VoidCallback onTap) => Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
|
@ -477,7 +471,7 @@ class _MenuBarState extends State<MenuBar> {
|
|||
FutureBuilder<bool>(
|
||||
future: _isLiked(widget.episodeItem),
|
||||
initialData: false,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
builder: (context, snapshot) {
|
||||
return (!snapshot.data)
|
||||
? _buttonOnMenu(
|
||||
Icon(
|
||||
|
@ -589,8 +583,8 @@ class _MenuBarState extends State<MenuBar> {
|
|||
color: context.accentColor,
|
||||
),
|
||||
child: Text(
|
||||
_stringForSeconds(
|
||||
snapshot.data.seconds),
|
||||
snapshot
|
||||
.data.seconds.toTime,
|
||||
style: TextStyle(
|
||||
color: Colors.white),
|
||||
),
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import 'dart:ui';
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:connectivity/connectivity.dart';
|
||||
import 'package:tsacdop/util/custom_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../state/download_state.dart';
|
||||
import '../state/audio_state.dart';
|
||||
import '../state/download_state.dart';
|
||||
import '../state/setting_state.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/general_dialog.dart';
|
||||
|
||||
|
@ -46,7 +45,7 @@ class _DownloadButtonState extends State<DownloadButton> {
|
|||
|
||||
void _requestDownload(EpisodeBrief episode, bool downloadUsingData) async {
|
||||
_permissionReady = await _checkPermmison();
|
||||
bool _dataConfirm = true;
|
||||
var _dataConfirm = true;
|
||||
if (_permissionReady) {
|
||||
if (downloadUsingData && _usingData) {
|
||||
_dataConfirm = await _useDataConfirem();
|
||||
|
@ -78,10 +77,9 @@ class _DownloadButtonState extends State<DownloadButton> {
|
|||
}
|
||||
|
||||
Future<bool> _checkPermmison() async {
|
||||
PermissionStatus permission = await Permission.storage.status;
|
||||
var permission = await Permission.storage.status;
|
||||
if (permission != PermissionStatus.granted) {
|
||||
Map<Permission, PermissionStatus> permissions =
|
||||
await [Permission.storage].request();
|
||||
var permissions = await [Permission.storage].request();
|
||||
if (permissions[Permission.storage] == PermissionStatus.granted) {
|
||||
return true;
|
||||
} else {
|
||||
|
@ -93,7 +91,7 @@ class _DownloadButtonState extends State<DownloadButton> {
|
|||
}
|
||||
|
||||
Future<bool> _useDataConfirem() async {
|
||||
bool ifUseData = false;
|
||||
var ifUseData = false;
|
||||
final s = context.s;
|
||||
await generalDialog(
|
||||
context,
|
||||
|
@ -138,7 +136,7 @@ class _DownloadButtonState extends State<DownloadButton> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<DownloadState>(builder: (_, downloader, __) {
|
||||
EpisodeTask _task = Provider.of<DownloadState>(context, listen: false)
|
||||
var _task = Provider.of<DownloadState>(context, listen: false)
|
||||
.episodeToTask(widget.episode);
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
|
|
|
@ -42,9 +42,8 @@ MessageLookupByLibrary _findExact(String localeName) {
|
|||
/// User programs should call this before using [localeName] for messages.
|
||||
Future<bool> initializeMessages(String localeName) async {
|
||||
var availableLocale = Intl.verifiedLocale(
|
||||
localeName,
|
||||
(locale) => _deferredLibraries[locale] != null,
|
||||
onFailure: (_) => null);
|
||||
localeName, (locale) => _deferredLibraries[locale] != null,
|
||||
onFailure: (_) => null);
|
||||
if (availableLocale == null) {
|
||||
return new Future.value(false);
|
||||
}
|
||||
|
@ -58,14 +57,15 @@ Future<bool> initializeMessages(String localeName) async {
|
|||
bool _messagesExistFor(String locale) {
|
||||
try {
|
||||
return _findExact(locale) != null;
|
||||
// ignore: avoid_catches_without_on_clauses
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
MessageLookupByLibrary _findGeneratedMessagesFor(String locale) {
|
||||
var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor,
|
||||
onFailure: (_) => null);
|
||||
var actualLocale =
|
||||
Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
|
||||
if (actualLocale == null) return null;
|
||||
return _findExact(actualLocale);
|
||||
}
|
||||
|
|
|
@ -19,9 +19,11 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
|||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'zh_Hans';
|
||||
|
||||
static m0(groupName, count) => "{count, plural, zero{} other{{group Name}分组${count}集节目添加到播放列表}}";
|
||||
static m0(groupName, count) =>
|
||||
"{count, plural, zero{} other{{group Name}分组${count}集节目添加到播放列表}}";
|
||||
|
||||
static m1(count) => "${Intl.plural(count, zero: '', other: '${count}集节目添加到播放列表')}";
|
||||
static m1(count) =>
|
||||
"${Intl.plural(count, zero: '', other: '${count}集节目添加到播放列表')}";
|
||||
|
||||
static m2(count) => "${Intl.plural(count, zero: '今天', other: '${count}天前')}";
|
||||
|
||||
|
@ -37,11 +39,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
|
||||
static m8(count) => "${Intl.plural(count, zero: '刚刚', other: '${count}小时前')}";
|
||||
|
||||
static m9(count) => "${Intl.plural(count, zero: '0小时', other: '${count} 小时')}";
|
||||
static m9(count) =>
|
||||
"${Intl.plural(count, zero: '0小时', other: '${count} 小时')}";
|
||||
|
||||
static m10(count) => "${Intl.plural(count, zero: '刚刚', other: '${count}分钟前')}";
|
||||
static m10(count) =>
|
||||
"${Intl.plural(count, zero: '刚刚', other: '${count}分钟前')}";
|
||||
|
||||
static m11(count) => "${Intl.plural(count, zero: '0分钟', other: '${count}分钟')}";
|
||||
static m11(count) =>
|
||||
"${Intl.plural(count, zero: '0分钟', other: '${count}分钟')}";
|
||||
|
||||
static m12(title) => "获取数据 ${title}";
|
||||
|
||||
|
@ -63,7 +68,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
|
||||
static m21(date) => "${date}移除";
|
||||
|
||||
static m22(count) => "${Intl.plural(count, zero: '0 秒', other: '${count} 秒')}";
|
||||
static m22(count) =>
|
||||
"${Intl.plural(count, zero: '0 秒', other: '${count} 秒')}";
|
||||
|
||||
static m23(count) => "${Intl.plural(count, zero: '刚刚', other: '${count}秒前')}";
|
||||
|
||||
|
@ -73,251 +79,301 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
|
||||
static m26(time) => "到${time}";
|
||||
|
||||
static m27(count) => "${Intl.plural(count, zero: '未有更新', other: '更新 ${count} 集节目')}";
|
||||
static m27(count) =>
|
||||
"${Intl.plural(count, zero: '未有更新', other: '更新 ${count} 集节目')}";
|
||||
|
||||
static m28(version) => "版本:${version}";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static _notInlinedMessages(_) => <String, Function> {
|
||||
"add" : MessageLookupByLibrary.simpleMessage("订阅"),
|
||||
"addEpisodeGroup" : m0,
|
||||
"addNewEpisodeAll" : m1,
|
||||
"addNewEpisodeTooltip" : MessageLookupByLibrary.simpleMessage("添加更新节目到播放列表"),
|
||||
"addSomeGroups" : MessageLookupByLibrary.simpleMessage("请添加分组"),
|
||||
"all" : MessageLookupByLibrary.simpleMessage("全部"),
|
||||
"autoDownload" : MessageLookupByLibrary.simpleMessage("自动下载"),
|
||||
"back" : MessageLookupByLibrary.simpleMessage("返回"),
|
||||
"buffering" : MessageLookupByLibrary.simpleMessage("缓冲"),
|
||||
"cancel" : MessageLookupByLibrary.simpleMessage("取消"),
|
||||
"cellularConfirm" : MessageLookupByLibrary.simpleMessage("流量确认"),
|
||||
"cellularConfirmDes" : MessageLookupByLibrary.simpleMessage("您确定使用流量下载吗"),
|
||||
"changeLayout" : MessageLookupByLibrary.simpleMessage("修改布局"),
|
||||
"changelog" : MessageLookupByLibrary.simpleMessage("更新日志"),
|
||||
"chooseA" : MessageLookupByLibrary.simpleMessage("选择"),
|
||||
"clear" : MessageLookupByLibrary.simpleMessage("清除"),
|
||||
"color" : MessageLookupByLibrary.simpleMessage("颜色"),
|
||||
"confirm" : MessageLookupByLibrary.simpleMessage("确认"),
|
||||
"darkMode" : MessageLookupByLibrary.simpleMessage("夜晚模式"),
|
||||
"daysAgo" : m2,
|
||||
"daysCount" : m3,
|
||||
"delete" : MessageLookupByLibrary.simpleMessage("删除"),
|
||||
"developer" : MessageLookupByLibrary.simpleMessage("关于我"),
|
||||
"dismiss" : MessageLookupByLibrary.simpleMessage("忽略"),
|
||||
"done" : MessageLookupByLibrary.simpleMessage("完成"),
|
||||
"download" : MessageLookupByLibrary.simpleMessage("下载"),
|
||||
"downloaded" : MessageLookupByLibrary.simpleMessage("已下载"),
|
||||
"editGroupName" : MessageLookupByLibrary.simpleMessage("修改组名"),
|
||||
"endOfEpisode" : MessageLookupByLibrary.simpleMessage("节目结束"),
|
||||
"episode" : m4,
|
||||
"featureDiscoveryEditGroup" : MessageLookupByLibrary.simpleMessage("点击修改分组"),
|
||||
"featureDiscoveryEditGroupDes" : MessageLookupByLibrary.simpleMessage("您可以修改分组名或者删除分组,注意 Home 分组无法修改,也不能被删除。"),
|
||||
"featureDiscoveryEpisode" : MessageLookupByLibrary.simpleMessage("节目界面"),
|
||||
"featureDiscoveryEpisodeDes" : MessageLookupByLibrary.simpleMessage("您可以长按播放节目或者添加节目到播放列表。"),
|
||||
"featureDiscoveryEpisodeTitle" : MessageLookupByLibrary.simpleMessage("您可以长按快速播放节目"),
|
||||
"featureDiscoveryGroup" : MessageLookupByLibrary.simpleMessage("点击添加分组"),
|
||||
"featureDiscoveryGroupDes" : MessageLookupByLibrary.simpleMessage("新订阅播客默认分组为 Home,您可以添加新的分组,移动播客到新的分组,每个播客可以被添加到多个分组。"),
|
||||
"featureDiscoveryGroupPodcast" : MessageLookupByLibrary.simpleMessage("长按可以移动播客位置"),
|
||||
"featureDiscoveryGroupPodcastDes" : MessageLookupByLibrary.simpleMessage("您可以点击对播客进行设置,或者长按重新排序。"),
|
||||
"featureDiscoveryOMPL" : MessageLookupByLibrary.simpleMessage("点击导入 OMPL"),
|
||||
"featureDiscoveryOMPLDes" : MessageLookupByLibrary.simpleMessage("在这里您可以导入OMPL文件,打开设置页面,或者刷新所有播客。"),
|
||||
"featureDiscoveryPlaylist" : MessageLookupByLibrary.simpleMessage("点击打开播放列表"),
|
||||
"featureDiscoveryPlaylistDes" : MessageLookupByLibrary.simpleMessage("您可以添加节目到播放列表,节目在播放后将会从播放列表自动移除。"),
|
||||
"featureDiscoveryPodcast" : MessageLookupByLibrary.simpleMessage("播客界面"),
|
||||
"featureDiscoveryPodcastDes" : MessageLookupByLibrary.simpleMessage("您可以点击“查看所有”新增或管理分组。"),
|
||||
"featureDiscoveryPodcastTitle" : MessageLookupByLibrary.simpleMessage("您可以通过上下滑动切换分组"),
|
||||
"featureDiscoverySearch" : MessageLookupByLibrary.simpleMessage("点击搜索播客"),
|
||||
"featureDiscoverySearchDes" : MessageLookupByLibrary.simpleMessage("您可以通过搜索播客名称、关键字或者RSS链接订阅播客。"),
|
||||
"feedbackEmail" : MessageLookupByLibrary.simpleMessage("发送邮件"),
|
||||
"feedbackGithub" : MessageLookupByLibrary.simpleMessage("提交Issue"),
|
||||
"feedbackPlay" : MessageLookupByLibrary.simpleMessage("Play评价"),
|
||||
"feedbackTelegram" : MessageLookupByLibrary.simpleMessage("加入小组"),
|
||||
"filter" : MessageLookupByLibrary.simpleMessage("过滤"),
|
||||
"fonts" : MessageLookupByLibrary.simpleMessage("字体"),
|
||||
"from" : m5,
|
||||
"goodNight" : MessageLookupByLibrary.simpleMessage("晚安"),
|
||||
"groupExisted" : MessageLookupByLibrary.simpleMessage("组名已使用"),
|
||||
"groupFilter" : MessageLookupByLibrary.simpleMessage("分组"),
|
||||
"groupRemoveConfirm" : MessageLookupByLibrary.simpleMessage("您确认要移除该分组吗?播客将被移动到 Home 分组。"),
|
||||
"groups" : m6,
|
||||
"homeGroupsSeeAll" : MessageLookupByLibrary.simpleMessage("查看全部"),
|
||||
"homeMenuPlaylist" : MessageLookupByLibrary.simpleMessage("播放列表"),
|
||||
"homeSubMenuSortBy" : MessageLookupByLibrary.simpleMessage("排序"),
|
||||
"homeTabMenuFavotite" : MessageLookupByLibrary.simpleMessage("收藏"),
|
||||
"homeTabMenuRecent" : MessageLookupByLibrary.simpleMessage("最近更新"),
|
||||
"homeToprightMenuAbout" : MessageLookupByLibrary.simpleMessage("关于"),
|
||||
"homeToprightMenuImportOMPL" : MessageLookupByLibrary.simpleMessage("导入OMPL"),
|
||||
"homeToprightMenuRefreshAll" : MessageLookupByLibrary.simpleMessage("全部刷新"),
|
||||
"hostedOn" : m7,
|
||||
"hoursAgo" : m8,
|
||||
"hoursCount" : m9,
|
||||
"import" : MessageLookupByLibrary.simpleMessage("导入"),
|
||||
"introFourthPage" : MessageLookupByLibrary.simpleMessage("您可以长按节目打开快捷菜单。"),
|
||||
"introSecondPage" : MessageLookupByLibrary.simpleMessage("您可以通过搜索订阅播客,也可以直接导入OMPL文件。"),
|
||||
"introThirdPage" : MessageLookupByLibrary.simpleMessage("您可以创建分组,上下滑动切换分组。"),
|
||||
"later" : MessageLookupByLibrary.simpleMessage("稍后"),
|
||||
"lightMode" : MessageLookupByLibrary.simpleMessage("明亮模式"),
|
||||
"like" : MessageLookupByLibrary.simpleMessage("喜欢"),
|
||||
"likeDate" : MessageLookupByLibrary.simpleMessage("收藏日期"),
|
||||
"liked" : MessageLookupByLibrary.simpleMessage("已收藏"),
|
||||
"listen" : MessageLookupByLibrary.simpleMessage("收听"),
|
||||
"listened" : MessageLookupByLibrary.simpleMessage("已收听"),
|
||||
"loadMore" : MessageLookupByLibrary.simpleMessage("加载更多"),
|
||||
"mark" : MessageLookupByLibrary.simpleMessage("标记"),
|
||||
"markConfirm" : MessageLookupByLibrary.simpleMessage("确认标记"),
|
||||
"markConfirmContent" : MessageLookupByLibrary.simpleMessage("是否确认标记全部节目为已收听?"),
|
||||
"markListened" : MessageLookupByLibrary.simpleMessage("标记已收听"),
|
||||
"menu" : MessageLookupByLibrary.simpleMessage("菜单"),
|
||||
"menuAllPodcasts" : MessageLookupByLibrary.simpleMessage("所有订阅"),
|
||||
"menuMarkAllListened" : MessageLookupByLibrary.simpleMessage("标记所有已收听"),
|
||||
"menuViewRSS" : MessageLookupByLibrary.simpleMessage("查看 RSS"),
|
||||
"menuVisitSite" : MessageLookupByLibrary.simpleMessage("访问网站"),
|
||||
"minsAgo" : m10,
|
||||
"minsCount" : m11,
|
||||
"network" : MessageLookupByLibrary.simpleMessage("网络"),
|
||||
"newGroup" : MessageLookupByLibrary.simpleMessage("创建分组"),
|
||||
"newestFirst" : MessageLookupByLibrary.simpleMessage("由新到旧"),
|
||||
"next" : MessageLookupByLibrary.simpleMessage("下一步"),
|
||||
"noEpisodeDownload" : MessageLookupByLibrary.simpleMessage("暂无下载节目"),
|
||||
"noEpisodeFavorite" : MessageLookupByLibrary.simpleMessage("暂无收藏节目"),
|
||||
"noEpisodeRecent" : MessageLookupByLibrary.simpleMessage("暂无节目"),
|
||||
"noPodcastGroup" : MessageLookupByLibrary.simpleMessage("分组无播客"),
|
||||
"noShownote" : MessageLookupByLibrary.simpleMessage("节目简介暂未收到。"),
|
||||
"notificaitonFatch" : m12,
|
||||
"notificationNetworkError" : m13,
|
||||
"notificationSubscribe" : m14,
|
||||
"notificationSubscribeExisted" : m15,
|
||||
"notificationSuccess" : m16,
|
||||
"notificationUpdate" : m17,
|
||||
"notificationUpdateError" : m18,
|
||||
"oldestFirst" : MessageLookupByLibrary.simpleMessage("由旧到新"),
|
||||
"play" : MessageLookupByLibrary.simpleMessage("播放"),
|
||||
"playback" : MessageLookupByLibrary.simpleMessage("播放控制"),
|
||||
"playing" : MessageLookupByLibrary.simpleMessage("正在播放"),
|
||||
"plugins" : MessageLookupByLibrary.simpleMessage("插件"),
|
||||
"podcast" : m19,
|
||||
"podcastSubscribed" : MessageLookupByLibrary.simpleMessage("播客已订阅"),
|
||||
"popupMenuDownloadDes" : MessageLookupByLibrary.simpleMessage("下载节目"),
|
||||
"popupMenuLaterDes" : MessageLookupByLibrary.simpleMessage("添加到播放列表"),
|
||||
"popupMenuLikeDes" : MessageLookupByLibrary.simpleMessage("添加到收藏"),
|
||||
"popupMenuMarkDes" : MessageLookupByLibrary.simpleMessage("设置为已收听"),
|
||||
"popupMenuPlayDes" : MessageLookupByLibrary.simpleMessage("播放节目"),
|
||||
"privacyPolicy" : MessageLookupByLibrary.simpleMessage("隐私条款"),
|
||||
"published" : m20,
|
||||
"publishedDaily" : MessageLookupByLibrary.simpleMessage("每日更新"),
|
||||
"publishedMonthly" : MessageLookupByLibrary.simpleMessage("每月更新"),
|
||||
"publishedWeekly" : MessageLookupByLibrary.simpleMessage("每周更新"),
|
||||
"publishedYearly" : MessageLookupByLibrary.simpleMessage("每年更新"),
|
||||
"recoverSubscribe" : MessageLookupByLibrary.simpleMessage("恢复订阅"),
|
||||
"refreshArtwork" : MessageLookupByLibrary.simpleMessage("更新头像"),
|
||||
"remove" : MessageLookupByLibrary.simpleMessage("移除"),
|
||||
"removeConfirm" : MessageLookupByLibrary.simpleMessage("取消订阅"),
|
||||
"removePodcastDes" : MessageLookupByLibrary.simpleMessage("您确认要取消订阅吗?"),
|
||||
"removedAt" : m21,
|
||||
"save" : MessageLookupByLibrary.simpleMessage("保存"),
|
||||
"schedule" : MessageLookupByLibrary.simpleMessage("定时"),
|
||||
"search" : MessageLookupByLibrary.simpleMessage("搜索"),
|
||||
"searchEpisode" : MessageLookupByLibrary.simpleMessage("搜索节目"),
|
||||
"searchInvalidRss" : MessageLookupByLibrary.simpleMessage("RSS 链接错误"),
|
||||
"searchPodcast" : MessageLookupByLibrary.simpleMessage("搜索播客"),
|
||||
"secCount" : m22,
|
||||
"secondsAgo" : m23,
|
||||
"settingStorage" : MessageLookupByLibrary.simpleMessage("储存空间"),
|
||||
"settings" : MessageLookupByLibrary.simpleMessage("设置"),
|
||||
"settingsAccentColor" : MessageLookupByLibrary.simpleMessage("次要颜色"),
|
||||
"settingsAccentColorDes" : MessageLookupByLibrary.simpleMessage("包括溢出颜色"),
|
||||
"settingsAppIntro" : MessageLookupByLibrary.simpleMessage("引导页"),
|
||||
"settingsAppearance" : MessageLookupByLibrary.simpleMessage("界面"),
|
||||
"settingsAppearanceDes" : MessageLookupByLibrary.simpleMessage("颜色与主题"),
|
||||
"settingsAudioCache" : MessageLookupByLibrary.simpleMessage("播放缓存"),
|
||||
"settingsAudioCacheDes" : MessageLookupByLibrary.simpleMessage("播放缓存设置"),
|
||||
"settingsAutoDelete" : MessageLookupByLibrary.simpleMessage("自动删除下载节目"),
|
||||
"settingsAutoDeleteDes" : MessageLookupByLibrary.simpleMessage("默认 30 天"),
|
||||
"settingsAutoPlayDes" : MessageLookupByLibrary.simpleMessage("自动播放下一节目"),
|
||||
"settingsBackup" : MessageLookupByLibrary.simpleMessage("备份"),
|
||||
"settingsBackupDes" : MessageLookupByLibrary.simpleMessage("备份应用数据"),
|
||||
"settingsDefaultGrid" : MessageLookupByLibrary.simpleMessage("默认布局"),
|
||||
"settingsDefaultGridDownload" : MessageLookupByLibrary.simpleMessage("下载页"),
|
||||
"settingsDefaultGridFavorite" : MessageLookupByLibrary.simpleMessage("收藏页"),
|
||||
"settingsDefaultGridPodcast" : MessageLookupByLibrary.simpleMessage("播客页"),
|
||||
"settingsDefaultGridRecent" : MessageLookupByLibrary.simpleMessage("最近页"),
|
||||
"settingsDiscovery" : MessageLookupByLibrary.simpleMessage("再次功能介绍"),
|
||||
"settingsEnableSyncing" : MessageLookupByLibrary.simpleMessage("开启自动更新"),
|
||||
"settingsEnableSyncingDes" : MessageLookupByLibrary.simpleMessage("在后台更新所有订阅播客"),
|
||||
"settingsExportDes" : MessageLookupByLibrary.simpleMessage("导出及恢复所有设置项"),
|
||||
"settingsFastForwardSec" : MessageLookupByLibrary.simpleMessage("快进时间"),
|
||||
"settingsFastForwardSecDes" : MessageLookupByLibrary.simpleMessage("修改播放器快进时间"),
|
||||
"settingsFeedback" : MessageLookupByLibrary.simpleMessage("反馈"),
|
||||
"settingsFeedbackDes" : MessageLookupByLibrary.simpleMessage("意见与建议"),
|
||||
"settingsHistory" : MessageLookupByLibrary.simpleMessage("历史记录"),
|
||||
"settingsHistoryDes" : MessageLookupByLibrary.simpleMessage("收听记录"),
|
||||
"settingsInfo" : MessageLookupByLibrary.simpleMessage("信息"),
|
||||
"settingsInterface" : MessageLookupByLibrary.simpleMessage("界面"),
|
||||
"settingsLanguages" : MessageLookupByLibrary.simpleMessage("语言"),
|
||||
"settingsLanguagesDes" : MessageLookupByLibrary.simpleMessage("设置语言"),
|
||||
"settingsLayout" : MessageLookupByLibrary.simpleMessage("布局"),
|
||||
"settingsLayoutDes" : MessageLookupByLibrary.simpleMessage("应用布局"),
|
||||
"settingsLibraries" : MessageLookupByLibrary.simpleMessage("开源"),
|
||||
"settingsLibrariesDes" : MessageLookupByLibrary.simpleMessage("开源项目使用"),
|
||||
"settingsManageDownload" : MessageLookupByLibrary.simpleMessage("下载管理"),
|
||||
"settingsManageDownloadDes" : MessageLookupByLibrary.simpleMessage("管理下载节目文件"),
|
||||
"settingsMenuAutoPlay" : MessageLookupByLibrary.simpleMessage("自动播放下一节目"),
|
||||
"settingsNetworkCellular" : MessageLookupByLibrary.simpleMessage("蜂窝数据确认"),
|
||||
"settingsNetworkCellularAuto" : MessageLookupByLibrary.simpleMessage("是否用蜂窝数据自动下载"),
|
||||
"settingsNetworkCellularAutoDes" : MessageLookupByLibrary.simpleMessage("你可以在分组管理页面设置自动下载"),
|
||||
"settingsNetworkCellularDes" : MessageLookupByLibrary.simpleMessage("在使用蜂窝数据下载前确认"),
|
||||
"settingsPlayDes" : MessageLookupByLibrary.simpleMessage("播放列表和播放器"),
|
||||
"settingsPopupMenu" : MessageLookupByLibrary.simpleMessage("节目弹出菜单"),
|
||||
"settingsPopupMenuDes" : MessageLookupByLibrary.simpleMessage("修改节目弹出菜单"),
|
||||
"settingsPrefrence" : MessageLookupByLibrary.simpleMessage("首选项"),
|
||||
"settingsRealDark" : MessageLookupByLibrary.simpleMessage("极黑"),
|
||||
"settingsRealDarkDes" : MessageLookupByLibrary.simpleMessage("如果夜不够黑,请开启"),
|
||||
"settingsRewindSec" : MessageLookupByLibrary.simpleMessage("快退时间"),
|
||||
"settingsRewindSecDes" : MessageLookupByLibrary.simpleMessage("修改播放器快退时间"),
|
||||
"settingsSTAuto" : MessageLookupByLibrary.simpleMessage("自动睡眠模式"),
|
||||
"settingsSTAutoDes" : MessageLookupByLibrary.simpleMessage("定期开启睡眠模式"),
|
||||
"settingsSTDefaultTime" : MessageLookupByLibrary.simpleMessage("默认时长"),
|
||||
"settingsSTDefautTimeDes" : MessageLookupByLibrary.simpleMessage("睡眠模式默认时长"),
|
||||
"settingsSTMode" : MessageLookupByLibrary.simpleMessage("自动睡眠模式默认时长"),
|
||||
"settingsStorageDes" : MessageLookupByLibrary.simpleMessage("管理缓存和下载空间"),
|
||||
"settingsSyncing" : MessageLookupByLibrary.simpleMessage("同步"),
|
||||
"settingsSyncingDes" : MessageLookupByLibrary.simpleMessage("在后台更新播客"),
|
||||
"settingsTapToOpenPopupMenu" : MessageLookupByLibrary.simpleMessage("轻点打开弹出菜单"),
|
||||
"settingsTapToOpenPopupMenuDes" : MessageLookupByLibrary.simpleMessage("开启后您需长按打开节目页"),
|
||||
"settingsTheme" : MessageLookupByLibrary.simpleMessage("主题"),
|
||||
"settingsUpdateInterval" : MessageLookupByLibrary.simpleMessage("更新频率"),
|
||||
"settingsUpdateIntervalDes" : MessageLookupByLibrary.simpleMessage("默认 24 小时"),
|
||||
"share" : MessageLookupByLibrary.simpleMessage("分享"),
|
||||
"size" : MessageLookupByLibrary.simpleMessage("大小"),
|
||||
"skipSecondsAtStart" : MessageLookupByLibrary.simpleMessage("开头跳过秒数"),
|
||||
"sleepTimer" : MessageLookupByLibrary.simpleMessage("睡眠模式"),
|
||||
"subscribe" : MessageLookupByLibrary.simpleMessage("订阅"),
|
||||
"subscribeExportDes" : MessageLookupByLibrary.simpleMessage("导出 OMPL 文件"),
|
||||
"systemDefault" : MessageLookupByLibrary.simpleMessage("系统默认"),
|
||||
"timeLastPlayed" : m24,
|
||||
"timeLeft" : m25,
|
||||
"to" : m26,
|
||||
"toastAddPlaylist" : MessageLookupByLibrary.simpleMessage("添加到播放列表"),
|
||||
"toastDiscovery" : MessageLookupByLibrary.simpleMessage("重启应用后可查看"),
|
||||
"toastFileError" : MessageLookupByLibrary.simpleMessage("文件错误,导入失败"),
|
||||
"toastFileNotValid" : MessageLookupByLibrary.simpleMessage("文件错误"),
|
||||
"toastHomeGroupNotSupport" : MessageLookupByLibrary.simpleMessage("Home 分组不支持此功能"),
|
||||
"toastImportSettingsSuccess" : MessageLookupByLibrary.simpleMessage("导入设置成功"),
|
||||
"toastOneGroup" : MessageLookupByLibrary.simpleMessage("请至少选择一个分组"),
|
||||
"toastPodcastRecovering" : MessageLookupByLibrary.simpleMessage("恢复中,请稍后"),
|
||||
"toastReadFile" : MessageLookupByLibrary.simpleMessage("读取文件成功"),
|
||||
"toastRecoverFailed" : MessageLookupByLibrary.simpleMessage("恢复订阅失败"),
|
||||
"toastRemovePlaylist" : MessageLookupByLibrary.simpleMessage("从播放列表移除"),
|
||||
"toastSettingSaved" : MessageLookupByLibrary.simpleMessage("设置已保存"),
|
||||
"toastTimeEqualEnd" : MessageLookupByLibrary.simpleMessage("与结束时刻相同"),
|
||||
"toastTimeEqualStart" : MessageLookupByLibrary.simpleMessage("与起始时刻相同"),
|
||||
"translators" : MessageLookupByLibrary.simpleMessage("翻译者"),
|
||||
"understood" : MessageLookupByLibrary.simpleMessage("了解"),
|
||||
"undo" : MessageLookupByLibrary.simpleMessage("撤销"),
|
||||
"unlike" : MessageLookupByLibrary.simpleMessage("取消喜欢"),
|
||||
"unliked" : MessageLookupByLibrary.simpleMessage("从收藏移除"),
|
||||
"updateDate" : MessageLookupByLibrary.simpleMessage("更新日期"),
|
||||
"updateEpisodesCount" : m27,
|
||||
"updateFailed" : MessageLookupByLibrary.simpleMessage("更新失败"),
|
||||
"version" : m28
|
||||
};
|
||||
static _notInlinedMessages(_) => <String, Function>{
|
||||
"add": MessageLookupByLibrary.simpleMessage("订阅"),
|
||||
"addEpisodeGroup": m0,
|
||||
"addNewEpisodeAll": m1,
|
||||
"addNewEpisodeTooltip":
|
||||
MessageLookupByLibrary.simpleMessage("添加更新节目到播放列表"),
|
||||
"addSomeGroups": MessageLookupByLibrary.simpleMessage("请添加分组"),
|
||||
"all": MessageLookupByLibrary.simpleMessage("全部"),
|
||||
"autoDownload": MessageLookupByLibrary.simpleMessage("自动下载"),
|
||||
"back": MessageLookupByLibrary.simpleMessage("返回"),
|
||||
"buffering": MessageLookupByLibrary.simpleMessage("缓冲"),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("取消"),
|
||||
"cellularConfirm": MessageLookupByLibrary.simpleMessage("流量确认"),
|
||||
"cellularConfirmDes":
|
||||
MessageLookupByLibrary.simpleMessage("您确定使用流量下载吗"),
|
||||
"changeLayout": MessageLookupByLibrary.simpleMessage("修改布局"),
|
||||
"changelog": MessageLookupByLibrary.simpleMessage("更新日志"),
|
||||
"chooseA": MessageLookupByLibrary.simpleMessage("选择"),
|
||||
"clear": MessageLookupByLibrary.simpleMessage("清除"),
|
||||
"color": MessageLookupByLibrary.simpleMessage("颜色"),
|
||||
"confirm": MessageLookupByLibrary.simpleMessage("确认"),
|
||||
"darkMode": MessageLookupByLibrary.simpleMessage("夜晚模式"),
|
||||
"daysAgo": m2,
|
||||
"daysCount": m3,
|
||||
"delete": MessageLookupByLibrary.simpleMessage("删除"),
|
||||
"developer": MessageLookupByLibrary.simpleMessage("关于我"),
|
||||
"dismiss": MessageLookupByLibrary.simpleMessage("忽略"),
|
||||
"done": MessageLookupByLibrary.simpleMessage("完成"),
|
||||
"download": MessageLookupByLibrary.simpleMessage("下载"),
|
||||
"downloaded": MessageLookupByLibrary.simpleMessage("已下载"),
|
||||
"editGroupName": MessageLookupByLibrary.simpleMessage("修改组名"),
|
||||
"endOfEpisode": MessageLookupByLibrary.simpleMessage("节目结束"),
|
||||
"episode": m4,
|
||||
"featureDiscoveryEditGroup":
|
||||
MessageLookupByLibrary.simpleMessage("点击修改分组"),
|
||||
"featureDiscoveryEditGroupDes": MessageLookupByLibrary.simpleMessage(
|
||||
"您可以修改分组名或者删除分组,注意 Home 分组无法修改,也不能被删除。"),
|
||||
"featureDiscoveryEpisode": MessageLookupByLibrary.simpleMessage("节目界面"),
|
||||
"featureDiscoveryEpisodeDes":
|
||||
MessageLookupByLibrary.simpleMessage("您可以长按播放节目或者添加节目到播放列表。"),
|
||||
"featureDiscoveryEpisodeTitle":
|
||||
MessageLookupByLibrary.simpleMessage("您可以长按快速播放节目"),
|
||||
"featureDiscoveryGroup": MessageLookupByLibrary.simpleMessage("点击添加分组"),
|
||||
"featureDiscoveryGroupDes": MessageLookupByLibrary.simpleMessage(
|
||||
"新订阅播客默认分组为 Home,您可以添加新的分组,移动播客到新的分组,每个播客可以被添加到多个分组。"),
|
||||
"featureDiscoveryGroupPodcast":
|
||||
MessageLookupByLibrary.simpleMessage("长按可以移动播客位置"),
|
||||
"featureDiscoveryGroupPodcastDes":
|
||||
MessageLookupByLibrary.simpleMessage("您可以点击对播客进行设置,或者长按重新排序。"),
|
||||
"featureDiscoveryOMPL":
|
||||
MessageLookupByLibrary.simpleMessage("点击导入 OMPL"),
|
||||
"featureDiscoveryOMPLDes": MessageLookupByLibrary.simpleMessage(
|
||||
"在这里您可以导入OMPL文件,打开设置页面,或者刷新所有播客。"),
|
||||
"featureDiscoveryPlaylist":
|
||||
MessageLookupByLibrary.simpleMessage("点击打开播放列表"),
|
||||
"featureDiscoveryPlaylistDes": MessageLookupByLibrary.simpleMessage(
|
||||
"您可以添加节目到播放列表,节目在播放后将会从播放列表自动移除。"),
|
||||
"featureDiscoveryPodcast": MessageLookupByLibrary.simpleMessage("播客界面"),
|
||||
"featureDiscoveryPodcastDes":
|
||||
MessageLookupByLibrary.simpleMessage("您可以点击“查看所有”新增或管理分组。"),
|
||||
"featureDiscoveryPodcastTitle":
|
||||
MessageLookupByLibrary.simpleMessage("您可以通过上下滑动切换分组"),
|
||||
"featureDiscoverySearch":
|
||||
MessageLookupByLibrary.simpleMessage("点击搜索播客"),
|
||||
"featureDiscoverySearchDes":
|
||||
MessageLookupByLibrary.simpleMessage("您可以通过搜索播客名称、关键字或者RSS链接订阅播客。"),
|
||||
"feedbackEmail": MessageLookupByLibrary.simpleMessage("发送邮件"),
|
||||
"feedbackGithub": MessageLookupByLibrary.simpleMessage("提交Issue"),
|
||||
"feedbackPlay": MessageLookupByLibrary.simpleMessage("Play评价"),
|
||||
"feedbackTelegram": MessageLookupByLibrary.simpleMessage("加入小组"),
|
||||
"filter": MessageLookupByLibrary.simpleMessage("过滤"),
|
||||
"fonts": MessageLookupByLibrary.simpleMessage("字体"),
|
||||
"from": m5,
|
||||
"goodNight": MessageLookupByLibrary.simpleMessage("晚安"),
|
||||
"groupExisted": MessageLookupByLibrary.simpleMessage("组名已使用"),
|
||||
"groupFilter": MessageLookupByLibrary.simpleMessage("分组"),
|
||||
"groupRemoveConfirm":
|
||||
MessageLookupByLibrary.simpleMessage("您确认要移除该分组吗?播客将被移动到 Home 分组。"),
|
||||
"groups": m6,
|
||||
"homeGroupsSeeAll": MessageLookupByLibrary.simpleMessage("查看全部"),
|
||||
"homeMenuPlaylist": MessageLookupByLibrary.simpleMessage("播放列表"),
|
||||
"homeSubMenuSortBy": MessageLookupByLibrary.simpleMessage("排序"),
|
||||
"homeTabMenuFavotite": MessageLookupByLibrary.simpleMessage("收藏"),
|
||||
"homeTabMenuRecent": MessageLookupByLibrary.simpleMessage("最近更新"),
|
||||
"homeToprightMenuAbout": MessageLookupByLibrary.simpleMessage("关于"),
|
||||
"homeToprightMenuImportOMPL":
|
||||
MessageLookupByLibrary.simpleMessage("导入OMPL"),
|
||||
"homeToprightMenuRefreshAll":
|
||||
MessageLookupByLibrary.simpleMessage("全部刷新"),
|
||||
"hostedOn": m7,
|
||||
"hoursAgo": m8,
|
||||
"hoursCount": m9,
|
||||
"import": MessageLookupByLibrary.simpleMessage("导入"),
|
||||
"introFourthPage":
|
||||
MessageLookupByLibrary.simpleMessage("您可以长按节目打开快捷菜单。"),
|
||||
"introSecondPage":
|
||||
MessageLookupByLibrary.simpleMessage("您可以通过搜索订阅播客,也可以直接导入OMPL文件。"),
|
||||
"introThirdPage":
|
||||
MessageLookupByLibrary.simpleMessage("您可以创建分组,上下滑动切换分组。"),
|
||||
"later": MessageLookupByLibrary.simpleMessage("稍后"),
|
||||
"lightMode": MessageLookupByLibrary.simpleMessage("明亮模式"),
|
||||
"like": MessageLookupByLibrary.simpleMessage("喜欢"),
|
||||
"likeDate": MessageLookupByLibrary.simpleMessage("收藏日期"),
|
||||
"liked": MessageLookupByLibrary.simpleMessage("已收藏"),
|
||||
"listen": MessageLookupByLibrary.simpleMessage("收听"),
|
||||
"listened": MessageLookupByLibrary.simpleMessage("已收听"),
|
||||
"loadMore": MessageLookupByLibrary.simpleMessage("加载更多"),
|
||||
"mark": MessageLookupByLibrary.simpleMessage("标记"),
|
||||
"markConfirm": MessageLookupByLibrary.simpleMessage("确认标记"),
|
||||
"markConfirmContent":
|
||||
MessageLookupByLibrary.simpleMessage("是否确认标记全部节目为已收听?"),
|
||||
"markListened": MessageLookupByLibrary.simpleMessage("标记已收听"),
|
||||
"menu": MessageLookupByLibrary.simpleMessage("菜单"),
|
||||
"menuAllPodcasts": MessageLookupByLibrary.simpleMessage("所有订阅"),
|
||||
"menuMarkAllListened": MessageLookupByLibrary.simpleMessage("标记所有已收听"),
|
||||
"menuViewRSS": MessageLookupByLibrary.simpleMessage("查看 RSS"),
|
||||
"menuVisitSite": MessageLookupByLibrary.simpleMessage("访问网站"),
|
||||
"minsAgo": m10,
|
||||
"minsCount": m11,
|
||||
"network": MessageLookupByLibrary.simpleMessage("网络"),
|
||||
"newGroup": MessageLookupByLibrary.simpleMessage("创建分组"),
|
||||
"newestFirst": MessageLookupByLibrary.simpleMessage("由新到旧"),
|
||||
"next": MessageLookupByLibrary.simpleMessage("下一步"),
|
||||
"noEpisodeDownload": MessageLookupByLibrary.simpleMessage("暂无下载节目"),
|
||||
"noEpisodeFavorite": MessageLookupByLibrary.simpleMessage("暂无收藏节目"),
|
||||
"noEpisodeRecent": MessageLookupByLibrary.simpleMessage("暂无节目"),
|
||||
"noPodcastGroup": MessageLookupByLibrary.simpleMessage("分组无播客"),
|
||||
"noShownote": MessageLookupByLibrary.simpleMessage("节目简介暂未收到。"),
|
||||
"notificaitonFatch": m12,
|
||||
"notificationNetworkError": m13,
|
||||
"notificationSubscribe": m14,
|
||||
"notificationSubscribeExisted": m15,
|
||||
"notificationSuccess": m16,
|
||||
"notificationUpdate": m17,
|
||||
"notificationUpdateError": m18,
|
||||
"oldestFirst": MessageLookupByLibrary.simpleMessage("由旧到新"),
|
||||
"play": MessageLookupByLibrary.simpleMessage("播放"),
|
||||
"playback": MessageLookupByLibrary.simpleMessage("播放控制"),
|
||||
"playing": MessageLookupByLibrary.simpleMessage("正在播放"),
|
||||
"plugins": MessageLookupByLibrary.simpleMessage("插件"),
|
||||
"podcast": m19,
|
||||
"podcastSubscribed": MessageLookupByLibrary.simpleMessage("播客已订阅"),
|
||||
"popupMenuDownloadDes": MessageLookupByLibrary.simpleMessage("下载节目"),
|
||||
"popupMenuLaterDes": MessageLookupByLibrary.simpleMessage("添加到播放列表"),
|
||||
"popupMenuLikeDes": MessageLookupByLibrary.simpleMessage("添加到收藏"),
|
||||
"popupMenuMarkDes": MessageLookupByLibrary.simpleMessage("设置为已收听"),
|
||||
"popupMenuPlayDes": MessageLookupByLibrary.simpleMessage("播放节目"),
|
||||
"privacyPolicy": MessageLookupByLibrary.simpleMessage("隐私条款"),
|
||||
"published": m20,
|
||||
"publishedDaily": MessageLookupByLibrary.simpleMessage("每日更新"),
|
||||
"publishedMonthly": MessageLookupByLibrary.simpleMessage("每月更新"),
|
||||
"publishedWeekly": MessageLookupByLibrary.simpleMessage("每周更新"),
|
||||
"publishedYearly": MessageLookupByLibrary.simpleMessage("每年更新"),
|
||||
"recoverSubscribe": MessageLookupByLibrary.simpleMessage("恢复订阅"),
|
||||
"refreshArtwork": MessageLookupByLibrary.simpleMessage("更新头像"),
|
||||
"remove": MessageLookupByLibrary.simpleMessage("移除"),
|
||||
"removeConfirm": MessageLookupByLibrary.simpleMessage("取消订阅"),
|
||||
"removePodcastDes": MessageLookupByLibrary.simpleMessage("您确认要取消订阅吗?"),
|
||||
"removedAt": m21,
|
||||
"save": MessageLookupByLibrary.simpleMessage("保存"),
|
||||
"schedule": MessageLookupByLibrary.simpleMessage("定时"),
|
||||
"search": MessageLookupByLibrary.simpleMessage("搜索"),
|
||||
"searchEpisode": MessageLookupByLibrary.simpleMessage("搜索节目"),
|
||||
"searchInvalidRss": MessageLookupByLibrary.simpleMessage("RSS 链接错误"),
|
||||
"searchPodcast": MessageLookupByLibrary.simpleMessage("搜索播客"),
|
||||
"secCount": m22,
|
||||
"secondsAgo": m23,
|
||||
"settingStorage": MessageLookupByLibrary.simpleMessage("储存空间"),
|
||||
"settings": MessageLookupByLibrary.simpleMessage("设置"),
|
||||
"settingsAccentColor": MessageLookupByLibrary.simpleMessage("次要颜色"),
|
||||
"settingsAccentColorDes":
|
||||
MessageLookupByLibrary.simpleMessage("包括溢出颜色"),
|
||||
"settingsAppIntro": MessageLookupByLibrary.simpleMessage("引导页"),
|
||||
"settingsAppearance": MessageLookupByLibrary.simpleMessage("界面"),
|
||||
"settingsAppearanceDes": MessageLookupByLibrary.simpleMessage("颜色与主题"),
|
||||
"settingsAudioCache": MessageLookupByLibrary.simpleMessage("播放缓存"),
|
||||
"settingsAudioCacheDes": MessageLookupByLibrary.simpleMessage("播放缓存设置"),
|
||||
"settingsAutoDelete": MessageLookupByLibrary.simpleMessage("自动删除下载节目"),
|
||||
"settingsAutoDeleteDes":
|
||||
MessageLookupByLibrary.simpleMessage("默认 30 天"),
|
||||
"settingsAutoPlayDes": MessageLookupByLibrary.simpleMessage("自动播放下一节目"),
|
||||
"settingsBackup": MessageLookupByLibrary.simpleMessage("备份"),
|
||||
"settingsBackupDes": MessageLookupByLibrary.simpleMessage("备份应用数据"),
|
||||
"settingsDefaultGrid": MessageLookupByLibrary.simpleMessage("默认布局"),
|
||||
"settingsDefaultGridDownload":
|
||||
MessageLookupByLibrary.simpleMessage("下载页"),
|
||||
"settingsDefaultGridFavorite":
|
||||
MessageLookupByLibrary.simpleMessage("收藏页"),
|
||||
"settingsDefaultGridPodcast":
|
||||
MessageLookupByLibrary.simpleMessage("播客页"),
|
||||
"settingsDefaultGridRecent":
|
||||
MessageLookupByLibrary.simpleMessage("最近页"),
|
||||
"settingsDiscovery": MessageLookupByLibrary.simpleMessage("再次功能介绍"),
|
||||
"settingsEnableSyncing": MessageLookupByLibrary.simpleMessage("开启自动更新"),
|
||||
"settingsEnableSyncingDes":
|
||||
MessageLookupByLibrary.simpleMessage("在后台更新所有订阅播客"),
|
||||
"settingsExportDes": MessageLookupByLibrary.simpleMessage("导出及恢复所有设置项"),
|
||||
"settingsFastForwardSec": MessageLookupByLibrary.simpleMessage("快进时间"),
|
||||
"settingsFastForwardSecDes":
|
||||
MessageLookupByLibrary.simpleMessage("修改播放器快进时间"),
|
||||
"settingsFeedback": MessageLookupByLibrary.simpleMessage("反馈"),
|
||||
"settingsFeedbackDes": MessageLookupByLibrary.simpleMessage("意见与建议"),
|
||||
"settingsHistory": MessageLookupByLibrary.simpleMessage("历史记录"),
|
||||
"settingsHistoryDes": MessageLookupByLibrary.simpleMessage("收听记录"),
|
||||
"settingsInfo": MessageLookupByLibrary.simpleMessage("信息"),
|
||||
"settingsInterface": MessageLookupByLibrary.simpleMessage("界面"),
|
||||
"settingsLanguages": MessageLookupByLibrary.simpleMessage("语言"),
|
||||
"settingsLanguagesDes": MessageLookupByLibrary.simpleMessage("设置语言"),
|
||||
"settingsLayout": MessageLookupByLibrary.simpleMessage("布局"),
|
||||
"settingsLayoutDes": MessageLookupByLibrary.simpleMessage("应用布局"),
|
||||
"settingsLibraries": MessageLookupByLibrary.simpleMessage("开源"),
|
||||
"settingsLibrariesDes": MessageLookupByLibrary.simpleMessage("开源项目使用"),
|
||||
"settingsManageDownload": MessageLookupByLibrary.simpleMessage("下载管理"),
|
||||
"settingsManageDownloadDes":
|
||||
MessageLookupByLibrary.simpleMessage("管理下载节目文件"),
|
||||
"settingsMenuAutoPlay":
|
||||
MessageLookupByLibrary.simpleMessage("自动播放下一节目"),
|
||||
"settingsNetworkCellular":
|
||||
MessageLookupByLibrary.simpleMessage("蜂窝数据确认"),
|
||||
"settingsNetworkCellularAuto":
|
||||
MessageLookupByLibrary.simpleMessage("是否用蜂窝数据自动下载"),
|
||||
"settingsNetworkCellularAutoDes":
|
||||
MessageLookupByLibrary.simpleMessage("你可以在分组管理页面设置自动下载"),
|
||||
"settingsNetworkCellularDes":
|
||||
MessageLookupByLibrary.simpleMessage("在使用蜂窝数据下载前确认"),
|
||||
"settingsPlayDes": MessageLookupByLibrary.simpleMessage("播放列表和播放器"),
|
||||
"settingsPopupMenu": MessageLookupByLibrary.simpleMessage("节目弹出菜单"),
|
||||
"settingsPopupMenuDes":
|
||||
MessageLookupByLibrary.simpleMessage("修改节目弹出菜单"),
|
||||
"settingsPrefrence": MessageLookupByLibrary.simpleMessage("首选项"),
|
||||
"settingsRealDark": MessageLookupByLibrary.simpleMessage("极黑"),
|
||||
"settingsRealDarkDes":
|
||||
MessageLookupByLibrary.simpleMessage("如果夜不够黑,请开启"),
|
||||
"settingsRewindSec": MessageLookupByLibrary.simpleMessage("快退时间"),
|
||||
"settingsRewindSecDes":
|
||||
MessageLookupByLibrary.simpleMessage("修改播放器快退时间"),
|
||||
"settingsSTAuto": MessageLookupByLibrary.simpleMessage("自动睡眠模式"),
|
||||
"settingsSTAutoDes": MessageLookupByLibrary.simpleMessage("定期开启睡眠模式"),
|
||||
"settingsSTDefaultTime": MessageLookupByLibrary.simpleMessage("默认时长"),
|
||||
"settingsSTDefautTimeDes":
|
||||
MessageLookupByLibrary.simpleMessage("睡眠模式默认时长"),
|
||||
"settingsSTMode": MessageLookupByLibrary.simpleMessage("自动睡眠模式默认时长"),
|
||||
"settingsStorageDes": MessageLookupByLibrary.simpleMessage("管理缓存和下载空间"),
|
||||
"settingsSyncing": MessageLookupByLibrary.simpleMessage("同步"),
|
||||
"settingsSyncingDes": MessageLookupByLibrary.simpleMessage("在后台更新播客"),
|
||||
"settingsTapToOpenPopupMenu":
|
||||
MessageLookupByLibrary.simpleMessage("轻点打开弹出菜单"),
|
||||
"settingsTapToOpenPopupMenuDes":
|
||||
MessageLookupByLibrary.simpleMessage("开启后您需长按打开节目页"),
|
||||
"settingsTheme": MessageLookupByLibrary.simpleMessage("主题"),
|
||||
"settingsUpdateInterval": MessageLookupByLibrary.simpleMessage("更新频率"),
|
||||
"settingsUpdateIntervalDes":
|
||||
MessageLookupByLibrary.simpleMessage("默认 24 小时"),
|
||||
"share": MessageLookupByLibrary.simpleMessage("分享"),
|
||||
"size": MessageLookupByLibrary.simpleMessage("大小"),
|
||||
"skipSecondsAtStart": MessageLookupByLibrary.simpleMessage("开头跳过秒数"),
|
||||
"sleepTimer": MessageLookupByLibrary.simpleMessage("睡眠模式"),
|
||||
"subscribe": MessageLookupByLibrary.simpleMessage("订阅"),
|
||||
"subscribeExportDes":
|
||||
MessageLookupByLibrary.simpleMessage("导出 OMPL 文件"),
|
||||
"systemDefault": MessageLookupByLibrary.simpleMessage("系统默认"),
|
||||
"timeLastPlayed": m24,
|
||||
"timeLeft": m25,
|
||||
"to": m26,
|
||||
"toastAddPlaylist": MessageLookupByLibrary.simpleMessage("添加到播放列表"),
|
||||
"toastDiscovery": MessageLookupByLibrary.simpleMessage("重启应用后可查看"),
|
||||
"toastFileError": MessageLookupByLibrary.simpleMessage("文件错误,导入失败"),
|
||||
"toastFileNotValid": MessageLookupByLibrary.simpleMessage("文件错误"),
|
||||
"toastHomeGroupNotSupport":
|
||||
MessageLookupByLibrary.simpleMessage("Home 分组不支持此功能"),
|
||||
"toastImportSettingsSuccess":
|
||||
MessageLookupByLibrary.simpleMessage("导入设置成功"),
|
||||
"toastOneGroup": MessageLookupByLibrary.simpleMessage("请至少选择一个分组"),
|
||||
"toastPodcastRecovering":
|
||||
MessageLookupByLibrary.simpleMessage("恢复中,请稍后"),
|
||||
"toastReadFile": MessageLookupByLibrary.simpleMessage("读取文件成功"),
|
||||
"toastRecoverFailed": MessageLookupByLibrary.simpleMessage("恢复订阅失败"),
|
||||
"toastRemovePlaylist": MessageLookupByLibrary.simpleMessage("从播放列表移除"),
|
||||
"toastSettingSaved": MessageLookupByLibrary.simpleMessage("设置已保存"),
|
||||
"toastTimeEqualEnd": MessageLookupByLibrary.simpleMessage("与结束时刻相同"),
|
||||
"toastTimeEqualStart": MessageLookupByLibrary.simpleMessage("与起始时刻相同"),
|
||||
"translators": MessageLookupByLibrary.simpleMessage("翻译者"),
|
||||
"understood": MessageLookupByLibrary.simpleMessage("了解"),
|
||||
"undo": MessageLookupByLibrary.simpleMessage("撤销"),
|
||||
"unlike": MessageLookupByLibrary.simpleMessage("取消喜欢"),
|
||||
"unliked": MessageLookupByLibrary.simpleMessage("从收藏移除"),
|
||||
"updateDate": MessageLookupByLibrary.simpleMessage("更新日期"),
|
||||
"updateEpisodesCount": m27,
|
||||
"updateFailed": MessageLookupByLibrary.simpleMessage("更新失败"),
|
||||
"version": m28
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:tsacdop/util/custom_widget.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
|
||||
const String version = '0.4.8';
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:marquee/marquee.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:marquee/marquee.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../episodes/episode_detail.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../state/audio_state.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import '../state/audio_state.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../util/pageroute.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/custom_slider.dart';
|
||||
import '../episodes/episode_detail.dart';
|
||||
import '../util/audiopanel.dart';
|
||||
import '../util/custom_slider.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/pageroute.dart';
|
||||
import 'playlist.dart';
|
||||
|
||||
final List<BoxShadow> _customShadow = [
|
||||
|
@ -40,7 +40,8 @@ final List<BoxShadow> _customShadowNight = [
|
|||
|
||||
String _stringForSeconds(double seconds) {
|
||||
if (seconds == null) return null;
|
||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
return '${(seconds ~/ 60)}:'
|
||||
'${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
const List minsToSelect = [10, 15, 20, 25, 30, 45, 60, 70, 80, 90, 99];
|
||||
|
@ -402,7 +403,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||
selector: (_, audio) =>
|
||||
Tuple2(audio.episode?.primaryColor, audio.seekSliderValue),
|
||||
builder: (_, data, __) {
|
||||
Color _c = (Theme.of(context).brightness == Brightness.light)
|
||||
var _c = (Theme.of(context).brightness == Brightness.light)
|
||||
? data.item1.colorizedark()
|
||||
: data.item1.colorizeLight();
|
||||
return SizedBox(
|
||||
|
@ -452,8 +453,8 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||
alignment: Alignment.center,
|
||||
child: data.item3 != null
|
||||
? Text(data.item3,
|
||||
style: const TextStyle(
|
||||
color: const Color(0xFFFF0000)))
|
||||
style:
|
||||
const TextStyle(color: Color(0xFFFF0000)))
|
||||
: data.item1
|
||||
? Text(
|
||||
s.buffering,
|
||||
|
@ -563,7 +564,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double _width = MediaQuery.of(context).size.width;
|
||||
var _width = MediaQuery.of(context).size.width;
|
||||
return Selector<AudioPlayerNotifier, bool>(
|
||||
selector: (_, audio) => audio.playerRunning,
|
||||
builder: (_, playerrunning, __) {
|
||||
|
@ -585,11 +586,6 @@ class LastPosition extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _LastPositionState extends State<LastPosition> {
|
||||
static String _stringForSeconds(double seconds) {
|
||||
if (seconds == null) return null;
|
||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
Future<PlayHistory> getPosition(EpisodeBrief episode) async {
|
||||
var dbHelper = DBHelper();
|
||||
return await dbHelper.getPosition(episode);
|
||||
|
@ -645,8 +641,7 @@ class _LastPositionState extends State<LastPosition> {
|
|||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10.0))),
|
||||
child: Text(s.timeLastPlayed(
|
||||
_stringForSeconds(
|
||||
snapshot.data.seconds))),
|
||||
snapshot.data.seconds.toTime)),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -680,10 +675,11 @@ class _ImageRotateState extends State<ImageRotate>
|
|||
);
|
||||
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
|
||||
..addListener(() {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_value = _animation.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
_controller.forward();
|
||||
_controller.addStatusListener((status) {
|
||||
|
@ -759,7 +755,7 @@ class _MeteorLoaderState extends State<MeteorLoader>
|
|||
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
|
||||
animation = Tween(begin: 0.0, end: 1.0).animate(controller)
|
||||
..addListener(() {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_move = animation.value;
|
||||
if (animation.value <= 0.5) {
|
||||
|
@ -768,6 +764,7 @@ class _MeteorLoaderState extends State<MeteorLoader>
|
|||
_fraction = 2 - (animation.value) * 2;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
controller.forward();
|
||||
// controller.addStatusListener((status) {
|
||||
|
@ -812,9 +809,8 @@ class SleepModeState extends State<SleepMode>
|
|||
Animation<double> _animation;
|
||||
|
||||
Future _getDefaultTime() async {
|
||||
KeyValueStorage defaultSleepTimerStorage =
|
||||
KeyValueStorage(defaultSleepTimerKey);
|
||||
int defaultTime = await defaultSleepTimerStorage.getInt(defaultValue: 30);
|
||||
var defaultSleepTimerStorage = KeyValueStorage(defaultSleepTimerKey);
|
||||
var defaultTime = await defaultSleepTimerStorage.getInt(defaultValue: 30);
|
||||
setState(() => _minSelected = defaultTime);
|
||||
}
|
||||
|
||||
|
@ -869,15 +865,15 @@ class SleepModeState extends State<SleepMode>
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = context.s;
|
||||
final ColorTween _colorTween =
|
||||
final _colorTween =
|
||||
ColorTween(begin: context.primaryColor, end: Colors.black);
|
||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||
return Selector<AudioPlayerNotifier, Tuple3<int, double, SleepTimerMode>>(
|
||||
selector: (_, audio) =>
|
||||
Tuple3(audio.timeLeft, audio.switchValue, audio.sleepTimerMode),
|
||||
builder: (_, data, __) {
|
||||
double fraction = data.item2 < 0.5 ? data.item2 * 2 : 1;
|
||||
double move = data.item2 > 0.5 ? data.item2 * 2 - 1 : 0;
|
||||
var fraction = data.item2 < 0.5 ? data.item2 * 2 : 1;
|
||||
var move = data.item2 > 0.5 ? data.item2 * 2 - 1 : 0;
|
||||
return Container(
|
||||
height: 300,
|
||||
color: _colorTween.transform(move),
|
||||
|
@ -1173,7 +1169,7 @@ class _ControlPanelState extends State<ControlPanel>
|
|||
children: <Widget>[
|
||||
Consumer<AudioPlayerNotifier>(
|
||||
builder: (_, data, __) {
|
||||
Color _c = (context.brightness == Brightness.light)
|
||||
var _c = (context.brightness == Brightness.light)
|
||||
? data.episode.primaryColor.colorizedark()
|
||||
: data.episode.primaryColor.colorizeLight();
|
||||
return Column(
|
||||
|
@ -1204,7 +1200,7 @@ class _ControlPanelState extends State<ControlPanel>
|
|||
),
|
||||
child: Slider(
|
||||
value: data.seekSliderValue,
|
||||
onChanged: (double val) {
|
||||
onChanged: (val) {
|
||||
audio.sliderSeek(val);
|
||||
}),
|
||||
),
|
||||
|
@ -1226,7 +1222,7 @@ class _ControlPanelState extends State<ControlPanel>
|
|||
child: data.remoteErrorMessage != null
|
||||
? Text(data.remoteErrorMessage,
|
||||
style: const TextStyle(
|
||||
color: const Color(0xFFFF0000)))
|
||||
color: Color(0xFFFF0000)))
|
||||
: Text(
|
||||
data.audioState ==
|
||||
AudioProcessingState
|
||||
|
@ -1472,10 +1468,11 @@ class _ControlPanelState extends State<ControlPanel>
|
|||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (_setSpeed == 0)
|
||||
if (_setSpeed == 0) {
|
||||
_controller.forward();
|
||||
else
|
||||
} else {
|
||||
_controller.reverse();
|
||||
}
|
||||
},
|
||||
icon: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../state/download_state.dart';
|
||||
import '../episodes/episode_detail.dart';
|
||||
import '../state/download_state.dart';
|
||||
import '../util/pageroute.dart';
|
||||
|
||||
class DownloadList extends StatefulWidget {
|
||||
|
@ -63,7 +64,7 @@ class _DownloadListState extends State<DownloadList> {
|
|||
padding: EdgeInsets.all(5.0),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
(context, index) {
|
||||
return ListTile(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
|
@ -101,7 +102,7 @@ class _DownloadListState extends State<DownloadList> {
|
|||
Radius.circular(6)),
|
||||
color: Colors.red),
|
||||
child: Text(
|
||||
tasks[index].progress.toString() + '%',
|
||||
'${tasks[index].progress}%',
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
style: TextStyle(color: Colors.white),
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:feature_discovery/feature_discovery.dart';
|
||||
import 'package:flutter/material.dart' hide NestedScrollView;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:feature_discovery/feature_discovery.dart';
|
||||
|
||||
import '../state/audio_state.dart';
|
||||
import '../type/playlist.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../util/episodegrid.dart';
|
||||
import '../util/mypopupmenu.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../state/audio_state.dart';
|
||||
import '../state/download_state.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../state/setting_state.dart';
|
||||
import 'playlist.dart';
|
||||
import 'import_ompl.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../type/playlist.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/episodegrid.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/mypopupmenu.dart';
|
||||
import 'audioplayer.dart';
|
||||
import 'search_podcast.dart';
|
||||
import 'home_menu.dart';
|
||||
import 'home_groups.dart';
|
||||
import 'download_list.dart';
|
||||
import 'home_groups.dart';
|
||||
import 'home_menu.dart';
|
||||
import 'import_ompl.dart';
|
||||
import 'playlist.dart';
|
||||
import 'search_podcast.dart';
|
||||
|
||||
const String addFeature = 'addFeature';
|
||||
const String menuFeature = 'menuFeature';
|
||||
|
@ -55,7 +55,7 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
));
|
||||
}
|
||||
|
||||
var _androidAppRetain = MethodChannel("android_app_retain");
|
||||
final _androidAppRetain = MethodChannel("android_app_retain");
|
||||
var feature1OverflowMode = OverflowMode.clipContent;
|
||||
var feature1EnablePulsingAnimation = false;
|
||||
|
||||
|
@ -64,7 +64,7 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
super.initState();
|
||||
_controller = TabController(length: 3, vsync: this);
|
||||
FeatureDiscovery.isDisplayed(context, addFeature).then((value) {
|
||||
if (!value)
|
||||
if (!value) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
FeatureDiscovery.discoverFeatures(
|
||||
context,
|
||||
|
@ -77,6 +77,7 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -89,8 +90,8 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
double top = 0;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double width = MediaQuery.of(context).size.width;
|
||||
double height = (width - 20) / 3 + 140;
|
||||
var width = MediaQuery.of(context).size.width;
|
||||
var height = (width - 20) / 3 + 140;
|
||||
var settings = Provider.of<SettingState>(context, listen: false);
|
||||
final s = context.s;
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
|
@ -119,11 +120,10 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
Expanded(
|
||||
child: NestedScrollView(
|
||||
innerScrollPositionKeyBuilder: () {
|
||||
return Key('tab' + _controller.index.toString());
|
||||
return Key('tab${_controller.index}');
|
||||
},
|
||||
pinnedHeaderSliverHeightBuilder: () => 50,
|
||||
headerSliverBuilder:
|
||||
(BuildContext context, bool innerBoxScrolled) {
|
||||
headerSliverBuilder: (context, innerBoxScrolled) {
|
||||
return <Widget>[
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
|
@ -276,7 +276,7 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
(context, index) {
|
||||
return DescribedFeatureOverlay(
|
||||
featureId: groupsFeature,
|
||||
tapTarget: Center(
|
||||
|
@ -686,26 +686,28 @@ class _RecentUpdate extends StatefulWidget {
|
|||
class _RecentUpdateState extends State<_RecentUpdate>
|
||||
with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin {
|
||||
Future<List<EpisodeBrief>> _getRssItem(int top, List<String> group) async {
|
||||
KeyValueStorage storage = KeyValueStorage(recentLayoutKey);
|
||||
int index = await storage.getInt(defaultValue: 1);
|
||||
var storage = KeyValueStorage(recentLayoutKey);
|
||||
var index = await storage.getInt(defaultValue: 1);
|
||||
if (_layout == null) _layout = Layout.values[index];
|
||||
|
||||
var dbHelper = DBHelper();
|
||||
List<EpisodeBrief> episodes;
|
||||
if (group.first == 'All')
|
||||
if (group.first == 'All') {
|
||||
episodes = await dbHelper.getRecentRssItem(top);
|
||||
else
|
||||
} else {
|
||||
episodes = await dbHelper.getGroupRssItem(top, group);
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
|
||||
Future<int> _getUpdateCounts(List<String> group) async {
|
||||
var dbHelper = DBHelper();
|
||||
List<EpisodeBrief> episodes = [];
|
||||
if (group.first == 'All')
|
||||
var episodes = <EpisodeBrief>[];
|
||||
if (group.first == 'All') {
|
||||
episodes = await dbHelper.getRecentNewRssItem();
|
||||
else
|
||||
} else {
|
||||
episodes = await dbHelper.getGroupNewRssItem(group);
|
||||
}
|
||||
return episodes.length;
|
||||
}
|
||||
|
||||
|
@ -713,11 +715,12 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
|||
_loadMoreEpisode() async {
|
||||
if (mounted) setState(() => _loadMore = true);
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_top = _top + 33;
|
||||
_loadMore = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Episodes loaded first time.
|
||||
|
@ -771,7 +774,7 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
|||
),
|
||||
)
|
||||
: NotificationListener<ScrollNotification>(
|
||||
onNotification: (ScrollNotification scrollInfo) {
|
||||
onNotification: (scrollInfo) {
|
||||
if (scrollInfo is ScrollStartNotification &&
|
||||
mounted &&
|
||||
!_scroll) {
|
||||
|
@ -779,8 +782,9 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
|||
}
|
||||
if (scrollInfo.metrics.pixels ==
|
||||
scrollInfo.metrics.maxScrollExtent &&
|
||||
snapshot.data.length == _top)
|
||||
snapshot.data.length == _top) {
|
||||
_loadMoreEpisode();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: CustomScrollView(
|
||||
|
@ -912,9 +916,10 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
|||
await audio
|
||||
.addNewEpisode(
|
||||
_group);
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(
|
||||
() {});
|
||||
}
|
||||
Fluttertoast
|
||||
.showToast(
|
||||
msg: _groupName ==
|
||||
|
@ -960,19 +965,21 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
|||
tooltip: s.changeLayout,
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (_layout == Layout.three)
|
||||
if (_layout ==
|
||||
Layout.three) {
|
||||
setState(() {
|
||||
_layout = Layout.one;
|
||||
});
|
||||
else if (_layout ==
|
||||
Layout.two)
|
||||
} else if (_layout ==
|
||||
Layout.two) {
|
||||
setState(() {
|
||||
_layout = Layout.three;
|
||||
});
|
||||
else
|
||||
} else {
|
||||
setState(() {
|
||||
_layout = Layout.two;
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: _layout == Layout.three
|
||||
? SizedBox(
|
||||
|
@ -1026,7 +1033,7 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
|||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
(context, index) {
|
||||
return _loadMore
|
||||
? Container(
|
||||
height: 2,
|
||||
|
@ -1055,22 +1062,23 @@ class _MyFavorite extends StatefulWidget {
|
|||
class _MyFavoriteState extends State<_MyFavorite>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
Future<List<EpisodeBrief>> _getLikedRssItem(int top, int sortBy) async {
|
||||
KeyValueStorage storage = KeyValueStorage(favLayoutKey);
|
||||
int index = await storage.getInt(defaultValue: 1);
|
||||
var storage = KeyValueStorage(favLayoutKey);
|
||||
var index = await storage.getInt(defaultValue: 1);
|
||||
if (_layout == null) _layout = Layout.values[index];
|
||||
var dbHelper = DBHelper();
|
||||
List<EpisodeBrief> episodes = await dbHelper.getLikedRssItem(top, sortBy);
|
||||
var episodes = await dbHelper.getLikedRssItem(top, sortBy);
|
||||
return episodes;
|
||||
}
|
||||
|
||||
_loadMoreEpisode() async {
|
||||
if (mounted) setState(() => _loadMore = true);
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_top = _top + 33;
|
||||
_loadMore = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int _top = 99;
|
||||
|
@ -1114,11 +1122,12 @@ class _MyFavoriteState extends State<_MyFavorite>
|
|||
),
|
||||
)
|
||||
: NotificationListener<ScrollNotification>(
|
||||
onNotification: (ScrollNotification scrollInfo) {
|
||||
onNotification: (scrollInfo) {
|
||||
if (scrollInfo.metrics.pixels ==
|
||||
scrollInfo.metrics.maxScrollExtent &&
|
||||
snapshot.data.length == _top)
|
||||
snapshot.data.length == _top) {
|
||||
_loadMoreEpisode();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: CustomScrollView(
|
||||
|
@ -1184,10 +1193,11 @@ class _MyFavoriteState extends State<_MyFavorite>
|
|||
)
|
||||
],
|
||||
onSelected: (value) {
|
||||
if (value == 0)
|
||||
if (value == 0) {
|
||||
setState(() => _sortBy = 0);
|
||||
else if (value == 1)
|
||||
} else if (value == 1) {
|
||||
setState(() => _sortBy = 1);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1197,18 +1207,20 @@ class _MyFavoriteState extends State<_MyFavorite>
|
|||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (_layout == Layout.three)
|
||||
if (_layout == Layout.three) {
|
||||
setState(() {
|
||||
_layout = Layout.one;
|
||||
});
|
||||
else if (_layout == Layout.two)
|
||||
} else if (_layout ==
|
||||
Layout.two) {
|
||||
setState(() {
|
||||
_layout = Layout.three;
|
||||
});
|
||||
else
|
||||
} else {
|
||||
setState(() {
|
||||
_layout = Layout.two;
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: _layout == Layout.three
|
||||
? SizedBox(
|
||||
|
@ -1259,7 +1271,7 @@ class _MyFavoriteState extends State<_MyFavorite>
|
|||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
(context, index) {
|
||||
return _loadMore
|
||||
? Container(
|
||||
height: 2,
|
||||
|
@ -1291,12 +1303,13 @@ class _MyDownloadState extends State<_MyDownload>
|
|||
with AutomaticKeepAliveClientMixin {
|
||||
Layout _layout;
|
||||
_getLayout() async {
|
||||
KeyValueStorage keyValueStorage = KeyValueStorage(downloadLayoutKey);
|
||||
int layout = await keyValueStorage.getInt(defaultValue: 1);
|
||||
if (_layout == null)
|
||||
var keyValueStorage = KeyValueStorage(downloadLayoutKey);
|
||||
var layout = await keyValueStorage.getInt(defaultValue: 1);
|
||||
if (_layout == null) {
|
||||
setState(() {
|
||||
_layout = Layout.values[layout];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1327,48 +1340,11 @@ class _MyDownloadState extends State<_MyDownload>
|
|||
Spacer(),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (_layout == Layout.three)
|
||||
setState(() {
|
||||
_layout = Layout.one;
|
||||
});
|
||||
else if (_layout == Layout.two)
|
||||
setState(() {
|
||||
_layout = Layout.three;
|
||||
});
|
||||
else
|
||||
setState(() {
|
||||
_layout = Layout.two;
|
||||
});
|
||||
},
|
||||
icon: _layout == Layout.three
|
||||
? SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
0, context.textTheme.bodyText1.color),
|
||||
),
|
||||
)
|
||||
: _layout == Layout.two
|
||||
? SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(1,
|
||||
context.textTheme.bodyText1.color),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(4,
|
||||
context.textTheme.bodyText1.color),
|
||||
),
|
||||
),
|
||||
child: LayoutButton(
|
||||
layout: _layout,
|
||||
onPressed: (layout) => setState(() {
|
||||
_layout = layout;
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:focused_menu/focused_menu.dart';
|
||||
import 'package:focused_menu/modals.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../type/episodebrief.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../state/download_state.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../state/audio_state.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/pageroute.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../episodes/episode_detail.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../podcasts/podcast_detail.dart';
|
||||
import '../podcasts/podcast_manage.dart';
|
||||
import '../state/audio_state.dart';
|
||||
import '../state/download_state.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/pageroute.dart';
|
||||
|
||||
class ScrollPodcasts extends StatefulWidget {
|
||||
@override
|
||||
|
@ -55,15 +55,15 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double _width = MediaQuery.of(context).size.width;
|
||||
var _width = MediaQuery.of(context).size.width;
|
||||
final s = context.s;
|
||||
return Selector<GroupList, Tuple3<List<PodcastGroup>, bool, bool>>(
|
||||
selector: (_, groupList) =>
|
||||
Tuple3(groupList.groups, groupList.created, groupList.isLoading),
|
||||
builder: (_, data, __) {
|
||||
var groups = data.item1;
|
||||
bool import = data.item2;
|
||||
bool isLoading = data.item3;
|
||||
var import = data.item2;
|
||||
var isLoading = data.item3;
|
||||
return isLoading
|
||||
? Container(
|
||||
height: (_width - 20) / 3 + 140,
|
||||
|
@ -84,12 +84,13 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
|||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
(_groupIndex != 0)
|
||||
? _groupIndex--
|
||||
: _groupIndex = groups.length - 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (event.primaryVelocity < -200) {
|
||||
if (groups.length == 1) {
|
||||
|
@ -131,12 +132,13 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
|||
alignment: Alignment.bottomRight,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (!import)
|
||||
if (!import) {
|
||||
Navigator.push(
|
||||
context,
|
||||
SlideLeftRoute(
|
||||
page: PodcastManage()),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
height: 30,
|
||||
|
@ -196,7 +198,7 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
|||
WidgetSpan(
|
||||
child:
|
||||
Icon(Icons.add_circle_outline)),
|
||||
TextSpan(text: ' to subscribe podcasts')
|
||||
TextSpan(text: ' to search podcasts')
|
||||
],
|
||||
))
|
||||
: Text(s.noPodcastGroup,
|
||||
|
@ -223,12 +225,13 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
|||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
(_groupIndex != 0)
|
||||
? _groupIndex--
|
||||
: _groupIndex = groups.length - 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (event.primaryVelocity < -200) {
|
||||
if (groups.length == 1) {
|
||||
|
@ -268,12 +271,13 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
|||
alignment: Alignment.bottomRight,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (!import)
|
||||
if (!import) {
|
||||
Navigator.push(
|
||||
context,
|
||||
SlideLeftRoute(
|
||||
page: PodcastManage()),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
height: 30,
|
||||
|
@ -309,14 +313,13 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
|||
isScrollable: true,
|
||||
tabs: groups[_groupIndex]
|
||||
.podcasts
|
||||
.map<Widget>((PodcastLocal podcastLocal) {
|
||||
Color color =
|
||||
(Theme.of(context).brightness ==
|
||||
Brightness.light)
|
||||
? podcastLocal.primaryColor
|
||||
.colorizedark()
|
||||
: podcastLocal.primaryColor
|
||||
.colorizeLight();
|
||||
.map<Widget>((podcastLocal) {
|
||||
var color = (Theme.of(context).brightness ==
|
||||
Brightness.light)
|
||||
? podcastLocal.primaryColor
|
||||
.colorizedark()
|
||||
: podcastLocal.primaryColor
|
||||
.colorizeLight();
|
||||
return Tab(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(
|
||||
|
@ -378,7 +381,7 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
|||
child: TabBarView(
|
||||
children: groups[_groupIndex]
|
||||
.podcasts
|
||||
.map<Widget>((PodcastLocal podcastLocal) {
|
||||
.map<Widget>((podcastLocal) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness ==
|
||||
|
@ -408,13 +411,13 @@ class PodcastPreview extends StatelessWidget {
|
|||
|
||||
Future<List<EpisodeBrief>> _getRssItemTop(PodcastLocal podcastLocal) async {
|
||||
var dbHelper = DBHelper();
|
||||
List<EpisodeBrief> episodes = await dbHelper.getRssItemTop(podcastLocal.id);
|
||||
var episodes = await dbHelper.getRssItemTop(podcastLocal.id);
|
||||
return episodes;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color _c = (Theme.of(context).brightness == Brightness.light)
|
||||
var _c = (Theme.of(context).brightness == Brightness.light)
|
||||
? podcastLocal.primaryColor.colorizedark()
|
||||
: podcastLocal.primaryColor.colorizeLight();
|
||||
return Column(
|
||||
|
@ -504,7 +507,7 @@ class ShowEpisode extends StatelessWidget {
|
|||
|
||||
String _dateToString(BuildContext context, {int pubDate}) {
|
||||
final s = context.s;
|
||||
DateTime date = DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true);
|
||||
var date = DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true);
|
||||
var difference = DateTime.now().toUtc().difference(date);
|
||||
if (difference.inHours < 24) {
|
||||
return s.hoursAgo(difference.inHours);
|
||||
|
@ -518,50 +521,48 @@ class ShowEpisode extends StatelessWidget {
|
|||
|
||||
Future<Tuple5<int, bool, bool, bool, List<int>>> _initData(
|
||||
EpisodeBrief episode) async {
|
||||
List<int> menuList = await _getEpisodeMenu();
|
||||
bool tapToOpen = await _getTapToOpenPopupMenu();
|
||||
int listened = await _isListened(episode);
|
||||
var menuList = await _getEpisodeMenu();
|
||||
var tapToOpen = await _getTapToOpenPopupMenu();
|
||||
var listened = await _isListened(episode);
|
||||
|
||||
bool liked = await _isLiked(episode);
|
||||
bool downloaded = await _isDownloaded(episode);
|
||||
var liked = await _isLiked(episode);
|
||||
var downloaded = await _isDownloaded(episode);
|
||||
|
||||
return Tuple5(listened, liked, downloaded, tapToOpen, menuList);
|
||||
}
|
||||
|
||||
Future<int> _isListened(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
var dbHelper = DBHelper();
|
||||
return await dbHelper.isListened(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
Future<bool> _isLiked(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
var dbHelper = DBHelper();
|
||||
return await dbHelper.isLiked(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
Future<List<int>> _getEpisodeMenu() async {
|
||||
KeyValueStorage popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
|
||||
List<int> list = await popupMenuStorage.getMenu();
|
||||
var popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
|
||||
var list = await popupMenuStorage.getMenu();
|
||||
return list;
|
||||
}
|
||||
|
||||
Future<bool> _isDownloaded(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
var dbHelper = DBHelper();
|
||||
return await dbHelper.isDownloaded(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
Future<bool> _getTapToOpenPopupMenu() async {
|
||||
KeyValueStorage tapToOpenPopupMenuStorage =
|
||||
KeyValueStorage(tapToOpenPopupMenuKey);
|
||||
int boo = await tapToOpenPopupMenuStorage.getInt(defaultValue: 0);
|
||||
var tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey);
|
||||
var boo = await tapToOpenPopupMenuStorage.getInt(defaultValue: 0);
|
||||
return boo == 1;
|
||||
}
|
||||
|
||||
_markListened(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
bool marked = await dbHelper.checkMarked(episode);
|
||||
var dbHelper = DBHelper();
|
||||
var marked = await dbHelper.checkMarked(episode);
|
||||
if (!marked) {
|
||||
final PlayHistory history =
|
||||
PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
final history = PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
await dbHelper.saveHistory(history);
|
||||
}
|
||||
}
|
||||
|
@ -578,7 +579,7 @@ class ShowEpisode extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double _width = context.width;
|
||||
var _width = context.width;
|
||||
final s = context.s;
|
||||
var downloader = Provider.of<DownloadState>(context, listen: false);
|
||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||
|
@ -596,8 +597,8 @@ class ShowEpisode extends StatelessWidget {
|
|||
crossAxisSpacing: 6.0,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
Color _c = (Theme.of(context).brightness == Brightness.light)
|
||||
(context, index) {
|
||||
var _c = (Theme.of(context).brightness == Brightness.light)
|
||||
? podcastLocal.primaryColor.colorizedark()
|
||||
: podcastLocal.primaryColor.colorizeLight();
|
||||
return Selector<AudioPlayerNotifier,
|
||||
|
@ -612,13 +613,12 @@ class ShowEpisode extends StatelessWidget {
|
|||
Tuple5<int, bool, bool, bool, List<int>>>(
|
||||
future: _initData(episodes[index]),
|
||||
initialData: Tuple5(0, false, false, false, []),
|
||||
builder:
|
||||
(BuildContext context, AsyncSnapshot snapshot) {
|
||||
int isListened = snapshot.data.item1;
|
||||
bool isLiked = snapshot.data.item2;
|
||||
bool isDownloaded = snapshot.data.item3;
|
||||
bool tapToOpen = snapshot.data.item4;
|
||||
List<int> menuList = snapshot.data.item5;
|
||||
builder: (context, snapshot) {
|
||||
var isListened = snapshot.data.item1;
|
||||
var isLiked = snapshot.data.item2;
|
||||
var isDownloaded = snapshot.data.item3;
|
||||
var tapToOpen = snapshot.data.item4;
|
||||
var menuList = snapshot.data.item5;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
|
@ -658,8 +658,9 @@ class ShowEpisode extends StatelessWidget {
|
|||
color: context.accentColor,
|
||||
),
|
||||
onPressed: () {
|
||||
if (data.item1 != episodes[index])
|
||||
if (data.item1 != episodes[index]) {
|
||||
audio.episodeLoad(episodes[index]);
|
||||
}
|
||||
}),
|
||||
menuList.contains(1)
|
||||
? FocusedMenuItem(
|
||||
|
@ -777,9 +778,10 @@ class ShowEpisode extends StatelessWidget {
|
|||
LineIcons.download_solid,
|
||||
color: Colors.green),
|
||||
onPressed: () {
|
||||
if (!isDownloaded)
|
||||
if (!isDownloaded) {
|
||||
downloader
|
||||
.startTask(episodes[index]);
|
||||
}
|
||||
})
|
||||
: null
|
||||
],
|
||||
|
@ -804,8 +806,8 @@ class ShowEpisode extends StatelessWidget {
|
|||
MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Hero(
|
||||
tag: episodes[index].enclosureUrl +
|
||||
'scroll',
|
||||
tag:
|
||||
'${episodes[index].enclosureUrl}scroll',
|
||||
child: Container(
|
||||
height: _width / 18,
|
||||
width: _width / 18,
|
||||
|
@ -929,11 +931,7 @@ class ShowEpisode extends StatelessWidget {
|
|||
? Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
((episodes[index]
|
||||
.enclosureLength) ~/
|
||||
1000000)
|
||||
.toString() +
|
||||
'MB',
|
||||
'${(episodes[index].enclosureLength) ~/ 1000000}MB',
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
_width / 35),
|
||||
|
@ -978,7 +976,7 @@ class _CirclePainter extends BoxPainter {
|
|||
|
||||
@override
|
||||
void paint(Canvas canvas, Offset offset, ImageConfiguration cfg) {
|
||||
final Offset circleOffset =
|
||||
final circleOffset =
|
||||
offset + Offset(cfg.size.width / 2, cfg.size.height - radius);
|
||||
canvas.drawCircle(circleOffset, radius, _paint);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:tsacdop/local_storage/key_value_storage.dart';
|
||||
import 'package:tsacdop/service/ompl_build.dart';
|
||||
import 'package:tsacdop/state/podcast_group.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../service/ompl_build.dart';
|
||||
import '../settings/settting.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../state/refresh_podcast.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import 'about.dart';
|
||||
|
@ -27,16 +26,16 @@ class _PopupMenuState extends State<PopupMenu> {
|
|||
Future<String> _getRefreshDate(BuildContext context) async {
|
||||
int refreshDate;
|
||||
final s = context.s;
|
||||
KeyValueStorage refreshstorage = KeyValueStorage('refreshdate');
|
||||
int i = await refreshstorage.getInt();
|
||||
var refreshstorage = KeyValueStorage('refreshdate');
|
||||
var i = await refreshstorage.getInt();
|
||||
if (i == 0) {
|
||||
KeyValueStorage refreshstorage = KeyValueStorage('refreshdate');
|
||||
var refreshstorage = KeyValueStorage('refreshdate');
|
||||
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
|
||||
refreshDate = DateTime.now().millisecondsSinceEpoch;
|
||||
} else {
|
||||
refreshDate = i;
|
||||
}
|
||||
DateTime date = DateTime.fromMillisecondsSinceEpoch(refreshDate);
|
||||
var date = DateTime.fromMillisecondsSinceEpoch(refreshDate);
|
||||
var difference = DateTime.now().difference(date);
|
||||
if (difference.inSeconds < 60) {
|
||||
return s.secondsAgo(difference.inSeconds);
|
||||
|
@ -54,19 +53,19 @@ class _PopupMenuState extends State<PopupMenu> {
|
|||
|
||||
void _saveOmpl(String path) async {
|
||||
var subscribeWorker = Provider.of<GroupList>(context, listen: false);
|
||||
RegExp rssExp = RegExp(r'^(https?):\/\/(.*)');
|
||||
var rssExp = RegExp(r'^(https?):\/\/(.*)');
|
||||
final s = context.s;
|
||||
File file = File(path);
|
||||
var file = File(path);
|
||||
try {
|
||||
Map<String, List<OmplOutline>> data = PodcastsBackup.parseOMPL(file);
|
||||
for (var entry in data.entries) {
|
||||
String title = entry.key;
|
||||
var title = entry.key;
|
||||
print(title);
|
||||
var list = entry.value.reversed;
|
||||
for (var rss in list) {
|
||||
String rssLink = rssExp.stringMatch(rss.xmlUrl);
|
||||
var rssLink = rssExp.stringMatch(rss.xmlUrl);
|
||||
if (rssLink != null) {
|
||||
SubscribeItem item = SubscribeItem(rssLink, rss.text, group: title);
|
||||
var item = SubscribeItem(rssLink, rss.text, group: title);
|
||||
await subscribeWorker.setSubscribeItem(item);
|
||||
await Future.delayed(Duration(milliseconds: 200));
|
||||
print(rss.text);
|
||||
|
@ -85,11 +84,11 @@ class _PopupMenuState extends State<PopupMenu> {
|
|||
void _getFilePath() async {
|
||||
final s = context.s;
|
||||
try {
|
||||
String filePath = await FilePicker.getFilePath(type: FileType.any);
|
||||
var filePath = await FilePicker.getFilePath(type: FileType.any);
|
||||
if (filePath == '') {
|
||||
return;
|
||||
}
|
||||
print('File Path' + filePath);
|
||||
print('File Path$filePath');
|
||||
//importOmpl.importState = ImportState.start;
|
||||
Fluttertoast.showToast(
|
||||
msg: s.toastReadFile,
|
||||
|
@ -131,13 +130,14 @@ class _PopupMenuState extends State<PopupMenu> {
|
|||
FutureBuilder<String>(
|
||||
future: _getRefreshDate(context),
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.hasData)
|
||||
if (snapshot.hasData) {
|
||||
return Text(
|
||||
snapshot.data,
|
||||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||
);
|
||||
else
|
||||
} else {
|
||||
return Center();
|
||||
}
|
||||
})
|
||||
],
|
||||
),
|
||||
|
|
|
@ -4,10 +4,9 @@ import 'package:provider/provider.dart';
|
|||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../state/download_state.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../state/refresh_podcast.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
|
||||
class Import extends StatelessWidget {
|
||||
|
@ -30,38 +29,39 @@ class Import extends StatelessWidget {
|
|||
}
|
||||
|
||||
_autoDownloadNew(BuildContext context) async {
|
||||
final DBHelper dbHelper = DBHelper();
|
||||
final dbHelper = DBHelper();
|
||||
var downloader = Provider.of<DownloadState>(context, listen: false);
|
||||
var result = await Connectivity().checkConnectivity();
|
||||
KeyValueStorage autoDownloadStorage =
|
||||
KeyValueStorage(autoDownloadNetworkKey);
|
||||
int autoDownloadNetwork = await autoDownloadStorage.getInt();
|
||||
var autoDownloadStorage = KeyValueStorage(autoDownloadNetworkKey);
|
||||
var autoDownloadNetwork = await autoDownloadStorage.getInt();
|
||||
if (autoDownloadNetwork == 1) {
|
||||
List<EpisodeBrief> episodes = await dbHelper.getNewEpisodes('all');
|
||||
var episodes = await dbHelper.getNewEpisodes('all');
|
||||
// For safety
|
||||
if (episodes.length < 100 && episodes.length > 0)
|
||||
if (episodes.length < 100 && episodes.length > 0) {
|
||||
for (var episode in episodes) {
|
||||
await downloader.startTask(episode, showNotification: true);
|
||||
}
|
||||
}
|
||||
} else if (result == ConnectivityResult.wifi) {
|
||||
List<EpisodeBrief> episodes = await dbHelper.getNewEpisodes('all');
|
||||
var episodes = await dbHelper.getNewEpisodes('all');
|
||||
//For safety
|
||||
if (episodes.length < 100 && episodes.length > 0)
|
||||
if (episodes.length < 100 && episodes.length > 0) {
|
||||
for (var episode in episodes) {
|
||||
await downloader.startTask(episode, showNotification: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = context.s;
|
||||
GroupList groupList = Provider.of<GroupList>(context, listen: false);
|
||||
var groupList = Provider.of<GroupList>(context, listen: false);
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Consumer<GroupList>(
|
||||
builder: (_, subscribeWorker, __) {
|
||||
SubscribeItem item = subscribeWorker.currentSubscribeItem;
|
||||
var item = subscribeWorker.currentSubscribeItem;
|
||||
switch (item.subscribeState) {
|
||||
case SubscribeState.start:
|
||||
return importColumn(
|
||||
|
@ -83,7 +83,7 @@ class Import extends StatelessWidget {
|
|||
),
|
||||
Consumer<RefreshWorker>(
|
||||
builder: (context, refreshWorker, child) {
|
||||
RefreshItem item = refreshWorker.currentRefreshItem;
|
||||
var item = refreshWorker.currentRefreshItem;
|
||||
if (refreshWorker.complete) {
|
||||
groupList.updateGroups();
|
||||
_autoDownloadNew(context);
|
||||
|
|
|
@ -2,17 +2,17 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../state/audio_state.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../type/playlist.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
|
||||
class PlaylistPage extends StatefulWidget {
|
||||
@override
|
||||
|
@ -23,7 +23,7 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
|||
final textstyle = TextStyle(fontSize: 15.0, color: Colors.black);
|
||||
|
||||
int _sumPlaylistLength(List<EpisodeBrief> episodes) {
|
||||
int sum = 0;
|
||||
var sum = 0;
|
||||
if (episodes.length == 0) {
|
||||
return sum;
|
||||
} else {
|
||||
|
@ -36,7 +36,7 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
|||
|
||||
ScrollController _controller;
|
||||
_scrollListener() {
|
||||
double value = _controller.offset;
|
||||
var value = _controller.offset;
|
||||
setState(() => _topHeight = (100 - value) > 60 ? 100 - value : 60);
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
|||
selector: (_, audio) =>
|
||||
Tuple3(audio.queue, audio.playerRunning, audio.queueUpdate),
|
||||
builder: (_, data, __) {
|
||||
final List<EpisodeBrief> episodes = data.item1.playlist;
|
||||
final episodes = data.item1.playlist;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -100,7 +100,7 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
|||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: _topHeight > 90
|
||||
? s.homeMenuPlaylist + '\n'
|
||||
? '${s.homeMenuPlaylist}\n'
|
||||
: '',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
|
@ -243,11 +243,11 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
|||
Expanded(
|
||||
child: ReorderableListView(
|
||||
scrollController: _controller,
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
if (newIndex > oldIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final EpisodeBrief episodeRemove = episodes[oldIndex];
|
||||
final episodeRemove = episodes[oldIndex];
|
||||
audio.delFromPlaylist(episodeRemove);
|
||||
audio.addToPlaylistAt(episodeRemove, newIndex);
|
||||
setState(() {});
|
||||
|
@ -256,15 +256,16 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
|||
children: data.item2
|
||||
? episodes.map<Widget>((episode) {
|
||||
if (episode.enclosureUrl !=
|
||||
episodes.first.enclosureUrl)
|
||||
episodes.first.enclosureUrl) {
|
||||
return DismissibleContainer(
|
||||
episode: episode,
|
||||
key: ValueKey(episode.enclosureUrl),
|
||||
);
|
||||
else
|
||||
} else {
|
||||
return Container(
|
||||
key: ValueKey('sd'),
|
||||
);
|
||||
}
|
||||
}).toList()
|
||||
: episodes
|
||||
.map<Widget>((episode) => DismissibleContainer(
|
||||
|
@ -315,7 +316,7 @@ class _DismissibleContainerState extends State<DismissibleContainer> {
|
|||
Widget build(BuildContext context) {
|
||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||
final s = context.s;
|
||||
Color _c = (Theme.of(context).brightness == Brightness.light)
|
||||
var _c = (Theme.of(context).brightness == Brightness.light)
|
||||
? widget.episode.primaryColor.colorizedark()
|
||||
: widget.episode.primaryColor.colorizeLight();
|
||||
return AnimatedContainer(
|
||||
|
@ -327,7 +328,7 @@ class _DismissibleContainerState extends State<DismissibleContainer> {
|
|||
color: Colors.transparent,
|
||||
)
|
||||
: Dismissible(
|
||||
key: ValueKey(widget.episode.enclosureUrl + 't'),
|
||||
key: ValueKey('${widget.episode.enclosureUrl}t'),
|
||||
background: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: Row(
|
||||
|
@ -364,7 +365,7 @@ class _DismissibleContainerState extends State<DismissibleContainer> {
|
|||
setState(() {
|
||||
_delete = true;
|
||||
});
|
||||
int index = await audio.delFromPlaylist(widget.episode);
|
||||
var index = await audio.delFromPlaylist(widget.episode);
|
||||
final episodeRemove = widget.episode;
|
||||
Fluttertoast.showToast(
|
||||
msg: s.toastRemovePlaylist,
|
||||
|
@ -430,9 +431,7 @@ class _DismissibleContainerState extends State<DismissibleContainer> {
|
|||
: Center(),
|
||||
widget.episode.enclosureLength != null
|
||||
? _episodeTag(
|
||||
((widget.episode.enclosureLength) ~/ 1000000)
|
||||
.toString() +
|
||||
'MB',
|
||||
'${(widget.episode.enclosureLength) ~/ 1000000}MB',
|
||||
Colors.lightBlue[300])
|
||||
: Center(),
|
||||
],
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:tsacdop/state/podcast_group.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../type/searchpodcast.dart';
|
||||
import '../type/searchepisodes.dart';
|
||||
import '../service/api_search.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../type/searchepisodes.dart';
|
||||
import '../type/searchpodcast.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../webfeed/webfeed.dart';
|
||||
|
||||
|
@ -27,14 +26,14 @@ class MyHomePageDelegate extends SearchDelegate<int> {
|
|||
|
||||
static Future getRss(String url) async {
|
||||
try {
|
||||
BaseOptions options = new BaseOptions(
|
||||
var options = BaseOptions(
|
||||
connectTimeout: 10000,
|
||||
receiveTimeout: 10000,
|
||||
);
|
||||
Response response = await Dio(options).get(url);
|
||||
var response = await Dio(options).get(url);
|
||||
return RssFeed.parse(response.data);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,7 +93,7 @@ class MyHomePageDelegate extends SearchDelegate<int> {
|
|||
|
||||
@override
|
||||
Widget buildResults(BuildContext context) {
|
||||
if (query.isEmpty)
|
||||
if (query.isEmpty) {
|
||||
return Container(
|
||||
height: 10,
|
||||
width: 10,
|
||||
|
@ -107,29 +106,31 @@ class MyHomePageDelegate extends SearchDelegate<int> {
|
|||
),
|
||||
),
|
||||
);
|
||||
else if (rssExp.stringMatch(query) != null)
|
||||
} else if (rssExp.stringMatch(query) != null) {
|
||||
return FutureBuilder(
|
||||
future: getRss(rssExp.stringMatch(query)),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError)
|
||||
if (snapshot.hasError) {
|
||||
return invalidRss(context);
|
||||
else if (snapshot.hasData)
|
||||
} else if (snapshot.hasData) {
|
||||
return RssResult(
|
||||
url: rssExp.stringMatch(query),
|
||||
rssFeed: snapshot.data,
|
||||
);
|
||||
else
|
||||
} else {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 200),
|
||||
alignment: Alignment.topCenter,
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
else
|
||||
} else {
|
||||
return SearchList(
|
||||
query: query,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,7 +166,7 @@ class _RssResultState extends State<RssResult> {
|
|||
var subscribeWorker = Provider.of<GroupList>(context, listen: false);
|
||||
final s = context.s;
|
||||
_subscribePodcast(OnlinePodcast podcast) {
|
||||
SubscribeItem item = SubscribeItem(podcast.rss, podcast.title,
|
||||
var item = SubscribeItem(podcast.rss, podcast.title,
|
||||
imgUrl: podcast.image, group: 'Home');
|
||||
subscribeWorker.setSubscribeItem(item);
|
||||
}
|
||||
|
@ -325,7 +326,7 @@ class _RssResultState extends State<RssResult> {
|
|||
ListView.builder(
|
||||
itemCount: math.min(_loadItems + 1, items.length),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == _loadItems)
|
||||
if (index == _loadItems) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 20.0),
|
||||
alignment: Alignment.center,
|
||||
|
@ -344,6 +345,7 @@ class _RssResultState extends State<RssResult> {
|
|||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return ListTile(
|
||||
title: Text(items[index].title),
|
||||
subtitle: Text('${items[index].pubDate}',
|
||||
|
@ -368,12 +370,12 @@ class SearchList extends StatefulWidget {
|
|||
|
||||
class _SearchListState extends State<SearchList> {
|
||||
int _nextOffset = 0;
|
||||
List<OnlinePodcast> _podcastList = [];
|
||||
final List<OnlinePodcast> _podcastList = [];
|
||||
int _offset;
|
||||
bool _loading;
|
||||
OnlinePodcast _selectedPodcast;
|
||||
Future _searchFuture;
|
||||
List<OnlinePodcast> _subscribed = [];
|
||||
final List<OnlinePodcast> _subscribed = [];
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -382,7 +384,7 @@ class _SearchListState extends State<SearchList> {
|
|||
|
||||
Future<List<OnlinePodcast>> _getList(
|
||||
String searchText, int nextOffset) async {
|
||||
SearchEngine searchEngine = SearchEngine();
|
||||
var searchEngine = SearchEngine();
|
||||
var searchResult = await searchEngine.searchPodcasts(
|
||||
searchText: searchText, nextOffset: nextOffset);
|
||||
_offset = searchResult.nextOffset;
|
||||
|
@ -398,13 +400,14 @@ class _SearchListState extends State<SearchList> {
|
|||
children: [
|
||||
FutureBuilder<List>(
|
||||
future: _searchFuture,
|
||||
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
|
||||
if (!snapshot.hasData && widget.query != null)
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData && widget.query != null) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 200),
|
||||
alignment: Alignment.topCenter,
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
var content = snapshot.data;
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
|
@ -519,7 +522,7 @@ class SearchResult extends StatelessWidget {
|
|||
var subscribeWorker = Provider.of<GroupList>(context, listen: false);
|
||||
final s = context.s;
|
||||
subscribePodcast(OnlinePodcast podcast) {
|
||||
SubscribeItem item = SubscribeItem(podcast.rss, podcast.title,
|
||||
var item = SubscribeItem(podcast.rss, podcast.title,
|
||||
imgUrl: podcast.image, group: 'Home');
|
||||
subscribeWorker.setSubscribeItem(item);
|
||||
onSubscribe(podcast);
|
||||
|
@ -647,7 +650,7 @@ class _SearchResultDetailState extends State<SearchResultDetail>
|
|||
int _nextEpisdoeDate = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
/// Search result.
|
||||
List<OnlineEpisode> _episodeList = [];
|
||||
final List<OnlineEpisode> _episodeList = [];
|
||||
|
||||
Future _searchFuture;
|
||||
|
||||
|
@ -683,7 +686,7 @@ class _SearchResultDetailState extends State<SearchResultDetail>
|
|||
|
||||
Future<List<OnlineEpisode>> _getEpisodes(
|
||||
{String id, int nextEpisodeDate}) async {
|
||||
SearchEngine searchEngine = SearchEngine();
|
||||
var searchEngine = SearchEngine();
|
||||
var searchResult = await searchEngine.fetchEpisode(
|
||||
id: id, nextEpisodeDate: nextEpisodeDate);
|
||||
_nextEpisdoeDate = searchResult.nextEpisodeDate;
|
||||
|
@ -761,15 +764,15 @@ class _SearchResultDetailState extends State<SearchResultDetail>
|
|||
var subscribeWorker = Provider.of<GroupList>(context, listen: false);
|
||||
final s = context.s;
|
||||
subscribePodcast(OnlinePodcast podcast) {
|
||||
SubscribeItem item = SubscribeItem(podcast.rss, podcast.title,
|
||||
var item = SubscribeItem(podcast.rss, podcast.title,
|
||||
imgUrl: podcast.image, group: 'Home');
|
||||
subscribeWorker.setSubscribeItem(item);
|
||||
widget.onSubscribe(podcast);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onVerticalDragStart: (event) => _start(event),
|
||||
onVerticalDragUpdate: (event) => _update(event),
|
||||
onVerticalDragStart: _start,
|
||||
onVerticalDragUpdate: _update,
|
||||
onVerticalDragEnd: (event) => _end(),
|
||||
child: SingleChildScrollView(
|
||||
child: Container(
|
||||
|
@ -947,7 +950,7 @@ class _SearchResultDetailState extends State<SearchResultDetail>
|
|||
: null,
|
||||
itemCount: content.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == content.length)
|
||||
if (index == content.length) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10.0, bottom: 20.0),
|
||||
|
@ -986,6 +989,7 @@ class _SearchResultDetailState extends State<SearchResultDetail>
|
|||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return ListTile(
|
||||
title: Text(content[index].title),
|
||||
subtitle: Text(
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../state/setting_state.dart';
|
||||
|
||||
import '../home/home.dart';
|
||||
import '../util/pageroute.dart';
|
||||
import '../state/setting_state.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/pageroute.dart';
|
||||
import 'firstpage.dart';
|
||||
import 'fourthpage.dart';
|
||||
import 'secondpage.dart';
|
||||
import 'thirdpage.dart';
|
||||
import 'firstpage.dart';
|
||||
|
||||
enum Goto { home, settings }
|
||||
|
||||
|
@ -21,7 +22,7 @@ class SlideIntro extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _SlideIntroState extends State<SlideIntro> {
|
||||
List<BoxShadow> _customShadow = [
|
||||
final List<BoxShadow> _customShadow = [
|
||||
BoxShadow(blurRadius: 2, offset: Offset(-2, -2), color: Colors.white54),
|
||||
BoxShadow(
|
||||
blurRadius: 8,
|
||||
|
|
|
@ -38,9 +38,9 @@ class KeyValueStorage {
|
|||
final String key;
|
||||
KeyValueStorage(this.key);
|
||||
Future<List<GroupEntity>> getGroups() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getString(key) == null) {
|
||||
PodcastGroup home = PodcastGroup('Home');
|
||||
var home = PodcastGroup('Home');
|
||||
await prefs.setString(
|
||||
key,
|
||||
json.encode({
|
||||
|
@ -56,7 +56,7 @@ class KeyValueStorage {
|
|||
}
|
||||
|
||||
Future<bool> saveGroup(List<GroupEntity> groupList) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
return prefs.setString(
|
||||
key,
|
||||
json.encode(
|
||||
|
@ -64,23 +64,23 @@ class KeyValueStorage {
|
|||
}
|
||||
|
||||
Future<bool> saveInt(int setting) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
return prefs.setInt(key, setting);
|
||||
}
|
||||
|
||||
Future<int> getInt({int defaultValue = 0}) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getInt(key) == null) await prefs.setInt(key, defaultValue);
|
||||
return prefs.getInt(key);
|
||||
}
|
||||
|
||||
Future<bool> saveStringList(List<String> playList) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
return prefs.setStringList(key, playList);
|
||||
}
|
||||
|
||||
Future<List<String>> getStringList() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getStringList(key) == null) {
|
||||
await prefs.setStringList(key, []);
|
||||
}
|
||||
|
@ -88,12 +88,12 @@ class KeyValueStorage {
|
|||
}
|
||||
|
||||
Future<bool> saveString(String string) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
return prefs.setString(key, string);
|
||||
}
|
||||
|
||||
Future<String> getString() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getString(key) == null) {
|
||||
await prefs.setString(key, '');
|
||||
}
|
||||
|
@ -101,34 +101,35 @@ class KeyValueStorage {
|
|||
}
|
||||
|
||||
saveMenu(List<int> list) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setStringList(key, list.map((e) => e.toString()).toList());
|
||||
}
|
||||
|
||||
Future<List<int>> getMenu() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getStringList(key) == null) {
|
||||
await prefs.setStringList(key, ['0', '1', '12', '13', '14']);
|
||||
}
|
||||
List<String> list = prefs.getStringList(key);
|
||||
return list.map((e) => int.parse(e)).toList();
|
||||
var list = prefs.getStringList(key);
|
||||
return list.map(int.parse).toList();
|
||||
}
|
||||
|
||||
/// Rreverse is used for compatite bool value save before which set true = 0, false = 1
|
||||
Future<bool> getBool(
|
||||
{@required bool defaultValue, bool reverse = false}) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getInt(key) == null)
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getInt(key) == null) {
|
||||
reverse
|
||||
? await prefs.setInt(key, defaultValue ? 0 : 1)
|
||||
: await prefs.setInt(key, defaultValue ? 1 : 0);
|
||||
int i = prefs.getInt(key);
|
||||
}
|
||||
var i = prefs.getInt(key);
|
||||
return reverse ? i == 0 : i == 1;
|
||||
}
|
||||
|
||||
/// Rreverse is used for compatite bool value save before which set true = 0, false = 1
|
||||
saveBool(bool boo, {bool reverse = false}) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
saveBool(boo, {reverse = false}) async {
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
reverse ? prefs.setInt(key, boo ? 0 : 1) : prefs.setInt(key, boo ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import 'package:sqflite/sqflite.dart';
|
||||
import 'dart:async';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
import '../type/episodebrief.dart';
|
||||
import '../webfeed/webfeed.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../type/sub_history.dart';
|
||||
import '../webfeed/webfeed.dart';
|
||||
|
||||
enum Filter { downloaded, liked, search, all }
|
||||
|
||||
|
@ -22,8 +24,8 @@ class DBHelper {
|
|||
|
||||
initDb() async {
|
||||
var documentsDirectory = await getDatabasesPath();
|
||||
String path = join(documentsDirectory, "podcasts.db");
|
||||
Database theDb = await openDatabase(path,
|
||||
var path = join(documentsDirectory, "podcasts.db");
|
||||
var theDb = await openDatabase(path,
|
||||
version: 3, onCreate: _onCreate, onUpgrade: _onUpgrade);
|
||||
return theDb;
|
||||
}
|
||||
|
@ -64,7 +66,7 @@ class DBHelper {
|
|||
|
||||
Future<List<PodcastLocal>> getPodcastLocal(List<String> podcasts) async {
|
||||
var dbClient = await database;
|
||||
List<PodcastLocal> podcastLocal = [];
|
||||
var podcastLocal = <PodcastLocal>[];
|
||||
|
||||
for (var s in podcasts) {
|
||||
List<Map> list;
|
||||
|
@ -72,7 +74,7 @@ class DBHelper {
|
|||
"""SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath , provider,
|
||||
link ,update_count, episode_count FROM PodcastLocal WHERE id = ?""",
|
||||
[s]);
|
||||
if (list.length > 0)
|
||||
if (list.length > 0) {
|
||||
podcastLocal.add(PodcastLocal(
|
||||
list.first['title'],
|
||||
list.first['imageUrl'],
|
||||
|
@ -85,6 +87,7 @@ class DBHelper {
|
|||
list.first['link'],
|
||||
upateCount: list.first['update_count'],
|
||||
episodeCount: list.first['episode_count']));
|
||||
}
|
||||
}
|
||||
return podcastLocal;
|
||||
}
|
||||
|
@ -94,7 +97,7 @@ class DBHelper {
|
|||
List<Map> list = await dbClient.rawQuery(
|
||||
'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath, provider, link FROM PodcastLocal ORDER BY add_date DESC');
|
||||
|
||||
List<PodcastLocal> podcastLocal = [];
|
||||
var podcastLocal = <PodcastLocal>[];
|
||||
|
||||
for (var i in list) {
|
||||
podcastLocal.add(PodcastLocal(
|
||||
|
@ -146,7 +149,7 @@ class DBHelper {
|
|||
return list.first['auto_download'] == 1;
|
||||
}
|
||||
|
||||
Future<int> saveAutoDownload(String id, bool boo) async {
|
||||
Future<int> saveAutoDownload(String id, {bool boo}) async {
|
||||
var dbClient = await database;
|
||||
return await dbClient.rawUpdate(
|
||||
"UPDATE PodcastLocal SET auto_download = ? WHERE id = ?",
|
||||
|
@ -162,7 +165,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future savePodcastLocal(PodcastLocal podcastLocal) async {
|
||||
int _milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
var _milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
var dbClient = await database;
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawInsert(
|
||||
|
@ -194,7 +197,7 @@ class DBHelper {
|
|||
|
||||
Future<int> saveFiresideData(List<String> list) async {
|
||||
var dbClient = await database;
|
||||
int result = await dbClient.rawUpdate(
|
||||
var result = await dbClient.rawUpdate(
|
||||
'UPDATE PodcastLocal SET background_image = ? , hosts = ? WHERE id = ?',
|
||||
[list[1], list[2], list[0]]);
|
||||
print('Fireside data save in sqllite');
|
||||
|
@ -206,7 +209,7 @@ class DBHelper {
|
|||
List<Map> list = await dbClient.rawQuery(
|
||||
'SELECT background_image, hosts FROM PodcastLocal WHERE id = ?', [id]);
|
||||
if (list.length > 0) {
|
||||
List<String> data = [list.first['background_image'], list.first['hosts']];
|
||||
var data = <String>[list.first['background_image'], list.first['hosts']];
|
||||
return data;
|
||||
}
|
||||
return ['', ''];
|
||||
|
@ -219,13 +222,14 @@ class DBHelper {
|
|||
"""SELECT downloaded FROM Episodes WHERE downloaded != 'ND' AND feed_id = ?""",
|
||||
[id]);
|
||||
for (var i in list) {
|
||||
if (i != null)
|
||||
if (i != null) {
|
||||
await FlutterDownloader.remove(
|
||||
taskId: i['downloaded'], shouldDeleteContent: true);
|
||||
}
|
||||
print('Removed all download tasks');
|
||||
}
|
||||
await dbClient.rawDelete('DELETE FROM Episodes WHERE feed_id=?', [id]);
|
||||
int _milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
var _milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
await dbClient.rawUpdate(
|
||||
"""UPDATE SubscribeHistory SET remove_date = ? , status = ? WHERE id = ?""",
|
||||
[_milliseconds, 1, id]);
|
||||
|
@ -233,15 +237,15 @@ class DBHelper {
|
|||
|
||||
Future<int> saveHistory(PlayHistory history) async {
|
||||
var dbClient = await database;
|
||||
int _milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
List<PlayHistory> recent = await getPlayHistory(1);
|
||||
var _milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
var recent = await getPlayHistory(1);
|
||||
if (recent.length == 1) {
|
||||
if (recent.first.url == history.url) {
|
||||
await dbClient.rawDelete("DELETE FROM PlayHistory WHERE add_date = ?",
|
||||
[recent.first.playdate.millisecondsSinceEpoch]);
|
||||
}
|
||||
}
|
||||
int result = await dbClient.transaction((txn) async {
|
||||
var result = await dbClient.transaction((txn) async {
|
||||
return await txn.rawInsert(
|
||||
"""REPLACE INTO PlayHistory (title, enclosure_url, seconds, seek_value, add_date, listen_time)
|
||||
VALUES (?, ?, ?, ?, ?, ?) """,
|
||||
|
@ -263,7 +267,7 @@ class DBHelper {
|
|||
"""SELECT title, enclosure_url, seconds, seek_value, add_date FROM PlayHistory
|
||||
ORDER BY add_date DESC LIMIT ?
|
||||
""", [top]);
|
||||
List<PlayHistory> playHistory = [];
|
||||
var playHistory = <PlayHistory>[];
|
||||
for (var record in list) {
|
||||
playHistory.add(PlayHistory(record['title'], record['enclosure_url'],
|
||||
record['seconds'], record['seek_value'],
|
||||
|
@ -274,12 +278,12 @@ class DBHelper {
|
|||
|
||||
Future<int> isListened(String url) async {
|
||||
var dbClient = await database;
|
||||
int i = 0;
|
||||
var i = 0;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"SELECT listen_time FROM PlayHistory WHERE enclosure_url = ?", [url]);
|
||||
if (list.length == 0)
|
||||
if (list.length == 0) {
|
||||
return 0;
|
||||
else {
|
||||
} else {
|
||||
for (var element in list) {
|
||||
i += element['listen_time'];
|
||||
}
|
||||
|
@ -314,11 +318,13 @@ class DBHelper {
|
|||
List<Map> list = await dbClient.rawQuery(
|
||||
"SELECT seconds FROM PlayHistory WHERE add_date > ? AND add_date < ?",
|
||||
[start, end]);
|
||||
double sum = 0;
|
||||
var sum = 0;
|
||||
if (list.isEmpty) {
|
||||
sum = 0;
|
||||
} else {
|
||||
for (var record in list) sum += record['seconds'];
|
||||
for (var record in list) {
|
||||
sum += record['seconds'];
|
||||
}
|
||||
}
|
||||
return (sum ~/ 60).toDouble();
|
||||
}
|
||||
|
@ -350,14 +356,14 @@ class DBHelper {
|
|||
DateTime _parsePubDate(String pubDate) {
|
||||
if (pubDate == null) return DateTime.now();
|
||||
DateTime date;
|
||||
RegExp yyyy = RegExp(r'[1-2][0-9]{3}');
|
||||
RegExp hhmm = RegExp(r'[0-2][0-9]\:[0-5][0-9]');
|
||||
RegExp ddmmm = RegExp(r'[0-3][0-9]\s[A-Z][a-z]{2}');
|
||||
RegExp mmDd = RegExp(r'([1-2][0-9]{3}\-[0-1]|\s)[0-9]\-[0-3][0-9]');
|
||||
var yyyy = RegExp(r'[1-2][0-9]{3}');
|
||||
var hhmm = RegExp(r'[0-2][0-9]\:[0-5][0-9]');
|
||||
var ddmmm = RegExp(r'[0-3][0-9]\s[A-Z][a-z]{2}');
|
||||
var mmDd = RegExp(r'([1-2][0-9]{3}\-[0-1]|\s)[0-9]\-[0-3][0-9]');
|
||||
// RegExp timezone
|
||||
RegExp z = RegExp(r'(\+|\-)[0-1][0-9]00');
|
||||
String timezone = z.stringMatch(pubDate);
|
||||
int timezoneInt = 0;
|
||||
var z = RegExp(r'(\+|\-)[0-1][0-9]00');
|
||||
var timezone = z.stringMatch(pubDate);
|
||||
var timezoneInt = 0;
|
||||
if (timezone != null) {
|
||||
if (timezone.substring(0, 1) == '-') {
|
||||
timezoneInt = int.parse(timezone.substring(1, 2));
|
||||
|
@ -375,16 +381,16 @@ class DBHelper {
|
|||
date = DateFormat('EEE, dd MMM yyyy HH:mm Z', 'en_US').parse(pubDate);
|
||||
} catch (e) {
|
||||
//parse date using regex, still have issue in parse maonth/day
|
||||
String year = yyyy.stringMatch(pubDate);
|
||||
String time = hhmm.stringMatch(pubDate);
|
||||
String month = ddmmm.stringMatch(pubDate);
|
||||
var year = yyyy.stringMatch(pubDate);
|
||||
var time = hhmm.stringMatch(pubDate);
|
||||
var month = ddmmm.stringMatch(pubDate);
|
||||
if (year != null && time != null && month != null) {
|
||||
date = DateFormat('dd MMM yyyy HH:mm', 'en_US')
|
||||
.parse(month + year + time);
|
||||
} else if (year != null && time != null && month == null) {
|
||||
String month = mmDd.stringMatch(pubDate);
|
||||
date = DateFormat('yyyy-MM-dd HH:mm', 'en_US')
|
||||
.parse(month + ' ' + time);
|
||||
var month = mmDd.stringMatch(pubDate);
|
||||
date =
|
||||
DateFormat('yyyy-MM-dd HH:mm', 'en_US').parse('$month $time');
|
||||
print(date.toString());
|
||||
} else {
|
||||
date = DateTime.now();
|
||||
|
@ -392,7 +398,7 @@ class DBHelper {
|
|||
}
|
||||
}
|
||||
}
|
||||
DateTime result = date
|
||||
var result = date
|
||||
.add(Duration(hours: timezoneInt))
|
||||
.add(DateTime.now().timeZoneOffset);
|
||||
return result;
|
||||
|
@ -410,7 +416,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
bool _isXimalaya(String input) {
|
||||
RegExp ximalaya = RegExp(r"ximalaya.com");
|
||||
var ximalaya = RegExp(r"ximalaya.com");
|
||||
return ximalaya.hasMatch(input);
|
||||
}
|
||||
|
||||
|
@ -430,10 +436,10 @@ class DBHelper {
|
|||
|
||||
Future<int> savePodcastRss(RssFeed feed, String id) async {
|
||||
feed.items.removeWhere((item) => item == null);
|
||||
int result = feed.items.length;
|
||||
var result = feed.items.length;
|
||||
var dbClient = await database;
|
||||
String description, url;
|
||||
for (int i = 0; i < result; i++) {
|
||||
for (var i = 0; i < result; i++) {
|
||||
print(feed.items[i].title);
|
||||
description = _getDescription(feed.items[i].content.value ?? '',
|
||||
feed.items[i].description ?? '', feed.items[i].itunes.summary ?? '');
|
||||
|
@ -472,7 +478,7 @@ class DBHelper {
|
|||
});
|
||||
}
|
||||
}
|
||||
int countUpdate = Sqflite.firstIntValue(await dbClient
|
||||
var countUpdate = Sqflite.firstIntValue(await dbClient
|
||||
.rawQuery('SELECT COUNT(*) FROM Episodes WHERE feed_id = ?', [id]));
|
||||
|
||||
await dbClient.rawUpdate(
|
||||
|
@ -483,25 +489,26 @@ class DBHelper {
|
|||
|
||||
Future<int> updatePodcastRss(PodcastLocal podcastLocal,
|
||||
{int removeMark = 0}) async {
|
||||
BaseOptions options = BaseOptions(
|
||||
var options = BaseOptions(
|
||||
connectTimeout: 20000,
|
||||
receiveTimeout: 20000,
|
||||
);
|
||||
try {
|
||||
Response response = await Dio(options).get(podcastLocal.rssUrl);
|
||||
var response = await Dio(options).get(podcastLocal.rssUrl);
|
||||
if (response.statusCode == 200) {
|
||||
var feed = RssFeed.parse(response.data);
|
||||
String url, description;
|
||||
feed.items.removeWhere((item) => item == null);
|
||||
|
||||
var dbClient = await database;
|
||||
int count = Sqflite.firstIntValue(await dbClient.rawQuery(
|
||||
var count = Sqflite.firstIntValue(await dbClient.rawQuery(
|
||||
'SELECT COUNT(*) FROM Episodes WHERE feed_id = ?',
|
||||
[podcastLocal.id]));
|
||||
if (removeMark == 0)
|
||||
if (removeMark == 0) {
|
||||
await dbClient.rawUpdate(
|
||||
"UPDATE Episodes SET is_new = 0 WHERE feed_id = ?",
|
||||
[podcastLocal.id]);
|
||||
}
|
||||
for (var item in feed.items) {
|
||||
print(item.title);
|
||||
description = _getDescription(item.content.value ?? '',
|
||||
|
@ -541,7 +548,7 @@ class DBHelper {
|
|||
});
|
||||
}
|
||||
}
|
||||
int countUpdate = Sqflite.firstIntValue(await dbClient.rawQuery(
|
||||
var countUpdate = Sqflite.firstIntValue(await dbClient.rawQuery(
|
||||
'SELECT COUNT(*) FROM Episodes WHERE feed_id = ?',
|
||||
[podcastLocal.id]));
|
||||
|
||||
|
@ -557,13 +564,14 @@ class DBHelper {
|
|||
}
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getRssItem(String id, int count, bool reverse,
|
||||
{Filter filter = Filter.all,
|
||||
Future<List<EpisodeBrief>> getRssItem(String id, int count,
|
||||
{bool reverse,
|
||||
Filter filter = Filter.all,
|
||||
String query = '',
|
||||
bool hideListened = false}) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
List<Map> list = [];
|
||||
var episodes = <EpisodeBrief>[];
|
||||
var list = <Map>[];
|
||||
if (count == -1) {
|
||||
switch (filter) {
|
||||
case Filter.all:
|
||||
|
@ -670,7 +678,7 @@ class DBHelper {
|
|||
}
|
||||
}
|
||||
if (list.isNotEmpty) {
|
||||
if (!hideListened)
|
||||
if (!hideListened) {
|
||||
for (var i in list) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
|
@ -684,10 +692,10 @@ class DBHelper {
|
|||
i['imagePath'],
|
||||
i['is_new']));
|
||||
}
|
||||
else
|
||||
} else {
|
||||
for (var i in list) {
|
||||
int listened = await isListened(i['enclosure_url']);
|
||||
if (listened == 0)
|
||||
var listened = await isListened(i['enclosure_url']);
|
||||
if (listened == 0) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
i['enclosure_url'],
|
||||
|
@ -699,30 +707,33 @@ class DBHelper {
|
|||
i['explicit'],
|
||||
i['imagePath'],
|
||||
i['is_new']));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getNewEpisodes(String id) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
var episodes = <EpisodeBrief>[];
|
||||
List<Map> list;
|
||||
if (id == 'all')
|
||||
if (id == 'all') {
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE E.is_new = 1 AND E.downloaded = 'ND' AND P.auto_download = 1 ORDER BY E.milliseconds ASC""",
|
||||
);
|
||||
else
|
||||
} else {
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE E.is_new = 1 AND E.downloaded = 'ND' AND E.feed_id = ? ORDER BY E.milliseconds ASC""",
|
||||
[id]);
|
||||
if (list.isNotEmpty)
|
||||
}
|
||||
if (list.isNotEmpty) {
|
||||
for (var i in list) {
|
||||
episodes.add(EpisodeBrief(
|
||||
i['title'],
|
||||
|
@ -736,12 +747,13 @@ class DBHelper {
|
|||
i['imagePath'],
|
||||
i['is_new']));
|
||||
}
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getRssItemTop(String id) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
var episodes = <EpisodeBrief>[];
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
|
@ -765,7 +777,7 @@ class DBHelper {
|
|||
|
||||
Future<List<EpisodeBrief>> getRecentRssItem(int top) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
var episodes = <EpisodeBrief>[];
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
|
@ -790,9 +802,9 @@ class DBHelper {
|
|||
Future<List<EpisodeBrief>> getGroupRssItem(
|
||||
int top, List<String> group) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
var episodes = <EpisodeBrief>[];
|
||||
if (group.length > 0) {
|
||||
List<String> s = group.map<String>((e) => "'$e'").toList();
|
||||
var s = group.map<String>((e) => "'$e'").toList();
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
|
@ -818,7 +830,7 @@ class DBHelper {
|
|||
|
||||
Future<List<EpisodeBrief>> getRecentNewRssItem() async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
var episodes = <EpisodeBrief>[];
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new, E.media_id,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
|
@ -844,7 +856,7 @@ class DBHelper {
|
|||
|
||||
Future<List<EpisodeBrief>> getOutdatedEpisode(int deadline) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
var episodes = <EpisodeBrief>[];
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
|
@ -869,10 +881,10 @@ class DBHelper {
|
|||
|
||||
Future<List<EpisodeBrief>> getDownloadedEpisode(int mode) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
var episodes = <EpisodeBrief>[];
|
||||
List<Map> list;
|
||||
//Ordered by date
|
||||
if (mode == 0)
|
||||
if (mode == 0) {
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date, E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
|
@ -880,8 +892,7 @@ class DBHelper {
|
|||
WHERE E.enclosure_url != E.media_id
|
||||
ORDER BY E.download_date DESC""",
|
||||
);
|
||||
//Ordered by date
|
||||
else if (mode == 1)
|
||||
} else if (mode == 1) {
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date,E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
|
@ -889,8 +900,7 @@ class DBHelper {
|
|||
WHERE E.enclosure_url != E.media_id
|
||||
ORDER BY E.download_date ASC""",
|
||||
);
|
||||
//Ordered by size
|
||||
else if (mode == 2)
|
||||
} else if (mode == 2) {
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date,E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
|
@ -898,6 +908,7 @@ class DBHelper {
|
|||
WHERE E.enclosure_url != E.media_id
|
||||
ORDER BY E.enclosure_length DESC""",
|
||||
);
|
||||
}
|
||||
for (var i in list) {
|
||||
episodes.add(
|
||||
EpisodeBrief(
|
||||
|
@ -926,9 +937,9 @@ class DBHelper {
|
|||
|
||||
Future<List<EpisodeBrief>> getGroupNewRssItem(List<String> group) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
var episodes = <EpisodeBrief>[];
|
||||
if (group.length > 0) {
|
||||
List<String> s = group.map<String>((e) => "'$e'").toList();
|
||||
var s = group.map<String>((e) => "'$e'").toList();
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new, E.media_id,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
|
@ -957,7 +968,7 @@ class DBHelper {
|
|||
removeGroupNewMark(List<String> group) async {
|
||||
var dbClient = await database;
|
||||
if (group.length > 0) {
|
||||
List<String> s = group.map<String>((e) => "'$e'").toList();
|
||||
var s = group.map<String>((e) => "'$e'").toList();
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawUpdate(
|
||||
"UPDATE Episodes SET is_new = 0 WHERE feed_id in (${s.join(',')})");
|
||||
|
@ -976,7 +987,7 @@ class DBHelper {
|
|||
|
||||
Future<List<EpisodeBrief>> getLikedRssItem(int i, int sortBy) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
var episodes = <EpisodeBrief>[];
|
||||
if (sortBy == 0) {
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||
|
@ -1021,7 +1032,7 @@ class DBHelper {
|
|||
|
||||
setLiked(String url) async {
|
||||
var dbClient = await database;
|
||||
int milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
var milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawUpdate(
|
||||
"UPDATE Episodes SET liked = 1, liked_date = ? WHERE enclosure_url= ?",
|
||||
|
@ -1053,7 +1064,7 @@ class DBHelper {
|
|||
|
||||
Future<int> saveDownloaded(String url, String id) async {
|
||||
var dbClient = await database;
|
||||
int milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
var milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
int count;
|
||||
await dbClient.transaction((txn) async {
|
||||
count = await txn.rawUpdate(
|
||||
|
@ -1065,7 +1076,7 @@ class DBHelper {
|
|||
|
||||
Future<int> saveMediaId(String url, String path, String id, int size) async {
|
||||
var dbClient = await database;
|
||||
int milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
var milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
int count;
|
||||
await dbClient.transaction((txn) async {
|
||||
count = await txn.rawUpdate(
|
||||
|
@ -1083,7 +1094,7 @@ class DBHelper {
|
|||
"UPDATE Episodes SET downloaded = 'ND', media_id = ? WHERE enclosure_url = ?",
|
||||
[url, url]);
|
||||
});
|
||||
print('Deleted ' + url);
|
||||
print('Deleted $url');
|
||||
return count;
|
||||
}
|
||||
|
||||
|
@ -1139,9 +1150,9 @@ class DBHelper {
|
|||
P.title as feed_title, E.duration, E.explicit, P.skip_seconds,E.is_new,
|
||||
P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE E.media_id = ?""", [id]);
|
||||
if (list.isEmpty)
|
||||
if (list.isEmpty) {
|
||||
return null;
|
||||
else {
|
||||
} else {
|
||||
episode = EpisodeBrief(
|
||||
list.first['title'],
|
||||
list.first['enclosure_url'],
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import 'package:feature_discovery/feature_discovery.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:feature_discovery/feature_discovery.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'generated/l10n.dart';
|
||||
import 'state/podcast_group.dart';
|
||||
import 'state/audio_state.dart';
|
||||
import 'state/setting_state.dart';
|
||||
import 'state/download_state.dart';
|
||||
import 'state/refresh_podcast.dart';
|
||||
import 'home/home.dart';
|
||||
import 'intro_slider/app_intro.dart';
|
||||
import 'state/audio_state.dart';
|
||||
import 'state/download_state.dart';
|
||||
import 'state/podcast_group.dart';
|
||||
import 'state/refresh_podcast.dart';
|
||||
import 'state/setting_state.dart';
|
||||
|
||||
final SettingState themeSetting = SettingState();
|
||||
Future main() async {
|
||||
|
@ -38,7 +38,7 @@ Future main() async {
|
|||
child: MyApp(),
|
||||
),
|
||||
);
|
||||
SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(
|
||||
var systemUiOverlayStyle = SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
systemNavigationBarColor: Colors.transparent);
|
||||
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:connectivity/connectivity.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:tsacdop/state/download_state.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../util/episodegrid.dart';
|
||||
import '../home/audioplayer.dart';
|
||||
import '../type/fireside_data.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/general_dialog.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../state/audio_state.dart';
|
||||
import '../state/download_state.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../type/fireside_data.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/episodegrid.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/general_dialog.dart';
|
||||
|
||||
class PodcastDetail extends StatefulWidget {
|
||||
PodcastDetail({Key key, @required this.podcastLocal, this.hide = false})
|
||||
|
@ -105,27 +105,28 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
|
||||
bool autoDownload = await dbHelper.getAutoDownload(podcastLocal.id);
|
||||
var autoDownload = await dbHelper.getAutoDownload(podcastLocal.id);
|
||||
if (autoDownload) {
|
||||
var downloader = Provider.of<DownloadState>(context, listen: false);
|
||||
var result = await Connectivity().checkConnectivity();
|
||||
KeyValueStorage autoDownloadStorage =
|
||||
KeyValueStorage(autoDownloadNetworkKey);
|
||||
int autoDownloadNetwork = await autoDownloadStorage.getInt();
|
||||
var autoDownloadStorage = KeyValueStorage(autoDownloadNetworkKey);
|
||||
var autoDownloadNetwork = await autoDownloadStorage.getInt();
|
||||
if (autoDownloadNetwork == 1) {
|
||||
List<EpisodeBrief> episodes =
|
||||
await dbHelper.getNewEpisodes(podcastLocal.id);
|
||||
var episodes = await dbHelper.getNewEpisodes(podcastLocal.id);
|
||||
// For safety
|
||||
if (episodes.length < 100)
|
||||
for (var episode in episodes)
|
||||
if (episodes.length < 100) {
|
||||
for (var episode in episodes) {
|
||||
downloader.startTask(episode, showNotification: false);
|
||||
}
|
||||
}
|
||||
} else if (result == ConnectivityResult.wifi) {
|
||||
List<EpisodeBrief> episodes =
|
||||
await dbHelper.getNewEpisodes(podcastLocal.id);
|
||||
var episodes = await dbHelper.getNewEpisodes(podcastLocal.id);
|
||||
//For safety
|
||||
if (episodes.length < 100)
|
||||
for (var episode in episodes)
|
||||
if (episodes.length < 100) {
|
||||
for (var episode in episodes) {
|
||||
downloader.startTask(episode, showNotification: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -140,16 +141,19 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
Future<List<EpisodeBrief>> _getRssItem(PodcastLocal podcastLocal,
|
||||
{int count, bool reverse, Filter filter, String query}) async {
|
||||
var dbHelper = DBHelper();
|
||||
List<EpisodeBrief> episodes = [];
|
||||
var episodes = <EpisodeBrief>[];
|
||||
_episodeCount = await dbHelper.getPodcastCounts(podcastLocal.id);
|
||||
KeyValueStorage storage = KeyValueStorage(podcastLayoutKey);
|
||||
int index = await storage.getInt(defaultValue: 1);
|
||||
var storage = KeyValueStorage(podcastLayoutKey);
|
||||
var index = await storage.getInt(defaultValue: 1);
|
||||
if (_layout == null) _layout = Layout.values[index];
|
||||
episodes = await dbHelper.getRssItem(podcastLocal.id, count, reverse,
|
||||
filter: filter, query: query, hideListened: _hideListened);
|
||||
episodes = await dbHelper.getRssItem(podcastLocal.id, count,
|
||||
reverse: reverse,
|
||||
filter: filter,
|
||||
query: query,
|
||||
hideListened: _hideListened);
|
||||
|
||||
if (podcastLocal.provider.contains('fireside')) {
|
||||
FiresideData data = FiresideData(podcastLocal.id, podcastLocal.link);
|
||||
var data = FiresideData(podcastLocal.id, podcastLocal.link);
|
||||
await data.getData();
|
||||
_backgroundImage = data.background;
|
||||
_hosts = data.hosts;
|
||||
|
@ -158,14 +162,12 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
}
|
||||
|
||||
_markListened(String podcastId) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
List<EpisodeBrief> episodes =
|
||||
await dbHelper.getRssItem(podcastId, -1, true);
|
||||
var dbHelper = DBHelper();
|
||||
var episodes = await dbHelper.getRssItem(podcastId, -1, reverse: true);
|
||||
for (var episode in episodes) {
|
||||
bool marked = await dbHelper.checkMarked(episode);
|
||||
var marked = await dbHelper.checkMarked(episode);
|
||||
if (!marked) {
|
||||
final PlayHistory history =
|
||||
PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
final history = PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
await dbHelper.saveHistory(history);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
@ -316,7 +318,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
return _customPopupMenu(
|
||||
tooltip: s.menu,
|
||||
clip: false,
|
||||
onSelected: (int value) {
|
||||
onSelected: (value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
widget.podcastLocal.link.launchUrl;
|
||||
|
@ -443,9 +445,9 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
)
|
||||
],
|
||||
onSelected: (value) {
|
||||
if (value == 0)
|
||||
if (value == 0) {
|
||||
setState(() => _reverse = false);
|
||||
else if (value == 1) setState(() => _reverse = true);
|
||||
} else if (value == 1) setState(() => _reverse = true);
|
||||
},
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
|
@ -538,25 +540,28 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
onSelected: (value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
if (_filter != Filter.all)
|
||||
if (_filter != Filter.all) {
|
||||
setState(() {
|
||||
_filter = Filter.all;
|
||||
_query = '';
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (_filter != Filter.liked)
|
||||
if (_filter != Filter.liked) {
|
||||
setState(() {
|
||||
_query = '';
|
||||
_filter = Filter.liked;
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (_filter != Filter.downloaded)
|
||||
if (_filter != Filter.downloaded) {
|
||||
setState(() {
|
||||
_query = '';
|
||||
_filter = Filter.downloaded;
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
showGeneralDialog(
|
||||
|
@ -566,17 +571,16 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
.modalBarrierDismissLabel,
|
||||
barrierColor: Colors.black54,
|
||||
transitionDuration: const Duration(milliseconds: 200),
|
||||
pageBuilder: (BuildContext context,
|
||||
Animation animaiton,
|
||||
Animation secondaryAnimation) =>
|
||||
SearchEpisdoe(
|
||||
onSearch: (query) {
|
||||
setState(() {
|
||||
_query = query;
|
||||
_filter = Filter.search;
|
||||
});
|
||||
},
|
||||
));
|
||||
pageBuilder:
|
||||
(context, animaiton, secondaryAnimation) =>
|
||||
SearchEpisdoe(
|
||||
onSearch: (query) {
|
||||
setState(() {
|
||||
_query = query;
|
||||
_filter = Filter.search;
|
||||
});
|
||||
},
|
||||
));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
@ -605,18 +609,19 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (_layout == Layout.three)
|
||||
if (_layout == Layout.three) {
|
||||
setState(() {
|
||||
_layout = Layout.one;
|
||||
});
|
||||
else if (_layout == Layout.two)
|
||||
} else if (_layout == Layout.two) {
|
||||
setState(() {
|
||||
_layout = Layout.three;
|
||||
});
|
||||
else
|
||||
} else {
|
||||
setState(() {
|
||||
_layout = Layout.two;
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: _layout == Layout.three
|
||||
? IconPainter(
|
||||
|
@ -638,7 +643,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color _color = widget.podcastLocal.primaryColor.colorizedark();
|
||||
var _color = widget.podcastLocal.primaryColor.colorizedark();
|
||||
final s = context.s;
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
|
@ -683,20 +688,23 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
_controller
|
||||
.position.maxScrollExtent &&
|
||||
snapshot.data.length == _top) {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() => _loadMore = true);
|
||||
}
|
||||
await Future.delayed(
|
||||
Duration(seconds: 3));
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_top = _top + 36;
|
||||
_loadMore = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (_controller.offset > 0 &&
|
||||
mounted &&
|
||||
!_scroll)
|
||||
!_scroll) {
|
||||
setState(() => _scroll = true);
|
||||
}
|
||||
}),
|
||||
physics:
|
||||
const AlwaysScrollableScrollPhysics(),
|
||||
|
@ -806,7 +814,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
(context, index) {
|
||||
return _loadMore
|
||||
? Container(
|
||||
height: 2,
|
||||
|
@ -856,7 +864,7 @@ class _AboutPodcastState extends State<AboutPodcast> {
|
|||
bool _expand;
|
||||
void getDescription(String id) async {
|
||||
var dbHelper = DBHelper();
|
||||
String description = await dbHelper.getFeedDescription(id);
|
||||
var description = await dbHelper.getFeedDescription(id);
|
||||
if (description == null || description.isEmpty) {
|
||||
_description = '';
|
||||
} else {
|
||||
|
|
|
@ -8,9 +8,9 @@ import 'package:fluttertoast/fluttertoast.dart';
|
|||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../util/duraiton_picker.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/general_dialog.dart';
|
||||
|
@ -33,20 +33,18 @@ class _PodcastGroupListState extends State<PodcastGroupList> {
|
|||
: Container(
|
||||
color: Theme.of(context).primaryColor,
|
||||
child: ReorderableListView(
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
setState(() {
|
||||
if (newIndex > oldIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final PodcastLocal podcast =
|
||||
widget.group.podcasts.removeAt(oldIndex);
|
||||
final podcast = widget.group.podcasts.removeAt(oldIndex);
|
||||
widget.group.podcasts.insert(newIndex, podcast);
|
||||
});
|
||||
widget.group.setOrderedPodcasts = widget.group.podcasts;
|
||||
groupList.addToOrderChanged(widget.group);
|
||||
},
|
||||
children: widget.group.podcasts
|
||||
.map<Widget>((PodcastLocal podcastLocal) {
|
||||
children: widget.group.podcasts.map<Widget>((podcastLocal) {
|
||||
return Container(
|
||||
decoration:
|
||||
BoxDecoration(color: Theme.of(context).primaryColor),
|
||||
|
@ -84,7 +82,7 @@ class _PodcastCardState extends State<PodcastCard>
|
|||
|
||||
Future<int> getSkipSecond(String id) async {
|
||||
var dbHelper = DBHelper();
|
||||
int seconds = await dbHelper.getSkipSeconds(id);
|
||||
var seconds = await dbHelper.getSkipSeconds(id);
|
||||
_skipSeconds = seconds;
|
||||
return seconds;
|
||||
}
|
||||
|
@ -95,23 +93,22 @@ class _PodcastCardState extends State<PodcastCard>
|
|||
}
|
||||
|
||||
_setAutoDownload(String id, bool boo) async {
|
||||
bool permission = await _checkPermmison();
|
||||
var permission = await _checkPermmison();
|
||||
if (permission) {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
await dbHelper.saveAutoDownload(id, boo);
|
||||
var dbHelper = DBHelper();
|
||||
await dbHelper.saveAutoDownload(id, boo: boo);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _getAutoDownload(String id) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
var dbHelper = DBHelper();
|
||||
return await dbHelper.getAutoDownload(id);
|
||||
}
|
||||
|
||||
Future<bool> _checkPermmison() async {
|
||||
PermissionStatus permission = await Permission.storage.status;
|
||||
var permission = await Permission.storage.status;
|
||||
if (permission != PermissionStatus.granted) {
|
||||
Map<Permission, PermissionStatus> permissions =
|
||||
await [Permission.storage].request();
|
||||
var permissions = await [Permission.storage].request();
|
||||
if (permissions[Permission.storage] == PermissionStatus.granted) {
|
||||
return true;
|
||||
} else {
|
||||
|
@ -169,11 +166,11 @@ class _PodcastCardState extends State<PodcastCard>
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color _c = (Theme.of(context).brightness == Brightness.light)
|
||||
var _c = (Theme.of(context).brightness == Brightness.light)
|
||||
? widget.podcastLocal.primaryColor.colorizedark()
|
||||
: widget.podcastLocal.primaryColor.colorizeLight();
|
||||
final s = context.s;
|
||||
double _width = MediaQuery.of(context).size.width;
|
||||
var _width = MediaQuery.of(context).size.width;
|
||||
var groupList = context.watch<GroupList>();
|
||||
_belongGroups = groupList.getPodcastGroup(widget.podcastLocal.id);
|
||||
|
||||
|
@ -190,10 +187,11 @@ class _PodcastCardState extends State<PodcastCard>
|
|||
onTap: () => setState(
|
||||
() {
|
||||
_loadMenu = !_loadMenu;
|
||||
if (_value == 0)
|
||||
if (_value == 0) {
|
||||
_controller.forward();
|
||||
else
|
||||
} else {
|
||||
_controller.reverse();
|
||||
}
|
||||
},
|
||||
),
|
||||
child: Container(
|
||||
|
@ -278,8 +276,8 @@ class _PodcastCardState extends State<PodcastCard>
|
|||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: groupList.groups
|
||||
.map<Widget>((PodcastGroup group) {
|
||||
children:
|
||||
groupList.groups.map<Widget>((group) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 5.0),
|
||||
child: FilterChip(
|
||||
|
@ -287,7 +285,7 @@ class _PodcastCardState extends State<PodcastCard>
|
|||
label: Text(group.name),
|
||||
selected:
|
||||
_selectedGroups.contains(group),
|
||||
onSelected: (bool value) {
|
||||
onSelected: (value) {
|
||||
setState(() {
|
||||
if (!value) {
|
||||
_selectedGroups.remove(group);
|
||||
|
@ -328,11 +326,12 @@ class _PodcastCardState extends State<PodcastCard>
|
|||
msg: s.toastSettingSaved,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else
|
||||
} else {
|
||||
Fluttertoast.showToast(
|
||||
msg: s.toastOneGroup,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: Icon(Icons.done),
|
||||
),
|
||||
|
@ -400,11 +399,8 @@ class _PodcastCardState extends State<PodcastCard>
|
|||
Icons.fast_forward,
|
||||
size: _value == 0 ? 1 : 20 * (_value),
|
||||
),
|
||||
tooltip: 'Skip' +
|
||||
(snapshot.data == 0
|
||||
? ''
|
||||
: _stringForSeconds(
|
||||
snapshot.data.toDouble())),
|
||||
tooltip:
|
||||
'Skip${snapshot.data == 0 ? '' : _stringForSeconds(snapshot.data.toDouble())}',
|
||||
onTap: () {
|
||||
generalDialog(
|
||||
context,
|
||||
|
@ -550,7 +546,7 @@ class _RenameGroupState extends State<RenameGroup> {
|
|||
if (list.contains(_newName)) {
|
||||
setState(() => _error = 1);
|
||||
} else {
|
||||
PodcastGroup newGroup = PodcastGroup(_newName,
|
||||
var newGroup = PodcastGroup(_newName,
|
||||
color: widget.group.color,
|
||||
id: widget.group.id,
|
||||
podcastList: widget.group.podcastList);
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import 'dart:math' as math;
|
||||
|
||||
import 'package:feature_discovery/feature_discovery.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:feature_discovery/feature_discovery.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../state/podcast_group.dart';
|
||||
import 'podcast_group.dart';
|
||||
import 'podcastlist.dart';
|
||||
import '../util/pageroute.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/general_dialog.dart';
|
||||
import '../util/pageroute.dart';
|
||||
import 'custom_tabview.dart';
|
||||
import 'podcast_group.dart';
|
||||
import 'podcastlist.dart';
|
||||
|
||||
const String addGroupFeature = 'addGroupFeature';
|
||||
const String configureGroup = 'configureFeature';
|
||||
|
@ -50,10 +50,11 @@ class _PodcastManageState extends State<PodcastManage>
|
|||
duration: const Duration(milliseconds: 500), vsync: this);
|
||||
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
|
||||
..addListener(() {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_fraction = _animation.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
_menuAnimation = Tween(begin: 0.0, end: 1.0)
|
||||
.animate(CurvedAnimation(parent: _menuController, curve: Curves.easeIn))
|
||||
|
@ -69,7 +70,7 @@ class _PodcastManageState extends State<PodcastManage>
|
|||
}
|
||||
});
|
||||
FeatureDiscovery.isDisplayed(context, addGroupFeature).then((value) {
|
||||
if (!value)
|
||||
if (!value) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
FeatureDiscovery.discoverFeatures(context, const <String>{
|
||||
addGroupFeature,
|
||||
|
@ -77,6 +78,7 @@ class _PodcastManageState extends State<PodcastManage>
|
|||
configurePodcast
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -247,8 +249,7 @@ class _PodcastManageState extends State<PodcastManage>
|
|||
.modalBarrierDismissLabel,
|
||||
barrierColor: Colors.black54,
|
||||
transitionDuration: const Duration(milliseconds: 200),
|
||||
pageBuilder: (BuildContext context, Animation animaiton,
|
||||
Animation secondaryAnimation) =>
|
||||
pageBuilder: (context, animaiton, secondaryAnimation) =>
|
||||
AddGroup()),
|
||||
icon: Icon(Icons.add)),
|
||||
),
|
||||
|
@ -262,8 +263,8 @@ class _PodcastManageState extends State<PodcastManage>
|
|||
return true;
|
||||
},
|
||||
child: Consumer<GroupList>(builder: (_, groupList, __) {
|
||||
bool _isLoading = groupList.isLoading;
|
||||
List<PodcastGroup> _groups = groupList.groups;
|
||||
var _isLoading = groupList.isLoading;
|
||||
var _groups = groupList.groups;
|
||||
return _isLoading
|
||||
? Center()
|
||||
: Stack(
|
||||
|
@ -390,11 +391,9 @@ class _PodcastManageState extends State<PodcastManage>
|
|||
transitionDuration:
|
||||
const Duration(
|
||||
milliseconds: 300),
|
||||
pageBuilder: (BuildContext
|
||||
context,
|
||||
Animation animaiton,
|
||||
Animation
|
||||
secondaryAnimation) =>
|
||||
pageBuilder: (context,
|
||||
animaiton,
|
||||
secondaryAnimation) =>
|
||||
RenameGroup(
|
||||
group: _groups[_index],
|
||||
));
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import 'podcast_detail.dart';
|
||||
import '../util/pageroute.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/pageroute.dart';
|
||||
import 'podcast_detail.dart';
|
||||
|
||||
class AboutPodcast extends StatefulWidget {
|
||||
final PodcastLocal podcastLocal;
|
||||
|
@ -29,7 +29,7 @@ class _AboutPodcastState extends State<AboutPodcast> {
|
|||
|
||||
void getDescription(String id) async {
|
||||
var dbHelper = DBHelper();
|
||||
String description = await dbHelper.getFeedDescription(id);
|
||||
var description = await dbHelper.getFeedDescription(id);
|
||||
_description = description;
|
||||
setState(() {
|
||||
_load = true;
|
||||
|
@ -100,7 +100,7 @@ class _PodcastListState extends State<PodcastList> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double _width = MediaQuery.of(context).size.width;
|
||||
var _width = MediaQuery.of(context).size.width;
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
|
||||
|
@ -132,7 +132,7 @@ class _PodcastListState extends State<PodcastList> {
|
|||
crossAxisCount: 3,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
(context, index) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
|
@ -153,9 +153,8 @@ class _PodcastListState extends State<PodcastList> {
|
|||
barrierColor: Colors.black54,
|
||||
transitionDuration:
|
||||
const Duration(milliseconds: 200),
|
||||
pageBuilder: (BuildContext context,
|
||||
Animation animaiton,
|
||||
Animation secondaryAnimation) =>
|
||||
pageBuilder: (context, animaiton,
|
||||
secondaryAnimation) =>
|
||||
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness:
|
||||
|
|
|
@ -2,18 +2,17 @@ import 'dart:convert';
|
|||
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../type/searchpodcast.dart';
|
||||
import '../type/searchepisodes.dart';
|
||||
import '../.env.dart';
|
||||
import '../type/searchepisodes.dart';
|
||||
import '../type/searchpodcast.dart';
|
||||
|
||||
class SearchEngine {
|
||||
Future<SearchPodcast<dynamic>> searchPodcasts(
|
||||
{String searchText, int nextOffset}) async {
|
||||
String apiKey = environment['apiKey'];
|
||||
String url = "https://listen-api.listennotes.com/api/v2/search?q=" +
|
||||
Uri.encodeComponent(searchText) +
|
||||
"&sort_by_date=0&type=podcast&offset=$nextOffset";
|
||||
Response response = await Dio().get(url,
|
||||
var apiKey = environment['apiKey'];
|
||||
var url = "https://listen-api.listennotes.com/api/v2/search?q="
|
||||
"${Uri.encodeComponent(searchText)}${"&sort_by_date=0&type=podcast&offset=$nextOffset"}";
|
||||
var response = await Dio().get(url,
|
||||
options: Options(headers: {
|
||||
'X-ListenAPI-Key': "$apiKey",
|
||||
'Accept': "application/json"
|
||||
|
@ -25,10 +24,10 @@ class SearchEngine {
|
|||
|
||||
Future<SearchEpisodes<dynamic>> fetchEpisode(
|
||||
{String id, int nextEpisodeDate}) async {
|
||||
String apiKey = environment['apiKey'];
|
||||
String url =
|
||||
var apiKey = environment['apiKey'];
|
||||
var url =
|
||||
"https://listen-api.listennotes.com/api/v2/podcasts/$id?next_episode_pub_date=$nextEpisodeDate";
|
||||
Response response = await Dio().get(url,
|
||||
var response = await Dio().get(url,
|
||||
options: Options(headers: {
|
||||
'X-ListenAPI-Key': "$apiKey",
|
||||
'Accept': "application/json"
|
||||
|
|
|
@ -35,7 +35,7 @@ class PodcastsBackup {
|
|||
builder.element('outline', nest: () {
|
||||
builder.attribute('text', '${group.name}');
|
||||
builder.attribute('title', '${group.name}');
|
||||
for (var e in group.podcasts)
|
||||
for (var e in group.podcasts) {
|
||||
builder.element(
|
||||
'outline',
|
||||
nest: () {
|
||||
|
@ -46,6 +46,7 @@ class PodcastsBackup {
|
|||
},
|
||||
isSelfClosing: true,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -54,15 +55,15 @@ class PodcastsBackup {
|
|||
}
|
||||
|
||||
static parseOMPL(File file) {
|
||||
Map<String, List<OmplOutline>> data = Map();
|
||||
String opml = file.readAsStringSync();
|
||||
var data = <String, List<OmplOutline>>{};
|
||||
var opml = file.readAsStringSync();
|
||||
var content = xml.XmlDocument.parse(opml);
|
||||
String title =
|
||||
var title =
|
||||
content.findAllElements('head').first.findElements('title').first.text;
|
||||
print(title);
|
||||
var groups = content.findAllElements('body').first.findElements('outline');
|
||||
if (title != 'Tsacdop Feed Groups') {
|
||||
List<OmplOutline> total = content
|
||||
var total = content
|
||||
.findAllElements('outline')
|
||||
.map((ele) => OmplOutline.parse(ele))
|
||||
.toList();
|
||||
|
@ -72,7 +73,7 @@ class PodcastsBackup {
|
|||
}
|
||||
|
||||
for (var element in groups) {
|
||||
String title = element.getAttribute('title');
|
||||
var title = element.getAttribute('title');
|
||||
var total = element
|
||||
.findElements('outline')
|
||||
.map((ele) => OmplOutline.parse(ele))
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tsacdop/type/settings_backup.dart';
|
||||
import 'package:wc_flutter_share/wc_flutter_share.dart';
|
||||
|
||||
import '../service/ompl_build.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../state/setting_state.dart';
|
||||
import '../type/settings_backup.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../service/ompl_build.dart';
|
||||
|
||||
class DataBackup extends StatefulWidget {
|
||||
@override
|
||||
|
@ -29,8 +28,8 @@ class _DataBackupState extends State<DataBackup> {
|
|||
var groups = context.read<GroupList>().groups;
|
||||
var ompl = PodcastsBackup(groups).omplBuilder();
|
||||
var tempdir = await getTemporaryDirectory();
|
||||
DateTime now = DateTime.now();
|
||||
String datePlus = now.year.toString() +
|
||||
var now = DateTime.now();
|
||||
var datePlus = now.year.toString() +
|
||||
now.month.toString() +
|
||||
now.day.toString() +
|
||||
now.second.toString();
|
||||
|
@ -45,7 +44,7 @@ class _DataBackupState extends State<DataBackup> {
|
|||
}
|
||||
|
||||
Future<void> _shareFile(File file) async {
|
||||
final Uint8List bytes = await file.readAsBytes();
|
||||
final bytes = await file.readAsBytes();
|
||||
await WcFlutterShare.share(
|
||||
sharePopupTitle: 'share Clip',
|
||||
fileName: file.path.split('/').last,
|
||||
|
@ -55,11 +54,11 @@ class _DataBackupState extends State<DataBackup> {
|
|||
|
||||
Future<File> _exportSetting(BuildContext context) async {
|
||||
var settings = context.read<SettingState>();
|
||||
SettingsBackup settingsBack = await settings.backup();
|
||||
var settingsBack = await settings.backup();
|
||||
var json = settingsBack.toJson();
|
||||
var tempdir = await getTemporaryDirectory();
|
||||
DateTime now = DateTime.now();
|
||||
String datePlus = now.year.toString() +
|
||||
var now = DateTime.now();
|
||||
var datePlus = now.year.toString() +
|
||||
now.month.toString() +
|
||||
now.day.toString() +
|
||||
now.second.toString();
|
||||
|
@ -71,10 +70,10 @@ class _DataBackupState extends State<DataBackup> {
|
|||
Future _importSetting(String path, BuildContext context) async {
|
||||
final s = context.s;
|
||||
var settings = context.read<SettingState>();
|
||||
File file = File(path);
|
||||
var file = File(path);
|
||||
try {
|
||||
String json = file.readAsStringSync();
|
||||
SettingsBackup backup = SettingsBackup.fromJson(jsonDecode(json));
|
||||
var json = file.readAsStringSync();
|
||||
var backup = SettingsBackup.fromJson(jsonDecode(json));
|
||||
await settings.restore(backup);
|
||||
Fluttertoast.showToast(
|
||||
msg: s.toastImportSettingsSuccess,
|
||||
|
@ -92,11 +91,11 @@ class _DataBackupState extends State<DataBackup> {
|
|||
void _getFilePath(BuildContext context) async {
|
||||
final s = context.s;
|
||||
try {
|
||||
String filePath = await FilePicker.getFilePath(type: FileType.any);
|
||||
var filePath = await FilePicker.getFilePath(type: FileType.any);
|
||||
if (filePath == '') {
|
||||
return;
|
||||
}
|
||||
print('File Path' + filePath);
|
||||
print('File Path$filePath');
|
||||
Fluttertoast.showToast(
|
||||
msg: s.toastReadFile,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
|
@ -166,7 +165,7 @@ class _DataBackupState extends State<DataBackup> {
|
|||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
File file = await _exportOmpl(context);
|
||||
var file = await _exportOmpl(context);
|
||||
await _saveFile(file);
|
||||
}),
|
||||
SizedBox(width: 10),
|
||||
|
@ -188,7 +187,7 @@ class _DataBackupState extends State<DataBackup> {
|
|||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
File file = await _exportOmpl(context);
|
||||
var file = await _exportOmpl(context);
|
||||
await _shareFile(file);
|
||||
})
|
||||
],
|
||||
|
@ -230,7 +229,7 @@ class _DataBackupState extends State<DataBackup> {
|
|||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
File file = await _exportSetting(context);
|
||||
var file = await _exportSetting(context);
|
||||
await _saveFile(file);
|
||||
}),
|
||||
SizedBox(width: 10),
|
||||
|
@ -253,7 +252,7 @@ class _DataBackupState extends State<DataBackup> {
|
|||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
File file = await _exportSetting(context);
|
||||
var file = await _exportSetting(context);
|
||||
await _shareFile(file);
|
||||
}),
|
||||
SizedBox(width: 10),
|
||||
|
|
|
@ -2,16 +2,16 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../state/download_state.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../state/download_state.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
|
||||
class DownloadsManage extends StatefulWidget {
|
||||
@override
|
||||
|
@ -29,14 +29,14 @@ class _DownloadsManageState extends State<DownloadsManage> {
|
|||
List<EpisodeBrief> _selectedList;
|
||||
|
||||
Future<List<EpisodeBrief>> _getDownloadedEpisode(int mode) async {
|
||||
List<EpisodeBrief> episodes = [];
|
||||
var episodes = <EpisodeBrief>[];
|
||||
var dbHelper = DBHelper();
|
||||
episodes = await dbHelper.getDownloadedEpisode(mode);
|
||||
return episodes;
|
||||
}
|
||||
|
||||
Future<int> _isListened(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
var dbHelper = DBHelper();
|
||||
return await dbHelper.isListened(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
|
@ -60,16 +60,17 @@ class _DownloadsManageState extends State<DownloadsManage> {
|
|||
_delSelectedEpisodes() async {
|
||||
setState(() => _clearing = true);
|
||||
// await Future.forEach(_selectedList, (EpisodeBrief episode) async
|
||||
for (EpisodeBrief episode in _selectedList) {
|
||||
for (var episode in _selectedList) {
|
||||
var downloader = Provider.of<DownloadState>(context, listen: false);
|
||||
await downloader.delTask(episode);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_clearing = false;
|
||||
});
|
||||
}
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
if (mounted) setState(() => _selectedList = []);
|
||||
_getStorageSize();
|
||||
|
@ -78,7 +79,7 @@ class _DownloadsManageState extends State<DownloadsManage> {
|
|||
String _downloadDateToString(BuildContext context,
|
||||
{int downloadDate, int pubDate}) {
|
||||
final s = context.s;
|
||||
DateTime date = DateTime.fromMillisecondsSinceEpoch(downloadDate);
|
||||
var date = DateTime.fromMillisecondsSinceEpoch(downloadDate);
|
||||
var diffrence = DateTime.now().toUtc().difference(date);
|
||||
if (diffrence.inHours < 24) {
|
||||
return s.hoursAgo(diffrence.inHours);
|
||||
|
@ -91,11 +92,13 @@ class _DownloadsManageState extends State<DownloadsManage> {
|
|||
}
|
||||
|
||||
int sumSelected() {
|
||||
int sum = 0;
|
||||
var sum = 0;
|
||||
if (_selectedList.length == 0) {
|
||||
return sum;
|
||||
} else {
|
||||
for (var episode in _selectedList) sum += episode.enclosureLength;
|
||||
for (var episode in _selectedList) {
|
||||
sum += episode.enclosureLength;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
@ -238,12 +241,13 @@ class _DownloadsManageState extends State<DownloadsManage> {
|
|||
),
|
||||
],
|
||||
onSelected: (value) {
|
||||
if (value == 0)
|
||||
if (value == 0) {
|
||||
setState(() => _mode = 0);
|
||||
else if (value == 1)
|
||||
} else if (value == 1) {
|
||||
setState(() => _mode = 1);
|
||||
else if (value == 2)
|
||||
} else if (value == 2) {
|
||||
setState(() => _mode = 2);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -340,17 +344,14 @@ class _DownloadsManageState extends State<DownloadsManage> {
|
|||
if (_episodes[index]
|
||||
.enclosureLength !=
|
||||
0)
|
||||
Text(((_episodes[index]
|
||||
.enclosureLength) ~/
|
||||
1000000)
|
||||
.toString() +
|
||||
' Mb'),
|
||||
Text(
|
||||
'${(_episodes[index].enclosureLength) ~/ 1000000} Mb'),
|
||||
],
|
||||
),
|
||||
trailing: Checkbox(
|
||||
value: _selectedList.contains(
|
||||
_episodes[index]),
|
||||
onChanged: (bool boo) {
|
||||
onChanged: (boo) {
|
||||
if (boo) {
|
||||
setState(() =>
|
||||
_selectedList.add(
|
||||
|
@ -385,7 +386,7 @@ class _DownloadsManageState extends State<DownloadsManage> {
|
|||
left: context.width / 2 - 50,
|
||||
bottom: _selectedList.length == 0 ? -100 : 30,
|
||||
child: InkWell(
|
||||
onTap: () => _delSelectedEpisodes(),
|
||||
onTap: _delSelectedEpisodes,
|
||||
child: Stack(
|
||||
alignment: _clearing
|
||||
? Alignment.centerLeft
|
||||
|
@ -407,7 +408,7 @@ class _DownloadsManageState extends State<DownloadsManage> {
|
|||
LineIcons.trash_alt_solid,
|
||||
color: Colors.white,
|
||||
),
|
||||
Text((sumSelected() ~/ 1000000).toString() + 'Mb',
|
||||
Text('${sumSelected() ~/ 1000000}Mb',
|
||||
style: TextStyle(color: Colors.white)),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
import 'package:dio/dio.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:tsacdop/state/podcast_group.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../webfeed/webfeed.dart';
|
||||
import '../type/searchpodcast.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import '../type/searchpodcast.dart';
|
||||
import '../type/sub_history.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../webfeed/webfeed.dart';
|
||||
|
||||
class PlayedHistory extends StatefulWidget {
|
||||
@override
|
||||
|
@ -24,42 +23,40 @@ class PlayedHistory extends StatefulWidget {
|
|||
class _PlayedHistoryState extends State<PlayedHistory>
|
||||
with SingleTickerProviderStateMixin {
|
||||
Future<List<PlayHistory>> getPlayHistory(int top) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
var dbHelper = DBHelper();
|
||||
List<PlayHistory> playHistory;
|
||||
playHistory = await dbHelper.getPlayHistory(top);
|
||||
for (var record in playHistory) await record.getEpisode();
|
||||
for (var record in playHistory) {
|
||||
await record.getEpisode();
|
||||
}
|
||||
return playHistory;
|
||||
}
|
||||
|
||||
_loadMoreData() async {
|
||||
// await Future.delayed(Duration(seconds: 3));
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_top = _top + 100;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int _top = 100;
|
||||
|
||||
Future<List<SubHistory>> getSubHistory() async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
var dbHelper = DBHelper();
|
||||
return await dbHelper.getSubHistory();
|
||||
}
|
||||
|
||||
static String _stringForSeconds(double seconds) {
|
||||
if (seconds == null) return null;
|
||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
TabController _controller;
|
||||
List<int> list = const [0, 1, 2, 3, 4, 5, 6];
|
||||
|
||||
Future<List<FlSpot>> getData() async {
|
||||
var dbHelper = DBHelper();
|
||||
List<FlSpot> stats = [];
|
||||
var stats = <FlSpot>[];
|
||||
|
||||
for (var day in list) {
|
||||
double mins = await dbHelper.listenMins(7 - day);
|
||||
var mins = await dbHelper.listenMins(7 - day);
|
||||
stats.add(FlSpot(day.toDouble(), mins));
|
||||
}
|
||||
return stats;
|
||||
|
@ -72,11 +69,11 @@ class _PlayedHistoryState extends State<PlayedHistory>
|
|||
);
|
||||
var subscribeWorker = context.watch<GroupList>();
|
||||
try {
|
||||
BaseOptions options = new BaseOptions(
|
||||
var options = BaseOptions(
|
||||
connectTimeout: 10000,
|
||||
receiveTimeout: 10000,
|
||||
);
|
||||
Response response = await Dio(options).get(url);
|
||||
var response = await Dio(options).get(url);
|
||||
var p = RssFeed.parse(response.data);
|
||||
var podcast = OnlinePodcast(
|
||||
rss: url,
|
||||
|
@ -84,7 +81,7 @@ class _PlayedHistoryState extends State<PlayedHistory>
|
|||
publisher: p.author,
|
||||
description: p.description,
|
||||
image: p.itunes.image.href);
|
||||
SubscribeItem item = SubscribeItem(podcast.rss, podcast.title,
|
||||
var item = SubscribeItem(podcast.rss, podcast.title,
|
||||
imgUrl: podcast.image, group: 'Home');
|
||||
subscribeWorker.setSubscribeItem(item);
|
||||
} on DioError catch (e) {
|
||||
|
@ -123,7 +120,7 @@ class _PlayedHistoryState extends State<PlayedHistory>
|
|||
backgroundColor: Theme.of(context).primaryColor,
|
||||
body: SafeArea(
|
||||
child: NestedScrollView(
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxScrolled) {
|
||||
headerSliverBuilder: (context, innerBoxScrolled) {
|
||||
return <Widget>[
|
||||
SliverAppBar(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
|
@ -132,8 +129,7 @@ class _PlayedHistoryState extends State<PlayedHistory>
|
|||
floating: false,
|
||||
pinned: true,
|
||||
flexibleSpace: LayoutBuilder(
|
||||
builder:
|
||||
(BuildContext context, BoxConstraints constraints) {
|
||||
builder: (context, constraints) {
|
||||
top = constraints.biggest.height;
|
||||
return FlexibleSpaceBar(
|
||||
title: top < 70 + MediaQuery.of(context).padding.top
|
||||
|
@ -181,10 +177,10 @@ class _PlayedHistoryState extends State<PlayedHistory>
|
|||
FutureBuilder<List<PlayHistory>>(
|
||||
future: getPlayHistory(_top),
|
||||
builder: (context, snapshot) {
|
||||
double _width = MediaQuery.of(context).size.width;
|
||||
var _width = MediaQuery.of(context).size.width;
|
||||
return snapshot.hasData
|
||||
? NotificationListener<ScrollNotification>(
|
||||
onNotification: (ScrollNotification scrollInfo) {
|
||||
onNotification: (scrollInfo) {
|
||||
if (scrollInfo.metrics.pixels ==
|
||||
scrollInfo.metrics.maxScrollExtent &&
|
||||
snapshot.data.length == _top) _loadMoreData();
|
||||
|
@ -194,10 +190,9 @@ class _PlayedHistoryState extends State<PlayedHistory>
|
|||
//shrinkWrap: true,
|
||||
scrollDirection: Axis.vertical,
|
||||
itemCount: snapshot.data.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
double seekValue =
|
||||
snapshot.data[index].seekValue;
|
||||
double seconds = snapshot.data[index].seconds;
|
||||
itemBuilder: (context, index) {
|
||||
var seekValue = snapshot.data[index].seekValue;
|
||||
var seconds = snapshot.data[index].seconds;
|
||||
return Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 5),
|
||||
|
@ -263,8 +258,7 @@ class _PlayedHistoryState extends State<PlayedHistory>
|
|||
child: Text(
|
||||
seconds == 0 && seekValue == 1
|
||||
? s.mark
|
||||
: _stringForSeconds(
|
||||
seconds),
|
||||
: seconds.toInt().toTime,
|
||||
style: TextStyle(
|
||||
color: Colors.white),
|
||||
),
|
||||
|
@ -293,8 +287,8 @@ class _PlayedHistoryState extends State<PlayedHistory>
|
|||
// shrinkWrap: true,
|
||||
scrollDirection: Axis.vertical,
|
||||
itemCount: snapshot.data.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
bool _status = snapshot.data[index].status;
|
||||
itemBuilder: (context, index) {
|
||||
var _status = snapshot.data[index].status;
|
||||
return Container(
|
||||
color: context.scaffoldBackgroundColor,
|
||||
child: Column(
|
||||
|
@ -381,7 +375,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
|||
@override
|
||||
Widget build(
|
||||
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||
return new Container(
|
||||
return Container(
|
||||
color: _color,
|
||||
child: _tabBar,
|
||||
);
|
||||
|
@ -481,7 +475,7 @@ class HistoryChart extends StatelessWidget {
|
|||
),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: this.stats,
|
||||
spots: stats,
|
||||
isCurved: true,
|
||||
colors: [context.accentColor],
|
||||
preventCurveOverShooting: true,
|
||||
|
|
|
@ -3,8 +3,8 @@ import 'package:flutter/services.dart';
|
|||
import 'package:intl/intl.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../util/extension_helper.dart';
|
||||
import '../generated/l10n.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
|
||||
class LanguagesSetting extends StatefulWidget {
|
||||
const LanguagesSetting({Key key}) : super(key: key);
|
||||
|
@ -52,7 +52,7 @@ class _LanguagesSettingState extends State<LanguagesSetting> {
|
|||
trailing: Radio<Locale>(
|
||||
value: Locale(Intl.systemLocale),
|
||||
groupValue: Locale(Intl.getCurrentLocale()),
|
||||
onChanged: (Locale locale) async {
|
||||
onChanged: (locale) async {
|
||||
await S.load(locale);
|
||||
setState(() {});
|
||||
}),
|
||||
|
@ -68,7 +68,7 @@ class _LanguagesSettingState extends State<LanguagesSetting> {
|
|||
trailing: Radio<Locale>(
|
||||
value: Locale('en'),
|
||||
groupValue: Locale(Intl.getCurrentLocale()),
|
||||
onChanged: (Locale locale) async {
|
||||
onChanged: (locale) async {
|
||||
await S.load(locale);
|
||||
setState(() {});
|
||||
}),
|
||||
|
@ -84,7 +84,7 @@ class _LanguagesSettingState extends State<LanguagesSetting> {
|
|||
trailing: Radio<Locale>(
|
||||
value: Locale('zh_Hans'),
|
||||
groupValue: Locale(Intl.getCurrentLocale()),
|
||||
onChanged: (Locale locale) async {
|
||||
onChanged: (locale) async {
|
||||
await S.load(locale);
|
||||
setState(() {});
|
||||
}),
|
||||
|
@ -100,7 +100,7 @@ class _LanguagesSettingState extends State<LanguagesSetting> {
|
|||
trailing: Radio<Locale>(
|
||||
value: Locale('fr'),
|
||||
groupValue: Locale(Intl.getCurrentLocale()),
|
||||
onChanged: (Locale locale) async {
|
||||
onChanged: (locale) async {
|
||||
await S.load(locale);
|
||||
setState(() {});
|
||||
}),
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/episodegrid.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/episodegrid.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import 'popup_menu.dart';
|
||||
|
||||
class LayoutSetting extends StatefulWidget {
|
||||
|
@ -16,8 +16,8 @@ class LayoutSetting extends StatefulWidget {
|
|||
|
||||
class _LayoutSettingState extends State<LayoutSetting> {
|
||||
Future<Layout> _getLayout(String key) async {
|
||||
KeyValueStorage keyValueStorage = KeyValueStorage(key);
|
||||
int layout = await keyValueStorage.getInt();
|
||||
var keyValueStorage = KeyValueStorage(key);
|
||||
var layout = await keyValueStorage.getInt();
|
||||
return Layout.values[layout];
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ class _LayoutSettingState extends State<LayoutSetting> {
|
|||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
KeyValueStorage storage = KeyValueStorage(key);
|
||||
var storage = KeyValueStorage(key);
|
||||
await storage.saveInt(option.index);
|
||||
setState(() {});
|
||||
},
|
||||
|
|
|
@ -2,17 +2,17 @@ import 'dart:ui';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_time_picker_spinner/flutter_time_picker_spinner.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:flutter_time_picker_spinner/flutter_time_picker_spinner.dart';
|
||||
|
||||
import '../state/setting_state.dart';
|
||||
import '../home/audioplayer.dart';
|
||||
import '../util/general_dialog.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../state/setting_state.dart';
|
||||
import '../util/custom_dropdown.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/general_dialog.dart';
|
||||
|
||||
const List secondsToSelect = [10, 15, 20, 25, 30, 45, 60];
|
||||
|
||||
|
@ -95,7 +95,7 @@ class PlaySetting extends StatelessWidget {
|
|||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
int startTime = data.item1;
|
||||
var startTime = data.item1;
|
||||
generalDialog(
|
||||
context,
|
||||
content: TimePickerSpinner(
|
||||
|
@ -111,7 +111,7 @@ class PlaySetting extends StatelessWidget {
|
|||
normalTextStyle: GoogleFonts.teko(
|
||||
textStyle:
|
||||
TextStyle(fontSize: 40, color: Colors.black38)),
|
||||
onTimeChange: (DateTime time) {
|
||||
onTimeChange: (time) {
|
||||
startTime = time.hour * 60 + time.minute;
|
||||
},
|
||||
),
|
||||
|
@ -177,7 +177,7 @@ class PlaySetting extends StatelessWidget {
|
|||
textStyle:
|
||||
TextStyle(fontSize: 40, color: Colors.black38)),
|
||||
is24HourMode: false,
|
||||
onTimeChange: (DateTime time) {
|
||||
onTimeChange: (time) {
|
||||
endTime = time.hour * 60 + time.minute;
|
||||
},
|
||||
),
|
||||
|
@ -329,7 +329,7 @@ class PlaySetting extends StatelessWidget {
|
|||
displayItemCount: 5,
|
||||
isDense: true,
|
||||
value: data,
|
||||
onChanged: (int value) =>
|
||||
onChanged: (value) =>
|
||||
settings.setFastForwardSeconds = value,
|
||||
items: secondsToSelect
|
||||
.map<DropdownMenuItem<int>>((e) {
|
||||
|
@ -352,7 +352,7 @@ class PlaySetting extends StatelessWidget {
|
|||
displayItemCount: 5,
|
||||
isDense: true,
|
||||
value: data,
|
||||
onChanged: (int value) =>
|
||||
onChanged: (value) =>
|
||||
settings.setRewindSeconds = value,
|
||||
items: secondsToSelect
|
||||
.map<DropdownMenuItem<int>>((e) {
|
||||
|
@ -392,7 +392,7 @@ class PlaySetting extends StatelessWidget {
|
|||
displayItemCount: 5,
|
||||
isDense: true,
|
||||
value: data,
|
||||
onChanged: (int value) =>
|
||||
onChanged: (value) =>
|
||||
settings.setDefaultSleepTimer = value,
|
||||
items:
|
||||
minsToSelect.map<DropdownMenuItem<int>>((e) {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import 'package:flare_flutter/flare_actor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:flare_flutter/flare_actor.dart';
|
||||
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
|
||||
class PopupMenuSetting extends StatefulWidget {
|
||||
const PopupMenuSetting({Key key}) : super(key: key);
|
||||
|
@ -16,27 +16,25 @@ class PopupMenuSetting extends StatefulWidget {
|
|||
|
||||
class _PopupMenuSettingState extends State<PopupMenuSetting> {
|
||||
Future<List<int>> _getEpisodeMenu() async {
|
||||
KeyValueStorage popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
|
||||
List<int> list = await popupMenuStorage.getMenu();
|
||||
var popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
|
||||
var list = await popupMenuStorage.getMenu();
|
||||
return list;
|
||||
}
|
||||
|
||||
Future<bool> _getTapToOpenPopupMenu() async {
|
||||
KeyValueStorage tapToOpenPopupMenuStorage =
|
||||
KeyValueStorage(tapToOpenPopupMenuKey);
|
||||
bool boo = await tapToOpenPopupMenuStorage.getBool(defaultValue: false);
|
||||
var tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey);
|
||||
var boo = await tapToOpenPopupMenuStorage.getBool(defaultValue: false);
|
||||
return boo;
|
||||
}
|
||||
|
||||
_saveEpisodeMene(List<int> list) async {
|
||||
KeyValueStorage popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
|
||||
var popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
|
||||
await popupMenuStorage.saveMenu(list);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
_saveTapToOpenPopupMenu(bool boo) async {
|
||||
KeyValueStorage tapToOpenPopupMenuStorage =
|
||||
KeyValueStorage(tapToOpenPopupMenuKey);
|
||||
var tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey);
|
||||
await tapToOpenPopupMenuStorage.saveBool(boo);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
@ -57,12 +55,12 @@ class _PopupMenuSettingState extends State<PopupMenuSetting> {
|
|||
? null
|
||||
: () {
|
||||
if (e >= 10) {
|
||||
int index = menu.indexOf(e);
|
||||
var index = menu.indexOf(e);
|
||||
menu.remove(e);
|
||||
menu.insert(index, e - 10);
|
||||
_saveEpisodeMene(menu);
|
||||
} else if (e < 10) {
|
||||
int index = menu.indexOf(e);
|
||||
var index = menu.indexOf(e);
|
||||
menu.remove(e);
|
||||
menu.insert(index, e + 10);
|
||||
_saveEpisodeMene(menu);
|
||||
|
@ -72,14 +70,14 @@ class _PopupMenuSettingState extends State<PopupMenuSetting> {
|
|||
value: e < 10,
|
||||
onChanged: e == 0
|
||||
? null
|
||||
: (bool boo) {
|
||||
: (boo) {
|
||||
if (boo && e >= 10) {
|
||||
int index = menu.indexOf(e);
|
||||
var index = menu.indexOf(e);
|
||||
menu.remove(e);
|
||||
menu.insert(index, e - 10);
|
||||
_saveEpisodeMene(menu);
|
||||
} else if (e < 10) {
|
||||
int index = menu.indexOf(e);
|
||||
var index = menu.indexOf(e);
|
||||
menu.remove(e);
|
||||
menu.insert(index, e + 10);
|
||||
_saveEpisodeMene(menu);
|
||||
|
@ -143,7 +141,7 @@ class _PopupMenuSettingState extends State<PopupMenuSetting> {
|
|||
scale: 0.9,
|
||||
child: Switch(
|
||||
value: snapshot.data,
|
||||
onChanged: (bool boo) => _saveTapToOpenPopupMenu(boo)),
|
||||
onChanged: _saveTapToOpenPopupMenu),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -151,13 +149,13 @@ class _PopupMenuSettingState extends State<PopupMenuSetting> {
|
|||
future: _getEpisodeMenu(),
|
||||
initialData: [0, 1, 12, 13, 14],
|
||||
builder: (context, snapshot) {
|
||||
List<int> menu = snapshot.data;
|
||||
var menu = snapshot.data;
|
||||
return Expanded(
|
||||
child: ListView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
children: menu.map<Widget>((int e) {
|
||||
int i = e % 10;
|
||||
children: menu.map<Widget>((e) {
|
||||
var i = e % 10;
|
||||
switch (i) {
|
||||
case 0:
|
||||
return _popupMenuItem(menu, e,
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import 'dart:math' as math;
|
||||
|
||||
import 'package:feature_discovery/feature_discovery.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:feature_discovery/feature_discovery.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
import '../util/extension_helper.dart';
|
||||
import '../intro_slider/app_intro.dart';
|
||||
import '../home/home.dart';
|
||||
import '../intro_slider/app_intro.dart';
|
||||
import '../podcasts/podcast_manage.dart';
|
||||
import 'theme.dart';
|
||||
import 'layouts.dart';
|
||||
import 'storage.dart';
|
||||
import 'history.dart';
|
||||
import 'syncing.dart';
|
||||
import 'libries.dart';
|
||||
import 'languages.dart';
|
||||
import 'play_setting.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import 'data_backup.dart';
|
||||
import 'history.dart';
|
||||
import 'languages.dart';
|
||||
import 'layouts.dart';
|
||||
import 'libries.dart';
|
||||
import 'play_setting.dart';
|
||||
import 'storage.dart';
|
||||
import 'syncing.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
class Settings extends StatefulWidget {
|
||||
@override
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../settings/downloads_manage.dart';
|
||||
import '../state/setting_state.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/custom_dropdown.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
|
||||
class StorageSetting extends StatefulWidget {
|
||||
@override
|
||||
|
@ -20,13 +20,13 @@ class _StorageSettingState extends State<StorageSetting>
|
|||
AnimationController _controller;
|
||||
Animation<double> _animation;
|
||||
_getCacheMax() async {
|
||||
int cache =
|
||||
var cache =
|
||||
await cacheStorage.getInt(defaultValue: (200 * 1024 * 1024).toInt());
|
||||
if (cache == 0) {
|
||||
await cacheStorage.saveInt((200 * 1024 * 1024).toInt());
|
||||
cache = 200 * 1024 * 1024;
|
||||
}
|
||||
int value = cache ~/ (1024 * 1024);
|
||||
var value = cache ~/ (1024 * 1024);
|
||||
if (value > 100) {
|
||||
_controller = AnimationController(
|
||||
vsync: this, duration: Duration(milliseconds: value * 2));
|
||||
|
@ -40,14 +40,14 @@ class _StorageSettingState extends State<StorageSetting>
|
|||
}
|
||||
|
||||
Future<bool> _getAutoDownloadNetwork() async {
|
||||
KeyValueStorage storage = KeyValueStorage(autoDownloadNetworkKey);
|
||||
bool value = await storage.getBool(defaultValue: false);
|
||||
var storage = KeyValueStorage(autoDownloadNetworkKey);
|
||||
var value = await storage.getBool(defaultValue: false);
|
||||
return value;
|
||||
}
|
||||
|
||||
Future<int> _getAutoDeleteDays() async {
|
||||
KeyValueStorage storage = KeyValueStorage(autoDeleteKey);
|
||||
int days = await storage.getInt();
|
||||
var storage = KeyValueStorage(autoDeleteKey);
|
||||
var days = await storage.getInt();
|
||||
if (days == 0) {
|
||||
storage.saveInt(30);
|
||||
return 30;
|
||||
|
@ -56,13 +56,13 @@ class _StorageSettingState extends State<StorageSetting>
|
|||
}
|
||||
|
||||
_setAutoDeleteDays(int days) async {
|
||||
KeyValueStorage storage = KeyValueStorage(autoDeleteKey);
|
||||
var storage = KeyValueStorage(autoDeleteKey);
|
||||
await storage.saveInt(days);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
_setAudtDownloadNetwork(bool boo) async {
|
||||
KeyValueStorage storage = KeyValueStorage(autoDownloadNetworkKey);
|
||||
var storage = KeyValueStorage(autoDownloadNetworkKey);
|
||||
await storage.saveBool(boo);
|
||||
}
|
||||
|
||||
|
@ -272,7 +272,7 @@ class _StorageSettingState extends State<StorageSetting>
|
|||
min: 100,
|
||||
max: 1000,
|
||||
divisions: 9,
|
||||
onChanged: (double val) {
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
_value = val;
|
||||
});
|
||||
|
|
|
@ -4,8 +4,8 @@ import 'package:provider/provider.dart';
|
|||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../state/setting_state.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import '../util/custom_dropdown.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
|
||||
class SyncingSetting extends StatelessWidget {
|
||||
@override
|
||||
|
@ -79,10 +79,11 @@ class SyncingSetting extends StatelessWidget {
|
|||
value: data.item1,
|
||||
onChanged: (boo) async {
|
||||
settings.autoUpdate = boo;
|
||||
if (boo)
|
||||
if (boo) {
|
||||
settings.setWorkManager(data.item2);
|
||||
else
|
||||
} else {
|
||||
settings.cancelWork();
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -54,8 +54,7 @@ class ThemeSetting extends StatelessWidget {
|
|||
.modalBarrierDismissLabel,
|
||||
barrierColor: Colors.black54,
|
||||
transitionDuration: const Duration(milliseconds: 200),
|
||||
pageBuilder: (BuildContext context, Animation animaiton,
|
||||
Animation secondaryAnimation) =>
|
||||
pageBuilder: (context, animaiton, secondaryAnimation) =>
|
||||
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import '../type/playlist.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
|
||||
MediaControl playControl = MediaControl(
|
||||
androidIcon: 'drawable/ic_stat_play_circle_filled',
|
||||
|
@ -68,7 +68,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
EpisodeBrief _episode;
|
||||
|
||||
/// Current playlist.
|
||||
Playlist _queue = Playlist();
|
||||
final Playlist _queue = Playlist();
|
||||
|
||||
/// Notifier for playlist change.
|
||||
bool _queueUpdate = false;
|
||||
|
@ -173,12 +173,12 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future _getAutoPlay() async {
|
||||
int i = await autoPlayStorage.getInt();
|
||||
var i = await autoPlayStorage.getInt();
|
||||
_autoPlay = i == 0;
|
||||
}
|
||||
|
||||
Future _getAutoSleepTimer() async {
|
||||
int i = await autoSleepTimerStorage.getInt();
|
||||
var i = await autoSleepTimerStorage.getInt();
|
||||
_autoSleepTimer = i == 1;
|
||||
}
|
||||
|
||||
|
@ -193,7 +193,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
_queueUpdate = false;
|
||||
await _getAutoSleepTimer();
|
||||
await AudioService.connect();
|
||||
bool running = AudioService.running;
|
||||
var running = AudioService.running;
|
||||
if (running) {}
|
||||
}
|
||||
|
||||
|
@ -202,26 +202,25 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
await _getAutoPlay();
|
||||
_lastPostion = await positionStorage.getInt();
|
||||
if (_lastPostion > 0 && _queue.playlist.length > 0) {
|
||||
final EpisodeBrief episode = _queue.playlist.first;
|
||||
final int duration = episode.duration * 1000;
|
||||
final double seekValue = duration != 0 ? _lastPostion / duration : 1;
|
||||
final PlayHistory history = PlayHistory(
|
||||
episode.title, episode.enclosureUrl, _lastPostion / 1000, seekValue);
|
||||
final episode = _queue.playlist.first;
|
||||
final duration = episode.duration * 1000;
|
||||
final seekValue = duration != 0 ? _lastPostion / duration : 1;
|
||||
final history = PlayHistory(
|
||||
episode.title, episode.enclosureUrl, _lastPostion ~/ 1000, seekValue);
|
||||
await dbHelper.saveHistory(history);
|
||||
}
|
||||
KeyValueStorage lastWorkStorage = KeyValueStorage(lastWorkKey);
|
||||
var lastWorkStorage = KeyValueStorage(lastWorkKey);
|
||||
await lastWorkStorage.saveInt(0);
|
||||
}
|
||||
|
||||
Future<void> episodeLoad(EpisodeBrief episode,
|
||||
{int startPosition = 0}) async {
|
||||
print(episode.enclosureUrl);
|
||||
final EpisodeBrief episodeNew =
|
||||
await dbHelper.getRssItemWithUrl(episode.enclosureUrl);
|
||||
final episodeNew = await dbHelper.getRssItemWithUrl(episode.enclosureUrl);
|
||||
//TODO load episode from last position when player running
|
||||
if (playerRunning) {
|
||||
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
||||
backgroundAudioPosition / 1000, seekSliderValue);
|
||||
final history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
||||
backgroundAudioPosition ~/ 1000, seekSliderValue);
|
||||
await dbHelper.saveHistory(history);
|
||||
AudioService.addQueueItemAt(episodeNew.toMediaItem(), 0);
|
||||
_queue.playlist
|
||||
|
@ -275,25 +274,26 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
//Check autoplay setting, if true only add one episode, else add playlist.
|
||||
await _getAutoPlay();
|
||||
if (_autoPlay) {
|
||||
for (var episode in _queue.playlist)
|
||||
for (var episode in _queue.playlist) {
|
||||
await AudioService.addQueueItem(episode.toMediaItem());
|
||||
}
|
||||
} else {
|
||||
await AudioService.addQueueItem(_queue.playlist.first.toMediaItem());
|
||||
}
|
||||
//Check auto sleep timer setting
|
||||
await _getAutoSleepTimer();
|
||||
if (_autoSleepTimer) {
|
||||
int startTime =
|
||||
var startTime =
|
||||
await autoSleepTimerStartStorage.getInt(defaultValue: 1380);
|
||||
int endTime = await autoSleepTimerEndStorage.getInt(defaultValue: 360);
|
||||
int currentTime = DateTime.now().hour * 60 + DateTime.now().minute;
|
||||
var endTime = await autoSleepTimerEndStorage.getInt(defaultValue: 360);
|
||||
var currentTime = DateTime.now().hour * 60 + DateTime.now().minute;
|
||||
if ((startTime > endTime &&
|
||||
(currentTime > startTime || currentTime < endTime)) ||
|
||||
((startTime < endTime) &&
|
||||
(currentTime > startTime && currentTime < endTime))) {
|
||||
int mode = await autoSleepTimerModeStorage.getInt();
|
||||
var mode = await autoSleepTimerModeStorage.getInt();
|
||||
_sleepTimerMode = SleepTimerMode.values[mode];
|
||||
int defaultTimer =
|
||||
var defaultTimer =
|
||||
await defaultSleepTimerStorage.getInt(defaultValue: 30);
|
||||
sleepTimer(defaultTimer);
|
||||
}
|
||||
|
@ -304,7 +304,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
AudioService.currentMediaItemStream
|
||||
.where((event) => event != null)
|
||||
.listen((item) async {
|
||||
EpisodeBrief episode = await dbHelper.getRssItemWithMediaId(item.id);
|
||||
var episode = await dbHelper.getRssItemWithMediaId(item.id);
|
||||
|
||||
_backgroundAudioDuration = item.duration?.inMilliseconds ?? 0;
|
||||
if (episode != null) {
|
||||
|
@ -334,11 +334,8 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
_lastPostion = 0;
|
||||
notifyListeners();
|
||||
await positionStorage.saveInt(_lastPostion);
|
||||
final PlayHistory history = PlayHistory(
|
||||
_episode.title,
|
||||
_episode.enclosureUrl,
|
||||
backgroundAudioPosition / 1000,
|
||||
seekSliderValue);
|
||||
final history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
||||
backgroundAudioPosition ~/ 1000, seekSliderValue);
|
||||
dbHelper.saveHistory(history);
|
||||
}
|
||||
});
|
||||
|
@ -370,9 +367,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
});
|
||||
|
||||
//double s = _currentSpeed ?? 1.0;
|
||||
int getPosition = 0;
|
||||
var getPosition = 0;
|
||||
Timer.periodic(Duration(milliseconds: 500), (timer) {
|
||||
double s = _currentSpeed ?? 1.0;
|
||||
var s = _currentSpeed ?? 1.0;
|
||||
if (_noSlide) {
|
||||
if (_playing && !buffering) {
|
||||
getPosition = _currentPosition +
|
||||
|
@ -380,16 +377,18 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
.toInt();
|
||||
_backgroundAudioPosition =
|
||||
math.min(getPosition, _backgroundAudioDuration);
|
||||
} else
|
||||
} else {
|
||||
_backgroundAudioPosition = _currentPosition ?? 0;
|
||||
}
|
||||
|
||||
if (_backgroundAudioDuration != null &&
|
||||
_backgroundAudioDuration != 0 &&
|
||||
_backgroundAudioPosition != null) {
|
||||
_seekSliderValue =
|
||||
_backgroundAudioPosition / _backgroundAudioDuration ?? 0;
|
||||
} else
|
||||
} else {
|
||||
_seekSliderValue = 0;
|
||||
}
|
||||
|
||||
if (_backgroundAudioPosition > 0 &&
|
||||
_backgroundAudioPosition < _backgroundAudioDuration) {
|
||||
|
@ -442,25 +441,29 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
}
|
||||
|
||||
addNewEpisode(List<String> group) async {
|
||||
List<EpisodeBrief> newEpisodes = [];
|
||||
if (group.first == 'All')
|
||||
var newEpisodes = <EpisodeBrief>[];
|
||||
if (group.first == 'All') {
|
||||
newEpisodes = await dbHelper.getRecentNewRssItem();
|
||||
else
|
||||
} else {
|
||||
newEpisodes = await dbHelper.getGroupNewRssItem(group);
|
||||
if (newEpisodes.length > 0 && newEpisodes.length < 100)
|
||||
for (var episode in newEpisodes) await addToPlaylist(episode);
|
||||
if (group.first == 'All')
|
||||
}
|
||||
if (newEpisodes.length > 0 && newEpisodes.length < 100) {
|
||||
for (var episode in newEpisodes) {
|
||||
await addToPlaylist(episode);
|
||||
}
|
||||
}
|
||||
if (group.first == 'All') {
|
||||
await dbHelper.removeAllNewMark();
|
||||
else
|
||||
} else {
|
||||
await dbHelper.removeGroupNewMark(group);
|
||||
}
|
||||
}
|
||||
|
||||
updateMediaItem(EpisodeBrief episode) async {
|
||||
int index = _queue.playlist
|
||||
var index = _queue.playlist
|
||||
.indexWhere((item) => item.enclosureUrl == episode.enclosureUrl);
|
||||
if (index > 0) {
|
||||
EpisodeBrief episodeNew =
|
||||
await dbHelper.getRssItemWithUrl(episode.enclosureUrl);
|
||||
var episodeNew = await dbHelper.getRssItemWithUrl(episode.enclosureUrl);
|
||||
await delFromPlaylist(episode);
|
||||
await addToPlaylistAt(episodeNew, index);
|
||||
}
|
||||
|
@ -471,7 +474,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
if (playerRunning) {
|
||||
await AudioService.removeQueueItem(episodeNew.toMediaItem());
|
||||
}
|
||||
int index = await _queue.delFromPlaylist(episodeNew);
|
||||
var index = await _queue.delFromPlaylist(episodeNew);
|
||||
_queueUpdate = !_queueUpdate;
|
||||
notifyListeners();
|
||||
return index;
|
||||
|
@ -499,7 +502,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
}
|
||||
|
||||
forwardAudio(int s) {
|
||||
int pos = _backgroundAudioPosition + s * 1000;
|
||||
var pos = _backgroundAudioPosition + s * 1000;
|
||||
AudioService.seekTo(Duration(milliseconds: pos));
|
||||
}
|
||||
|
||||
|
@ -513,8 +516,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
|
||||
seekTo(int position) async {
|
||||
if (_audioState != AudioProcessingState.connecting &&
|
||||
_audioState != AudioProcessingState.none)
|
||||
_audioState != AudioProcessingState.none) {
|
||||
await AudioService.seekTo(Duration(milliseconds: position));
|
||||
}
|
||||
}
|
||||
|
||||
sliderSeek(double val) async {
|
||||
|
@ -597,8 +601,8 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
class AudioPlayerTask extends BackgroundAudioTask {
|
||||
KeyValueStorage cacheStorage = KeyValueStorage(cacheMaxKey);
|
||||
|
||||
List<MediaItem> _queue = [];
|
||||
AudioPlayer _audioPlayer = AudioPlayer();
|
||||
final List<MediaItem> _queue = [];
|
||||
final AudioPlayer _audioPlayer = AudioPlayer();
|
||||
AudioProcessingState _skipState;
|
||||
bool _playing;
|
||||
bool _interrupted = false;
|
||||
|
@ -664,10 +668,11 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
}
|
||||
|
||||
void playPause() {
|
||||
if (AudioServiceBackground.state.playing)
|
||||
if (AudioServiceBackground.state.playing) {
|
||||
onPause();
|
||||
else
|
||||
} else {
|
||||
onPlay();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -688,10 +693,11 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
await AudioServiceBackground.setMediaItem(mediaItem);
|
||||
await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax);
|
||||
print(mediaItem.title);
|
||||
Duration duration = await _audioPlayer.durationFuture;
|
||||
if (duration != null)
|
||||
var duration = await _audioPlayer.durationFuture;
|
||||
if (duration != null) {
|
||||
await AudioServiceBackground.setMediaItem(
|
||||
mediaItem.copyWith(duration: duration));
|
||||
}
|
||||
_skipState = null;
|
||||
// Resume playback if we were playing
|
||||
// if (_playing) {
|
||||
|
@ -716,15 +722,17 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
}
|
||||
await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax);
|
||||
var duration = await _audioPlayer.durationFuture;
|
||||
if (duration != null)
|
||||
if (duration != null) {
|
||||
await AudioServiceBackground.setMediaItem(
|
||||
mediaItem.copyWith(duration: duration));
|
||||
}
|
||||
playFromStart();
|
||||
} else {
|
||||
_playing = true;
|
||||
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting ||
|
||||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none)
|
||||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none) {
|
||||
_audioPlayer.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -732,12 +740,13 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
playFromStart() async {
|
||||
_playing = true;
|
||||
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting ||
|
||||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none)
|
||||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none) {
|
||||
try {
|
||||
_audioPlayer.play();
|
||||
} catch (e) {
|
||||
_setState(processingState: AudioProcessingState.error);
|
||||
}
|
||||
}
|
||||
if (mediaItem.extras['skip'] > 0) {
|
||||
_audioPlayer.seek(Duration(seconds: mediaItem.extras['skip']));
|
||||
}
|
||||
|
@ -757,17 +766,18 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
@override
|
||||
void onSeekTo(Duration position) {
|
||||
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting ||
|
||||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none)
|
||||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none) {
|
||||
_audioPlayer.seek(position);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClick(MediaButton button) {
|
||||
if (button == MediaButton.media)
|
||||
if (button == MediaButton.media) {
|
||||
playPause();
|
||||
else if (button == MediaButton.next)
|
||||
} else if (button == MediaButton.next) {
|
||||
_seekRelative(fastForwardInterval);
|
||||
else if (button == MediaButton.previous) _seekRelative(-rewindInterval);
|
||||
} else if (button == MediaButton.previous) _seekRelative(-rewindInterval);
|
||||
}
|
||||
|
||||
Future<void> _seekRelative(Duration offset) async {
|
||||
|
@ -812,7 +822,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
await AudioServiceBackground.setQueue(_queue);
|
||||
await AudioServiceBackground.setMediaItem(mediaItem);
|
||||
await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax);
|
||||
Duration duration = await _audioPlayer.durationFuture ?? Duration.zero;
|
||||
var duration = await _audioPlayer.durationFuture ?? Duration.zero;
|
||||
AudioServiceBackground.setMediaItem(
|
||||
mediaItem.copyWith(duration: duration));
|
||||
playFromStart();
|
||||
|
|
|
@ -28,30 +28,28 @@ class EpisodeTask {
|
|||
|
||||
void downloadCallback(String id, DownloadTaskStatus status, int progress) {
|
||||
print('Homepage callback task in $id status ($status) $progress');
|
||||
final SendPort send =
|
||||
IsolateNameServer.lookupPortByName('downloader_send_port');
|
||||
final send = IsolateNameServer.lookupPortByName('downloader_send_port');
|
||||
send.send([id, status, progress]);
|
||||
}
|
||||
|
||||
void autoDownloadCallback(String id, DownloadTaskStatus status, int progress) {
|
||||
print('Autodownload callback task in $id status ($status) $progress');
|
||||
final SendPort send =
|
||||
IsolateNameServer.lookupPortByName('auto_downloader_send_port');
|
||||
final send = IsolateNameServer.lookupPortByName('auto_downloader_send_port');
|
||||
send.send([id, status, progress]);
|
||||
}
|
||||
|
||||
//For background auto downlaod
|
||||
class AutoDownloader {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
List<EpisodeTask> _episodeTasks = [];
|
||||
Completer _completer = Completer();
|
||||
final List<EpisodeTask> _episodeTasks = [];
|
||||
final Completer _completer = Completer();
|
||||
AutoDownloader() {
|
||||
FlutterDownloader.registerCallback(autoDownloadCallback);
|
||||
}
|
||||
|
||||
bindBackgroundIsolate() {
|
||||
ReceivePort _port = ReceivePort();
|
||||
bool isSuccess = IsolateNameServer.registerPortWithName(
|
||||
var _port = ReceivePort();
|
||||
var isSuccess = IsolateNameServer.registerPortWithName(
|
||||
_port.sendPort, 'auto_downloader_send_port');
|
||||
if (!isSuccess) {
|
||||
IsolateNameServer.removePortNameMapping('auto_downloader_send_port');
|
||||
|
@ -89,10 +87,9 @@ class AutoDownloader {
|
|||
Future _saveMediaId(EpisodeTask episodeTask) async {
|
||||
final completeTask = await FlutterDownloader.loadTasksWithRawQuery(
|
||||
query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'");
|
||||
String filePath = 'file://' +
|
||||
path.join(completeTask.first.savedDir,
|
||||
Uri.encodeComponent(completeTask.first.filename));
|
||||
FileStat fileStat = await File(
|
||||
var filePath =
|
||||
'file://${path.join(completeTask.first.savedDir, Uri.encodeComponent(completeTask.first.filename))}';
|
||||
var fileStat = await File(
|
||||
path.join(completeTask.first.savedDir, completeTask.first.filename))
|
||||
.stat();
|
||||
await dbHelper.saveMediaId(episodeTask.episode.enclosureUrl, filePath,
|
||||
|
@ -106,22 +103,20 @@ class AutoDownloader {
|
|||
{bool showNotification = false}) async {
|
||||
for (var episode in episodes) {
|
||||
final dir = await getExternalStorageDirectory();
|
||||
String localPath = path.join(dir.path, episode.feedTitle);
|
||||
var localPath = path.join(dir.path, episode.feedTitle);
|
||||
final saveDir = Directory(localPath);
|
||||
bool hasExisted = await saveDir.exists();
|
||||
var hasExisted = await saveDir.exists();
|
||||
if (!hasExisted) {
|
||||
saveDir.create();
|
||||
}
|
||||
DateTime now = DateTime.now();
|
||||
String datePlus = now.year.toString() +
|
||||
var now = DateTime.now();
|
||||
var datePlus = now.year.toString() +
|
||||
now.month.toString() +
|
||||
now.day.toString() +
|
||||
now.second.toString();
|
||||
String fileName = episode.title +
|
||||
datePlus +
|
||||
'.' +
|
||||
episode.enclosureUrl.split('/').last.split('.').last;
|
||||
String taskId = await FlutterDownloader.enqueue(
|
||||
var fileName =
|
||||
'${episode.title}$datePlus.${episode.enclosureUrl.split('/').last.split('.').last}';
|
||||
var taskId = await FlutterDownloader.enqueue(
|
||||
fileName: fileName,
|
||||
url: episode.enclosureUrl,
|
||||
savedDir: localPath,
|
||||
|
@ -157,24 +152,26 @@ class DownloadState extends ChangeNotifier {
|
|||
|
||||
_loadTasks() async {
|
||||
_episodeTasks = [];
|
||||
DBHelper dbHelper = DBHelper();
|
||||
var dbHelper = DBHelper();
|
||||
var tasks = await FlutterDownloader.loadTasks();
|
||||
if (tasks.length != 0)
|
||||
if (tasks.length != 0) {
|
||||
for (var task in tasks) {
|
||||
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(task.url);
|
||||
if (episode == null)
|
||||
var episode = await dbHelper.getRssItemWithUrl(task.url);
|
||||
if (episode == null) {
|
||||
await FlutterDownloader.remove(
|
||||
taskId: task.taskId, shouldDeleteContent: true);
|
||||
else
|
||||
} else {
|
||||
_episodeTasks.add(EpisodeTask(episode, task.taskId,
|
||||
progress: task.progress, status: task.status));
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _bindBackgroundIsolate() {
|
||||
ReceivePort _port = ReceivePort();
|
||||
bool isSuccess = IsolateNameServer.registerPortWithName(
|
||||
var _port = ReceivePort();
|
||||
var isSuccess = IsolateNameServer.registerPortWithName(
|
||||
_port.sendPort, 'downloader_send_port');
|
||||
if (!isSuccess) {
|
||||
_unbindBackgroundIsolate();
|
||||
|
@ -195,8 +192,9 @@ class DownloadState extends ChangeNotifier {
|
|||
_saveMediaId(episodeTask).then((value) {
|
||||
notifyListeners();
|
||||
});
|
||||
} else
|
||||
} else {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -206,16 +204,15 @@ class DownloadState extends ChangeNotifier {
|
|||
episodeTask.status = DownloadTaskStatus.complete;
|
||||
final completeTask = await FlutterDownloader.loadTasksWithRawQuery(
|
||||
query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'");
|
||||
String filePath = 'file://' +
|
||||
path.join(completeTask.first.savedDir,
|
||||
Uri.encodeComponent(completeTask.first.filename));
|
||||
var filePath =
|
||||
'file://${path.join(completeTask.first.savedDir, Uri.encodeComponent(completeTask.first.filename))}';
|
||||
print(filePath);
|
||||
FileStat fileStat = await File(
|
||||
var fileStat = await File(
|
||||
path.join(completeTask.first.savedDir, completeTask.first.filename))
|
||||
.stat();
|
||||
dbHelper.saveMediaId(episodeTask.episode.enclosureUrl, filePath,
|
||||
episodeTask.taskId, fileStat.size);
|
||||
EpisodeBrief episode =
|
||||
var episode =
|
||||
await dbHelper.getRssItemWithUrl(episodeTask.episode.enclosureUrl);
|
||||
_removeTask(episodeTask.episode);
|
||||
_episodeTasks.add(EpisodeTask(episode, episodeTask.taskId,
|
||||
|
@ -245,25 +242,23 @@ class DownloadState extends ChangeNotifier {
|
|||
|
||||
Future startTask(EpisodeBrief episode, {bool showNotification = true}) async {
|
||||
var dbHelper = DBHelper();
|
||||
bool isDownloaded = await dbHelper.isDownloaded(episode.enclosureUrl);
|
||||
var isDownloaded = await dbHelper.isDownloaded(episode.enclosureUrl);
|
||||
if (!isDownloaded) {
|
||||
final dir = await getExternalStorageDirectory();
|
||||
String localPath = path.join(dir.path, episode.feedTitle);
|
||||
var localPath = path.join(dir.path, episode.feedTitle);
|
||||
final saveDir = Directory(localPath);
|
||||
bool hasExisted = await saveDir.exists();
|
||||
var hasExisted = await saveDir.exists();
|
||||
if (!hasExisted) {
|
||||
saveDir.create();
|
||||
}
|
||||
DateTime now = DateTime.now();
|
||||
String datePlus = now.year.toString() +
|
||||
var now = DateTime.now();
|
||||
var datePlus = now.year.toString() +
|
||||
now.month.toString() +
|
||||
now.day.toString() +
|
||||
now.second.toString();
|
||||
String fileName = episode.title +
|
||||
datePlus +
|
||||
'.' +
|
||||
episode.enclosureUrl.split('/').last.split('.').last;
|
||||
String taskId = await FlutterDownloader.enqueue(
|
||||
var fileName =
|
||||
'${episode.title}$datePlus.${episode.enclosureUrl.split('/').last.split('.').last}';
|
||||
var taskId = await FlutterDownloader.enqueue(
|
||||
fileName: fileName,
|
||||
url: episode.enclosureUrl,
|
||||
savedDir: localPath,
|
||||
|
@ -277,14 +272,14 @@ class DownloadState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future pauseTask(EpisodeBrief episode) async {
|
||||
EpisodeTask task = episodeToTask(episode);
|
||||
var task = episodeToTask(episode);
|
||||
await FlutterDownloader.pause(taskId: task.taskId);
|
||||
}
|
||||
|
||||
Future resumeTask(EpisodeBrief episode) async {
|
||||
EpisodeTask task = episodeToTask(episode);
|
||||
String newTaskId = await FlutterDownloader.resume(taskId: task.taskId);
|
||||
int index = _episodeTasks.indexOf(task);
|
||||
var task = episodeToTask(episode);
|
||||
var newTaskId = await FlutterDownloader.resume(taskId: task.taskId);
|
||||
var index = _episodeTasks.indexOf(task);
|
||||
_removeTask(episode);
|
||||
FlutterDownloader.remove(taskId: task.taskId);
|
||||
var dbHelper = DBHelper();
|
||||
|
@ -293,10 +288,10 @@ class DownloadState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future retryTask(EpisodeBrief episode) async {
|
||||
EpisodeTask task = episodeToTask(episode);
|
||||
String newTaskId = await FlutterDownloader.retry(taskId: task.taskId);
|
||||
var task = episodeToTask(episode);
|
||||
var newTaskId = await FlutterDownloader.retry(taskId: task.taskId);
|
||||
await FlutterDownloader.remove(taskId: task.taskId);
|
||||
int index = _episodeTasks.indexOf(task);
|
||||
var index = _episodeTasks.indexOf(task);
|
||||
_removeTask(episode);
|
||||
var dbHelper = DBHelper();
|
||||
_episodeTasks.insert(index, EpisodeTask(episode, newTaskId));
|
||||
|
@ -304,20 +299,21 @@ class DownloadState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future removeTask(EpisodeBrief episode) async {
|
||||
EpisodeTask task = episodeToTask(episode);
|
||||
var task = episodeToTask(episode);
|
||||
await FlutterDownloader.remove(
|
||||
taskId: task.taskId, shouldDeleteContent: false);
|
||||
}
|
||||
|
||||
Future delTask(EpisodeBrief episode) async {
|
||||
EpisodeTask task = episodeToTask(episode);
|
||||
var task = episodeToTask(episode);
|
||||
await FlutterDownloader.remove(
|
||||
taskId: task.taskId, shouldDeleteContent: true);
|
||||
await dbHelper.delDownloaded(episode.enclosureUrl);
|
||||
|
||||
for (var episodeTask in _episodeTasks) {
|
||||
if (episodeTask.taskId == task.taskId)
|
||||
if (episodeTask.taskId == task.taskId) {
|
||||
episodeTask.status = DownloadTaskStatus.undefined;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
_removeTask(episode);
|
||||
|
@ -330,24 +326,27 @@ class DownloadState extends ChangeNotifier {
|
|||
|
||||
_autoDelete() async {
|
||||
print('Start auto delete outdated episodes');
|
||||
KeyValueStorage autoDeleteStorage = KeyValueStorage(autoDeleteKey);
|
||||
int autoDelete = await autoDeleteStorage.getInt();
|
||||
if (autoDelete == 0)
|
||||
var autoDeleteStorage = KeyValueStorage(autoDeleteKey);
|
||||
var autoDelete = await autoDeleteStorage.getInt();
|
||||
if (autoDelete == 0) {
|
||||
await autoDeleteStorage.saveInt(30);
|
||||
else if (autoDelete > 0) {
|
||||
int deadline = DateTime.now()
|
||||
} else if (autoDelete > 0) {
|
||||
var deadline = DateTime.now()
|
||||
.subtract(Duration(days: autoDelete))
|
||||
.millisecondsSinceEpoch;
|
||||
List<EpisodeBrief> episodes = await dbHelper.getOutdatedEpisode(deadline);
|
||||
var episodes = await dbHelper.getOutdatedEpisode(deadline);
|
||||
if (episodes.isNotEmpty) {
|
||||
for (var episode in episodes) await delTask(episode);
|
||||
for (var episode in episodes) {
|
||||
await delTask(episode);
|
||||
}
|
||||
}
|
||||
final tasks = await FlutterDownloader.loadTasksWithRawQuery(
|
||||
query:
|
||||
'SELECT * FROM task WHERE time_created < $deadline AND status = 3');
|
||||
for (var task in tasks)
|
||||
for (var task in tasks) {
|
||||
FlutterDownloader.remove(
|
||||
taskId: task.taskId, shouldDeleteContent: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,22 +3,20 @@ import 'dart:io';
|
|||
import 'dart:isolate';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:color_thief_flutter/color_thief_flutter.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_isolate/flutter_isolate.dart';
|
||||
import 'package:tsacdop/local_storage/key_value_storage.dart';
|
||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:color_thief_flutter/color_thief_flutter.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../webfeed/webfeed.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../type/fireside_data.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../webfeed/webfeed.dart';
|
||||
|
||||
class GroupEntity {
|
||||
final String name;
|
||||
|
@ -33,7 +31,7 @@ class GroupEntity {
|
|||
}
|
||||
|
||||
static GroupEntity fromJson(Map<String, Object> json) {
|
||||
List<String> list = List.from(json['podcastList']);
|
||||
var list = List<String>.from(json['podcastList']);
|
||||
return GroupEntity(json['name'] as String, json['id'] as String,
|
||||
json['color'] as String, list);
|
||||
}
|
||||
|
@ -65,7 +63,7 @@ class PodcastGroup {
|
|||
|
||||
Color getColor() {
|
||||
if (color != '#000000') {
|
||||
int colorInt = int.parse('FF' + color.toUpperCase(), radix: 16);
|
||||
var colorInt = int.parse('FF${color.toUpperCase()}', radix: 16);
|
||||
return Color(colorInt).withOpacity(1.0);
|
||||
} else {
|
||||
return Colors.blue[400];
|
||||
|
@ -80,9 +78,7 @@ class PodcastGroup {
|
|||
List<PodcastLocal> _orderedPodcasts;
|
||||
List<PodcastLocal> get ordereddPodcasts => _orderedPodcasts;
|
||||
|
||||
set setOrderedPodcasts(List<PodcastLocal> list) {
|
||||
_orderedPodcasts = list;
|
||||
}
|
||||
set setOrderedPodcasts(List<PodcastLocal> list) => _orderedPodcasts = list;
|
||||
|
||||
GroupEntity toEntity() {
|
||||
return GroupEntity(name, id, color, podcastList);
|
||||
|
@ -127,7 +123,7 @@ class SubscribeItem {
|
|||
|
||||
class GroupList extends ChangeNotifier {
|
||||
/// List of all gourps.
|
||||
List<PodcastGroup> _groups = [];
|
||||
final List<PodcastGroup> _groups = [];
|
||||
List<PodcastGroup> get groups => _groups;
|
||||
|
||||
DBHelper dbHelper = DBHelper();
|
||||
|
@ -142,7 +138,7 @@ class GroupList extends ChangeNotifier {
|
|||
bool get isLoading => _isLoading;
|
||||
|
||||
/// Svae ordered gourps info before saved.
|
||||
List<PodcastGroup> _orderChanged = [];
|
||||
final List<PodcastGroup> _orderChanged = [];
|
||||
List<PodcastGroup> get orderChanged => _orderChanged;
|
||||
|
||||
/// Subscribe worker isolate
|
||||
|
@ -175,13 +171,14 @@ class GroupList extends ChangeNotifier {
|
|||
await _createIsolate();
|
||||
_created = true;
|
||||
listen();
|
||||
} else
|
||||
} else {
|
||||
subSendPort.send([
|
||||
_subscribeItem.url,
|
||||
_subscribeItem.title,
|
||||
_subscribeItem.imgUrl,
|
||||
_subscribeItem.group
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _createIsolate() async {
|
||||
|
@ -207,8 +204,9 @@ class GroupList extends ChangeNotifier {
|
|||
message[0],
|
||||
subscribeState: SubscribeState.values[message[2]],
|
||||
));
|
||||
if (message.length == 5)
|
||||
if (message.length == 5) {
|
||||
_subscribeNewPodcast(id: message[3], groupName: message[4]);
|
||||
}
|
||||
} else if (message is String && message == "done") {
|
||||
subIsolate.kill();
|
||||
subIsolate = null;
|
||||
|
@ -231,7 +229,9 @@ class GroupList extends ChangeNotifier {
|
|||
|
||||
clearOrderChanged() async {
|
||||
if (_orderChanged.length > 0) {
|
||||
for (var group in _orderChanged) await group.getPodcasts();
|
||||
for (var group in _orderChanged) {
|
||||
await group.getPodcasts();
|
||||
}
|
||||
_orderChanged.clear();
|
||||
// notifyListeners();
|
||||
}
|
||||
|
@ -254,8 +254,10 @@ class GroupList extends ChangeNotifier {
|
|||
_isLoading = true;
|
||||
notifyListeners();
|
||||
storage.getGroups().then((loadgroups) async {
|
||||
_groups.addAll(loadgroups.map((e) => PodcastGroup.fromEntity(e)));
|
||||
for (var group in _groups) await group.getPodcasts();
|
||||
_groups.addAll(loadgroups.map(PodcastGroup.fromEntity));
|
||||
for (var group in _groups) {
|
||||
await group.getPodcasts();
|
||||
}
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
});
|
||||
|
@ -263,7 +265,9 @@ class GroupList extends ChangeNotifier {
|
|||
|
||||
/// Update podcasts of each group
|
||||
Future updateGroups() async {
|
||||
for (var group in _groups) await group.getPodcasts();
|
||||
for (var group in _groups) {
|
||||
await group.getPodcasts();
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
@ -279,10 +283,11 @@ class GroupList extends ChangeNotifier {
|
|||
/// Remove group.
|
||||
Future delGroup(PodcastGroup podcastGroup) async {
|
||||
_isLoading = true;
|
||||
for (var podcast in podcastGroup.podcastList)
|
||||
for (var podcast in podcastGroup.podcastList) {
|
||||
if (!_groups.first.podcastList.contains(podcast)) {
|
||||
_groups[0].podcastList.insert(0, podcast);
|
||||
}
|
||||
}
|
||||
await _saveGroup();
|
||||
_groups.remove(podcastGroup);
|
||||
await _groups[0].getPodcasts();
|
||||
|
@ -313,24 +318,25 @@ class GroupList extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future updatePodcast(String id) async {
|
||||
int counts = await dbHelper.getPodcastCounts(id);
|
||||
for (var group in _groups)
|
||||
var counts = await dbHelper.getPodcastCounts(id);
|
||||
for (var group in _groups) {
|
||||
if (group.podcastList.contains(id)) {
|
||||
group.podcasts.firstWhere((podcast) => podcast.id == id)
|
||||
..episodeCount = counts;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribe podcast from OMPL.
|
||||
Future<bool> _subscribeNewPodcast(
|
||||
{String id, String groupName = 'Home'}) async {
|
||||
//List<String> groupNames = _groups.map((e) => e.name).toList();
|
||||
for (PodcastGroup group in _groups) {
|
||||
for (var group in _groups) {
|
||||
if (group.name == groupName) {
|
||||
if (group.podcastList.contains(id))
|
||||
if (group.podcastList.contains(id)) {
|
||||
return true;
|
||||
else {
|
||||
} else {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
group.podcastList.insert(0, id);
|
||||
|
@ -354,11 +360,12 @@ class GroupList extends ChangeNotifier {
|
|||
}
|
||||
|
||||
List<PodcastGroup> getPodcastGroup(String id) {
|
||||
List<PodcastGroup> result = [];
|
||||
for (var group in _groups)
|
||||
var result = <PodcastGroup>[];
|
||||
for (var group in _groups) {
|
||||
if (group.podcastList.contains(id)) {
|
||||
result.add(group);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -374,9 +381,13 @@ class GroupList extends ChangeNotifier {
|
|||
group.podcastList.remove(id);
|
||||
}
|
||||
}
|
||||
for (var s in list) s.podcastList.insert(0, id);
|
||||
for (var s in list) {
|
||||
s.podcastList.insert(0, id);
|
||||
}
|
||||
await _saveGroup();
|
||||
for (var group in _groups) await group.getPodcasts();
|
||||
for (var group in _groups) {
|
||||
await group.getPodcasts();
|
||||
}
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -385,10 +396,14 @@ class GroupList extends ChangeNotifier {
|
|||
removePodcast(String id) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
for (var group in _groups) group.podcastList.remove(id);
|
||||
for (var group in _groups) {
|
||||
group.podcastList.remove(id);
|
||||
}
|
||||
await _saveGroup();
|
||||
await dbHelper.delPodcastLocal(id);
|
||||
for (var group in _groups) await group.getPodcasts();
|
||||
for (var group in _groups) {
|
||||
await group.getPodcasts();
|
||||
}
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -402,37 +417,37 @@ class GroupList extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<void> subIsolateEntryPoint(SendPort sendPort) async {
|
||||
List<SubscribeItem> items = [];
|
||||
bool _running = false;
|
||||
final List<String> listColor = [
|
||||
var items = <SubscribeItem>[];
|
||||
var _running = false;
|
||||
final listColor = <String>[
|
||||
'388E3C',
|
||||
'1976D2',
|
||||
'D32F2F',
|
||||
'00796B',
|
||||
];
|
||||
ReceivePort subReceivePort = ReceivePort();
|
||||
var subReceivePort = ReceivePort();
|
||||
sendPort.send(subReceivePort.sendPort);
|
||||
|
||||
Future<String> _getColor(File file) async {
|
||||
final imageProvider = FileImage(file);
|
||||
var colorImage = await getImageFromProvider(imageProvider);
|
||||
var color = await getColorFromImage(colorImage);
|
||||
String primaryColor = color.toString();
|
||||
var primaryColor = color.toString();
|
||||
return primaryColor;
|
||||
}
|
||||
|
||||
Future<void> _subscribe(SubscribeItem item) async {
|
||||
var dbHelper = DBHelper();
|
||||
String rss = item.url;
|
||||
var rss = item.url;
|
||||
sendPort.send([item.title, item.url, 1]);
|
||||
BaseOptions options = BaseOptions(
|
||||
var options = BaseOptions(
|
||||
connectTimeout: 20000,
|
||||
receiveTimeout: 20000,
|
||||
);
|
||||
print(rss);
|
||||
|
||||
try {
|
||||
Response response = await Dio(options).get(rss);
|
||||
var response = await Dio(options).get(rss);
|
||||
RssFeed p;
|
||||
try {
|
||||
p = RssFeed.parse(response.data);
|
||||
|
@ -444,47 +459,46 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
|
|||
items.removeWhere((element) => element.url == item.url);
|
||||
if (items.isNotEmpty) {
|
||||
await _subscribe(items.first);
|
||||
} else
|
||||
} else {
|
||||
sendPort.send("done");
|
||||
}
|
||||
}
|
||||
|
||||
var dir = await getApplicationDocumentsDirectory();
|
||||
|
||||
String realUrl =
|
||||
var realUrl =
|
||||
response.redirects.isEmpty ? rss : response.realUri.toString();
|
||||
|
||||
String checkUrl = await dbHelper.checkPodcast(realUrl);
|
||||
var checkUrl = await dbHelper.checkPodcast(realUrl);
|
||||
|
||||
/// If url not existe in database.
|
||||
if (checkUrl == '') {
|
||||
img.Image thumbnail;
|
||||
String imageUrl;
|
||||
try {
|
||||
Response<List<int>> imageResponse =
|
||||
await Dio().get<List<int>>(p.itunes.image.href,
|
||||
options: Options(
|
||||
responseType: ResponseType.bytes,
|
||||
receiveTimeout: 90000,
|
||||
));
|
||||
var imageResponse = await Dio().get<List<int>>(p.itunes.image.href,
|
||||
options: Options(
|
||||
responseType: ResponseType.bytes,
|
||||
receiveTimeout: 90000,
|
||||
));
|
||||
imageUrl = p.itunes.image.href;
|
||||
img.Image image = img.decodeImage(imageResponse.data);
|
||||
var image = img.decodeImage(imageResponse.data);
|
||||
thumbnail = img.copyResize(image, width: 300);
|
||||
} catch (e) {
|
||||
try {
|
||||
Response<List<int>> imageResponse =
|
||||
await Dio().get<List<int>>(item.imgUrl,
|
||||
options: Options(
|
||||
responseType: ResponseType.bytes,
|
||||
receiveTimeout: 90000,
|
||||
));
|
||||
var imageResponse = await Dio().get<List<int>>(item.imgUrl,
|
||||
options: Options(
|
||||
responseType: ResponseType.bytes,
|
||||
receiveTimeout: 90000,
|
||||
));
|
||||
imageUrl = item.imgUrl;
|
||||
img.Image image = img.decodeImage(imageResponse.data);
|
||||
var image = img.decodeImage(imageResponse.data);
|
||||
thumbnail = img.copyResize(image, width: 300);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
try {
|
||||
int index = math.Random().nextInt(3);
|
||||
Response<List<int>> imageResponse = await Dio().get<List<int>>(
|
||||
var index = math.Random().nextInt(3);
|
||||
var imageResponse = await Dio().get<List<int>>(
|
||||
"https://ui-avatars.com/api/?size=300&background="
|
||||
"${listColor[index]}&color=fff&name=${item.title}&length=2&bold=true",
|
||||
options: Options(responseType: ResponseType.bytes));
|
||||
|
@ -499,28 +513,29 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
|
|||
items.removeWhere((element) => element.url == item.url);
|
||||
if (items.length > 0) {
|
||||
await _subscribe(items.first);
|
||||
} else
|
||||
} else {
|
||||
sendPort.send("done");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
String uuid = Uuid().v4();
|
||||
var uuid = Uuid().v4();
|
||||
File("${dir.path}/$uuid.png")
|
||||
..writeAsBytesSync(img.encodePng(thumbnail));
|
||||
|
||||
String imagePath = "${dir.path}/$uuid.png";
|
||||
String primaryColor = await _getColor(File("${dir.path}/$uuid.png"));
|
||||
String author = p.itunes.author ?? p.author ?? '';
|
||||
String provider = p.generator ?? '';
|
||||
String link = p.link ?? '';
|
||||
PodcastLocal podcastLocal = PodcastLocal(p.title, imageUrl, realUrl,
|
||||
var imagePath = "${dir.path}/$uuid.png";
|
||||
var primaryColor = await _getColor(File("${dir.path}/$uuid.png"));
|
||||
var author = p.itunes.author ?? p.author ?? '';
|
||||
var provider = p.generator ?? '';
|
||||
var link = p.link ?? '';
|
||||
var podcastLocal = PodcastLocal(p.title, imageUrl, realUrl,
|
||||
primaryColor, author, uuid, imagePath, provider, link,
|
||||
description: p.description);
|
||||
|
||||
await dbHelper.savePodcastLocal(podcastLocal);
|
||||
sendPort.send([item.title, item.url, 2, uuid, item.group]);
|
||||
if (provider.contains('fireside')) {
|
||||
FiresideData data = FiresideData(uuid, link);
|
||||
var data = FiresideData(uuid, link);
|
||||
try {
|
||||
await data.fatchData();
|
||||
} catch (e) {
|
||||
|
@ -537,8 +552,9 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
|
|||
items.removeAt(0);
|
||||
if (items.length > 0) {
|
||||
await _subscribe(items.first);
|
||||
} else
|
||||
} else {
|
||||
sendPort.send("done");
|
||||
}
|
||||
} else {
|
||||
sendPort.send([item.title, realUrl, 5, checkUrl, item.group]);
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
|
@ -546,8 +562,9 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
|
|||
items.removeAt(0);
|
||||
if (items.length > 0) {
|
||||
await _subscribe(items.first);
|
||||
} else
|
||||
} else {
|
||||
sendPort.send("done");
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
|
@ -557,8 +574,9 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
|
|||
items.removeWhere((element) => element.url == item.url);
|
||||
if (items.length > 0) {
|
||||
await _subscribe(items.first);
|
||||
} else
|
||||
} else {
|
||||
sendPort.send("done");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:flutter_isolate/flutter_isolate.dart';
|
|||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
|
||||
enum RefreshState { none, fetch, error, artwork }
|
||||
|
||||
|
@ -68,14 +67,14 @@ class RefreshWorker extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<void> refreshIsolateEntryPoint(SendPort sendPort) async {
|
||||
KeyValueStorage refreshstorage = KeyValueStorage(refreshdateKey);
|
||||
var refreshstorage = KeyValueStorage(refreshdateKey);
|
||||
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
|
||||
var dbHelper = DBHelper();
|
||||
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
|
||||
var podcastList = await dbHelper.getPodcastLocalAll();
|
||||
for (var podcastLocal in podcastList) {
|
||||
sendPort.send([podcastLocal.title, 1]);
|
||||
int updateCount = await dbHelper.updatePodcastRss(podcastLocal);
|
||||
print('Refresh ' + podcastLocal.title + updateCount.toString());
|
||||
var updateCount = await dbHelper.updatePodcastRss(podcastLocal);
|
||||
print('Refresh ${podcastLocal.title}$updateCount');
|
||||
}
|
||||
sendPort.send("done");
|
||||
}
|
||||
|
|
|
@ -1,47 +1,45 @@
|
|||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:connectivity/connectivity.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../type/settings_backup.dart';
|
||||
import 'download_state.dart';
|
||||
|
||||
void callbackDispatcher() {
|
||||
if (Platform.isAndroid)
|
||||
if (Platform.isAndroid) {
|
||||
Workmanager.executeTask((task, inputData) async {
|
||||
var dbHelper = DBHelper();
|
||||
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
|
||||
var podcastList = await dbHelper.getPodcastLocalAll();
|
||||
//lastWork is a indicator for if the app was opened since last backgroundwork
|
||||
//if the app wes opend,then the old marked new episode would be marked not new.
|
||||
KeyValueStorage lastWorkStorage = KeyValueStorage(lastWorkKey);
|
||||
int lastWork = await lastWorkStorage.getInt();
|
||||
for (PodcastLocal podcastLocal in podcastList) {
|
||||
var lastWorkStorage = KeyValueStorage(lastWorkKey);
|
||||
var lastWork = await lastWorkStorage.getInt();
|
||||
for (var podcastLocal in podcastList) {
|
||||
await dbHelper.updatePodcastRss(podcastLocal, removeMark: lastWork);
|
||||
print('Refresh ' + podcastLocal.title);
|
||||
print('Refresh ${podcastLocal.title}');
|
||||
}
|
||||
await FlutterDownloader.initialize();
|
||||
AutoDownloader downloader = AutoDownloader();
|
||||
var downloader = AutoDownloader();
|
||||
|
||||
KeyValueStorage autoDownloadStorage =
|
||||
KeyValueStorage(autoDownloadNetworkKey);
|
||||
int autoDownloadNetwork = await autoDownloadStorage.getInt();
|
||||
var autoDownloadStorage = KeyValueStorage(autoDownloadNetworkKey);
|
||||
var autoDownloadNetwork = await autoDownloadStorage.getInt();
|
||||
var result = await Connectivity().checkConnectivity();
|
||||
if (autoDownloadNetwork == 1) {
|
||||
List<EpisodeBrief> episodes = await dbHelper.getNewEpisodes('all');
|
||||
var episodes = await dbHelper.getNewEpisodes('all');
|
||||
// For safety
|
||||
if (episodes.length < 100 && episodes.length > 0) {
|
||||
downloader.bindBackgroundIsolate();
|
||||
await downloader.startTask(episodes);
|
||||
}
|
||||
} else if (result == ConnectivityResult.wifi) {
|
||||
List<EpisodeBrief> episodes = await dbHelper.getNewEpisodes('all');
|
||||
var episodes = await dbHelper.getNewEpisodes('all');
|
||||
//For safety
|
||||
if (episodes.length < 100 && episodes.length > 0) {
|
||||
downloader.bindBackgroundIsolate();
|
||||
|
@ -49,10 +47,11 @@ void callbackDispatcher() {
|
|||
}
|
||||
}
|
||||
await lastWorkStorage.saveInt(1);
|
||||
KeyValueStorage refreshstorage = KeyValueStorage(refreshdateKey);
|
||||
var refreshstorage = KeyValueStorage(refreshdateKey);
|
||||
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
|
||||
return Future.value(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ThemeData lightTheme = ThemeData(
|
||||
|
@ -259,12 +258,9 @@ class SettingState extends ChangeNotifier {
|
|||
_getSleepTimerData();
|
||||
_getPlayerSeconds();
|
||||
_getUpdateInterval().then((value) async {
|
||||
if (_initUpdateTag == 0)
|
||||
if (_initUpdateTag == 0) {
|
||||
setWorkManager(24);
|
||||
|
||||
/// Restart worker if anythin changed in worker callback.
|
||||
/// varsion 2 add auto download new episodes
|
||||
else if (_autoUpdate && _initialShowIntor == 1) {
|
||||
} else if (_autoUpdate && _initialShowIntor == 1) {
|
||||
await cancelWork();
|
||||
setWorkManager(_initUpdateTag);
|
||||
await saveShowIntro(2);
|
||||
|
@ -273,14 +269,14 @@ class SettingState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future _getTheme() async {
|
||||
int mode = await themeStorage.getInt();
|
||||
var mode = await themeStorage.getInt();
|
||||
_theme = ThemeMode.values[mode];
|
||||
}
|
||||
|
||||
Future _getAccentSetColor() async {
|
||||
String colorString = await accentStorage.getString();
|
||||
var colorString = await accentStorage.getString();
|
||||
if (colorString.isNotEmpty) {
|
||||
int color = int.parse('FF' + colorString.toUpperCase(), radix: 16);
|
||||
var color = int.parse('FF${colorString.toUpperCase()}', radix: 16);
|
||||
_accentSetColor = Color(color).withOpacity(1.0);
|
||||
} else {
|
||||
_accentSetColor = Colors.teal[500];
|
||||
|
@ -393,37 +389,37 @@ class SettingState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<SettingsBackup> backup() async {
|
||||
int theme = await themeStorage.getInt();
|
||||
String accentColor = await accentStorage.getString();
|
||||
bool realDark = await realDarkStorage.getBool(defaultValue: false);
|
||||
bool autoPlay =
|
||||
var theme = await themeStorage.getInt();
|
||||
var accentColor = await accentStorage.getString();
|
||||
var realDark = await realDarkStorage.getBool(defaultValue: false);
|
||||
var autoPlay =
|
||||
await autoPlayStorage.getBool(defaultValue: true, reverse: true);
|
||||
bool autoUpdate =
|
||||
var autoUpdate =
|
||||
await autoupdateStorage.getBool(defaultValue: true, reverse: true);
|
||||
int updateInterval = await intervalStorage.getInt();
|
||||
bool downloadUsingData = await downloadUsingDataStorage.getBool(
|
||||
var updateInterval = await intervalStorage.getInt();
|
||||
var downloadUsingData = await downloadUsingDataStorage.getBool(
|
||||
defaultValue: true, reverse: true);
|
||||
int cacheMax = await cacheStorage.getInt(defaultValue: 500 * 1024 * 1024);
|
||||
int podcastLayout = await podcastLayoutStorage.getInt();
|
||||
int recentLayout = await recentLayoutStorage.getInt();
|
||||
int favLayout = await favLayoutStorage.getInt();
|
||||
int downloadLayout = await downloadLayoutStorage.getInt();
|
||||
bool autoDownloadNetwork =
|
||||
var cacheMax = await cacheStorage.getInt(defaultValue: 500 * 1024 * 1024);
|
||||
var podcastLayout = await podcastLayoutStorage.getInt();
|
||||
var recentLayout = await recentLayoutStorage.getInt();
|
||||
var favLayout = await favLayoutStorage.getInt();
|
||||
var downloadLayout = await downloadLayoutStorage.getInt();
|
||||
var autoDownloadNetwork =
|
||||
await autoDownloadStorage.getBool(defaultValue: false);
|
||||
List<String> episodePopupMenu =
|
||||
var episodePopupMenu =
|
||||
await KeyValueStorage(episodePopupMenuKey).getStringList();
|
||||
int autoDelete = await autoDeleteStorage.getInt();
|
||||
bool autoSleepTimer =
|
||||
var autoDelete = await autoDeleteStorage.getInt();
|
||||
var autoSleepTimer =
|
||||
await autoSleepTimerStorage.getBool(defaultValue: false);
|
||||
int autoSleepTimerStart = await autoSleepTimerStartStorage.getInt();
|
||||
int autoSleepTimerEnd = await autoSleepTimerEndStorage.getInt();
|
||||
int autoSleepTimerMode = await autoSleepTimerModeStorage.getInt();
|
||||
int defaultSleepTime = await defaultSleepTimerStorage.getInt();
|
||||
bool tapToOpenPopupMenu = await KeyValueStorage(tapToOpenPopupMenuKey)
|
||||
var autoSleepTimerStart = await autoSleepTimerStartStorage.getInt();
|
||||
var autoSleepTimerEnd = await autoSleepTimerEndStorage.getInt();
|
||||
var autoSleepTimerMode = await autoSleepTimerModeStorage.getInt();
|
||||
var defaultSleepTime = await defaultSleepTimerStorage.getInt();
|
||||
var tapToOpenPopupMenu = await KeyValueStorage(tapToOpenPopupMenuKey)
|
||||
.getBool(defaultValue: false);
|
||||
int fastForwardSeconds =
|
||||
var fastForwardSeconds =
|
||||
await fastForwardSecondsStorage.getInt(defaultValue: 30);
|
||||
int rewindSeconds = await rewindSecondsStorage.getInt(defaultValue: 10);
|
||||
var rewindSeconds = await rewindSecondsStorage.getInt(defaultValue: 10);
|
||||
|
||||
return SettingsBackup(
|
||||
theme: theme,
|
||||
|
|
|
@ -17,29 +17,29 @@ class FiresideData {
|
|||
DBHelper dbHelper = DBHelper();
|
||||
|
||||
String parseLink(String link) {
|
||||
if (link == "http://www.shengfm.cn/")
|
||||
if (link == "http://www.shengfm.cn/") {
|
||||
return "https://guiguzaozhidao.fireside.fm/";
|
||||
else
|
||||
} else {
|
||||
return link;
|
||||
}
|
||||
}
|
||||
|
||||
Future fatchData() async {
|
||||
Response response = await Dio().get(parseLink(link));
|
||||
var response = await Dio().get(parseLink(link));
|
||||
if (response.statusCode == 200) {
|
||||
var doc = parse(response.data);
|
||||
RegExp reg = RegExp(r'https(.+)jpg');
|
||||
String backgroundImage = reg.stringMatch(doc.body
|
||||
var reg = RegExp(r'https(.+)jpg');
|
||||
var backgroundImage = reg.stringMatch(doc.body
|
||||
.getElementsByClassName('hero-background')
|
||||
.first
|
||||
.attributes
|
||||
.toString());
|
||||
var ul = doc.body.getElementsByClassName('episode-hosts').first.children;
|
||||
List<PodcastHost> hosts = [];
|
||||
var hosts = <PodcastHost>[];
|
||||
for (var element in ul) {
|
||||
PodcastHost host;
|
||||
String name = element.text.trim();
|
||||
String image =
|
||||
element.children.first.children.first.attributes.toString();
|
||||
var name = element.text.trim();
|
||||
var image = element.children.first.children.first.attributes.toString();
|
||||
print(reg.stringMatch(image));
|
||||
|
||||
host = PodcastHost(
|
||||
|
@ -49,7 +49,7 @@ class FiresideData {
|
|||
|
||||
hosts.add(host);
|
||||
}
|
||||
List<String> data = [
|
||||
var data = <String>[
|
||||
id,
|
||||
backgroundImage,
|
||||
json.encode({'hosts': hosts.map((host) => host.toJson()).toList()})
|
||||
|
@ -59,7 +59,7 @@ class FiresideData {
|
|||
}
|
||||
|
||||
Future getData() async {
|
||||
List<String> data = await dbHelper.getFiresideData(id);
|
||||
var data = await dbHelper.getFiresideData(id);
|
||||
_background = data[0];
|
||||
if (data[1] != '') {
|
||||
_hosts = json
|
||||
|
@ -67,8 +67,9 @@ class FiresideData {
|
|||
.cast<Map<String, Object>>()
|
||||
.map<PodcastHost>(PodcastHost.fromJson)
|
||||
.toList();
|
||||
} else
|
||||
} else {
|
||||
_hosts = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,25 @@ import 'episodebrief.dart';
|
|||
|
||||
class PlayHistory {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
|
||||
/// Episdoe title.
|
||||
String title;
|
||||
|
||||
/// Episode url
|
||||
String url;
|
||||
double seconds;
|
||||
|
||||
/// Play record seconds.
|
||||
int seconds;
|
||||
|
||||
/// Play record count,
|
||||
double seekValue;
|
||||
|
||||
/// Listened date.
|
||||
DateTime playdate;
|
||||
|
||||
PlayHistory(this.title, this.url, this.seconds, this.seekValue,
|
||||
{this.playdate});
|
||||
|
||||
EpisodeBrief _episode;
|
||||
EpisodeBrief get episode => _episode;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import 'episodebrief.dart';
|
||||
|
||||
class Playlist {
|
||||
|
@ -12,21 +12,21 @@ class Playlist {
|
|||
KeyValueStorage storage = KeyValueStorage('playlist');
|
||||
|
||||
getPlaylist() async {
|
||||
List<String> urls = await storage.getStringList();
|
||||
var urls = await storage.getStringList();
|
||||
if (urls.length == 0) {
|
||||
_playlist = [];
|
||||
} else {
|
||||
_playlist = [];
|
||||
|
||||
for (String url in urls) {
|
||||
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(url);
|
||||
for (var url in urls) {
|
||||
var episode = await dbHelper.getRssItemWithUrl(url);
|
||||
if (episode != null) _playlist.add(episode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
savePlaylist() async {
|
||||
List<String> urls = [];
|
||||
var urls = <String>[];
|
||||
urls.addAll(_playlist.map((e) => e.enclosureUrl));
|
||||
await storage.saveStringList(urls.toSet().toList());
|
||||
}
|
||||
|
@ -48,10 +48,10 @@ class Playlist {
|
|||
}
|
||||
|
||||
Future<int> delFromPlaylist(EpisodeBrief episodeBrief) async {
|
||||
int index = _playlist.indexOf(episodeBrief);
|
||||
var index = _playlist.indexOf(episodeBrief);
|
||||
_playlist.removeWhere(
|
||||
(episode) => episode.enclosureUrl == episodeBrief.enclosureUrl);
|
||||
print('delete' + episodeBrief.title);
|
||||
print('delete${episodeBrief.title}');
|
||||
await savePlaylist();
|
||||
return index;
|
||||
}
|
||||
|
|
|
@ -69,7 +69,9 @@ class OnlinePodcast {
|
|||
int get hashCode => hashValues(id, title);
|
||||
|
||||
int get interval {
|
||||
if (count < 1) return null;
|
||||
if (count < 1) {
|
||||
return null;
|
||||
}
|
||||
return (latestPubDate - earliestPubDate) ~/ count;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ class SettingsBackup {
|
|||
}
|
||||
|
||||
static SettingsBackup fromJson(Map<String, Object> json) {
|
||||
List<String> list = List.from(json['episodePopupMenu']);
|
||||
var list = List<String>.from(json['episodePopupMenu']);
|
||||
return SettingsBackup(
|
||||
theme: json['theme'] as int,
|
||||
accentColor: json['accentColor'] as String,
|
||||
|
|
|
@ -41,7 +41,8 @@ class _AudioPanelState extends State<AudioPanel> with TickerProviderStateMixin {
|
|||
..addListener(() {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
_animation = Tween<double>(begin: 0, end: initSize).animate(_controller);
|
||||
_animation =
|
||||
Tween<double>(begin: 0, end: initSize).animate(_slowController);
|
||||
_controller.forward();
|
||||
super.initState();
|
||||
}
|
||||
|
@ -60,7 +61,7 @@ class _AudioPanelState extends State<AudioPanel> with TickerProviderStateMixin {
|
|||
child: (_animation.value > widget.minHeight + 30)
|
||||
? Positioned.fill(
|
||||
child: GestureDetector(
|
||||
onTap: () => _backToMini(),
|
||||
onTap: _backToMini,
|
||||
child: Container(
|
||||
color: Theme.of(context)
|
||||
.scaffoldBackgroundColor
|
||||
|
@ -73,8 +74,8 @@ class _AudioPanelState extends State<AudioPanel> with TickerProviderStateMixin {
|
|||
Container(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: GestureDetector(
|
||||
onVerticalDragStart: (event) => _start(event),
|
||||
onVerticalDragUpdate: (event) => _update(event),
|
||||
onVerticalDragStart: _start,
|
||||
onVerticalDragUpdate: _update,
|
||||
onVerticalDragEnd: (event) => _end(),
|
||||
child: Container(
|
||||
height: (_animation.value >= widget.maxHeight)
|
||||
|
|
|
@ -51,20 +51,20 @@ class _DropdownMenuPainter extends CustomPainter {
|
|||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final double selectedItemOffset = getSelectedItemOffset();
|
||||
final Tween<double> top = Tween<double>(
|
||||
final selectedItemOffset = getSelectedItemOffset();
|
||||
final top = Tween<double>(
|
||||
begin: selectedItemOffset.clamp(0.0, size.height - _kMenuItemHeight)
|
||||
as double,
|
||||
end: 0.0,
|
||||
);
|
||||
|
||||
final Tween<double> bottom = Tween<double>(
|
||||
final bottom = Tween<double>(
|
||||
begin: (top.begin + _kMenuItemHeight).clamp(_kMenuItemHeight, size.height)
|
||||
as double,
|
||||
end: size.height,
|
||||
);
|
||||
|
||||
final Rect rect = Rect.fromLTRB(
|
||||
final rect = Rect.fromLTRB(
|
||||
0.0, top.evaluate(resize), size.width, bottom.evaluate(resize));
|
||||
|
||||
_painter.paint(canvas, rect.topLeft, ImageConfiguration(size: rect.size));
|
||||
|
@ -134,7 +134,7 @@ class _DropdownMenuItemButtonState<T>
|
|||
}
|
||||
|
||||
if (focused && inTraditionalMode) {
|
||||
final _MenuLimits menuLimits = widget.route.getMenuLimits(
|
||||
final menuLimits = widget.route.getMenuLimits(
|
||||
widget.buttonRect, widget.constraints.maxHeight, widget.itemIndex);
|
||||
widget.route.scrollController.animateTo(
|
||||
menuLimits.scrollOffset,
|
||||
|
@ -145,8 +145,7 @@ class _DropdownMenuItemButtonState<T>
|
|||
}
|
||||
|
||||
void _handleOnTap() {
|
||||
final DropdownMenuItem<T> dropdownMenuItem =
|
||||
widget.route.items[widget.itemIndex].item;
|
||||
final dropdownMenuItem = widget.route.items[widget.itemIndex].item;
|
||||
|
||||
if (dropdownMenuItem.onTap != null) {
|
||||
dropdownMenuItem.onTap();
|
||||
|
@ -161,14 +160,14 @@ class _DropdownMenuItemButtonState<T>
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
CurvedAnimation opacity;
|
||||
final double unit = 0.5 / (widget.route.items.length + 1.5);
|
||||
final unit = 0.5 / (widget.route.items.length + 1.5);
|
||||
if (widget.itemIndex == widget.route.selectedIndex) {
|
||||
opacity = CurvedAnimation(
|
||||
parent: widget.route.animation, curve: const Threshold(0.0));
|
||||
} else {
|
||||
final double start =
|
||||
final start =
|
||||
(0.5 + (widget.itemIndex + 1) * unit).clamp(0.0, 1.0) as double;
|
||||
final double end = (start + 1.5 * unit).clamp(0.0, 1.0) as double;
|
||||
final end = (start + 1.5 * unit).clamp(0.0, 1.0) as double;
|
||||
opacity = CurvedAnimation(
|
||||
parent: widget.route.animation, curve: Interval(start, end));
|
||||
}
|
||||
|
@ -244,10 +243,9 @@ class _DropdownMenuState<T> extends State<_DropdownMenu<T>> {
|
|||
// When the menu is dismissed we just fade the entire thing out
|
||||
// in the first 0.25s.
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
final MaterialLocalizations localizations =
|
||||
MaterialLocalizations.of(context);
|
||||
final _DropdownRoute<T> route = widget.route;
|
||||
final List<Widget> children = <Widget>[
|
||||
final localizations = MaterialLocalizations.of(context);
|
||||
final route = widget.route;
|
||||
final children = <Widget>[
|
||||
for (int itemIndex = 0; itemIndex < route.items.length; ++itemIndex)
|
||||
_DropdownMenuItemButton<T>(
|
||||
route: widget.route,
|
||||
|
@ -315,13 +313,13 @@ class _DropdownMenuRouteLayout<T> extends SingleChildLayoutDelegate {
|
|||
// the view height. This ensures a tappable area outside of the simple menu
|
||||
// with which to dismiss the menu.
|
||||
// -- https://material.io/design/components/menus.html#usage
|
||||
final double maxHeight = displayItemCount == null
|
||||
final maxHeight = displayItemCount == null
|
||||
? math.max(0.0, constraints.maxHeight - 2 * _kMenuItemHeight)
|
||||
: math.min(_kMenuItemHeight * displayItemCount,
|
||||
constraints.maxHeight - 2 * _kMenuItemHeight);
|
||||
// The width of a menu should be at most the view width. This ensures that
|
||||
// the menu does not extend past the left and right edges of the screen.
|
||||
final double width = math.min(constraints.maxWidth, buttonRect.width);
|
||||
final width = math.min(constraints.maxWidth, buttonRect.width);
|
||||
return BoxConstraints(
|
||||
minWidth: width,
|
||||
maxWidth: width,
|
||||
|
@ -332,11 +330,11 @@ class _DropdownMenuRouteLayout<T> extends SingleChildLayoutDelegate {
|
|||
|
||||
@override
|
||||
Offset getPositionForChild(Size size, Size childSize) {
|
||||
final _MenuLimits menuLimits =
|
||||
final menuLimits =
|
||||
route.getMenuLimits(buttonRect, size.height, route.selectedIndex);
|
||||
|
||||
assert(() {
|
||||
final Rect container = Offset.zero & size;
|
||||
final container = Offset.zero & size;
|
||||
if (container.intersect(buttonRect) == buttonRect) {
|
||||
// If the button was entirely on-screen, then verify
|
||||
// that the menu is also on-screen.
|
||||
|
@ -440,8 +438,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
|
|||
@override
|
||||
Widget buildPage(BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation) {
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
return _DropdownRoutePage<T>(
|
||||
route: this,
|
||||
constraints: constraints,
|
||||
|
@ -463,12 +460,12 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
|
|||
}
|
||||
|
||||
double getItemOffset(int index) {
|
||||
double offset = kMaterialListPadding.top;
|
||||
var offset = kMaterialListPadding.top;
|
||||
if (items.isNotEmpty && index > 0) {
|
||||
assert(items.length == itemHeights?.length);
|
||||
offset += itemHeights
|
||||
.sublist(0, index)
|
||||
.reduce((double total, double height) => total + height);
|
||||
.reduce((total, height) => total + height);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
@ -479,30 +476,31 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
|
|||
// that's possible given availableHeight.
|
||||
_MenuLimits getMenuLimits(
|
||||
Rect buttonRect, double availableHeight, int index) {
|
||||
final double maxMenuHeight = availableHeight - 2.0 * _kMenuItemHeight;
|
||||
final double buttonTop = buttonRect.top;
|
||||
final double buttonBottom = math.min(buttonRect.bottom, availableHeight);
|
||||
final double selectedItemOffset = getItemOffset(index);
|
||||
final maxMenuHeight = availableHeight - 2.0 * _kMenuItemHeight;
|
||||
final buttonTop = buttonRect.top;
|
||||
final buttonBottom = math.min(buttonRect.bottom, availableHeight);
|
||||
final selectedItemOffset = getItemOffset(index);
|
||||
|
||||
// If the button is placed on the bottom or top of the screen, its top or
|
||||
// bottom may be less than [_kMenuItemHeight] from the edge of the screen.
|
||||
// In this case, we want to change the menu limits to align with the top
|
||||
// or bottom edge of the button.
|
||||
final double topLimit = math.min(_kMenuItemHeight, buttonTop);
|
||||
final double bottomLimit =
|
||||
final topLimit = math.min(_kMenuItemHeight, buttonTop);
|
||||
final bottomLimit =
|
||||
math.max(availableHeight - _kMenuItemHeight, buttonBottom);
|
||||
|
||||
double menuTop = (buttonTop - selectedItemOffset) -
|
||||
var menuTop = (buttonTop - selectedItemOffset) -
|
||||
(itemHeights[selectedIndex] - buttonRect.height) / 2.0;
|
||||
double preferredMenuHeight = kMaterialListPadding.vertical;
|
||||
if (items.isNotEmpty)
|
||||
var preferredMenuHeight = kMaterialListPadding.vertical;
|
||||
if (items.isNotEmpty) {
|
||||
preferredMenuHeight +=
|
||||
itemHeights.reduce((double total, double height) => total + height);
|
||||
itemHeights.reduce((total, height) => total + height);
|
||||
}
|
||||
|
||||
// If there are too many elements in the menu, we need to shrink it down
|
||||
// so it is at most the maxMenuHeight.
|
||||
final double menuHeight = math.min(maxMenuHeight, preferredMenuHeight);
|
||||
double menuBottom = menuTop + menuHeight;
|
||||
final menuHeight = math.min(maxMenuHeight, preferredMenuHeight);
|
||||
var menuBottom = menuTop + menuHeight;
|
||||
|
||||
// If the computed top or bottom of the menu are outside of the range
|
||||
// specified, we need to bring them into range. If the item height is larger
|
||||
|
@ -522,7 +520,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
|
|||
// shown - subsequently we leave the scroll offset where the user left
|
||||
// it. This scroll offset is only accurate for fixed height menu items
|
||||
// (the default).
|
||||
final double scrollOffset = preferredMenuHeight <= maxMenuHeight
|
||||
final scrollOffset = preferredMenuHeight <= maxMenuHeight
|
||||
? 0
|
||||
: math.max(0.0, selectedItemOffset - (buttonTop - menuTop));
|
||||
|
||||
|
@ -569,13 +567,13 @@ class _DropdownRoutePage<T> extends StatelessWidget {
|
|||
// Otherwise the initialScrollOffset is just a rough approximation based on
|
||||
// treating the items as if their heights were all equal to kMinInteractveDimension.
|
||||
if (route.scrollController == null) {
|
||||
final _MenuLimits menuLimits =
|
||||
final menuLimits =
|
||||
route.getMenuLimits(buttonRect, constraints.maxHeight, selectedIndex);
|
||||
route.scrollController =
|
||||
ScrollController(initialScrollOffset: menuLimits.scrollOffset);
|
||||
}
|
||||
|
||||
final TextDirection textDirection = Directionality.of(context);
|
||||
final textDirection = Directionality.of(context);
|
||||
Widget menu = _DropdownMenu<T>(
|
||||
route: route,
|
||||
padding: padding.resolve(textDirection),
|
||||
|
@ -593,7 +591,7 @@ class _DropdownRoutePage<T> extends StatelessWidget {
|
|||
removeLeft: true,
|
||||
removeRight: true,
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
builder: (context) {
|
||||
return CustomSingleChildLayout(
|
||||
delegate: _DropdownMenuRouteLayout<T>(
|
||||
buttonRect: buttonRect,
|
||||
|
@ -850,7 +848,7 @@ class MyDropdownButton<T> extends StatefulWidget {
|
|||
items == null ||
|
||||
items.isEmpty ||
|
||||
value == null ||
|
||||
items.where((DropdownMenuItem<T> item) {
|
||||
items.where((item) {
|
||||
return item.value == value;
|
||||
}).length ==
|
||||
1,
|
||||
|
@ -1128,7 +1126,7 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
// ActivateAction.key: _createAction,
|
||||
// };
|
||||
focusNode.addListener(_handleFocusChanged);
|
||||
final FocusManager focusManager = WidgetsBinding.instance.focusManager;
|
||||
final focusManager = WidgetsBinding.instance.focusManager;
|
||||
_focusHighlightMode = focusManager.highlightMode;
|
||||
focusManager.addHighlightModeListener(_handleFocusHighlightModeChange);
|
||||
}
|
||||
|
@ -1187,12 +1185,9 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
}
|
||||
|
||||
assert(widget.value == null ||
|
||||
widget.items
|
||||
.where((DropdownMenuItem<T> item) => item.value == widget.value)
|
||||
.length ==
|
||||
1);
|
||||
widget.items.where((item) => item.value == widget.value).length == 1);
|
||||
_selectedIndex = null;
|
||||
for (int itemIndex = 0; itemIndex < widget.items.length; itemIndex++) {
|
||||
for (var itemIndex = 0; itemIndex < widget.items.length; itemIndex++) {
|
||||
if (widget.items[itemIndex].value == widget.value) {
|
||||
_selectedIndex = itemIndex;
|
||||
return;
|
||||
|
@ -1204,20 +1199,18 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
widget.style ?? Theme.of(context).textTheme.subtitle1;
|
||||
|
||||
void _handleTap() {
|
||||
final RenderBox itemBox = context.findRenderObject() as RenderBox;
|
||||
final Rect itemRect = itemBox.localToGlobal(Offset.zero) & itemBox.size;
|
||||
final TextDirection textDirection = Directionality.of(context);
|
||||
final EdgeInsetsGeometry menuMargin =
|
||||
ButtonTheme.of(context).alignedDropdown
|
||||
? _kAlignedMenuMargin
|
||||
: _kUnalignedMenuMargin;
|
||||
final itemBox = context.findRenderObject() as RenderBox;
|
||||
final itemRect = itemBox.localToGlobal(Offset.zero) & itemBox.size;
|
||||
final textDirection = Directionality.of(context);
|
||||
final menuMargin = ButtonTheme.of(context).alignedDropdown
|
||||
? _kAlignedMenuMargin
|
||||
: _kUnalignedMenuMargin;
|
||||
|
||||
final List<_MenuItem<T>> menuItems =
|
||||
List<_MenuItem<T>>(widget.items.length);
|
||||
for (int index = 0; index < widget.items.length; index += 1) {
|
||||
final menuItems = List<_MenuItem<T>>(widget.items.length);
|
||||
for (var index = 0; index < widget.items.length; index += 1) {
|
||||
menuItems[index] = _MenuItem<T>(
|
||||
item: widget.items[index],
|
||||
onLayout: (Size size) {
|
||||
onLayout: (size) {
|
||||
// If [_dropdownRoute] is null and onLayout is called, this means
|
||||
// that performLayout was called on a _DropdownRoute that has not
|
||||
// left the widget tree but is already on its way out.
|
||||
|
@ -1248,8 +1241,7 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
displayItemCount: widget.displayItemCount,
|
||||
);
|
||||
|
||||
Navigator.push(context, _dropdownRoute)
|
||||
.then<void>((_DropdownRouteResult<T> newValue) {
|
||||
Navigator.push(context, _dropdownRoute).then<void>((newValue) {
|
||||
_removeDropdownRoute();
|
||||
if (!mounted || newValue == null) return;
|
||||
if (widget.onChanged != null) widget.onChanged(newValue.result);
|
||||
|
@ -1274,7 +1266,7 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
// Similarly, we don't reduce the height of the button so much that its icon
|
||||
// would be clipped.
|
||||
double get _denseButtonHeight {
|
||||
final double fontSize =
|
||||
final fontSize =
|
||||
_textStyle.fontSize ?? Theme.of(context).textTheme.subtitle1.fontSize;
|
||||
return math.max(fontSize, math.max(widget.iconSize, _kDenseButtonHeight));
|
||||
}
|
||||
|
@ -1311,11 +1303,11 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
widget.onChanged != null;
|
||||
|
||||
Orientation _getOrientation(BuildContext context) {
|
||||
Orientation result = MediaQuery.of(context, nullOk: true)?.orientation;
|
||||
var result = MediaQuery.of(context, nullOk: true)?.orientation;
|
||||
if (result == null) {
|
||||
// If there's no MediaQuery, then use the window aspect to determine
|
||||
// orientation.
|
||||
final Size size = window.physicalSize;
|
||||
final size = window.physicalSize;
|
||||
result = size.width > size.height
|
||||
? Orientation.landscape
|
||||
: Orientation.portrait;
|
||||
|
@ -1337,7 +1329,7 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
final Orientation newOrientation = _getOrientation(context);
|
||||
final newOrientation = _getOrientation(context);
|
||||
_lastOrientation ??= newOrientation;
|
||||
if (newOrientation != _lastOrientation) {
|
||||
_removeDropdownRoute();
|
||||
|
@ -1359,10 +1351,11 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
|
||||
int hintIndex;
|
||||
if (widget.hint != null || (!_enabled && widget.disabledHint != null)) {
|
||||
Widget displayedHint =
|
||||
var displayedHint =
|
||||
_enabled ? widget.hint : widget.disabledHint ?? widget.hint;
|
||||
if (widget.selectedItemBuilder == null)
|
||||
if (widget.selectedItemBuilder == null) {
|
||||
displayedHint = _DropdownMenuItemContainer(child: displayedHint);
|
||||
}
|
||||
|
||||
hintIndex = items.length;
|
||||
items.add(DefaultTextStyle(
|
||||
|
@ -1374,13 +1367,13 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
));
|
||||
}
|
||||
|
||||
final EdgeInsetsGeometry padding = ButtonTheme.of(context).alignedDropdown
|
||||
final padding = ButtonTheme.of(context).alignedDropdown
|
||||
? _kAlignedButtonPadding
|
||||
: _kUnalignedButtonPadding;
|
||||
|
||||
// If value is null (then _selectedIndex is null) or if disabled then we
|
||||
// display the hint or nothing at all.
|
||||
final int index = _enabled ? (_selectedIndex ?? hintIndex) : hintIndex;
|
||||
final index = _enabled ? (_selectedIndex ?? hintIndex) : hintIndex;
|
||||
Widget innerItemsWidget;
|
||||
if (items.isEmpty) {
|
||||
innerItemsWidget = Container();
|
||||
|
@ -1390,7 +1383,7 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
alignment: AlignmentDirectional.centerStart,
|
||||
children: widget.isDense
|
||||
? items
|
||||
: items.map((Widget item) {
|
||||
: items.map((item) {
|
||||
return widget.itemHeight != null
|
||||
? SizedBox(height: widget.itemHeight, child: item)
|
||||
: Column(
|
||||
|
@ -1400,7 +1393,7 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
);
|
||||
}
|
||||
|
||||
const Icon defaultIcon = Icon(Icons.arrow_drop_down);
|
||||
const defaultIcon = Icon(Icons.arrow_drop_down);
|
||||
|
||||
Widget result = DefaultTextStyle(
|
||||
style: _textStyle,
|
||||
|
@ -1434,8 +1427,7 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
);
|
||||
|
||||
if (!DropdownButtonHideUnderline.at(context)) {
|
||||
final double bottom =
|
||||
(widget.isDense || widget.itemHeight == null) ? 0.0 : 8.0;
|
||||
final bottom = (widget.isDense || widget.itemHeight == null) ? 0.0 : 8.0;
|
||||
result = Stack(
|
||||
children: <Widget>[
|
||||
result,
|
||||
|
@ -1508,7 +1500,7 @@ class DropdownButtonFormField<T> extends FormField<T> {
|
|||
items == null ||
|
||||
items.isEmpty ||
|
||||
value == null ||
|
||||
items.where((DropdownMenuItem<T> item) {
|
||||
items.where((item) {
|
||||
return item.value == value;
|
||||
}).length ==
|
||||
1,
|
||||
|
@ -1529,11 +1521,9 @@ class DropdownButtonFormField<T> extends FormField<T> {
|
|||
initialValue: value,
|
||||
validator: validator,
|
||||
autovalidate: autovalidate,
|
||||
builder: (FormFieldState<T> field) {
|
||||
final _DropdownButtonFormFieldState<T> state =
|
||||
field as _DropdownButtonFormFieldState<T>;
|
||||
final InputDecoration effectiveDecoration =
|
||||
decoration.applyDefaults(
|
||||
builder: (field) {
|
||||
final state = field as _DropdownButtonFormFieldState<T>;
|
||||
final effectiveDecoration = decoration.applyDefaults(
|
||||
Theme.of(field.context).inputDecorationTheme,
|
||||
);
|
||||
return InputDecorator(
|
||||
|
|
|
@ -8,11 +8,10 @@ class MyRectangularTrackShape extends RectangularSliderTrackShape {
|
|||
bool isEnabled = false,
|
||||
bool isDiscrete = false,
|
||||
}) {
|
||||
final double trackHeight = sliderTheme.trackHeight;
|
||||
final double trackLeft = offset.dx;
|
||||
final double trackTop =
|
||||
offset.dy + (parentBox.size.height - trackHeight) / 2;
|
||||
final double trackWidth = parentBox.size.width;
|
||||
final trackHeight = sliderTheme.trackHeight;
|
||||
final trackLeft = offset.dx;
|
||||
final trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
|
||||
final trackWidth = parentBox.size.width;
|
||||
return Rect.fromLTWH(trackLeft - 5, trackTop, trackWidth, trackHeight);
|
||||
}
|
||||
}
|
||||
|
@ -49,8 +48,8 @@ class MyRoundSliderThumpShape extends SliderComponentShape {
|
|||
double textScaleFactor,
|
||||
Size sizeWithOverflow,
|
||||
}) {
|
||||
final Canvas canvas = context.canvas;
|
||||
final Tween<double> radiusTween = Tween<double>(
|
||||
final canvas = context.canvas;
|
||||
final radiusTween = Tween<double>(
|
||||
begin: _disabledThumbRadius,
|
||||
end: enabledThumbRadius,
|
||||
);
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'episodegrid.dart';
|
||||
import 'extension_helper.dart';
|
||||
|
||||
//Layout change indicator
|
||||
|
@ -12,7 +14,7 @@ class LayoutPainter extends CustomPainter {
|
|||
LayoutPainter(this.scale, this.color);
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint _paint = Paint()
|
||||
var _paint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = 1.0
|
||||
..style = PaintingStyle.stroke
|
||||
|
@ -76,7 +78,7 @@ class StarSky extends CustomPainter {
|
|||
Offset(10, 26)
|
||||
].map((e) => Offset(e.dx * 10 + 250, e.dy * 10)).toList();
|
||||
|
||||
Paint paint = Paint()
|
||||
var paint = Paint()
|
||||
..color = Colors.white
|
||||
..strokeWidth = 2.0
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
@ -93,17 +95,17 @@ class StarSky extends CustomPainter {
|
|||
|
||||
//Listened indicator
|
||||
class ListenedPainter extends CustomPainter {
|
||||
Color _color;
|
||||
final Color _color;
|
||||
double stroke;
|
||||
ListenedPainter(this._color, {this.stroke = 1.0});
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint _paint = Paint()
|
||||
var _paint = Paint()
|
||||
..color = _color
|
||||
..strokeWidth = stroke
|
||||
..strokeCap = StrokeCap.round
|
||||
..style = PaintingStyle.stroke;
|
||||
Path _path = Path();
|
||||
var _path = Path();
|
||||
_path.moveTo(size.width / 6, size.height * 3 / 8);
|
||||
_path.lineTo(size.width / 6, size.height * 5 / 8);
|
||||
_path.moveTo(size.width / 3, size.height / 4);
|
||||
|
@ -126,17 +128,17 @@ class ListenedPainter extends CustomPainter {
|
|||
|
||||
//Listened Completely indicator
|
||||
class ListenedAllPainter extends CustomPainter {
|
||||
Color _color;
|
||||
double stroke;
|
||||
ListenedAllPainter(this._color, {this.stroke = 1.0});
|
||||
final Color color;
|
||||
final double stroke;
|
||||
ListenedAllPainter(this.color, {this.stroke = 1.0});
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint _paint = Paint()
|
||||
..color = _color
|
||||
var _paint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = stroke
|
||||
..strokeCap = StrokeCap.round
|
||||
..style = PaintingStyle.stroke;
|
||||
Path _path = Path();
|
||||
var _path = Path();
|
||||
_path.moveTo(size.width / 6, size.height * 3 / 8);
|
||||
_path.lineTo(size.width / 6, size.height * 5 / 8);
|
||||
_path.moveTo(size.width / 3, size.height / 4);
|
||||
|
@ -160,17 +162,17 @@ class ListenedAllPainter extends CustomPainter {
|
|||
|
||||
//Mark Listened indicator
|
||||
class MarkListenedPainter extends CustomPainter {
|
||||
Color _color;
|
||||
final Color color;
|
||||
double stroke;
|
||||
MarkListenedPainter(this._color, {this.stroke = 1.0});
|
||||
MarkListenedPainter(this.color, {this.stroke = 1.0});
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint _paint = Paint()
|
||||
..color = _color
|
||||
var _paint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = stroke
|
||||
..strokeCap = StrokeCap.round
|
||||
..style = PaintingStyle.stroke;
|
||||
Path _path = Path();
|
||||
var _path = Path();
|
||||
_path.moveTo(size.width / 6, size.height * 3 / 8);
|
||||
_path.lineTo(size.width / 6, size.height * 5 / 8);
|
||||
_path.moveTo(size.width / 3, size.height / 4);
|
||||
|
@ -203,17 +205,17 @@ class HideListenedPainter extends CustomPainter {
|
|||
{this.color, this.stroke = 1.0, this.backgroundColor, this.fraction});
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint _paint = Paint()
|
||||
var _paint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = stroke
|
||||
..strokeCap = StrokeCap.round
|
||||
..style = PaintingStyle.stroke;
|
||||
Paint _linePaint = Paint()
|
||||
var _linePaint = Paint()
|
||||
..color = backgroundColor
|
||||
..strokeWidth = stroke * 2
|
||||
..strokeCap = StrokeCap.round
|
||||
..style = PaintingStyle.stroke;
|
||||
Path _path = Path();
|
||||
var _path = Path();
|
||||
|
||||
_path.moveTo(size.width / 6, size.height * 3 / 8);
|
||||
_path.lineTo(size.width / 6, size.height * 5 / 8);
|
||||
|
@ -227,12 +229,13 @@ class HideListenedPainter extends CustomPainter {
|
|||
_path.lineTo(size.width * 2 / 3, size.height * 3 / 4);
|
||||
|
||||
canvas.drawPath(_path, _paint);
|
||||
if (fraction > 0)
|
||||
if (fraction > 0) {
|
||||
canvas.drawLine(
|
||||
Offset(size.width, size.height) / 5,
|
||||
Offset(size.width, size.height) / 5 +
|
||||
Offset(size.width, size.height) * 3 / 5 * fraction,
|
||||
_linePaint);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -260,10 +263,11 @@ class _HideListenedState extends State<HideListened>
|
|||
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
|
||||
animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
|
||||
..addListener(() {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_fraction = animation.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
if (widget.hideListened) _controller.forward();
|
||||
}
|
||||
|
@ -271,10 +275,11 @@ class _HideListenedState extends State<HideListened>
|
|||
@override
|
||||
void didUpdateWidget(HideListened oldWidget) {
|
||||
if (oldWidget.hideListened != widget.hideListened) {
|
||||
if (widget.hideListened)
|
||||
if (widget.hideListened) {
|
||||
_controller.forward();
|
||||
else
|
||||
} else {
|
||||
_controller.reverse();
|
||||
}
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
@ -297,17 +302,17 @@ class _HideListenedState extends State<HideListened>
|
|||
|
||||
//Add new episode to palylist
|
||||
class AddToPlaylistPainter extends CustomPainter {
|
||||
Color _color;
|
||||
Color _textColor;
|
||||
AddToPlaylistPainter(this._color, this._textColor);
|
||||
final Color color;
|
||||
final Color textColor;
|
||||
AddToPlaylistPainter(this.color, this.textColor);
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint _paint = Paint()
|
||||
..color = _color
|
||||
var _paint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = 1
|
||||
..strokeCap = StrokeCap.round
|
||||
..style = PaintingStyle.stroke;
|
||||
Path _path = Path();
|
||||
var _path = Path();
|
||||
_path.moveTo(0, 0);
|
||||
_path.lineTo(size.width * 4 / 7, 0);
|
||||
_path.moveTo(0, size.height / 3);
|
||||
|
@ -328,7 +333,7 @@ class AddToPlaylistPainter extends CustomPainter {
|
|||
text: TextSpan(
|
||||
text: 'N',
|
||||
style: TextStyle(
|
||||
fontStyle: FontStyle.italic, color: _textColor, fontSize: 10),
|
||||
fontStyle: FontStyle.italic, color: textColor, fontSize: 10),
|
||||
))
|
||||
..layout();
|
||||
textPainter.paint(canvas, Offset(size.width * 4 / 7, size.height / 3));
|
||||
|
@ -343,9 +348,9 @@ class AddToPlaylistPainter extends CustomPainter {
|
|||
|
||||
//Wave play indicator
|
||||
class WavePainter extends CustomPainter {
|
||||
double _fraction;
|
||||
final double _fraction;
|
||||
double _value;
|
||||
Color _color;
|
||||
final Color _color;
|
||||
WavePainter(this._fraction, this._color);
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
|
@ -354,8 +359,8 @@ class WavePainter extends CustomPainter {
|
|||
} else {
|
||||
_value = 1 - _fraction;
|
||||
}
|
||||
Path _path = Path();
|
||||
Paint _paint = Paint()
|
||||
var _path = Path();
|
||||
var _paint = Paint()
|
||||
..color = _color
|
||||
..strokeWidth = 2.0
|
||||
..strokeCap = StrokeCap.round
|
||||
|
@ -410,10 +415,11 @@ class _WaveLoaderState extends State<WaveLoader>
|
|||
vsync: this, duration: Duration(milliseconds: 1000));
|
||||
animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
|
||||
..addListener(() {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_fraction = animation.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
_controller.forward();
|
||||
_controller.addStatusListener((status) {
|
||||
|
@ -442,8 +448,8 @@ class _WaveLoaderState extends State<WaveLoader>
|
|||
class LovePainter extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Path _path = Path();
|
||||
Paint _paint = Paint()
|
||||
var _path = Path();
|
||||
var _paint = Paint()
|
||||
..color = Colors.red
|
||||
..strokeWidth = 2.0
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
@ -473,9 +479,9 @@ class LovePainter extends CustomPainter {
|
|||
//Line buffer indicator
|
||||
//Not used
|
||||
class LinePainter extends CustomPainter {
|
||||
double _fraction;
|
||||
final double _fraction;
|
||||
Paint _paint;
|
||||
Color _maincolor;
|
||||
final Color _maincolor;
|
||||
LinePainter(this._fraction, this._maincolor) {
|
||||
_paint = Paint()
|
||||
..color = _maincolor
|
||||
|
@ -512,10 +518,11 @@ class _LineLoaderState extends State<LineLoader>
|
|||
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
|
||||
animation = Tween(begin: 0.0, end: 1.0).animate(controller)
|
||||
..addListener(() {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_fraction = animation.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
controller.forward();
|
||||
controller.addStatusListener((status) {
|
||||
|
@ -563,10 +570,11 @@ class _ImageRotateState extends State<ImageRotate>
|
|||
);
|
||||
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
|
||||
..addListener(() {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_value = _animation.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
_controller.forward();
|
||||
_controller.addStatusListener((status) {
|
||||
|
@ -721,10 +729,11 @@ class _HeartSetState extends State<HeartSet>
|
|||
|
||||
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
|
||||
..addListener(() {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_value = _animation.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_controller.forward();
|
||||
|
@ -778,10 +787,11 @@ class _HeartOpenState extends State<HeartOpen>
|
|||
|
||||
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
|
||||
..addListener(() {
|
||||
if (mounted)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_value = _animation.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_controller.forward();
|
||||
|
@ -799,8 +809,8 @@ class _HeartOpenState extends State<HeartOpen>
|
|||
}
|
||||
|
||||
Widget _position(int i) {
|
||||
double scale = _list[i];
|
||||
double position = _list[i + 1];
|
||||
var scale = _list[i];
|
||||
var position = _list[i + 1];
|
||||
return Positioned(
|
||||
left: widget.width * position,
|
||||
bottom: widget.height * _value * scale,
|
||||
|
@ -812,9 +822,9 @@ class _HeartOpenState extends State<HeartOpen>
|
|||
);
|
||||
}
|
||||
|
||||
List<double> _list =
|
||||
final List<double> _list =
|
||||
List<double>.generate(20, (index) => math.Random().nextDouble());
|
||||
List<int> _index = List<int>.generate(19, (index) => index);
|
||||
final List<int> _index = List<int>.generate(19, (index) => index);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
|
@ -826,7 +836,7 @@ class _HeartOpenState extends State<HeartOpen>
|
|||
child: Icon(Icons.favorite,
|
||||
color: Colors.blue.withOpacity(0.7), size: 20 * _value),
|
||||
),
|
||||
..._index.map<Widget>((e) => _position(e)).toList(),
|
||||
..._index.map<Widget>(_position).toList(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -887,10 +897,6 @@ class DownloadPainter extends CustomPainter {
|
|||
..color = color
|
||||
..strokeWidth = 2.0
|
||||
..strokeCap = StrokeCap.round;
|
||||
var _linePaint = Paint()
|
||||
..color = color.withAlpha(70)
|
||||
..strokeWidth = 2.0
|
||||
..strokeCap = StrokeCap.round;
|
||||
var _circlePaint = Paint()
|
||||
..color = color.withAlpha(70)
|
||||
..style = PaintingStyle.stroke
|
||||
|
@ -899,8 +905,8 @@ class DownloadPainter extends CustomPainter {
|
|||
..color = progressColor
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2;
|
||||
double width = size.width;
|
||||
double height = size.height;
|
||||
var width = size.width;
|
||||
var height = size.height;
|
||||
var center = Offset(size.width / 2, size.height / 2);
|
||||
if (pauseProgress == 0) {
|
||||
canvas.drawLine(
|
||||
|
@ -911,10 +917,10 @@ class DownloadPainter extends CustomPainter {
|
|||
Offset(width / 2, height * 4 / 5), _paint);
|
||||
}
|
||||
|
||||
if (fraction == 0)
|
||||
if (fraction == 0) {
|
||||
canvas.drawLine(
|
||||
Offset(width / 5, height), Offset(width * 4 / 5, height), _paint);
|
||||
else {
|
||||
} else {
|
||||
canvas.drawArc(Rect.fromCircle(center: center, radius: width / 2),
|
||||
math.pi / 2, math.pi * fraction, false, _circlePaint);
|
||||
canvas.drawArc(Rect.fromCircle(center: center, radius: width / 2),
|
||||
|
@ -947,3 +953,50 @@ class DownloadPainter extends CustomPainter {
|
|||
oldDelegate.pauseProgress != pauseProgress;
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout icon button.
|
||||
class LayoutButton extends StatelessWidget {
|
||||
const LayoutButton({this.layout, this.onPressed, Key key}) : super(key: key);
|
||||
final Layout layout;
|
||||
final ValueChanged<Layout> onPressed;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (layout == Layout.three) {
|
||||
onPressed(Layout.one);
|
||||
} else if (layout == Layout.two) {
|
||||
onPressed(Layout.three);
|
||||
} else {
|
||||
onPressed(Layout.two);
|
||||
}
|
||||
},
|
||||
icon: layout == Layout.three
|
||||
? SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(0, context.textTheme.bodyText1.color),
|
||||
),
|
||||
)
|
||||
: layout == Layout.two
|
||||
? SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter:
|
||||
LayoutPainter(1, context.textTheme.bodyText1.color),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter:
|
||||
LayoutPainter(4, context.textTheme.bodyText1.color),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import 'package:flutter/rendering.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
const Duration _kDialAnimateDuration = const Duration(milliseconds: 200);
|
||||
const Duration _kDialAnimateDuration = Duration(milliseconds: 200);
|
||||
|
||||
const double _kDurationPickerWidthPortrait = 328.0;
|
||||
const double _kDurationPickerWidthLandscape = 512.0;
|
||||
|
@ -51,50 +51,49 @@ class _DialPainter extends CustomPainter {
|
|||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
const double _epsilon = .001;
|
||||
const double _sweep = _kTwoPi - _epsilon;
|
||||
const double _startAngle = -math.pi / 2.0;
|
||||
const _epsilon = .001;
|
||||
const _sweep = _kTwoPi - _epsilon;
|
||||
const _startAngle = -math.pi / 2.0;
|
||||
|
||||
final double radius = size.shortestSide / 2.0;
|
||||
final Offset center = new 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;
|
||||
|
||||
double pctTheta = (0.25 - (theta % _kTwoPi) / _kTwoPi) % 1.0;
|
||||
var pctTheta = (0.25 - (theta % _kTwoPi) / _kTwoPi) % 1.0;
|
||||
|
||||
// Draw the background outer ring
|
||||
canvas.drawCircle(
|
||||
centerPoint, radius, new Paint()..color = backgroundColor);
|
||||
canvas.drawCircle(centerPoint, radius, Paint()..color = backgroundColor);
|
||||
|
||||
// Draw a translucent circle for every hour
|
||||
for (int i = 0; i < multiplier; i = i + 1) {
|
||||
for (var i = 0; i < multiplier; i = i + 1) {
|
||||
canvas.drawCircle(centerPoint, radius,
|
||||
new Paint()..color = accentColor.withOpacity((i == 0) ? 0.3 : 0.1));
|
||||
Paint()..color = accentColor.withOpacity((i == 0) ? 0.3 : 0.1));
|
||||
}
|
||||
|
||||
// Draw the inner background circle
|
||||
canvas.drawCircle(centerPoint, radius * 0.88,
|
||||
new Paint()..color = Theme.of(context).canvasColor);
|
||||
Paint()..color = Theme.of(context).canvasColor);
|
||||
|
||||
// Get the offset point for an angle value of theta, and a distance of _radius
|
||||
Offset getOffsetForTheta(double theta, double _radius) {
|
||||
return center +
|
||||
new Offset(_radius * math.cos(theta), -_radius * math.sin(theta));
|
||||
Offset(_radius * math.cos(theta), -_radius * math.sin(theta));
|
||||
}
|
||||
|
||||
// Draw the handle that is used to drag and to indicate the position around the circle
|
||||
final Paint handlePaint = new Paint()..color = accentColor;
|
||||
final Offset handlePoint = getOffsetForTheta(theta, radius - 10.0);
|
||||
final handlePaint = Paint()..color = accentColor;
|
||||
final handlePoint = getOffsetForTheta(theta, radius - 10.0);
|
||||
canvas.drawCircle(handlePoint, 20.0, handlePaint);
|
||||
|
||||
// Draw the Text in the center of the circle which displays hours and mins
|
||||
String minutes = (multiplier == 0) ? '' : "${multiplier}min ";
|
||||
var minutes = (multiplier == 0) ? '' : "${multiplier}min ";
|
||||
// int minutes = (pctTheta * 60).round();
|
||||
// minutes = minutes == 60 ? 0 : minutes;
|
||||
String seconds = "$secondHand";
|
||||
var seconds = "$secondHand";
|
||||
|
||||
TextPainter textDurationValuePainter = new TextPainter(
|
||||
var textDurationValuePainter = TextPainter(
|
||||
textAlign: TextAlign.center,
|
||||
text: new TextSpan(
|
||||
text: TextSpan(
|
||||
text: '$minutes$seconds',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
|
@ -102,28 +101,28 @@ class _DialPainter extends CustomPainter {
|
|||
.copyWith(fontSize: size.shortestSide * 0.15)),
|
||||
textDirection: TextDirection.ltr)
|
||||
..layout();
|
||||
Offset middleForValueText = new Offset(
|
||||
var middleForValueText = Offset(
|
||||
centerPoint.dx - (textDurationValuePainter.width / 2),
|
||||
centerPoint.dy - textDurationValuePainter.height / 2);
|
||||
textDurationValuePainter.paint(canvas, middleForValueText);
|
||||
|
||||
TextPainter textMinPainter = new TextPainter(
|
||||
var textMinPainter = TextPainter(
|
||||
textAlign: TextAlign.center,
|
||||
text: new TextSpan(
|
||||
text: TextSpan(
|
||||
text: 'sec', //th: ${theta}',
|
||||
style: Theme.of(context).textTheme.bodyText1),
|
||||
textDirection: TextDirection.ltr)
|
||||
..layout();
|
||||
textMinPainter.paint(
|
||||
canvas,
|
||||
new Offset(
|
||||
Offset(
|
||||
centerPoint.dx - (textMinPainter.width / 2),
|
||||
centerPoint.dy +
|
||||
(textDurationValuePainter.height / 2) -
|
||||
textMinPainter.height / 2));
|
||||
|
||||
// Draw an arc around the circle for the amount of the circle that has elapsed.
|
||||
var elapsedPainter = new Paint()
|
||||
var elapsedPainter = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeCap = StrokeCap.round
|
||||
..color = accentColor.withOpacity(0.3)
|
||||
|
@ -131,7 +130,7 @@ class _DialPainter extends CustomPainter {
|
|||
..strokeWidth = radius * 0.12;
|
||||
|
||||
canvas.drawArc(
|
||||
new Rect.fromCircle(
|
||||
Rect.fromCircle(
|
||||
center: centerPoint,
|
||||
radius: radius - radius * 0.12 / 2,
|
||||
),
|
||||
|
@ -144,12 +143,11 @@ class _DialPainter extends CustomPainter {
|
|||
// Paint the labels (the minute strings)
|
||||
void paintLabels(List<TextPainter> labels) {
|
||||
if (labels == null) return;
|
||||
final double labelThetaIncrement = -_kTwoPi / labels.length;
|
||||
double labelTheta = _kPiByTwo;
|
||||
final labelThetaIncrement = -_kTwoPi / labels.length;
|
||||
var labelTheta = _kPiByTwo;
|
||||
|
||||
for (TextPainter label in labels) {
|
||||
final Offset labelOffset =
|
||||
new Offset(-label.width / 2.0, -label.height / 2.0);
|
||||
for (var label in labels) {
|
||||
final labelOffset = Offset(-label.width / 2.0, -label.height / 2.0);
|
||||
|
||||
label.paint(
|
||||
canvas, getOffsetForTheta(labelTheta, radius - 40.0) + labelOffset);
|
||||
|
@ -183,21 +181,20 @@ class _Dial extends StatefulWidget {
|
|||
/// The resolution of mins of the dial, i.e. if snapToMins = 5.0, only durations of 5min intervals will be selectable.
|
||||
final double snapToMins;
|
||||
@override
|
||||
_DialState createState() => new _DialState();
|
||||
_DialState createState() => _DialState();
|
||||
}
|
||||
|
||||
class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_thetaController = new AnimationController(
|
||||
_thetaController = AnimationController(
|
||||
duration: _kDialAnimateDuration,
|
||||
vsync: this,
|
||||
);
|
||||
_thetaTween =
|
||||
new Tween<double>(begin: _getThetaForDuration(widget.duration));
|
||||
_theta = _thetaTween.animate(new CurvedAnimation(
|
||||
parent: _thetaController, curve: Curves.fastOutSlowIn))
|
||||
_thetaTween = Tween<double>(begin: _getThetaForDuration(widget.duration));
|
||||
_theta = _thetaTween.animate(
|
||||
CurvedAnimation(parent: _thetaController, curve: Curves.fastOutSlowIn))
|
||||
..addListener(() => setState(() {}));
|
||||
_thetaController.addStatusListener((status) {
|
||||
// if (status == AnimationStatus.completed && _hours != _snappedHours) {
|
||||
|
@ -238,7 +235,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
|
|||
Animation<double> _theta;
|
||||
AnimationController _thetaController;
|
||||
|
||||
double _pct = 0.0;
|
||||
final double _pct = 0.0;
|
||||
int _seconds = 0;
|
||||
bool _dragging = false;
|
||||
int _minutes = 0;
|
||||
|
@ -249,8 +246,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
|
||||
|
@ -284,9 +281,8 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
|
|||
|
||||
void _updateThetaForPan() {
|
||||
setState(() {
|
||||
final Offset offset = _position - _center;
|
||||
final double angle =
|
||||
(math.atan2(offset.dx, offset.dy) - _kPiByTwo) % _kTwoPi;
|
||||
final offset = _position - _center;
|
||||
final angle = (math.atan2(offset.dx, offset.dy) - _kPiByTwo) % _kTwoPi;
|
||||
|
||||
// Stop accidental abrupt pans from making the dial seem like it starts from 1h.
|
||||
// (happens when wanting to pan from 0 clockwise, but when doing so quickly, one actually pans from before 0 (e.g. setting the duration to 59mins, and then crossing 0, which would then mean 1h 1min).
|
||||
|
@ -315,10 +311,10 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
|
|||
}
|
||||
|
||||
void _handlePanUpdate(DragUpdateDetails details) {
|
||||
double oldTheta = _theta.value;
|
||||
var oldTheta = _theta.value;
|
||||
_position += details.delta;
|
||||
_updateThetaForPan();
|
||||
double newTheta = _theta.value;
|
||||
var newTheta = _theta.value;
|
||||
// _updateRotations(oldTheta, newTheta);
|
||||
_updateTurningAngle(oldTheta, newTheta);
|
||||
_notifyOnChangedIfNeeded();
|
||||
|
@ -344,7 +340,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
|
|||
|
||||
double _angleToSeconds(double angle) {
|
||||
// Coordinate transformation from mathematical COS to dial COS
|
||||
double dialAngle = _kPiByTwo - angle;
|
||||
var dialAngle = _kPiByTwo - angle;
|
||||
|
||||
// Turn dial angle into minutes, may go beyond 60 minutes (multiple turns)
|
||||
return dialAngle / _kTwoPi * 60.0;
|
||||
|
@ -393,27 +389,27 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
|
|||
}
|
||||
|
||||
List<TextPainter> _buildSeconds(TextTheme textTheme) {
|
||||
final TextStyle style = textTheme.subtitle1;
|
||||
final style = textTheme.subtitle1;
|
||||
|
||||
const List<Duration> _secondsMarkerValues = const <Duration>[
|
||||
const Duration(seconds: 0),
|
||||
const Duration(seconds: 5),
|
||||
const Duration(seconds: 10),
|
||||
const Duration(seconds: 15),
|
||||
const Duration(seconds: 20),
|
||||
const Duration(seconds: 25),
|
||||
const Duration(seconds: 30),
|
||||
const Duration(seconds: 35),
|
||||
const Duration(seconds: 40),
|
||||
const Duration(seconds: 45),
|
||||
const Duration(seconds: 50),
|
||||
const Duration(seconds: 55),
|
||||
const _secondsMarkerValues = <Duration>[
|
||||
Duration(seconds: 0),
|
||||
Duration(seconds: 5),
|
||||
Duration(seconds: 10),
|
||||
Duration(seconds: 15),
|
||||
Duration(seconds: 20),
|
||||
Duration(seconds: 25),
|
||||
Duration(seconds: 30),
|
||||
Duration(seconds: 35),
|
||||
Duration(seconds: 40),
|
||||
Duration(seconds: 45),
|
||||
Duration(seconds: 50),
|
||||
Duration(seconds: 55),
|
||||
];
|
||||
|
||||
final List<TextPainter> labels = <TextPainter>[];
|
||||
for (Duration duration in _secondsMarkerValues) {
|
||||
var painter = new TextPainter(
|
||||
text: new TextSpan(style: style, text: duration.inSeconds.toString()),
|
||||
final labels = <TextPainter>[];
|
||||
for (var duration in _secondsMarkerValues) {
|
||||
var painter = TextPainter(
|
||||
text: TextSpan(style: style, text: duration.inSeconds.toString()),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
labels.add(painter);
|
||||
|
@ -433,20 +429,20 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
|
|||
break;
|
||||
}
|
||||
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
int selectedDialValue;
|
||||
_minutes = _minuteHand(_turningAngle);
|
||||
_seconds = _secondHand(_turningAngle);
|
||||
|
||||
return new GestureDetector(
|
||||
return GestureDetector(
|
||||
excludeFromSemantics: true,
|
||||
onPanStart: _handlePanStart,
|
||||
onPanUpdate: _handlePanUpdate,
|
||||
onPanEnd: _handlePanEnd,
|
||||
onTapUp: _handleTapUp,
|
||||
child: new CustomPaint(
|
||||
painter: new _DialPainter(
|
||||
child: CustomPaint(
|
||||
painter: _DialPainter(
|
||||
pct: _pct,
|
||||
multiplier: _minutes,
|
||||
secondHand: _seconds,
|
||||
|
@ -482,7 +478,7 @@ class _DurationPickerDialog extends StatefulWidget {
|
|||
final double snapToMins;
|
||||
|
||||
@override
|
||||
_DurationPickerDialogState createState() => new _DurationPickerDialogState();
|
||||
_DurationPickerDialogState createState() => _DurationPickerDialogState();
|
||||
}
|
||||
|
||||
class _DurationPickerDialogState extends State<_DurationPickerDialog> {
|
||||
|
@ -520,34 +516,34 @@ class _DurationPickerDialogState extends State<_DurationPickerDialog> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMediaQuery(context));
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final Widget picker = new Padding(
|
||||
final Widget picker = Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: new AspectRatio(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: new _Dial(
|
||||
child: _Dial(
|
||||
duration: _selectedDuration,
|
||||
onChanged: _handleTimeChanged,
|
||||
snapToMins: widget.snapToMins,
|
||||
)));
|
||||
|
||||
final Widget actions = ButtonBar(children: <Widget>[
|
||||
new FlatButton(
|
||||
child: new Text(localizations.cancelButtonLabel),
|
||||
FlatButton(
|
||||
child: Text(localizations.cancelButtonLabel),
|
||||
onPressed: _handleCancel),
|
||||
new FlatButton(
|
||||
child: new Text(localizations.okButtonLabel), onPressed: _handleOk),
|
||||
FlatButton(
|
||||
child: Text(localizations.okButtonLabel), onPressed: _handleOk),
|
||||
]);
|
||||
|
||||
final Dialog dialog = new Dialog(child: new OrientationBuilder(
|
||||
builder: (BuildContext context, Orientation orientation) {
|
||||
final Widget pickerAndActions = new Container(
|
||||
final dialog =
|
||||
Dialog(child: OrientationBuilder(builder: (context, orientation) {
|
||||
final Widget pickerAndActions = Container(
|
||||
color: theme.dialogBackgroundColor,
|
||||
child: new Column(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
new Expanded(
|
||||
Expanded(
|
||||
child:
|
||||
picker), // picker grows and shrinks with the available space
|
||||
actions,
|
||||
|
@ -558,26 +554,26 @@ class _DurationPickerDialogState extends State<_DurationPickerDialog> {
|
|||
assert(orientation != null);
|
||||
switch (orientation) {
|
||||
case Orientation.portrait:
|
||||
return new SizedBox(
|
||||
return SizedBox(
|
||||
width: _kDurationPickerWidthPortrait,
|
||||
height: _kDurationPickerHeightPortrait,
|
||||
child: new Column(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Expanded(
|
||||
Expanded(
|
||||
child: pickerAndActions,
|
||||
),
|
||||
]));
|
||||
case Orientation.landscape:
|
||||
return new SizedBox(
|
||||
return SizedBox(
|
||||
width: _kDurationPickerWidthLandscape,
|
||||
height: _kDurationPickerHeightLandscape,
|
||||
child: new Row(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Flexible(
|
||||
Flexible(
|
||||
child: pickerAndActions,
|
||||
),
|
||||
]));
|
||||
|
@ -585,7 +581,7 @@ class _DurationPickerDialogState extends State<_DurationPickerDialog> {
|
|||
return null;
|
||||
}));
|
||||
|
||||
return new Theme(
|
||||
return Theme(
|
||||
data: theme.copyWith(
|
||||
dialogBackgroundColor: Colors.transparent,
|
||||
),
|
||||
|
@ -621,8 +617,8 @@ Future<Duration> showDurationPicker(
|
|||
|
||||
return await showDialog<Duration>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => new _DurationPickerDialog(
|
||||
initialTime: initialTime, snapToMins: snapToMins),
|
||||
builder: (context) =>
|
||||
_DurationPickerDialog(initialTime: initialTime, snapToMins: snapToMins),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:auto_animated/auto_animated.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:focused_menu/focused_menu.dart';
|
||||
import 'package:focused_menu/modals.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:auto_animated/auto_animated.dart';
|
||||
import 'open_container.dart';
|
||||
|
||||
import '../episodes/episode_detail.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../state/audio_state.dart';
|
||||
import '../state/download_state.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import '../episodes/episode_detail.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import 'extension_helper.dart';
|
||||
import 'custom_widget.dart';
|
||||
import 'extension_helper.dart';
|
||||
import 'open_container.dart';
|
||||
|
||||
enum Layout { three, two, one }
|
||||
|
||||
|
@ -50,49 +50,47 @@ class EpisodeGrid extends StatelessWidget {
|
|||
}) : super(key: key);
|
||||
|
||||
Future<int> _isListened(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
var dbHelper = DBHelper();
|
||||
return await dbHelper.isListened(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
Future<Tuple5<int, bool, bool, bool, List<int>>> _initData(
|
||||
EpisodeBrief episode) async {
|
||||
List<int> menuList = await _getEpisodeMenu();
|
||||
bool tapToOpen = await _getTapToOpenPopupMenu();
|
||||
int listened = await _isListened(episode);
|
||||
bool liked = await _isLiked(episode);
|
||||
bool downloaded = await _isDownloaded(episode);
|
||||
var menuList = await _getEpisodeMenu();
|
||||
var tapToOpen = await _getTapToOpenPopupMenu();
|
||||
var listened = await _isListened(episode);
|
||||
var liked = await _isLiked(episode);
|
||||
var downloaded = await _isDownloaded(episode);
|
||||
return Tuple5(listened, liked, downloaded, tapToOpen, menuList);
|
||||
}
|
||||
|
||||
Future<bool> _isLiked(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
var dbHelper = DBHelper();
|
||||
return await dbHelper.isLiked(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
Future<List<int>> _getEpisodeMenu() async {
|
||||
KeyValueStorage popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
|
||||
List<int> list = await popupMenuStorage.getMenu();
|
||||
var popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
|
||||
var list = await popupMenuStorage.getMenu();
|
||||
return list;
|
||||
}
|
||||
|
||||
Future<bool> _isDownloaded(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
var dbHelper = DBHelper();
|
||||
return await dbHelper.isDownloaded(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
Future<bool> _getTapToOpenPopupMenu() async {
|
||||
KeyValueStorage tapToOpenPopupMenuStorage =
|
||||
KeyValueStorage(tapToOpenPopupMenuKey);
|
||||
bool boo = await tapToOpenPopupMenuStorage.getBool(defaultValue: false);
|
||||
var tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey);
|
||||
var boo = await tapToOpenPopupMenuStorage.getBool(defaultValue: false);
|
||||
return boo;
|
||||
}
|
||||
|
||||
_markListened(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
bool marked = await dbHelper.checkMarked(episode);
|
||||
var dbHelper = DBHelper();
|
||||
var marked = await dbHelper.checkMarked(episode);
|
||||
if (!marked) {
|
||||
final PlayHistory history =
|
||||
PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
final history = PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
await dbHelper.saveHistory(history);
|
||||
}
|
||||
}
|
||||
|
@ -107,11 +105,6 @@ class EpisodeGrid extends StatelessWidget {
|
|||
await dbHelper.setUniked(url);
|
||||
}
|
||||
|
||||
String _stringForSeconds(double seconds) {
|
||||
if (seconds == null) return null;
|
||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
/// Episode title widget.
|
||||
Widget _title(EpisodeBrief episode) => Container(
|
||||
alignment:
|
||||
|
@ -201,7 +194,7 @@ class EpisodeGrid extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double _width = context.width;
|
||||
var _width = context.width;
|
||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||
var downloader = Provider.of<DownloadState>(context, listen: false);
|
||||
final options = LiveOptions(
|
||||
|
@ -227,7 +220,7 @@ class EpisodeGrid extends StatelessWidget {
|
|||
crossAxisSpacing: 6.0,
|
||||
),
|
||||
itemBuilder: (context, index, animation) {
|
||||
Color _c = (Theme.of(context).brightness == Brightness.light)
|
||||
var _c = (Theme.of(context).brightness == Brightness.light)
|
||||
? episodes[index].primaryColor.colorizedark()
|
||||
: episodes[index].primaryColor.colorizeLight();
|
||||
scrollController.addListener(() {});
|
||||
|
@ -247,12 +240,12 @@ class EpisodeGrid extends StatelessWidget {
|
|||
Tuple5<int, bool, bool, bool, List<int>>>(
|
||||
future: _initData(episodes[index]),
|
||||
initialData: Tuple5(0, false, false, false, []),
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
int isListened = snapshot.data.item1;
|
||||
bool isLiked = snapshot.data.item2;
|
||||
bool isDownloaded = snapshot.data.item3;
|
||||
bool tapToOpen = snapshot.data.item4;
|
||||
List<int> menuList = snapshot.data.item5;
|
||||
builder: (context, snapshot) {
|
||||
var isListened = snapshot.data.item1;
|
||||
var isLiked = snapshot.data.item2;
|
||||
var isDownloaded = snapshot.data.item3;
|
||||
var tapToOpen = snapshot.data.item4;
|
||||
var menuList = snapshot.data.item5;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
|
@ -314,8 +307,9 @@ class EpisodeGrid extends StatelessWidget {
|
|||
color: Theme.of(context).accentColor,
|
||||
),
|
||||
onPressed: () {
|
||||
if (data.item1 != episodes[index])
|
||||
if (data.item1 != episodes[index]) {
|
||||
audio.episodeLoad(episodes[index]);
|
||||
}
|
||||
}),
|
||||
menuList.contains(1)
|
||||
? FocusedMenuItem(
|
||||
|
@ -431,8 +425,9 @@ class EpisodeGrid extends StatelessWidget {
|
|||
LineIcons.download_solid,
|
||||
color: Colors.green),
|
||||
onPressed: () {
|
||||
if (!isDownloaded)
|
||||
if (!isDownloaded) {
|
||||
downloader.startTask(episodes[index]);
|
||||
}
|
||||
})
|
||||
: null
|
||||
],
|
||||
|
@ -509,10 +504,9 @@ class EpisodeGrid extends StatelessWidget {
|
|||
? Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_stringForSeconds(
|
||||
episodes[index]
|
||||
.duration
|
||||
.toDouble()),
|
||||
episodes[index]
|
||||
.duration
|
||||
.toTime,
|
||||
style: TextStyle(
|
||||
fontSize: _width / 35),
|
||||
),
|
||||
|
@ -545,11 +539,7 @@ class EpisodeGrid extends StatelessWidget {
|
|||
? Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
((episodes[index]
|
||||
.enclosureLength) ~/
|
||||
1000000)
|
||||
.toString() +
|
||||
'MB',
|
||||
'${(episodes[index].enclosureLength) ~/ 1000000}MB',
|
||||
style: TextStyle(
|
||||
fontSize: _width / 35),
|
||||
),
|
||||
|
@ -623,7 +613,7 @@ class OpenContainerWrapper extends StatelessWidget {
|
|||
closedShape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0))),
|
||||
transitionType: ContainerTransitionType.fadeThrough,
|
||||
openBuilder: (BuildContext context, VoidCallback _, bool boo) {
|
||||
openBuilder: (context, _, boo) {
|
||||
return EpisodeDetail(
|
||||
episodeItem: episode,
|
||||
hide: boo,
|
||||
|
|
|
@ -25,7 +25,7 @@ extension IntExtension on int {
|
|||
String toDate(BuildContext context) {
|
||||
if (this == null) return '';
|
||||
final s = context.s;
|
||||
DateTime date = DateTime.fromMillisecondsSinceEpoch(this, isUtc: true);
|
||||
var date = DateTime.fromMillisecondsSinceEpoch(this, isUtc: true);
|
||||
var difference = DateTime.now().toUtc().difference(date);
|
||||
if (difference.inHours < 24) {
|
||||
return s.hoursAgo(difference.inHours);
|
||||
|
@ -38,20 +38,21 @@ extension IntExtension on int {
|
|||
}
|
||||
|
||||
String get toTime =>
|
||||
'${(this ~/ 60)}:${(this.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
'${(this ~/ 60)}:${(truncate() % 60).toString().padLeft(2, '0')}';
|
||||
|
||||
String toInterval(BuildContext context) {
|
||||
if (this == null || this.isNegative) return '';
|
||||
if (this == null || isNegative) return '';
|
||||
final s = context.s;
|
||||
var interval = Duration(milliseconds: this);
|
||||
if (interval.inHours <= 48)
|
||||
if (interval.inHours <= 48) {
|
||||
return s.publishedDaily;
|
||||
else if (interval.inDays > 2 && interval.inDays <= 14)
|
||||
} else if (interval.inDays > 2 && interval.inDays <= 14) {
|
||||
return s.publishedWeekly;
|
||||
else if (interval.inDays > 14 && interval.inDays < 60)
|
||||
} else if (interval.inDays > 14 && interval.inDays < 60) {
|
||||
return s.publishedMonthly;
|
||||
else
|
||||
} else {
|
||||
return s.publishedYearly;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,7 @@ generalDialog(BuildContext context,
|
|||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
barrierColor: Colors.black54,
|
||||
transitionDuration: const Duration(milliseconds: 200),
|
||||
pageBuilder: (BuildContext context, Animation animaiton,
|
||||
Animation secondaryAnimation) =>
|
||||
pageBuilder: (context, animaiton, secondaryAnimation) =>
|
||||
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
|
|
|
@ -48,7 +48,7 @@ class _RenderMenuItem extends RenderShiftedBox {
|
|||
child.layout(constraints, parentUsesSize: true);
|
||||
size = constraints.constrain(child.size);
|
||||
}
|
||||
final BoxParentData childParentData = child.parentData as BoxParentData;
|
||||
final childParentData = child.parentData as BoxParentData;
|
||||
childParentData.offset = Offset.zero;
|
||||
onLayout(size);
|
||||
}
|
||||
|
@ -66,16 +66,16 @@ class _PopupMenu<T> extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double unit = 1.0 /
|
||||
final unit = 1.0 /
|
||||
(route.items.length +
|
||||
1.5); // 1.0 for the width and 0.5 for the last item's fade.
|
||||
final List<Widget> children = <Widget>[];
|
||||
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||
final children = <Widget>[];
|
||||
final popupMenuTheme = PopupMenuTheme.of(context);
|
||||
|
||||
for (int i = 0; i < route.items.length; i += 1) {
|
||||
final double start = (i + 1) * unit;
|
||||
final double end = (start + 1.5 * unit).clamp(0.0, 1.0) as double;
|
||||
final CurvedAnimation opacity = CurvedAnimation(
|
||||
for (var i = 0; i < route.items.length; i += 1) {
|
||||
final start = (i + 1) * unit;
|
||||
final end = (start + 1.5 * unit).clamp(0.0, 1.0) as double;
|
||||
final opacity = CurvedAnimation(
|
||||
parent: route.animation,
|
||||
curve: Interval(start, end),
|
||||
);
|
||||
|
@ -89,7 +89,7 @@ class _PopupMenu<T> extends StatelessWidget {
|
|||
}
|
||||
children.add(
|
||||
_MenuItem(
|
||||
onLayout: (Size size) {
|
||||
onLayout: (size) {
|
||||
route.itemSizes[i] = size;
|
||||
},
|
||||
child: FadeTransition(
|
||||
|
@ -100,11 +100,9 @@ class _PopupMenu<T> extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
final CurveTween opacity =
|
||||
CurveTween(curve: const Interval(0.0, 1.0 / 3.0));
|
||||
final CurveTween width = CurveTween(curve: Interval(0.0, unit));
|
||||
final CurveTween height =
|
||||
CurveTween(curve: Interval(0.0, unit * route.items.length));
|
||||
final opacity = CurveTween(curve: const Interval(0.0, 1.0 / 3.0));
|
||||
final width = CurveTween(curve: Interval(0.0, unit));
|
||||
final height = CurveTween(curve: Interval(0.0, unit * route.items.length));
|
||||
|
||||
final Widget child = ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
|
@ -119,9 +117,7 @@ class _PopupMenu<T> extends StatelessWidget {
|
|||
explicitChildNodes: true,
|
||||
label: semanticLabel,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: _kMenuVerticalPadding
|
||||
),
|
||||
padding: const EdgeInsets.only(bottom: _kMenuVerticalPadding),
|
||||
child: ListBody(children: children),
|
||||
),
|
||||
),
|
||||
|
@ -130,12 +126,12 @@ class _PopupMenu<T> extends StatelessWidget {
|
|||
|
||||
return AnimatedBuilder(
|
||||
animation: route.animation,
|
||||
builder: (BuildContext context, Widget child) {
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: opacity.evaluate(route.animation),
|
||||
child: Material(
|
||||
shape: route.shape ?? popupMenuTheme.shape,
|
||||
color: route.color ?? popupMenuTheme.color,
|
||||
color: route.color ?? popupMenuTheme.color,
|
||||
type: MaterialType.card,
|
||||
elevation: route.elevation ?? popupMenuTheme.elevation ?? 8.0,
|
||||
child: Align(
|
||||
|
@ -175,12 +171,12 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|
|||
|
||||
@override
|
||||
Offset getPositionForChild(Size size, Size childSize) {
|
||||
double y = position.top;
|
||||
var y = position.top;
|
||||
if (selectedItemIndex != null && itemSizes != null) {
|
||||
double selectedItemOffset =
|
||||
_kMenuVerticalPadding;
|
||||
for (int index = 0; index < selectedItemIndex; index += 1)
|
||||
var selectedItemOffset = _kMenuVerticalPadding;
|
||||
for (var index = 0; index < selectedItemIndex; index += 1) {
|
||||
selectedItemOffset += itemSizes[index].height;
|
||||
}
|
||||
selectedItemOffset += itemSizes[selectedItemIndex].height / 2;
|
||||
y = position.top +
|
||||
(size.height - position.top - position.bottom) / 2.0 -
|
||||
|
@ -206,14 +202,16 @@ class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
if (x < _kMenuScreenPadding)
|
||||
if (x < _kMenuScreenPadding) {
|
||||
x = _kMenuScreenPadding;
|
||||
else if (x + childSize.width > size.width - _kMenuScreenPadding)
|
||||
} else if (x + childSize.width > size.width - _kMenuScreenPadding) {
|
||||
x = size.width - childSize.width - _kMenuScreenPadding;
|
||||
if (y < _kMenuScreenPadding)
|
||||
}
|
||||
if (y < _kMenuScreenPadding) {
|
||||
y = _kMenuScreenPadding;
|
||||
else if (y + childSize.height > size.height - _kMenuScreenPadding)
|
||||
} else if (y + childSize.height > size.height - _kMenuScreenPadding) {
|
||||
y = size.height - childSize.height - _kMenuScreenPadding;
|
||||
}
|
||||
return Offset(x, y);
|
||||
}
|
||||
|
||||
|
@ -283,7 +281,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||
Animation<double> secondaryAnimation) {
|
||||
int selectedItemIndex;
|
||||
if (initialValue != null) {
|
||||
for (int index = 0;
|
||||
for (var index = 0;
|
||||
selectedItemIndex == null && index < items.length;
|
||||
index += 1) {
|
||||
if (items[index].represents(initialValue)) selectedItemIndex = index;
|
||||
|
@ -304,7 +302,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||
removeLeft: true,
|
||||
removeRight: true,
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
builder: (context) {
|
||||
return CustomSingleChildLayout(
|
||||
delegate: _PopupMenuRouteLayout(
|
||||
position,
|
||||
|
@ -339,7 +337,7 @@ Future<T> _showMenu<T>({
|
|||
assert(captureInheritedThemes != null);
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
|
||||
String label = semanticLabel;
|
||||
var label = semanticLabel;
|
||||
switch (Theme.of(context).platform) {
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
|
@ -348,11 +346,13 @@ Future<T> _showMenu<T>({
|
|||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
label = semanticLabel ?? MaterialLocalizations.of(context)?.popupMenuLabel;
|
||||
case TargetPlatform.windows:
|
||||
label =
|
||||
semanticLabel ?? MaterialLocalizations.of(context)?.popupMenuLabel;
|
||||
}
|
||||
|
||||
return Navigator.of(context, rootNavigator: useRootNavigator).push(_PopupMenuRoute<T>(
|
||||
return Navigator.of(context, rootNavigator: useRootNavigator)
|
||||
.push(_PopupMenuRoute<T>(
|
||||
position: position,
|
||||
items: items,
|
||||
initialValue: initialValue,
|
||||
|
@ -368,7 +368,6 @@ Future<T> _showMenu<T>({
|
|||
));
|
||||
}
|
||||
|
||||
|
||||
class MyPopupMenuButton<T> extends StatefulWidget {
|
||||
/// Creates a button that shows a popup menu.
|
||||
///
|
||||
|
@ -428,11 +427,10 @@ class MyPopupMenuButton<T> extends StatefulWidget {
|
|||
|
||||
class MyPopupMenuButtonState<T> extends State<MyPopupMenuButton<T>> {
|
||||
void showButtonMenu() {
|
||||
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||
final RenderBox button = context.findRenderObject() as RenderBox;
|
||||
final RenderBox overlay =
|
||||
Overlay.of(context).context.findRenderObject() as RenderBox;
|
||||
final RelativeRect position = RelativeRect.fromRect(
|
||||
final popupMenuTheme = PopupMenuTheme.of(context);
|
||||
final button = context.findRenderObject() as RenderBox;
|
||||
final overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
|
||||
final position = RelativeRect.fromRect(
|
||||
Rect.fromPoints(
|
||||
button.localToGlobal(widget.offset, ancestor: overlay),
|
||||
button.localToGlobal(button.size.bottomRight(Offset.zero),
|
||||
|
@ -440,7 +438,7 @@ class MyPopupMenuButtonState<T> extends State<MyPopupMenuButton<T>> {
|
|||
),
|
||||
Offset.zero & overlay.size,
|
||||
);
|
||||
final List<PopupMenuEntry<T>> items = widget.itemBuilder(context);
|
||||
final items = widget.itemBuilder(context);
|
||||
// Only show the menu if there is something to show
|
||||
if (items.isNotEmpty) {
|
||||
_showMenu<T>(
|
||||
|
@ -452,7 +450,7 @@ class MyPopupMenuButtonState<T> extends State<MyPopupMenuButton<T>> {
|
|||
shape: widget.shape ?? popupMenuTheme.shape,
|
||||
color: widget.color ?? popupMenuTheme.color,
|
||||
captureInheritedThemes: widget.captureInheritedThemes,
|
||||
).then<void>((T newValue) {
|
||||
).then<void>((newValue) {
|
||||
if (!mounted) return null;
|
||||
if (newValue == null) {
|
||||
if (widget.onCanceled != null) widget.onCanceled();
|
||||
|
@ -482,7 +480,7 @@ class MyPopupMenuButtonState<T> extends State<MyPopupMenuButton<T>> {
|
|||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
|
||||
if (widget.child != null)
|
||||
if (widget.child != null) {
|
||||
return Tooltip(
|
||||
message:
|
||||
widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
|
||||
|
@ -492,6 +490,7 @@ class MyPopupMenuButtonState<T> extends State<MyPopupMenuButton<T>> {
|
|||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return IconButton(
|
||||
icon: widget.icon ?? _getIcon(Theme.of(context).platform),
|
||||
|
@ -545,9 +544,9 @@ class MyPopupMenuItemState<int, W extends MyPopupMenuItem<int>>
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||
TextStyle style = widget.textStyle ??
|
||||
final theme = Theme.of(context);
|
||||
final popupMenuTheme = PopupMenuTheme.of(context);
|
||||
var style = widget.textStyle ??
|
||||
popupMenuTheme.textStyle ??
|
||||
theme.textTheme.subtitle1;
|
||||
|
||||
|
|
|
@ -265,7 +265,7 @@ class _OpenContainerState extends State<OpenContainer> {
|
|||
// shape: widget.closedShape,
|
||||
child: Builder(
|
||||
key: _closedBuilderKey,
|
||||
builder: (BuildContext context) {
|
||||
builder: (context) {
|
||||
return widget.closedBuilder(context, openContainer, false);
|
||||
},
|
||||
),
|
||||
|
@ -546,7 +546,7 @@ class _OpenContainerRoute extends ModalRoute<void> {
|
|||
TickerFuture didPush() {
|
||||
_takeMeasurements(navigatorContext: hideableKey.currentContext);
|
||||
|
||||
animation.addStatusListener((AnimationStatus status) {
|
||||
animation.addStatusListener((status) {
|
||||
_lastAnimationStatus = _currentAnimationStatus;
|
||||
_currentAnimationStatus = status;
|
||||
switch (status) {
|
||||
|
@ -584,7 +584,7 @@ class _OpenContainerRoute extends ModalRoute<void> {
|
|||
}) {
|
||||
final RenderBox navigator =
|
||||
Navigator.of(navigatorContext).context.findRenderObject();
|
||||
final Size navSize = _getSize(navigator);
|
||||
final navSize = _getSize(navigator);
|
||||
_rectTween.end = Offset.zero & navSize;
|
||||
void takeMeasurementsInSourceRoute([Duration _]) {
|
||||
if (!navigator.attached || hideableKey.currentContext == null) {
|
||||
|
@ -622,8 +622,8 @@ class _OpenContainerRoute extends ModalRoute<void> {
|
|||
}
|
||||
|
||||
bool get _transitionWasInterrupted {
|
||||
bool wasInProgress = false;
|
||||
bool isInProgress = false;
|
||||
var wasInProgress = false;
|
||||
var isInProgress = false;
|
||||
|
||||
switch (_currentAnimationStatus) {
|
||||
case AnimationStatus.completed:
|
||||
|
@ -662,7 +662,7 @@ class _OpenContainerRoute extends ModalRoute<void> {
|
|||
alignment: Alignment.topLeft,
|
||||
child: AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (BuildContext context, Widget child) {
|
||||
builder: (context, child) {
|
||||
if (animation.isCompleted) {
|
||||
return SizedBox.expand(
|
||||
child: Material(
|
||||
|
@ -671,7 +671,7 @@ class _OpenContainerRoute extends ModalRoute<void> {
|
|||
shape: openShape,
|
||||
child: Builder(
|
||||
key: _openBuilderKey,
|
||||
builder: (BuildContext context) {
|
||||
builder: (context) {
|
||||
return openBuilder(context, closeContainer, false);
|
||||
},
|
||||
),
|
||||
|
@ -719,7 +719,7 @@ class _OpenContainerRoute extends ModalRoute<void> {
|
|||
assert(closedOpacityTween != null);
|
||||
assert(openOpacityTween != null);
|
||||
|
||||
final Rect rect = _rectTween.evaluate(curvedAnimation);
|
||||
final rect = _rectTween.evaluate(curvedAnimation);
|
||||
_positionTween.begin =
|
||||
Offset(_rectTween.begin.left + 10, _rectTween.begin.top + 10);
|
||||
_positionTween.end = Offset(
|
||||
|
@ -728,7 +728,7 @@ class _OpenContainerRoute extends ModalRoute<void> {
|
|||
? MediaQuery.of(context).size.height - 100
|
||||
: MediaQuery.of(context).size.height - 40);
|
||||
|
||||
double _width = MediaQuery.of(context).size.width;
|
||||
var _width = MediaQuery.of(context).size.width;
|
||||
_avatarScaleTween.begin = _width / 16;
|
||||
_avatarScaleTween.end = 30;
|
||||
return SizedBox.expand(
|
||||
|
@ -766,7 +766,7 @@ class _OpenContainerRoute extends ModalRoute<void> {
|
|||
.evaluate(animation),
|
||||
child: Builder(
|
||||
key: closedBuilderKey,
|
||||
builder: (BuildContext context) {
|
||||
builder: (context) {
|
||||
// Use dummy "open container" callback
|
||||
// since we are in the process of opening.
|
||||
return closedBuilder(
|
||||
|
@ -789,7 +789,7 @@ class _OpenContainerRoute extends ModalRoute<void> {
|
|||
openOpacityTween.evaluate(animation),
|
||||
child: Builder(
|
||||
key: _openBuilderKey,
|
||||
builder: (BuildContext context) {
|
||||
builder: (context) {
|
||||
return openBuilder(
|
||||
context, closeContainer, true);
|
||||
},
|
||||
|
@ -845,8 +845,8 @@ class _FlippableTweenSequence<T> extends TweenSequence<T> {
|
|||
|
||||
_FlippableTweenSequence<T> get flipped {
|
||||
if (_flipped == null) {
|
||||
final List<TweenSequenceItem<T>> newItems = <TweenSequenceItem<T>>[];
|
||||
for (int i = 0; i < _items.length; i++) {
|
||||
final newItems = <TweenSequenceItem<T>>[];
|
||||
for (var i = 0; i < _items.length; i++) {
|
||||
newItems.add(TweenSequenceItem<T>(
|
||||
tween: _items[i].tween,
|
||||
weight: _items[_items.length - 1 - i].weight,
|
||||
|
|
|
@ -6,16 +6,16 @@ class SlideLeftRoute extends PageRouteBuilder {
|
|||
SlideLeftRoute({this.page})
|
||||
: super(
|
||||
pageBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
context,
|
||||
animation,
|
||||
secondaryAnimation,
|
||||
) =>
|
||||
page,
|
||||
transitionsBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
context,
|
||||
animation,
|
||||
secondaryAnimation,
|
||||
child,
|
||||
) =>
|
||||
SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
|
@ -33,17 +33,17 @@ class SlideLeftHideRoute extends PageRouteBuilder {
|
|||
SlideLeftHideRoute({this.page, this.transitionPage})
|
||||
: super(
|
||||
pageBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
context,
|
||||
animation,
|
||||
secondaryAnimation,
|
||||
) =>
|
||||
page,
|
||||
transitionDuration: Duration(milliseconds: 300),
|
||||
transitionsBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
context,
|
||||
animation,
|
||||
secondaryAnimation,
|
||||
child,
|
||||
) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
|
@ -60,16 +60,16 @@ class SlideUptRoute extends PageRouteBuilder {
|
|||
SlideUptRoute({this.page})
|
||||
: super(
|
||||
pageBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
context,
|
||||
animation,
|
||||
secondaryAnimation,
|
||||
) =>
|
||||
page,
|
||||
transitionsBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
context,
|
||||
animation,
|
||||
secondaryAnimation,
|
||||
child,
|
||||
) =>
|
||||
SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
|
@ -87,16 +87,16 @@ class ScaleRoute extends PageRouteBuilder {
|
|||
ScaleRoute({this.page})
|
||||
: super(
|
||||
pageBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
context,
|
||||
animation,
|
||||
secondaryAnimation,
|
||||
) =>
|
||||
page,
|
||||
transitionsBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
context,
|
||||
animation,
|
||||
secondaryAnimation,
|
||||
child,
|
||||
) =>
|
||||
ScaleTransition(
|
||||
scale: Tween<double>(
|
||||
|
|
|
@ -44,7 +44,7 @@ class AtomFeed {
|
|||
try {
|
||||
feedElement = document.findElements("feed").first;
|
||||
} on StateError {
|
||||
throw new ArgumentError("feed not found");
|
||||
throw ArgumentError("feed not found");
|
||||
}
|
||||
|
||||
return AtomFeed(
|
||||
|
|
|
@ -14,6 +14,6 @@ class AtomGenerator {
|
|||
var uri = element.getAttribute("uri");
|
||||
var version = element.getAttribute("version");
|
||||
var value = element.text;
|
||||
return new AtomGenerator(uri, version, value);
|
||||
return AtomGenerator(uri, version, value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import 'package:tsacdop/webfeed/domain/media/category.dart';
|
||||
import 'package:tsacdop/webfeed/domain/media/content.dart';
|
||||
import 'package:tsacdop/webfeed/domain/media/credit.dart';
|
||||
import 'package:tsacdop/webfeed/domain/media/rating.dart';
|
||||
import 'package:tsacdop/webfeed/util/helpers.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
import '../../util/helpers.dart';
|
||||
import 'category.dart';
|
||||
import 'content.dart';
|
||||
import 'credit.dart';
|
||||
import 'rating.dart';
|
||||
|
||||
class Group {
|
||||
final List<Content> contents;
|
||||
final List<Credit> credits;
|
||||
|
@ -22,17 +23,17 @@ class Group {
|
|||
if (element == null) {
|
||||
return null;
|
||||
}
|
||||
return new Group(
|
||||
return Group(
|
||||
contents: element.findElements("media:content").map((e) {
|
||||
return new Content.parse(e);
|
||||
return Content.parse(e);
|
||||
}).toList(),
|
||||
credits: element.findElements("media:credit").map((e) {
|
||||
return new Credit.parse(e);
|
||||
return Credit.parse(e);
|
||||
}).toList(),
|
||||
category: new Category.parse(
|
||||
category: Category.parse(
|
||||
findElementOrNull(element, "media:category"),
|
||||
),
|
||||
rating: new Rating.parse(
|
||||
rating: Rating.parse(
|
||||
findElementOrNull(element, "media:rating"),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -19,6 +19,7 @@ dependencies:
|
|||
connectivity: ^0.4.9
|
||||
dio: ^3.0.9
|
||||
extended_nested_scroll_view: ^1.0.1
|
||||
effective_dart: ^1.2.4
|
||||
feature_discovery: ^0.10.0
|
||||
file_picker: ^1.12.0
|
||||
flutter_html: ^0.11.1
|
||||
|
|
Loading…
Reference in New Issue