Move subscribe_worker to group state namagement.

Improve OMPL file export/import, support groups now.
This commit is contained in:
stonegate 2020-07-14 23:45:45 +08:00
parent 21fc7e027b
commit 98fd594eb5
20 changed files with 565 additions and 425 deletions

View File

@ -202,6 +202,7 @@ class MessageLookup extends MessageLookupByLibrary {
"removeConfirm" : MessageLookupByLibrary.simpleMessage("Remove confirm"),
"removePodcastDes" : MessageLookupByLibrary.simpleMessage("Are you sure you want to unsubscribe?"),
"removedAt" : m21,
"save" : MessageLookupByLibrary.simpleMessage("Save"),
"schedule" : MessageLookupByLibrary.simpleMessage("Schedule"),
"searchInvalidRss" : MessageLookupByLibrary.simpleMessage("Invalid RSS link"),
"searchPodcast" : MessageLookupByLibrary.simpleMessage("Search podcast"),
@ -217,6 +218,8 @@ class MessageLookup extends MessageLookupByLibrary {
"settingsAutoDelete" : MessageLookupByLibrary.simpleMessage("Auto delete downloads after"),
"settingsAutoDeleteDes" : MessageLookupByLibrary.simpleMessage("Default 30 days"),
"settingsAutoPlayDes" : MessageLookupByLibrary.simpleMessage("Auto play next episode in playlist"),
"settingsBackup" : MessageLookupByLibrary.simpleMessage("Backup"),
"settingsBackupDes" : MessageLookupByLibrary.simpleMessage("Backup app data"),
"settingsDefaultGrid" : MessageLookupByLibrary.simpleMessage("Default grid view"),
"settingsDefaultGridDownload" : MessageLookupByLibrary.simpleMessage("Download tab"),
"settingsDefaultGridFavorite" : MessageLookupByLibrary.simpleMessage("Favorite tab"),
@ -265,6 +268,7 @@ class MessageLookup extends MessageLookupByLibrary {
"settingsTheme" : MessageLookupByLibrary.simpleMessage("Theme"),
"settingsUpdateInterval" : MessageLookupByLibrary.simpleMessage("Update interval"),
"settingsUpdateIntervalDes" : MessageLookupByLibrary.simpleMessage("Default 24 hours"),
"share" : MessageLookupByLibrary.simpleMessage("Share"),
"size" : MessageLookupByLibrary.simpleMessage("Size"),
"skipSecondsAtStart" : MessageLookupByLibrary.simpleMessage("Skip seconds at start"),
"sleepTimer" : MessageLookupByLibrary.simpleMessage("Sleep timer"),

View File

@ -202,6 +202,7 @@ class MessageLookup extends MessageLookupByLibrary {
"removeConfirm" : MessageLookupByLibrary.simpleMessage("取消订阅"),
"removePodcastDes" : MessageLookupByLibrary.simpleMessage("您确认要取消订阅吗?"),
"removedAt" : m21,
"save" : MessageLookupByLibrary.simpleMessage("保存"),
"schedule" : MessageLookupByLibrary.simpleMessage("定时"),
"searchInvalidRss" : MessageLookupByLibrary.simpleMessage("RSS 链接错误"),
"searchPodcast" : MessageLookupByLibrary.simpleMessage("搜索播客"),
@ -217,6 +218,8 @@ class MessageLookup extends MessageLookupByLibrary {
"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("收藏页"),
@ -265,6 +268,7 @@ class MessageLookup extends MessageLookupByLibrary {
"settingsTheme" : MessageLookupByLibrary.simpleMessage("主题"),
"settingsUpdateInterval" : MessageLookupByLibrary.simpleMessage("更新频率"),
"settingsUpdateIntervalDes" : MessageLookupByLibrary.simpleMessage("默认 24 小时"),
"share" : MessageLookupByLibrary.simpleMessage("分享"),
"size" : MessageLookupByLibrary.simpleMessage("大小"),
"skipSecondsAtStart" : MessageLookupByLibrary.simpleMessage("开头跳过秒数"),
"sleepTimer" : MessageLookupByLibrary.simpleMessage("睡眠模式"),

View File

@ -1316,6 +1316,16 @@ class S {
);
}
/// `Save`
String get save {
return Intl.message(
'Save',
name: 'save',
desc: '',
args: [],
);
}
/// `Schedule`
String get schedule {
return Intl.message(
@ -1459,6 +1469,26 @@ class S {
);
}
/// `Backup`
String get settingsBackup {
return Intl.message(
'Backup',
name: 'settingsBackup',
desc: '',
args: [],
);
}
/// `Backup app data`
String get settingsBackupDes {
return Intl.message(
'Backup app data',
name: 'settingsBackupDes',
desc: '',
args: [],
);
}
/// `Default grid view`
String get settingsDefaultGrid {
return Intl.message(
@ -1949,6 +1979,16 @@ class S {
);
}
/// `Share`
String get share {
return Intl.message(
'Share',
name: 'share',
desc: '',
args: [],
);
}
/// `Size`
String get size {
return Intl.message(

View File

@ -10,9 +10,10 @@ import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:tsacdop/state/podcast_group.dart';
import '../type/searchpodcast.dart';
import '../state/subscribe_podcast.dart';
import '../state/podcast_group.dart';
import '../util/context_extension.dart';
import '../webfeed/webfeed.dart';
import '../.env.dart';
@ -346,7 +347,7 @@ class _SearchResultState extends State<SearchResult>
@override
Widget build(BuildContext context) {
var subscribeWorker = Provider.of<SubscribeWorker>(context, listen: false);
var subscribeWorker = Provider.of<GroupList>(context, listen: false);
final s = context.s;
savePodcast(OnlinePodcast podcast) {
SubscribeItem item =

View File

@ -20,7 +20,6 @@ import '../util/context_extension.dart';
import '../util/custompaint.dart';
import '../state/download_state.dart';
import '../state/podcast_group.dart';
import '../state/subscribe_podcast.dart';
import 'playlist.dart';
import 'importompl.dart';
import 'audioplayer.dart';
@ -726,7 +725,7 @@ class _RecentUpdateState extends State<_RecentUpdate>
super.build(context);
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
final s = context.s;
return Selector<SubscribeWorker, bool>(
return Selector<GroupList, bool>(
selector: (_, worker) => worker.created,
builder: (context, created, child) {
return FutureBuilder<List<EpisodeBrief>>(

View File

@ -13,7 +13,6 @@ import 'package:line_icons/line_icons.dart';
import '../type/episodebrief.dart';
import '../state/podcast_group.dart';
import '../state/subscribe_podcast.dart';
import '../state/download_state.dart';
import '../type/podcastlocal.dart';
import '../state/audio_state.dart';
@ -397,7 +396,7 @@ class PodcastPreview extends StatelessWidget {
return Column(
children: <Widget>[
Expanded(
child: Selector<SubscribeWorker, bool>(
child: Selector<GroupList, bool>(
selector: (_, worker) => worker.created,
builder: (context, created, child) {
return FutureBuilder<List<EpisodeBrief>>(

View File

@ -5,7 +5,6 @@ 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/subscribe_podcast.dart';
import '../state/download_state.dart';
import '../state/refresh_podcast.dart';
import '../type/episodebrief.dart';
@ -60,7 +59,7 @@ class Import extends StatelessWidget {
GroupList groupList = Provider.of<GroupList>(context, listen: false);
return Column(
children: <Widget>[
Consumer<SubscribeWorker>(
Consumer<GroupList>(
builder: (_, subscribeWorker, __) {
SubscribeItem item = subscribeWorker.currentSubscribeItem;
switch (item.subscribeState) {
@ -70,11 +69,8 @@ class Import extends StatelessWidget {
case SubscribeState.subscribe:
return importColumn(s.notificaitonFatch(item.title), context);
case SubscribeState.fetch:
groupList.subscribeNewPodcast(item.id);
// groupList.updatePodcast(item.id);
return importColumn(s.notificationSuccess(item.title), context);
case SubscribeState.exist:
//groupList.subscribeNewPodcast(item.id);
return importColumn(
s.notificationSubscribeExisted(item.title), context);
case SubscribeState.error:

View File

@ -6,7 +6,7 @@ import 'package:provider/provider.dart';
import 'package:tsacdop/local_storage/key_value_storage.dart';
import 'package:tsacdop/service/ompl_build.dart';
import 'package:tsacdop/webfeed/webfeed.dart';
import 'package:tsacdop/state/podcast_group.dart';
import 'package:xml/xml.dart' as xml;
import 'package:file_picker/file_picker.dart';
import 'package:flutter/services.dart';
@ -16,24 +16,9 @@ import 'package:intl/intl.dart';
import '../settings/settting.dart';
import '../state/refresh_podcast.dart';
import '../state/subscribe_podcast.dart';
import '../util/context_extension.dart';
import 'about.dart';
class OmplOutline {
final String text;
final String xmlUrl;
OmplOutline({this.text, this.xmlUrl});
factory OmplOutline.parse(xml.XmlElement element) {
if (element == null) return null;
return OmplOutline(
text: element.getAttribute("text")?.trim(),
xmlUrl: element.getAttribute("xmlUrl")?.trim(),
);
}
}
class PopupMenu extends StatefulWidget {
@override
_PopupMenuState createState() => _PopupMenuState();
@ -69,70 +54,30 @@ class _PopupMenuState extends State<PopupMenu> {
}
void _saveOmpl(String path) async {
var subscribeWorker = Provider.of<SubscribeWorker>(context, listen: false);
var subscribeWorker = Provider.of<GroupList>(context, listen: false);
final s = context.s;
File file = File(path);
try {
Map data = PodcastsBackup.parseOMPL(file);
data.forEach((title, list) async {
Map<String, List<OmplOutline>> data = PodcastsBackup.parseOMPL(file);
for (var entry in data.entries) {
String title = entry.key;
var list = entry.value;
for (var rss in list) {
if (rss.xmlUrl != null) {
SubscribeItem item = SubscribeItem(rss.xmlUrl, rss.text);
SubscribeItem item =
SubscribeItem(rss.xmlUrl, rss.text, group: title);
await subscribeWorker.setSubscribeItem(item);
await Future.delayed(Duration(milliseconds: 500));
await Future.delayed(Duration(seconds: 1));
print(rss.text);
}
}
});
}
} catch (e) {
print(e);
Fluttertoast.showToast(
msg: s.toastFileError,
gravity: ToastGravity.TOP,
);
// try {
// String opml = file.readAsStringSync();
// var content = xml.XmlDocument.parse(opml);
// String title = content
// .findAllElements('head')
// .first
// .findElements('title')
// .first
// .text;
// print(title);
// if (title != 'Tsacdop Subscriptions') {
// var total = content
// .findAllElements('outline')
// .map((ele) => OmplOutline.parse(ele))
// .toList();
// if (total.length == 0) {
// Fluttertoast.showToast(
// msg: s.toastFileNotValid,
// gravity: ToastGravity.BOTTOM,
// );
// } else {
// for (int i = 0; i < total.length; i++) {
// if (total[i].xmlUrl != null) {
// // importOmpl.rssTitle = total[i].text;
// //await saveOmpl(total[i].xmlUrl);
// SubscribeItem item =
// SubscribeItem(total[i].xmlUrl, total[i].text);
// await subscribeWorker.setSubscribeItem(item);
// await Future.delayed(Duration(milliseconds: 500));
// print(total[i].text);
// }
// }
// }
// print('Import fisnished');
// }
// } catch (e) {
// print(e);
// Fluttertoast.showToast(
// msg: s.toastFileError,
// gravity: ToastGravity.TOP,
// );
//await Future.delayed(Duration(seconds: 5));
// importOmpl.importState = ImportState.stop;
}
}

View File

@ -307,6 +307,8 @@
},
"removePodcastDes": "Are you sure you want to unsubscribe?",
"@removePodcastDes": {},
"save": "Save",
"@save": {},
"schedule": "Schedule",
"@schedule": {},
"searchInvalidRss": "Invalid RSS link",
@ -335,6 +337,10 @@
"@settingsAutoDeleteDes": {},
"settingsAutoPlayDes": "Auto play next episode in playlist",
"@settingsAutoPlayDes": {},
"settingsBackup": "Backup",
"@settingsBackup": {},
"settingsBackupDes": "Backup app data",
"@settingsBackupDes": {},
"settingsDefaultGrid": "Default grid view",
"@settingsDefaultGrid": {},
"settingsDefaultGridDownload": "Download tab",
@ -433,6 +439,8 @@
"@settingsUpdateInterval": {},
"settingsUpdateIntervalDes": "Default 24 hours",
"@settingsUpdateIntervalDes": {},
"share": "Share",
"@share": {},
"size": "Size",
"@size": {},
"skipSecondsAtStart": "Skip seconds at start",

View File

@ -307,6 +307,8 @@
},
"removePodcastDes": "您确认要取消订阅吗?",
"@removePodcastDes": {},
"save": "保存",
"@save": {},
"schedule": "定时",
"@schedule": {},
"searchInvalidRss": "RSS 链接错误",
@ -335,6 +337,10 @@
"@settingsAutoDeleteDes": {},
"settingsAutoPlayDes": "自动播放下一节目",
"@settingsAutoPlayDes": {},
"settingsBackup": "备份",
"@settingsBackup": {},
"settingsBackupDes": "备份应用数据",
"@settingsBackupDes": {},
"settingsDefaultGrid": "默认布局",
"@settingsDefaultGrid": {},
"settingsDefaultGridDownload": "下载页",
@ -433,6 +439,8 @@
"@settingsUpdateInterval": {},
"settingsUpdateIntervalDes": "默认 24 小时",
"@settingsUpdateIntervalDes": {},
"share": "分享",
"@share": {},
"size": "大小",
"@size": {},
"skipSecondsAtStart": "开头跳过秒数",

View File

@ -12,7 +12,6 @@ import 'state/audio_state.dart';
import 'state/setting_state.dart';
import 'state/download_state.dart';
import 'state/refresh_podcast.dart';
import 'state/subscribe_podcast.dart';
import 'home/home.dart';
import 'intro_slider/app_intro.dart';
@ -30,7 +29,6 @@ Future main() async {
),
ChangeNotifierProvider(create: (_) => AudioPlayerNotifier()),
ChangeNotifierProvider(create: (_) => GroupList()),
ChangeNotifierProvider(create: (_) => SubscribeWorker()),
ChangeNotifierProvider(create: (_) => RefreshWorker()),
ChangeNotifierProvider(
lazy: false,

View File

@ -9,7 +9,7 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:feature_discovery/feature_discovery.dart';
import '../state/podcast_group.dart';
import 'podcastgroup.dart';
import 'podcast_group.dart';
import 'podcastlist.dart';
import '../util/pageroute.dart';
import '../util/context_extension.dart';
@ -586,7 +586,7 @@ class _AddGroupState extends State<AddGroup> {
@override
Widget build(BuildContext context) {
final s = context.s;
var groupList = Provider.of<GroupList>(context);
var groupList = Provider.of<GroupList>(context, listen: false);
List list = groupList.groups.map((e) => e.name).toList();
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
@ -600,8 +600,9 @@ class _AddGroupState extends State<AddGroup> {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
elevation: 1,
contentPadding: EdgeInsets.symmetric(horizontal: 20),
titlePadding: EdgeInsets.only(top: 20, left: 20, right: 20, bottom: 20),
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
titlePadding:
const EdgeInsets.only(top: 20, left: 20, right: 20, bottom: 20),
actionsPadding: EdgeInsets.all(0),
actions: <Widget>[
FlatButton(

View File

@ -1,6 +1,5 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:xml/xml.dart' as xml;
import '../state/podcast_group.dart';
@ -29,7 +28,7 @@ class PodcastsBackup {
builder.element('ompl', nest: () {
builder.attribute('version', '1.0');
builder.element('head', nest: () {
builder.element('title', nest: 'Tsacdop Subscriptions');
builder.element('title', nest: 'Tsacdop Feed Groups');
});
builder.element('body', nest: () {
for (var group in groups) {
@ -55,20 +54,20 @@ class PodcastsBackup {
}
static parseOMPL(File file) {
var data = Map();
Map<String, List<OmplOutline>> data = Map();
String opml = file.readAsStringSync();
var content = xml.XmlDocument.parse(opml);
String title =
content.findAllElements('head').first.findElements('title').first.text;
print(title);
var groups = content.findAllElements('body').first.findElements('outline');
if (title != 'Tsacdop Subscriptions' &&
groups.first.getAttribute('title') != 'Home') {
var total = content
if (title != 'Tsacdop Feed Groups') {
List<OmplOutline> total = content
.findAllElements('outline')
.map((ele) => OmplOutline.parse(ele))
.toList()
..removeWhere((element) => element == null);
.toList();
data['Home'] = total;
print(data.toString());
return data;
}
@ -77,11 +76,10 @@ class PodcastsBackup {
var total = element
.findElements('outline')
.map((ele) => OmplOutline.parse(ele))
.toList()
..removeWhere((element) => element == null);
.toList();
data[title] = total;
}
print(data.toString());
return data;
}
}

View File

@ -1 +0,0 @@
import 'package:flutter/material.dart';

View File

@ -0,0 +1,132 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.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:wc_flutter_share/wc_flutter_share.dart';
import '../state/podcast_group.dart';
import '../util/context_extension.dart';
import '../service/ompl_build.dart';
class DataBackup extends StatelessWidget {
Future<File> _exportOmpl(BuildContext context) async {
var groups = context.read<GroupList>().groups;
var ompl = PodcastsBackup(groups).omplBuilder();
var tempdir = await getTemporaryDirectory();
DateTime now = DateTime.now();
String datePlus = now.year.toString() +
now.month.toString() +
now.day.toString() +
now.second.toString();
var file = File(join(tempdir.path, 'tsacdop_ompl_$datePlus.xml'));
await file.writeAsString(ompl.toString());
return file;
}
Future<void> _saveOmpl(File file) async {
final params = SaveFileDialogParams(sourceFilePath: file.path);
await FlutterFileDialog.saveFile(params: params);
}
Future<void> _shareOmpl(File file) async {
final Uint8List bytes = await file.readAsBytes();
await WcFlutterShare.share(
sharePopupTitle: 'share Clip',
fileName: file.path.split('/').last,
mimeType: 'text/plain',
bytesOfFile: bytes.buffer.asUint8List());
}
@override
Widget build(BuildContext context) {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
child: Scaffold(
appBar: AppBar(
elevation: 0,
title: Text(s.settingsBackup),
backgroundColor: context.primaryColor,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.subscribe,
style: context.textTheme.bodyText1
.copyWith(color: Theme.of(context).accentColor)),
),
Padding(
padding:
EdgeInsets.only(left: 70.0, right: 20, top: 10, bottom: 10),
child: Text(s.settingsExportDes),
),
Padding(
padding: EdgeInsets.only(left: 70.0, right: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
OutlineButton(
highlightedBorderColor: context.accentColor,
child: Row(
children: [
Icon(
LineIcons.save,
color: Colors.green[700],
size: context.textTheme.headline6.fontSize,
),
SizedBox(width: 10),
Text(s.save,
style: TextStyle(color: Colors.green[700])),
],
),
onPressed: () async {
File file = await _exportOmpl(context);
await _saveOmpl(file);
}),
SizedBox(width: 50),
OutlineButton(
highlightedBorderColor: Colors.blue[700],
child: Row(
children: [
Icon(
Icons.share,
size: context.textTheme.headline6.fontSize,
color: Colors.blue[700],
),
SizedBox(width: 10),
Text(s.share,
style: TextStyle(color: Colors.blue[700])),
],
),
onPressed: () async {
File file = await _exportOmpl(context);
await _shareOmpl(file);
})
],
),
),
Divider(),
],
)),
);
}
}

View File

@ -6,13 +6,14 @@ import 'package:intl/intl.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart';
import 'package:line_icons/line_icons.dart';
import 'package:tsacdop/state/podcast_group.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../webfeed/webfeed.dart';
import '../type/searchpodcast.dart';
import '../util/context_extension.dart';
import '../state/audio_state.dart';
import '../state/subscribe_podcast.dart';
import '../state/podcast_group.dart';
import '../type/sub_history.dart';
class PlayedHistory extends StatefulWidget {
@ -69,7 +70,7 @@ class _PlayedHistoryState extends State<PlayedHistory>
msg: context.s.toastPodcastRecovering,
gravity: ToastGravity.BOTTOM,
);
var subscribeWorker = context.read<SubscribeWorker>();
var subscribeWorker = context.watch<GroupList>();
try {
BaseOptions options = new BaseOptions(
connectTimeout: 10000,

View File

@ -1,23 +1,14 @@
import 'dart:io';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path/path.dart';
import 'package:line_icons/line_icons.dart';
import 'package:tsacdop/state/podcast_group.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:feature_discovery/feature_discovery.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart';
import '../service/ompl_build.dart';
import '../util/context_extension.dart';
import '../intro_slider/app_intro.dart';
import '../type/podcastlocal.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../home/home.dart';
import '../podcasts/podcast_manage.dart';
import 'theme.dart';
@ -28,6 +19,7 @@ import 'syncing.dart';
import 'libries.dart';
import 'languages.dart';
import 'play_setting.dart';
import 'data_backup.dart';
class Settings extends StatefulWidget {
@override
@ -44,18 +36,6 @@ class _SettingsState extends State<Settings>
}
}
_exportOmpl(BuildContext context) async {
var groups = context.read<GroupList>().groups;
var ompl = PodcastsBackup(groups).omplBuilder();
var tempdir = await getTemporaryDirectory();
var file = File(join(tempdir.path, 'tsacdop_ompl.xml'));
await file.writeAsString(ompl.toString());
final params = SaveFileDialogParams(sourceFilePath: file.path);
final filePath = await FlutterFileDialog.saveFile(params: params);
print(filePath);
print(ompl.toString());
}
bool _showFeedback;
Animation _animation;
AnimationController _controller;
@ -239,14 +219,18 @@ class _SettingsState extends State<Settings>
Divider(height: 2),
ListTile(
onTap: () {
_exportOmpl(context);
//_exportOmpl(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DataBackup()));
},
contentPadding:
EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.file_code_solid,
color: Colors.lightGreen[700]),
title: Text(s.settingsExport),
subtitle: Text(s.settingsExportDes),
title: Text(s.settingsBackup),
subtitle: Text(s.settingsBackupDes),
),
Divider(height: 2),
],

View File

@ -1,11 +1,23 @@
import 'dart:core';
import 'dart:io';
import 'dart:isolate';
import 'dart:math' as math;
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 '../webfeed/webfeed.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../local_storage/key_value_storage.dart';
import '../type/fireside_data.dart';
import '../type/podcastlocal.dart';
class GroupEntity {
@ -28,9 +40,15 @@ class GroupEntity {
}
class PodcastGroup {
/// Group name.
final String name;
final String id;
/// Group theme color, not used.
final String color;
/// Id lists of podcasts in group.
List<String> podcastList;
PodcastGroup(this.name,
@ -56,11 +74,11 @@ class PodcastGroup {
///Podcast in group.
List<PodcastLocal> _podcasts;
List<PodcastLocal> get podcasts => _podcasts;
///Ordered podcast list.
List<PodcastLocal> _orderedPodcasts;
List<PodcastLocal> get ordereddPodcasts => _orderedPodcasts;
List<PodcastLocal> get podcasts => _podcasts;
set setOrderedPodcasts(List<PodcastLocal> list) {
_orderedPodcasts = list;
@ -80,20 +98,127 @@ class PodcastGroup {
}
}
enum SubscribeState { none, start, subscribe, fetch, stop, exist, error }
class SubscribeItem {
///Rss url.
String url;
///Rss title.
String title;
/// Subscribe status.
SubscribeState subscribeState;
/// Podcast id.
String id;
///Avatar image link.
String imgUrl;
///Podcast group, default Home.
String group;
SubscribeItem(this.url, this.title,
{this.subscribeState = SubscribeState.none,
this.id = '',
this.imgUrl = '',
this.group = ''});
}
class GroupList extends ChangeNotifier {
List<PodcastGroup> _groups;
DBHelper dbHelper = DBHelper();
/// List of all gourps.
List<PodcastGroup> _groups = [];
List<PodcastGroup> get groups => _groups;
KeyValueStorage storage = KeyValueStorage('groups');
GroupList({List<PodcastGroup> groups}) : _groups = groups ?? [];
DBHelper dbHelper = DBHelper();
/// Groups save in shared_prefrences.
KeyValueStorage storage = KeyValueStorage('groups');
//GroupList({List<PodcastGroup> groups}) : _groups = groups ?? [];
/// Default false, true during loading groups from storage.
bool _isLoading = false;
bool get isLoading => _isLoading;
/// Svae ordered gourps info before saved.
List<PodcastGroup> _orderChanged = [];
List<PodcastGroup> get orderChanged => _orderChanged;
/// Subscribe worker isolate
FlutterIsolate subIsolate;
ReceivePort receivePort;
SendPort subSendPort;
/// Current subsribe item from isolate.
SubscribeItem _currentSubscribeItem = SubscribeItem('', '');
SubscribeItem get currentSubscribeItem => _currentSubscribeItem;
/// Default false, true if subscribe isolate is created.
bool _created = false;
bool get created => _created;
/// Add subsribe item
SubscribeItem _subscribeItem;
setSubscribeItem(SubscribeItem item) async {
_subscribeItem = item;
await _start();
}
_setCurrentSubscribeItem(SubscribeItem item) {
_currentSubscribeItem = item;
notifyListeners();
}
Future _start() async {
if (_created == false) {
await _createIsolate();
_created = true;
listen();
} else
subSendPort.send([
_subscribeItem.url,
_subscribeItem.title,
_subscribeItem.imgUrl,
_subscribeItem.group
]);
}
Future<void> _createIsolate() async {
receivePort = ReceivePort();
subIsolate =
await FlutterIsolate.spawn(subIsolateEntryPoint, receivePort.sendPort);
}
/// Isolate listener to get subscrribe status.
void listen() {
receivePort.distinct().listen((message) {
if (message is SendPort) {
subSendPort = message;
subSendPort.send([
_subscribeItem.url,
_subscribeItem.title,
_subscribeItem.imgUrl,
_subscribeItem.group
]);
} else if (message is List) {
_setCurrentSubscribeItem(SubscribeItem(
message[1],
message[0],
subscribeState: SubscribeState.values[message[2]],
));
if (message.length == 5)
_subscribeNewPodcast(id: message[3], groupName: message[4]);
} else if (message is String && message == "done") {
subIsolate.kill();
subIsolate = null;
_currentSubscribeItem = SubscribeItem('', '');
_created = false;
notifyListeners();
}
});
}
void addToOrderChanged(PodcastGroup group) {
_orderChanged.add(group);
notifyListeners();
@ -112,20 +237,19 @@ class GroupList extends ChangeNotifier {
}
}
// _initGroup() async {
// storage.getGroups().then((loadgroups) async {
// _groups.addAll(loadgroups.map((e) => PodcastGroup.fromEntity(e)));
// await Future.forEach(_groups, (group) async {
// await group.getPodcasts();
// });
// });
// }
@override
void addListener(VoidCallback listener) {
loadGroups().then((value) => super.addListener(listener));
}
@override
void dispose() {
subIsolate?.kill();
subIsolate = null;
super.dispose();
}
/// Load groups from storage at start.
Future loadGroups() async {
_isLoading = true;
notifyListeners();
@ -137,12 +261,13 @@ class GroupList extends ChangeNotifier {
});
}
//update podcasts of each group
/// Update podcasts of each group
Future updateGroups() async {
for (var group in _groups) await group.getPodcasts();
notifyListeners();
}
/// Add new group.
Future addGroup(PodcastGroup podcastGroup) async {
_isLoading = true;
_groups.add(podcastGroup);
@ -151,6 +276,7 @@ class GroupList extends ChangeNotifier {
notifyListeners();
}
/// Remove group.
Future delGroup(PodcastGroup podcastGroup) async {
_isLoading = true;
for (var podcast in podcastGroup.podcastList)
@ -177,6 +303,7 @@ class GroupList extends ChangeNotifier {
await storage.saveGroup(_groups.map((it) => it.toEntity()).toList());
}
/// Subscribe podcast from search result.
Future subscribe(PodcastLocal podcastLocal) async {
_groups[0].podcastList.insert(0, podcastLocal.id);
await _saveGroup();
@ -195,13 +322,26 @@ class GroupList extends ChangeNotifier {
}
}
Future subscribeNewPodcast(String id) async {
if (!_groups[0].podcastList.contains(id)) {
_groups[0].podcastList.insert(0, id);
await _saveGroup();
await _groups[0].getPodcasts();
notifyListeners();
/// Subscribe podcast from OMPL.
Future _subscribeNewPodcast({String id, String groupName = 'Home'}) async {
for (PodcastGroup group in _groups) {
if (group.name == groupName) {
if (!group.podcastList.contains(id)) {
group.podcastList.insert(0, id);
await _saveGroup();
await group.getPodcasts();
notifyListeners();
return;
}
}
}
_isLoading = true;
_groups.add(PodcastGroup(groupName));
_groups.last.podcastList.insert(0, id);
await _saveGroup();
await _groups.last.getPodcasts();
_isLoading = false;
notifyListeners();
}
List<PodcastGroup> getPodcastGroup(String id) {
@ -225,18 +365,14 @@ class GroupList extends ChangeNotifier {
group.podcastList.remove(id);
}
}
for (var s in list) s.podcastList.insert(0, id);
await _saveGroup();
for (var group in _groups) await group.getPodcasts();
_isLoading = false;
notifyListeners();
}
//Unsubscribe podcast
/// Unsubscribe podcast
removePodcast(String id) async {
_isLoading = true;
notifyListeners();
@ -255,3 +391,169 @@ class GroupList extends ChangeNotifier {
notifyListeners();
}
}
Future<void> subIsolateEntryPoint(SendPort sendPort) async {
List<SubscribeItem> items = [];
bool _running = false;
final List<String> listColor = [
'388E3C',
'1976D2',
'D32F2F',
'00796B',
];
ReceivePort 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();
return primaryColor;
}
Future<void> _subscribe(SubscribeItem item) async {
var dbHelper = DBHelper();
String rss = item.url;
sendPort.send([item.title, item.url, 1]);
BaseOptions options = new BaseOptions(
connectTimeout: 20000,
receiveTimeout: 20000,
);
print(rss);
try {
Response response = await Dio(options).get(rss);
RssFeed p;
try {
p = RssFeed.parse(response.data);
} on ArgumentError catch (e) {
print(e);
sendPort.send([item.title, item.url, 6]);
await Future.delayed(Duration(seconds: 2));
sendPort.send([item.title, item.url, 4]);
items.removeWhere((element) => element.url == item.url);
if (items.isNotEmpty) {
await _subscribe(items.first);
} else
sendPort.send("done");
}
var dir = await getApplicationDocumentsDirectory();
String realUrl =
response.redirects.isEmpty ? rss : response.realUri.toString();
bool checkUrl = await dbHelper.checkPodcast(realUrl);
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));
imageUrl = p.itunes.image.href;
img.Image 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));
imageUrl = item.imgUrl;
img.Image 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>>(
"https://ui-avatars.com/api/?size=300&background="
"${listColor[index]}&color=fff&name=${item.title}&length=2&bold=true",
options: Options(responseType: ResponseType.bytes));
imageUrl = "https://ui-avatars.com/api/?size=300&background="
"${listColor[index]}&color=fff&name=${item.title}&length=2&bold=true";
thumbnail = img.decodeImage(imageResponse.data);
} catch (e) {
print(e);
sendPort.send([item.title, item.url, 6]);
await Future.delayed(Duration(seconds: 2));
sendPort.send([item.title, item.url, 4]);
items.removeWhere((element) => element.url == item.url);
if (items.length > 0) {
await _subscribe(items.first);
} else
sendPort.send("done");
}
}
}
String 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,
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);
try {
await data.fatchData();
} catch (e) {
print(e);
}
}
await dbHelper.savePodcastRss(p, uuid);
sendPort.send([item.title, item.url, 3, uuid]);
await Future.delayed(Duration(seconds: 2));
sendPort.send([item.title, item.url, 4]);
items.removeWhere((element) => element.url == item.url);
if (items.length > 0) {
await _subscribe(items.first);
} else
sendPort.send("done");
} else {
sendPort.send([item.title, item.url, 5]);
await Future.delayed(Duration(seconds: 2));
sendPort.send([item.title, item.url, 4]);
items.removeWhere((element) => element.url == item.url);
if (items.length > 0) {
await _subscribe(items.first);
} else
sendPort.send("done");
}
} on DioError catch (e) {
print(e);
sendPort.send([item.title, item.url, 6]);
await Future.delayed(Duration(seconds: 2));
sendPort.send([item.title, item.url, 4]);
items.removeWhere((element) => element.url == item.url);
if (items.length > 0) {
await _subscribe(items.first);
} else
sendPort.send("done");
}
}
subReceivePort.distinct().listen((message) {
if (message is List<String>) {
items.add(SubscribeItem(message[0], message[1],
imgUrl: message[2], group: message[3]));
if (!_running) {
_subscribe(items.first);
_running = true;
}
}
});
}

View File

@ -1,279 +0,0 @@
import 'dart:io';
import 'dart:isolate';
import 'dart:math' as math;
import 'package:flutter/material.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:uuid/uuid.dart';
import 'package:dio/dio.dart';
import 'package:flutter_isolate/flutter_isolate.dart';
import '../webfeed/webfeed.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../local_storage/key_value_storage.dart';
import '../type/fireside_data.dart';
import '../type/podcastlocal.dart';
import 'podcast_group.dart';
enum SubscribeState { none, start, subscribe, fetch, stop, exist, error }
class SubscribeItem {
///Rss url.
String url;
///Rss title.
String title;
SubscribeState subscribeState;
///Uuid for podcast.
String id;
///Avatat image link.
String imgUrl;
///Podcast group, default Home.
String group;
SubscribeItem(this.url, this.title,
{this.subscribeState = SubscribeState.none,
this.id = '',
this.imgUrl = '',
this.group = 'Home'});
}
class SubscribeWorker extends ChangeNotifier {
FlutterIsolate subIsolate;
ReceivePort receivePort;
SendPort subSendPort;
SubscribeItem _subscribeItem;
SubscribeItem _currentSubscribeItem = SubscribeItem('', '');
bool _created = false;
bool get created => _created;
setSubscribeItem(SubscribeItem item) async {
_subscribeItem = item;
await _start();
}
_setCurrentSubscribeItem(SubscribeItem item) {
_currentSubscribeItem = item;
notifyListeners();
}
SubscribeItem get currentSubscribeItem => _currentSubscribeItem;
Future<void> _createIsolate() async {
receivePort = ReceivePort();
subIsolate =
await FlutterIsolate.spawn(subIsolateEntryPoint, receivePort.sendPort);
}
void listen() {
receivePort.distinct().listen((message) {
if (message is SendPort) {
subSendPort = message;
subSendPort.send(
[_subscribeItem.url, _subscribeItem.title, _subscribeItem.imgUrl]);
} else if (message is List) {
_setCurrentSubscribeItem(SubscribeItem(message[1], message[0],
subscribeState: SubscribeState.values[message[2]],
id: message.length == 4 ? message[3] : ''));
} else if (message is String && message == "done") {
subIsolate.kill();
subIsolate = null;
_currentSubscribeItem = SubscribeItem('', '');
_created = false;
notifyListeners();
}
});
}
Future _start() async {
if (_created == false) {
await _createIsolate();
_created = true;
listen();
} else
subSendPort.send([
_subscribeItem.url,
_subscribeItem.title,
_subscribeItem.imgUrl,
_subscribeItem.group
]);
}
void dispose() {
subIsolate?.kill();
subIsolate = null;
super.dispose();
}
}
Future<void> subIsolateEntryPoint(SendPort sendPort) async {
List<SubscribeItem> items = [];
bool _running = false;
final List<String> listColor = [
'388E3C',
'1976D2'
'D32F2F',
'00796B',
];
ReceivePort 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();
return primaryColor;
}
Future<void> _subscribe(SubscribeItem item) async {
var dbHelper = DBHelper();
String rss = item.url;
sendPort.send([item.title, item.url, 1]);
BaseOptions options = new BaseOptions(
connectTimeout: 20000,
receiveTimeout: 20000,
);
print(rss);
try {
Response response = await Dio(options).get(rss);
RssFeed p;
try {
p = RssFeed.parse(response.data);
} on ArgumentError catch (e) {
print(e);
sendPort.send([item.title, item.url, 6]);
await Future.delayed(Duration(seconds: 2));
sendPort.send([item.title, item.url, 4]);
items.removeWhere((element) => element.url == item.url);
if (items.isNotEmpty) {
await _subscribe(items.first);
} else
sendPort.send("done");
}
var dir = await getApplicationDocumentsDirectory();
String realUrl =
response.redirects.isEmpty ? rss : response.realUri.toString();
bool checkUrl = await dbHelper.checkPodcast(realUrl);
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));
imageUrl = p.itunes.image.href;
img.Image 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));
imageUrl = item.imgUrl;
img.Image 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>>(
"https://ui-avatars.com/api/?size=300&background="
"${listColor[index]}&color=fff&name=${item.title}&length=2&bold=true",
options: Options(responseType: ResponseType.bytes));
imageUrl = "https://ui-avatars.com/api/?size=300&background="
"${listColor[index]}&color=fff&name=${item.title}&length=2&bold=true";
thumbnail = img.decodeImage(imageResponse.data);
} catch (e) {
print(e);
sendPort.send([item.title, item.url, 6]);
await Future.delayed(Duration(seconds: 2));
sendPort.send([item.title, item.url, 4]);
items.removeWhere((element) => element.url == item.url);
if (items.length > 0) {
await _subscribe(items.first);
} else
sendPort.send("done");
}
}
}
String 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,
primaryColor, author, uuid, imagePath, provider, link,
description: p.description);
// await groupList.subscribe(podcastLocal);
await dbHelper.savePodcastLocal(podcastLocal);
sendPort.send([item.title, item.url, 2, uuid]);
if (provider.contains('fireside')) {
FiresideData data = FiresideData(uuid, link);
try {
await data.fatchData();
} catch (e) {
print(e);
}
}
await dbHelper.savePodcastRss(p, uuid);
sendPort.send([item.title, item.url, 3, uuid]);
await Future.delayed(Duration(seconds: 2));
sendPort.send([item.title, item.url, 4]);
items.removeWhere((element) => element.url == item.url);
if (items.length > 0) {
await _subscribe(items.first);
} else
sendPort.send("done");
} else {
sendPort.send([item.title, item.url, 5]);
await Future.delayed(Duration(seconds: 2));
sendPort.send([item.title, item.url, 4]);
items.removeWhere((element) => element.url == item.url);
if (items.length > 0) {
await _subscribe(items.first);
} else
sendPort.send("done");
}
} on DioError catch (e) {
print(e);
sendPort.send([item.title, item.url, 6]);
await Future.delayed(Duration(seconds: 2));
sendPort.send([item.title, item.url, 4]);
items.removeWhere((element) => element.url == item.url);
if (items.length > 0) {
await _subscribe(items.first);
} else
sendPort.send("done");
}
}
subReceivePort.distinct().listen((message) {
if (message is List<String>) {
items.add(SubscribeItem(message[0], message[1], imgUrl: message[2]));
if (!_running) {
_subscribe(items.first);
_running = true;
}
}
});
}