From 4f51556e4ca5944b633097565d815d8c41eb73d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=B2=AB=E8=8A=B1=E7=81=ABzzz=F0=9F=8C=99?= Date: Wed, 4 Sep 2024 08:07:19 +0800 Subject: [PATCH] Auto-update RSS task --- README.md | 2 +- lib/main.dart | 4 + lib/models/task_basic.dart | 4 + lib/storages/rss/rss_cache.dart | 7 ++ lib/tasks/android_subscribe_update.dart | 46 +++++++++ lib/tasks/subscribe_update.dart | 118 +++++++++++++++--------- lib/tasks/task.dart | 23 +++-- lib/ui/home.dart | 25 +++++ lib/ui/settings.dart | 9 ++ lib/ui/settings/about.dart | 78 ++++++++++++++++ lib/utils/app_info.dart | 15 +++ lib/utils/task_scheduler.dart | 15 +++ pubspec.lock | 6 +- pubspec.yaml | 1 + 14 files changed, 299 insertions(+), 54 deletions(-) create mode 100644 lib/models/task_basic.dart create mode 100644 lib/tasks/android_subscribe_update.dart create mode 100644 lib/ui/settings/about.dart create mode 100644 lib/utils/app_info.dart create mode 100644 lib/utils/task_scheduler.dart diff --git a/README.md b/README.md index d0f91d8..02cd41c 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ MioFeed - Modern RSS reader - [x] 时间排序 - [x] 主题功能 - [ ] 订阅分组 - - [ ] 定时订阅拉取任务 + - [x] 定时订阅拉取任务 - [ ] 自定义渲染功能 ## 一些已知问题 diff --git a/lib/main.dart b/lib/main.dart index e26bd84..1634dd5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,10 +6,12 @@ import 'package:miofeed/storages/settings_storage.dart'; import 'package:miofeed/tasks/task.dart'; import 'package:miofeed/ui/home.dart'; import 'package:miofeed/ui/settings.dart'; +import 'package:miofeed/ui/settings/about.dart'; import 'package:miofeed/ui/settings/render.dart'; import 'package:miofeed/ui/settings/rss_subscribe.dart'; import 'package:miofeed/ui/settings/theme.dart'; import 'package:miofeed/storages/rss/rss_cache.dart'; +import 'package:miofeed/utils/app_info.dart'; import 'package:miofeed/utils/shared_data.dart'; const String title = "MioFeed"; @@ -21,6 +23,7 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); await SharedData.init(); await RssCache.readAllToRemCache(); + await AppInfo.init(); await _initSelfConfig(); Task.register(); @@ -103,6 +106,7 @@ class MyApp extends StatelessWidget { const RssSubSettingUI(title: title), '/settings/theme': (context) => ThemeSettingUI(title: title), '/settings/render': (context) => RenderSettingUI(title: title), + '/about': (context) => AboutUI(title: title), }, home: HomeUI(title: title), ); diff --git a/lib/models/task_basic.dart b/lib/models/task_basic.dart new file mode 100644 index 0000000..4530c3c --- /dev/null +++ b/lib/models/task_basic.dart @@ -0,0 +1,4 @@ +abstract class TaskBasic { + late Function? callback; + startUp({Function? callback}); +} \ No newline at end of file diff --git a/lib/storages/rss/rss_cache.dart b/lib/storages/rss/rss_cache.dart index daf56a5..06d6d25 100644 --- a/lib/storages/rss/rss_cache.dart +++ b/lib/storages/rss/rss_cache.dart @@ -47,6 +47,13 @@ class RssCache { 'data': feed, }); static Future removeMemCache(rss) async => rssList.remove(rss); + static Future removeMemCacheBySubName(String name) async { + late final Map res; + for (var item in rssList) { + if (item['sub'].name == name) res = item; + } + rssList.remove(res); + } static get rssMemCache => rssList; static Future readAllToRemCache() async { diff --git a/lib/tasks/android_subscribe_update.dart b/lib/tasks/android_subscribe_update.dart new file mode 100644 index 0000000..87ed4ed --- /dev/null +++ b/lib/tasks/android_subscribe_update.dart @@ -0,0 +1,46 @@ +import 'package:background_fetch/background_fetch.dart'; +import 'package:miofeed/tasks/subscribe_update.dart'; + +class AndroidSubscribeUpdateTask { + @pragma('vm:entry-point') + static void backgroundFetchHeadlessTask(HeadlessTask task) async { + String taskId = task.taskId; + bool isTimeout = task.timeout; + if (isTimeout) { + // This task has exceeded its allowed running-time. + // You must stop what you're doing and immediately .finish(taskId) + print("[BackgroundFetch] Headless task timed-out: $taskId"); + BackgroundFetch.finish(taskId); + return; + } + print('[BackgroundFetch] Headless event received.'); + // Do your work here... + await SubscribeUpdateTask.run(); + BackgroundFetch.finish(taskId); + } +} + + // void _onClickEnable(enabled) { + // setState(() { + // _enabled = enabled; + // }); + // if (enabled) { + // BackgroundFetch.start().then((int status) { + // print('[BackgroundFetch] start success: $status'); + // }).catchError((e) { + // print('[BackgroundFetch] start FAILURE: $e'); + // }); + // } else { + // BackgroundFetch.stop().then((int status) { + // print('[BackgroundFetch] stop success: $status'); + // }); + // } + // } + // + // void _onClickStatus() async { + // int status = await BackgroundFetch.status; + // print('[BackgroundFetch] status: $status'); + // setState(() { + // _status = status; + // }); + // } \ No newline at end of file diff --git a/lib/tasks/subscribe_update.dart b/lib/tasks/subscribe_update.dart index 1b5ad10..7e9afd1 100644 --- a/lib/tasks/subscribe_update.dart +++ b/lib/tasks/subscribe_update.dart @@ -1,44 +1,78 @@ -import 'package:background_fetch/background_fetch.dart'; - -class SubscribeUpdateTask { - @pragma('vm:entry-point') - static void backgroundFetchHeadlessTask(HeadlessTask task) async { - String taskId = task.taskId; - bool isTimeout = task.timeout; - if (isTimeout) { - // This task has exceeded its allowed running-time. - // You must stop what you're doing and immediately .finish(taskId) - print("[BackgroundFetch] Headless task timed-out: $taskId"); - BackgroundFetch.finish(taskId); - return; +import 'package:dart_rss/domain/atom_feed.dart'; +import 'package:dart_rss/domain/rss1_feed.dart'; +import 'package:dart_rss/domain/rss_feed.dart'; +import 'package:miofeed/models/task_basic.dart'; +import 'package:miofeed/storages/rss/rss_storage.dart'; + +import '../models/rss.dart'; +import '../models/universal_feed.dart'; +import '../storages/rss/rss_cache.dart'; +import '../utils/network/get_rss.dart'; + +class SubscribeUpdateTask extends TaskBasic { + + static final _storage = RssStorage(); + + static Future run() async { + final list = await _storage.getRssList() ?? []; + for (var sub in list) { + RSS data = await _storage.getRss(sub); + + // 如果没开自动更新直接跳过 + if (!data.autoUpdate) break; + + print('Updating subscribe: $sub'); + + final url = data.subscribeUrl; + final String res; + try { + res = await NetworkGetRss().get(url); + } catch (e, s) { + print('Update RSS sub ${data.name} failed:'); + print(e); + print(s); + return; + } + //print(res); + late final UniversalFeed parsed; + try { + final parsedData = _parse(res, data.type); + if (parsedData is AtomFeed) { + parsed = UniversalFeed.fromAtom(parsedData); + // print(data.items.first.links.first.href); + } else if (parsedData is RssFeed) { + parsed = UniversalFeed.fromRss(parsedData); + } else if (parsedData is Rss1Feed) { + parsed = UniversalFeed.fromRss1(parsedData); + } + } catch (e, s) { + print('Parse result for RSS sub ${data.name} failed:'); + print(e); + print(s); + return; + } + data.data = parsed; + // print(parsed); + await RssCache.save(data, res); + await RssCache.removeMemCacheBySubName(sub); + await RssCache.toMemCache(data, data.data); + print('Update for subscribe finished: $sub'); + } + } + + static _parse(String data, int type) { + switch (type) { + case 0: + return AtomFeed.parse(data); + case 1: + return Rss1Feed.parse(data); + case 2: + return RssFeed.parse(data); } - print('[BackgroundFetch] Headless event received.'); - // Do your work here... - BackgroundFetch.finish(taskId); } -} - - // void _onClickEnable(enabled) { - // setState(() { - // _enabled = enabled; - // }); - // if (enabled) { - // BackgroundFetch.start().then((int status) { - // print('[BackgroundFetch] start success: $status'); - // }).catchError((e) { - // print('[BackgroundFetch] start FAILURE: $e'); - // }); - // } else { - // BackgroundFetch.stop().then((int status) { - // print('[BackgroundFetch] stop success: $status'); - // }); - // } - // } - // - // void _onClickStatus() async { - // int status = await BackgroundFetch.status; - // print('[BackgroundFetch] status: $status'); - // setState(() { - // _status = status; - // }); - // } \ No newline at end of file + + @override + startUp({Function? callback}) async { + await run(); + } +} \ No newline at end of file diff --git a/lib/tasks/task.dart b/lib/tasks/task.dart index f79cbbf..ab8a314 100644 --- a/lib/tasks/task.dart +++ b/lib/tasks/task.dart @@ -1,7 +1,8 @@ import 'dart:io'; import 'package:background_fetch/background_fetch.dart'; -import 'package:miofeed/tasks/subscribe_update.dart'; +import 'package:miofeed/tasks/android_subscribe_update.dart'; +import 'package:miofeed/utils/task_scheduler.dart'; class Task { static final _config = BackgroundFetchConfig( @@ -16,12 +17,18 @@ class Task { ); static Future doConfigure() async { - int status = await BackgroundFetch.configure( - _config, - _callbackHandler, - _errorHandler, - ); - print('[BackgroundFetch] configure success: $status'); + if (Platform.isAndroid) { + int status = await BackgroundFetch.configure( + _config, + _callbackHandler, + _errorHandler, + ); + print('[BackgroundFetch] configure success: $status'); + } else if (Platform.isIOS) { + + } else { + TaskScheduler.start(); + } } static void _callbackHandler(String taskId) async { @@ -41,7 +48,7 @@ class Task { static void register() { if (Platform.isAndroid) { BackgroundFetch.registerHeadlessTask( - SubscribeUpdateTask.backgroundFetchHeadlessTask, + AndroidSubscribeUpdateTask.backgroundFetchHeadlessTask, ); } } diff --git a/lib/ui/home.dart b/lib/ui/home.dart index d688c1b..db0de6e 100644 --- a/lib/ui/home.dart +++ b/lib/ui/home.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import 'package:miofeed/controllers/navigator_controller.dart'; import 'package:miofeed/models/universal_feed.dart'; import 'package:miofeed/models/universal_item.dart'; +import 'package:miofeed/tasks/subscribe_update.dart'; import 'package:miofeed/ui/paragraph.dart'; import 'package:miofeed/utils/after_layout.dart'; import 'package:miofeed/utils/paragraph_utils.dart'; @@ -26,6 +27,30 @@ class HomeUI extends StatelessWidget { return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, + actions: [ + IconButton( + onPressed: () async { + progressbar.start(); + await SubscribeUpdateTask.run(); + progressbar.finish(); + }, + icon: const Icon(Icons.refresh), + ), + IconButton( + onPressed: () async { + Get.dialog( + const SimpleDialog( + children: [ + Center( + child: Text('Comming soon'), + ) + ], + ), + ); + }, + icon: const Icon(Icons.search), + ) + ], title: Text(title), bottom: PreferredSize( preferredSize: Size(MediaQuery.of(context).size.width, 3), diff --git a/lib/ui/settings.dart b/lib/ui/settings.dart index 753350d..1520182 100644 --- a/lib/ui/settings.dart +++ b/lib/ui/settings.dart @@ -57,6 +57,15 @@ class SettingsUI extends StatelessWidget { Get.toNamed('/settings/render'); }, ), + InkWell( + child: const ListTile( + leading: Icon(Icons.info), + title: Text("关于"), + ), + onTap: () async { + Get.toNamed('/about'); + }, + ), ], ), bottomNavigationBar: NavigationBarX().build(), diff --git a/lib/ui/settings/about.dart b/lib/ui/settings/about.dart new file mode 100644 index 0000000..809bcd0 --- /dev/null +++ b/lib/ui/settings/about.dart @@ -0,0 +1,78 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:miofeed/ui/models/navigation_bar.dart'; +import 'package:miofeed/utils/app_info.dart'; + +import '../../controllers/progressbar_controller.dart'; + +class AboutUI extends StatelessWidget { + AboutUI({super.key, required this.title}); + + final String title; + + final ProgressbarController progressbar = Get.find(); + @override + Widget build(BuildContext context) { + int ic = 0; + + return Scaffold( + appBar: AppBar( + title: Text(title), + bottom: PreferredSize( + preferredSize: Size(MediaQuery.of(context).size.width, 3), + child: SizedBox( + width: MediaQuery.of(context).size.width, + height: 3, + child: Obx(() => progressbar.widget.value), + ), + ), + ), + body: ListView( + children: [ + InkWell( + child: ListTile( + title: Text( + 'MioFeed 版本 ${AppInfo.appVersion}(+${AppInfo.appBuildNumber})'), + ), + onTap: () async {}, + ), + InkWell( + child: ListTile( + title: Text( + '平台 ${Platform.operatingSystem} ${Platform.operatingSystemVersion}'), + ), + onTap: () async { + ic++; + if (ic == 10) { + Get.dialog( + SimpleDialog( + children: [ + ListTile( + title: const Text( + '🏳️‍🌈🏳‍⚧ 无论身陷何处,无论世界如何,终将对你温柔以待❤🧡💛💚💙💜。'), + subtitle: Container( + alignment: Alignment.bottomRight, + child: const Text('—— Author of MioFeed'), + ), + ), + ], + ), + ); + ic = 0; + } + }, + ), + InkWell( + child: ListTile( + title: Text('包名 ${AppInfo.appPackageName}'), + ), + onTap: () async {}, + ), + ], + ), + bottomNavigationBar: NavigationBarX().build(), + ); + } +} diff --git a/lib/utils/app_info.dart b/lib/utils/app_info.dart new file mode 100644 index 0000000..6b9baaf --- /dev/null +++ b/lib/utils/app_info.dart @@ -0,0 +1,15 @@ +import 'package:package_info_plus/package_info_plus.dart'; + +class AppInfo { + static Future get _instance async => await PackageInfo.fromPlatform(); + + static late final String appVersion; + static late final String appBuildNumber; + static late final String appPackageName; + + static Future init() async { + appVersion = (await _instance).version; + appBuildNumber = (await _instance).buildNumber; + appPackageName = (await _instance).packageName; + } +} \ No newline at end of file diff --git a/lib/utils/task_scheduler.dart b/lib/utils/task_scheduler.dart new file mode 100644 index 0000000..0d8184c --- /dev/null +++ b/lib/utils/task_scheduler.dart @@ -0,0 +1,15 @@ +import 'package:miofeed/tasks/subscribe_update.dart'; + +class TaskScheduler { + static Future start() async { + _taskSubscribeUpdate(); + } + + static _taskSubscribeUpdate() async { + SubscribeUpdateTask().startUp( + callback: () => Future.delayed(const Duration(minutes: 15), () { + SubscribeUpdateTask().startUp(); + }), + ); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index ce0325d..db52504 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -505,7 +505,7 @@ packages: source: hosted version: "2.1.0" package_info_plus: - dependency: transitive + dependency: "direct main" description: name: package_info_plus sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 @@ -937,10 +937,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.5" wakelock_plus: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 878e5dc..3d1de58 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,7 @@ dependencies: intl: ^0.19.0 dynamic_color: ^1.7.0 background_fetch: ^1.3.5 + package_info_plus: ^8.0.2 dev_dependencies: flutter_test: