diff --git a/.metadata b/.metadata index fd70cab..77d4f23 100644 --- a/.metadata +++ b/.metadata @@ -1,10 +1,36 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b + revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 channel: stable project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + - platform: linux + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + - platform: macos + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + - platform: windows + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/lib/base/http/api.dart b/lib/base/http/api.dart index 7fa55b3..d504dcc 100644 --- a/lib/base/http/api.dart +++ b/lib/base/http/api.dart @@ -9,6 +9,7 @@ import 'package:qinglong_app/module/login/user_bean.dart'; import 'package:qinglong_app/module/others/dependencies/dependency_bean.dart'; import 'package:qinglong_app/module/others/login_log/login_log_bean.dart'; import 'package:qinglong_app/module/others/scripts/script_bean.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_bean.dart'; import 'package:qinglong_app/module/others/task_log/task_log_bean.dart'; import 'package:qinglong_app/module/task/task_bean.dart'; import 'package:qinglong_app/utils/utils.dart'; @@ -377,4 +378,89 @@ class Api { [id], ); } + + + // 获取订阅 + static Future>> getSubscription()async{ + return await Http.get>( + Url.subscriptions, + {} + ); + } + + // 新增订阅 + static Future> postSubscription(Subscription subscription)async{ + var data = subscription.toJson(); + data.remove("id"); + data.remove("status"); + data.remove("pid"); + data.remove("log_path"); + data.remove("is_disabled"); + data.remove("createdAt"); + data.remove("updatedAt"); + data.remove("pullType"); + data.remove("pullOption"); + return await Http.post( + Url.subscriptions, + data + ); + } + + // 修改订阅 + static Future> putSubscription(Subscription subscription)async{ + return await Http.put( + Url.subscriptions, + subscription.toJson() + ); + } + + // 删除订阅 参数未一个id数组 + static Future> deleteSubscription(List ids)async{ + return await Http.delete( + Url.subscriptions, + ids + ); + } + + // 删除订阅 参数未一个id数组 + static Future> disableSubscription(List ids)async{ + return await Http.put( + Url.disableSubscriptions, + ids + ); + } + + // 删除订阅 参数未一个id数组 + static Future> enableSubscription(List ids)async{ + return await Http.put( + Url.enableSubscriptions, + ids + ); + } + + // 开始执行订阅 + static Future> startSubscription( + List subscriptions) async { + return await Http.put( + Url.runSubscriptions, + subscriptions, + ); + } + + // 暂停订阅执行 + static Future> stopSubscription( + List subscriptions) async { + return await Http.put( + Url.stopSubscriptions, + subscriptions, + ); + } + + // 获取订阅的日志 + static Future> subTimeLog(int subId) async { + return await Http.get( + Url.subtimeLog(subId), + null, + ); + } } diff --git a/lib/base/http/url.dart b/lib/base/http/url.dart index a96842f..aabb9f4 100644 --- a/lib/base/http/url.dart +++ b/lib/base/http/url.dart @@ -130,6 +130,38 @@ class Url { : "/api/envs/$envId/move"; } + // 运行订阅 + static get runSubscriptions => getIt().useSecretLogined + ? "/open/subscriptions/run" + : "/api/subscriptions/run"; + + // 停止订阅 + static get stopSubscriptions => getIt().useSecretLogined + ? "/open/subscriptions/stop" + : "/api/subscriptions/stop"; + + // 启用订阅 + static get enableSubscriptions => getIt().useSecretLogined + ? "/open/subscriptions/enable" + : "/api/subscriptions/enable"; + + // 禁用订阅 + static get disableSubscriptions => getIt().useSecretLogined + ? "/open/subscriptions/disable" + : "/api/subscriptions/disable"; + + // GET 获取订阅 POST 提交订阅 PUT 修改订阅 DELETE 删除订阅 + static get subscriptions => getIt().useSecretLogined + ? "/open/subscriptions" + : "/api/subscriptions"; + + // 获取订阅日志 + static subtimeLog(int cronId) { + return getIt().useSecretLogined + ? "/open/subscriptions/$cronId/log" + : "/api/subscriptions/$cronId/log"; + } + static bool inWhiteList(String path) { if (path == login || path == loginByClientId || diff --git a/lib/base/routes.dart b/lib/base/routes.dart index b9425b2..f8473d2 100644 --- a/lib/base/routes.dart +++ b/lib/base/routes.dart @@ -8,12 +8,18 @@ import 'package:qinglong_app/module/env/env_detail_page.dart'; import 'package:qinglong_app/module/home/home_page.dart'; import 'package:qinglong_app/module/login/login_page.dart'; import 'package:qinglong_app/module/others/about_page.dart'; +import 'package:qinglong_app/module/others/backup/backup_page.dart'; import 'package:qinglong_app/module/others/dependencies/add_dependency_page.dart'; import 'package:qinglong_app/module/others/dependencies/dependency_page.dart'; import 'package:qinglong_app/module/others/login_log/login_log_page.dart'; +import 'package:qinglong_app/module/others/scripts/acript_add_page.dart'; import 'package:qinglong_app/module/others/scripts/script_detail_page.dart'; import 'package:qinglong_app/module/others/scripts/script_edit_page.dart'; import 'package:qinglong_app/module/others/scripts/script_page.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_add_page.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_bean.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_detail_page.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_page.dart'; import 'package:qinglong_app/module/others/task_log/task_log_detail_page.dart'; import 'package:qinglong_app/module/others/task_log/task_log_page.dart'; import 'package:qinglong_app/module/others/theme_page.dart'; @@ -43,6 +49,12 @@ class Routes { static const String routeAbout = "/about"; static const String routeTheme = "/theme"; static const String routeChangeAccount = "/changeAccount"; + static const String routerAddScript = "/script/add"; + static const String routerSubscription = "/subscription"; + static const String routerSubscriptionDetail = "/subscription/detail"; + static const String routerSubscriptionAdd = "/subscription/add"; + + static const String routerBackup = "/backup"; static Route? generateRoute(RouteSettings settings) { switch (settings.name) { @@ -151,8 +163,36 @@ class Routes { (settings.arguments as Map)["content"], ), ); + case routeScriptAdd: + return CupertinoPageRoute( + builder: (context) => const ScriptAddPage(), + ); + case routerSubscription: + return CupertinoPageRoute( + builder: (context) => const SubscriptionPage(), + ); + case routerSubscriptionDetail: + return CupertinoPageRoute( + builder: (context) => SubscriptionDetailPage( + settings.arguments as Subscription, + ), + ); + case routerSubscriptionAdd: + if (settings.arguments != null) { + return MaterialPageRoute( + builder: (context) => SubscriptionAddPage( + settings.arguments as Subscription, + )); + } else { + return MaterialPageRoute(builder: (context) => const SubscriptionAddPage(null)); + } + case routerBackup: + return CupertinoPageRoute( + builder: (context) => const BackUpPage(), + ); } + return null; } } diff --git a/lib/base/ui/ql_context_menu.dart b/lib/base/ui/ql_context_menu.dart index 1e73d68..a759ae9 100644 --- a/lib/base/ui/ql_context_menu.dart +++ b/lib/base/ui/ql_context_menu.dart @@ -89,7 +89,7 @@ enum _ContextMenuLocation { /// See also: /// /// * [Apple's HIG for Context Menus](https://developer.apple.com/design/human-interface-guidelines/ios/controls/context-menus/) -class QlCupertinoContextMenu extends StatefulWidget { +class QlCupertinoContextMenu extends StatefulWidget { /// Create a context menu. /// /// [actions] is required and cannot be null or empty. @@ -105,7 +105,7 @@ class QlCupertinoContextMenu extends StatefulWidget { assert(child != null), super(key: key); - final TaskBean bean; + final T bean; /// The widget that can be "opened" with the [QlCupertinoContextMenu]. /// @@ -322,8 +322,14 @@ class _QlCupertinoContextMenuState extends State _openController.reverse(); } else { if (_openController.isDismissed) { - Navigator.of(context) - .pushNamed(Routes.routeTaskDetail, arguments: widget.bean); + if (widget.bean is TaskBean){ + Navigator.of(context) + .pushNamed(Routes.routeTaskDetail, arguments: widget.bean); + }else{ + Navigator.of(context) + .pushNamed(Routes.routerSubscriptionDetail, arguments: widget.bean); + } + } } } diff --git a/lib/json.jc.dart b/lib/json.jc.dart index 9c82c48..cf6c4ca 100644 --- a/lib/json.jc.dart +++ b/lib/json.jc.dart @@ -8,11 +8,14 @@ import 'package:qinglong_app/module/login/user_bean.dart'; import 'package:qinglong_app/module/others/dependencies/dependency_bean.dart'; import 'package:qinglong_app/module/others/login_log/login_log_bean.dart'; import 'package:qinglong_app/module/others/scripts/script_bean.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_bean.dart'; import 'package:qinglong_app/module/others/task_log/task_log_bean.dart'; import 'package:qinglong_app/module/task/task_bean.dart'; + class JsonConversion$Json { - static M fromJson(dynamic json) { + + static M fromJson(dynamic json) { if (json is List) { return _getListChildType(json); } else { @@ -21,104 +24,101 @@ class JsonConversion$Json { } static M _fromJsonSingle(dynamic json) { + String type = M.toString(); - if (type == (ConfigBean).toString()) { - return ConfigBean.jsonConversion(json) as M; + if(type == (ConfigBean).toString()){ + return ConfigBean.jsonConversion(json) as M; + } + + if(type == (EnvBean).toString()){ + return EnvBean.jsonConversion(json) as M; + } + + if(type == (SystemBean).toString()){ + return SystemBean.jsonConversion(json) as M; + } + + if(type == (LoginBean).toString()){ + return LoginBean.jsonConversion(json) as M; + } + + if(type == (UserBean).toString()){ + return UserBean.jsonConversion(json) as M; + } + + if(type == (DependencyBean).toString()){ + return DependencyBean.jsonConversion(json) as M; + } + + if(type == (LoginLogBean).toString()){ + return LoginLogBean.jsonConversion(json) as M; + } + + if(type == (ScriptBean).toString()){ + return ScriptBean.jsonConversion(json) as M; + } + + if(type == (TaskLogBean).toString()){ + return TaskLogBean.jsonConversion(json) as M; + } + + if(type == (TaskBean).toString()){ + return TaskBean.jsonConversion(json) as M; } - if (type == (EnvBean).toString()) { - return EnvBean.jsonConversion(json) as M; + if (type == (Subscription).toString()){ + return Subscription.fromJson(json) as M; } - - if (type == (SystemBean).toString()) { - return SystemBean.jsonConversion(json) as M; - } - - if (type == (LoginBean).toString()) { - return LoginBean.jsonConversion(json) as M; - } - - if (type == (UserBean).toString()) { - return UserBean.jsonConversion(json) as M; - } - - if (type == (DependencyBean).toString()) { - return DependencyBean.jsonConversion(json) as M; - } - - if (type == (LoginLogBean).toString()) { - return LoginLogBean.jsonConversion(json) as M; - } - - if (type == (ScriptBean).toString()) { - return ScriptBean.jsonConversion(json) as M; - } - - if (type == (TaskLogBean).toString()) { - return TaskLogBean.jsonConversion(json) as M; - } - - if (type == (TaskBean).toString()) { - return TaskBean.jsonConversion(json) as M; - } - + throw Exception("not found"); } static M _getListChildType(List data) { - if ([] is M) { - return data.map((e) => ConfigBean.jsonConversion(e)).toList() - as M; + if([] is M){ + return data.map((e) => ConfigBean.jsonConversion(e)).toList() as M; } - - if ([] is M) { + + if([] is M){ return data.map((e) => EnvBean.jsonConversion(e)).toList() as M; } - - if ([] is M) { - return data.map((e) => SystemBean.jsonConversion(e)).toList() - as M; + + if([] is M){ + return data.map((e) => SystemBean.jsonConversion(e)).toList() as M; + } + + if([] is M){ + return data.map((e) => LoginBean.jsonConversion(e)).toList() as M; + } + + if([] is M){ + return data.map((e) => UserBean.jsonConversion(e)).toList() as M; + } + + if([] is M){ + return data.map((e) => DependencyBean.jsonConversion(e)).toList() as M; + } + + if([] is M){ + return data.map((e) => LoginLogBean.jsonConversion(e)).toList() as M; + } + + if([] is M){ + return data.map((e) => ScriptBean.jsonConversion(e)).toList() as M; + } + + if([] is M){ + return data.map((e) => TaskLogBean.jsonConversion(e)).toList() as M; + } + + if([] is M){ + return data.map((e) => TaskBean.jsonConversion(e)).toList() as M; } - if ([] is M) { - return data.map((e) => LoginBean.jsonConversion(e)).toList() - as M; + if ([] is M){ + return data.map((e) => Subscription.jsonConversion(e)).toList() as M; } - - if ([] is M) { - return data.map((e) => UserBean.jsonConversion(e)).toList() - as M; - } - - if ([] is M) { - return data - .map((e) => DependencyBean.jsonConversion(e)) - .toList() as M; - } - - if ([] is M) { - return data - .map((e) => LoginLogBean.jsonConversion(e)) - .toList() as M; - } - - if ([] is M) { - return data.map((e) => ScriptBean.jsonConversion(e)).toList() - as M; - } - - if ([] is M) { - return data - .map((e) => TaskLogBean.jsonConversion(e)) - .toList() as M; - } - - if ([] is M) { - return data.map((e) => TaskBean.jsonConversion(e)).toList() - as M; - } - + throw Exception("not found"); } } diff --git a/lib/module/others/backup/backup_page.dart b/lib/module/others/backup/backup_page.dart new file mode 100644 index 0000000..151399e --- /dev/null +++ b/lib/module/others/backup/backup_page.dart @@ -0,0 +1,278 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:qinglong_app/base/http/api.dart'; +import 'package:qinglong_app/base/ql_app_bar.dart'; +import 'package:qinglong_app/json.jc.dart'; +import 'package:qinglong_app/module/env/env_bean.dart'; +import 'package:qinglong_app/utils/extension.dart'; +import 'package:share_plus/share_plus.dart'; + +import '../../../base/theme.dart'; +import '../../../base/userinfo_viewmodel.dart'; +import '../../../main.dart'; + +class BackUpPage extends ConsumerStatefulWidget { + const BackUpPage({Key? key}) : super(key: key); + + @override + _BackUpPageState createState() => _BackUpPageState(); +} + +class _BackUpPageState extends ConsumerState { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: QlAppBar( + canBack: true, + title: '数据备份', + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 15, + ), + decoration: BoxDecoration( + color: ref.watch(themeProvider).themeColor.settingBordorColor(), + borderRadius: const BorderRadius.all( + Radius.circular(15), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + getEnv(); + }, + child: Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 5, + left: 15, + right: 15, + ), + child: Row( + children: [ + Text( + "环境变量备份到本地", + style: TextStyle( + color: ref + .watch(themeProvider) + .themeColor + .titleColor(), + fontSize: 16, + ), + ), + const Spacer(), + const Icon( + CupertinoIcons.right_chevron, + size: 16, + ), + ], + ), + ), + ), + const Divider( + indent: 15, + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + uploadEnv(context); + }, + child: Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 5, + left: 15, + right: 15, + ), + child: Row( + children: [ + Text( + "从本地导入环境变量", + style: TextStyle( + color: ref + .watch(themeProvider) + .themeColor + .titleColor(), + fontSize: 16, + ), + ), + const Spacer(), + const Icon( + CupertinoIcons.right_chevron, + size: 16, + ), + ], + ), + ), + ), + const Divider( + indent: 15, + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + uploadEnvFromFile(); + }, + child: Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 5, + left: 15, + right: 15, + ), + child: Row( + children: [ + Text( + "从文件导入环境变量", + style: TextStyle( + color: ref + .watch(themeProvider) + .themeColor + .titleColor(), + fontSize: 16, + ), + ), + const Spacer(), + const Icon( + CupertinoIcons.right_chevron, + size: 16, + ), + ], + ), + ), + ), + const Divider( + indent: 15, + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + shareEnvs(); + }, + child: Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 5, + left: 15, + right: 15, + ), + child: Row( + children: [ + Text( + "导出环境变量", + style: TextStyle( + color: ref + .watch(themeProvider) + .themeColor + .titleColor(), + fontSize: 16, + ), + ), + const Spacer(), + const Icon( + CupertinoIcons.right_chevron, + size: 16, + ), + ], + ), + ), + ), + const Divider( + indent: 15, + ), + ]), + ) + ], + ), + ); + } + + Future shareEnvs() async { + Directory appDocDir = await getApplicationDocumentsDirectory(); + var dir = Directory("${appDocDir.path}/backup/"); + var fileList = await dir.list().toList(); + List paths = []; + for(var i=0;i().host!); + File file = File("${appDocDir.path}/backup/${uri.host}-${uri.port}.env"); + file = await file.writeAsString(data, flush: true); + "备份成功".toast(); + } + + void uploadEnvFromFile()async{ + var result = await FilePicker.platform.pickFiles(dialogTitle: "请选择env文件"); + if (result == null)return; + File f = File(result.paths[0]!); + var data = await f.readAsString(); + var content = jsonDecode(data) as List; + for (var j = 0; j < content.length; j++) { + var bean = JsonConversion$Json.fromJson(content[j]); + var resp = await Api.addEnv(bean.name!, bean.value!, bean.remarks!); + if (!resp.success){ + resp.message.toast(); + } + } + "备份完成".toast(); + } + } + + void uploadEnv(BuildContext context) async { + Directory appDocDir = await getApplicationDocumentsDirectory(); + List widgets = []; + var dir = Directory("${appDocDir.path}/backup/"); + var fileList = await dir.list().toList(); + for (var i = 0; i < fileList.length; i++) { + var paths = fileList[i].path.split("/"); + widgets.add( + ListTile( + title: Text(paths[paths.length - 1]), + onTap: () async { + File f = File(fileList[i].path); + var data = await f.readAsString(); + var content = jsonDecode(data) as List; + for (var j = 0; j < content.length; j++) { + var bean = JsonConversion$Json.fromJson(content[j]); + var resp = await Api.addEnv(bean.name!, bean.value!, bean.remarks!); + if (!resp.success){ + resp.message.toast(); + } + } + "备份完成".toast(); + + + }, + )); + } + var dialog = SimpleDialog(title: const Text("请选择备份文件"), children: widgets); + await showDialog( + context: context, + builder: (BuildContext context) { + return dialog; + }); + } + diff --git a/lib/module/others/other_page.dart b/lib/module/others/other_page.dart index 17153b8..840040e 100644 --- a/lib/module/others/other_page.dart +++ b/lib/module/others/other_page.dart @@ -1,13 +1,11 @@ -import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:qinglong_app/base/routes.dart'; -import 'package:qinglong_app/base/sp_const.dart'; import 'package:qinglong_app/base/theme.dart'; import 'package:qinglong_app/base/userinfo_viewmodel.dart'; import 'package:qinglong_app/main.dart'; import 'package:qinglong_app/utils/extension.dart'; -import 'package:qinglong_app/utils/sp_utils.dart'; class OtherPage extends ConsumerStatefulWidget { const OtherPage({Key? key}) : super(key: key); @@ -28,8 +26,15 @@ class _OtherPageState extends ConsumerState { children: [ Container( margin: const EdgeInsets.symmetric( + horizontal: 15, vertical: 15, ), + decoration: BoxDecoration( + color: ref.watch(themeProvider).themeColor.settingBordorColor(), + borderRadius: const BorderRadius.all( + Radius.circular(15), + ), + ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -230,13 +235,103 @@ class _OtherPageState extends ConsumerState { ), ), ), + const Divider( + indent: 15, + ), + GestureDetector( + onTap: () { + Navigator.of(context).pushNamed( + Routes.routerSubscription, + ); + }, + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.only( + top: 5, + bottom: 10, + left: 15, + right: 15, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "订阅管理", + style: TextStyle( + color: ref + .watch(themeProvider) + .themeColor + .titleColor(), + fontSize: 16, + ), + ), + const Spacer(), + const Icon( + CupertinoIcons.right_chevron, + size: 16, + ), + ], + ), + ), + ), + const Divider( + indent: 15, + ), + GestureDetector( + onTap: () { + Navigator.of(context).pushNamed( + Routes.routerBackup, + ); + }, + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.only( + top: 5, + bottom: 10, + left: 15, + right: 15, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "数据备份", + style: TextStyle( + color: ref + .watch(themeProvider) + .themeColor + .titleColor(), + fontSize: 16, + ), + ), + const Spacer(), + const Icon( + CupertinoIcons.right_chevron, + size: 16, + ), + ], + ), + ), + ), + const Divider( + indent: 15, + ), ], ), ), Container( margin: const EdgeInsets.symmetric( + horizontal: 15, vertical: 15, ), + decoration: BoxDecoration( + color: ref.watch(themeProvider).themeColor.settingBordorColor(), + borderRadius: const BorderRadius.all( + Radius.circular(15), + ), + ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -272,40 +367,6 @@ class _OtherPageState extends ConsumerState { const Divider( indent: 15, ), - Padding( - padding: const EdgeInsets.only( - left: 15, - right: 15, - bottom: 5, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "查看代码是否显示行号", - style: TextStyle( - color: - ref.watch(themeProvider).themeColor.titleColor(), - fontSize: 16, - ), - ), - const Spacer(), - CupertinoSwitch( - activeColor: ref.watch(themeProvider).primaryColor, - value: SpUtil.getBool(spShowLine, defValue: false), - onChanged: (open) async { - await SpUtil.putBool(spShowLine, open); - setState(() {}); - }, - ), - ], - ), - ), - const Divider( - indent: 15, - height: 1, - ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { @@ -316,7 +377,7 @@ class _OtherPageState extends ConsumerState { child: Padding( padding: const EdgeInsets.symmetric( horizontal: 15, - vertical: 10, + vertical: 5, ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/module/others/subscription/subscription_add_page.dart b/lib/module/others/subscription/subscription_add_page.dart new file mode 100644 index 0000000..8a2ad13 --- /dev/null +++ b/lib/module/others/subscription/subscription_add_page.dart @@ -0,0 +1,596 @@ +import 'package:flutter/material.dart'; +import 'package:qinglong_app/base/http/api.dart'; +import 'package:qinglong_app/base/ql_app_bar.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_bean.dart'; +import 'package:qinglong_app/utils/extension.dart'; + +class SubscriptionAddPage extends StatefulWidget { + final Subscription? bean; + + const SubscriptionAddPage(this.bean, {Key? key}) : super(key: key); + + @override + State createState() => _SubscriptionAddPageState(); +} + +class _SubscriptionAddPageState extends State { + late Subscription _bean; + + late String? _scheduleType; + late String? _type; + + late String? _intervalScheduleType; + late String? _intervalScheduleValue; + + + @override + void initState() { + super.initState(); + if (widget.bean == null) { + _bean = Subscription(); + _bean.scheduleType = ""; + _type = "public-repo"; + _scheduleType = "crontab"; + + _intervalScheduleType = "days"; + } else { + _bean = widget.bean!; + _type = widget.bean?.type; + _scheduleType = widget.bean?.scheduleType; + } + } + + final TextEditingController _nameController = TextEditingController(); + final TextEditingController _urlController = TextEditingController(); + final TextEditingController _branchController = TextEditingController(); + final TextEditingController _intervalScheduleValueController = TextEditingController(); + final TextEditingController _scheduleController = TextEditingController(); + final TextEditingController _whiteListController = TextEditingController(); + final TextEditingController _blackListController = TextEditingController(); + final TextEditingController _dependenceController = TextEditingController(); + final TextEditingController _extensionsController = TextEditingController(); + final TextEditingController _taskBeforeController = TextEditingController(); + final TextEditingController _taskAfterController = TextEditingController(); + FocusNode focusNode = FocusNode(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: QlAppBar( + canBack: true, + backCall: () { + Navigator.of(context).pop(); + }, + title: _bean.name == null ? "新增订阅" : "编辑订阅", + actions: [ + InkWell( + onTap: () { + submit(context); + }, + child: const Padding( + padding: EdgeInsets.symmetric( + horizontal: 15, + ), + child: Center( + child: Text( + "提交", + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + ), + ), + ) + ], + ), + body: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + const Text( + "名称:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + TextField( + focusNode: focusNode, + controller: _nameController, + decoration: const InputDecoration( + hintText: "请输入名称", + ), + autofocus: false, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + const Text( + "类型:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + Radio( + value: "public-repo", + groupValue: _type, + onChanged: (data) { + setState(() { + _type = data as String?; + }); + }), + const Text("公开仓库"), + ], + ), + Row( + children: [ + Radio( + value: "private-repo", + groupValue: _type, + onChanged: (data) { + setState(() { + _type = data as String?; + }); + }), + const Text("私有仓库"), + ], + ), + Row( + children: [ + Radio( + value: "file", + groupValue: _type, + onChanged: (data) { + setState(() { + _type = data as String?; + }); + }), + const Text("单文件"), + ], + ), + ], + ) + ], + ), + ), + const Divider( + height: 10, + thickness: 2, + indent: 15, + endIndent: 15, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + const Text( + "链接:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + TextField( + controller: _urlController, + maxLines: 8, + minLines: 1, + decoration: const InputDecoration( + hintText: "请输入仓库链接", + ), + autofocus: false, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + const Text( + "分支:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + TextField( + controller: _branchController, + decoration: const InputDecoration( + hintText: "请输入分支", + ), + autofocus: false, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + const Text( + "定时类型:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + Radio( + value: "crontab", + groupValue: _scheduleType, + onChanged: (data) { + setState(() { + _scheduleType = data as String?; + }); + }), + const Text("crontab"), + ], + ), + Row( + children: [ + Radio( + value: "interval", + groupValue: _scheduleType, + onChanged: (data) { + setState(() { + _scheduleType = data as String?; + }); + }), + const Text("interval"), + ], + ), + ], + ) + ], + ), + ), + const Divider( + height: 10, + thickness: 2, + indent: 15, + endIndent: 15, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + const Text( + "定时规则:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + getScheduler(context) + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + const Text( + "白名单:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + TextField( + controller: _whiteListController, + decoration: const InputDecoration( + hintText: "请输入白名单", + ), + autofocus: false, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + const Text( + "黑名单:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + TextField( + controller: _blackListController, + decoration: const InputDecoration( + hintText: "请输入黑名单", + ), + autofocus: false, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + const Text( + "依赖文件:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + TextField( + controller: _dependenceController, + decoration: const InputDecoration( + hintText: "请输入依赖文件", + ), + autofocus: false, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + const Text( + "文件后缀:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + TextField( + controller: _extensionsController, + decoration: const InputDecoration( + hintText: "请输入文件后缀", + ), + autofocus: false, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + const Text( + "执行前:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + TextField( + controller: _taskBeforeController, + decoration: const InputDecoration( + hintText: "执行前", + ), + autofocus: false, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + const Text( + "执行后:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + TextField( + controller: _taskAfterController, + decoration: const InputDecoration( + hintText: "执行后", + ), + autofocus: false, + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget getScheduler(BuildContext context) { + if (_scheduleType == "crontab") { + return TextField( + controller: _scheduleController, + decoration: const InputDecoration( + hintText: "秒(可选) 分 时 天 月 周", + ), + autofocus: false, + ); + } else { + return Row( + children: [ + const Text("每"), + SizedBox( + width: 350, + child: TextField( + controller: _intervalScheduleValueController, + textAlign: TextAlign.center, + decoration: const InputDecoration( + hintText: "", + ), + autofocus: false, + ), + ), + + DropdownButton( + underline: Container(height: 0), + value: _intervalScheduleType, + items: const [ + DropdownMenuItem(child: Text("时"),value: "hours",), + DropdownMenuItem(child: Text("天"),value: "days",), + DropdownMenuItem(child: Text("周"),value: "weeks",), + ], + onChanged: (data) {setState(() { + _intervalScheduleType = data; + });}) + ], + ); + } + } + + void submit(BuildContext context) async { + if (_nameController.text.isEmpty){ + "名称不能为空".toast(); + return; + }else{ + _bean.name = _nameController.value.text; + } + if (_urlController.text.isEmpty){ + "链接不能为空".toast(); + return; + }else{ + _bean.url = _urlController.value.text; + } + + _bean.type = _type; + _bean.branch = _branchController.value.text; + if (_scheduleType != null && _scheduleType == "crontab"){ + if (_scheduleController.text.isEmpty){ + "定时规则不能为空".toast(); + return; + }else{ + _bean.schedule = _scheduleController.value.text; + } + }else{ + if (_intervalScheduleValueController.text.isEmpty){ + "定时规则不能为空".toast(); + return; + }else{ + var inter = IntervalSchedule(); + inter.type = _intervalScheduleType; + inter.value = int.tryParse(_intervalScheduleValueController.value.text); + _bean.intervalSchedule = inter; + } + } + _bean.alias = _bean.name; + _bean.scheduleType = _scheduleType; + _bean.whitelist = _whiteListController.value.text; + _bean.blacklist = _blackListController.value.text; + _bean.extensions = _extensionsController.value.text; + _bean.dependences = _dependenceController.value.text; + _bean.subBefore = _taskBeforeController.value.text; + _bean.subAfter = _taskAfterController.value.text; + var data = await Api.postSubscription(_bean); + if (!data.success){ + data.message.toast(); + }else{ + "添加成功".toast(); + Navigator.of(context).pop(); + } + } +} diff --git a/lib/module/others/subscription/subscription_bean.dart b/lib/module/others/subscription/subscription_bean.dart new file mode 100644 index 0000000..8bc422f --- /dev/null +++ b/lib/module/others/subscription/subscription_bean.dart @@ -0,0 +1,310 @@ +class Subscription { + int? _id; + String? _name; + String? _url; + dynamic _schedule; + IntervalSchedule? _intervalSchedule; + String? _type; + String? _whitelist; + String? _blacklist; + int? _status; + String? _dependences; + dynamic _extensions; + dynamic _subBefore; + dynamic _subAfter; + String? _branch; + dynamic _pullType; + dynamic _pullOption; + int? _pid; + int? _isDisabled; + String? _logPath; + String? _scheduleType; + String? _alias; + String? _createdAt; + String? _updatedAt; + + Subscription( + {int? id, + String? name, + String? url, + dynamic schedule, + IntervalSchedule? intervalSchedule, + String? type, + String? whitelist, + String? blacklist, + int? status, + String? dependences, + dynamic extensions, + dynamic subBefore, + dynamic subAfter, + String? branch, + dynamic pullType, + dynamic pullOption, + int? pid, + int? isDisabled, + String? logPath, + String? scheduleType, + String? alias, + String? createdAt, + String? updatedAt}) { + if (id != null) { + this._id = id; + } + if (name != null) { + this._name = name; + } + if (url != null) { + this._url = url; + } + if (schedule != null) { + this._schedule = schedule; + } + if (intervalSchedule != null) { + this._intervalSchedule = intervalSchedule; + } + if (type != null) { + this._type = type; + } + if (whitelist != null) { + this._whitelist = whitelist; + } + if (blacklist != null) { + this._blacklist = blacklist; + } + if (status != null) { + this._status = status; + } + if (dependences != null) { + this._dependences = dependences; + } + if (extensions != null) { + this._extensions = extensions; + } + if (subBefore != null) { + this._subBefore = subBefore; + } + if (subAfter != null) { + this._subAfter = subAfter; + } + if (branch != null) { + this._branch = branch; + } + if (pullType != null) { + this._pullType = pullType; + } + if (pullOption != null) { + this._pullOption = pullOption; + } + if (pid != null) { + this._pid = pid; + } + if (isDisabled != null) { + this._isDisabled = isDisabled; + } + if (logPath != null) { + this._logPath = logPath; + } + if (scheduleType != null) { + this._scheduleType = scheduleType; + } + if (alias != null) { + this._alias = alias; + } + if (createdAt != null) { + this._createdAt = createdAt; + } + if (updatedAt != null) { + this._updatedAt = updatedAt; + } + } + + int? get id => _id; + + set id(int? id) => _id = id; + + String? get name => _name; + + set name(String? name) => _name = name; + + String? get url => _url; + + set url(String? url) => _url = url; + + dynamic get schedule => _schedule; + + set schedule(dynamic schedule) => _schedule = schedule; + + IntervalSchedule? get intervalSchedule => _intervalSchedule; + + set intervalSchedule(IntervalSchedule? intervalSchedule) => + _intervalSchedule = intervalSchedule; + + String? get type => _type; + + set type(String? type) => _type = type; + + String? get whitelist => _whitelist; + + set whitelist(String? whitelist) => _whitelist = whitelist; + + String? get blacklist => _blacklist; + + set blacklist(String? blacklist) => _blacklist = blacklist; + + int? get status => _status; + + set status(int? status) => _status = status; + + String? get dependences => _dependences; + + set dependences(String? dependences) => _dependences = dependences; + + dynamic get extensions => _extensions; + + set extensions(dynamic extensions) => _extensions = extensions; + + dynamic get subBefore => _subBefore; + + set subBefore(dynamic subBefore) => _subBefore = subBefore; + + dynamic get subAfter => _subAfter; + + set subAfter(dynamic subAfter) => _subAfter = subAfter; + + String? get branch => _branch; + + set branch(String? branch) => _branch = branch; + + dynamic get pullType => _pullType; + + set pullType(dynamic pullType) => _pullType = pullType; + + dynamic get pullOption => _pullOption; + + set pullOption(dynamic pullOption) => _pullOption = pullOption; + + int? get pid => _pid; + + set pid(int? pid) => _pid = pid; + + int? get isDisabled => _isDisabled; + + set isDisabled(dynamic isDisabled) => _isDisabled = isDisabled; + + String? get logPath => _logPath; + + set logPath(String? logPath) => _logPath = logPath; + + String? get scheduleType => _scheduleType; + + set scheduleType(String? scheduleType) => _scheduleType = scheduleType; + + String? get alias => _alias; + + set alias(String? alias) => _alias = alias; + + String? get createdAt => _createdAt; + + set createdAt(String? createdAt) => _createdAt = createdAt; + + String? get updatedAt => _updatedAt; + + set updatedAt(String? updatedAt) => _updatedAt = updatedAt; + + Subscription.fromJson(Map json) { + _id = json['id']; + _name = json['name']; + _url = json['url']; + _schedule = json['schedule']; + _intervalSchedule = json['interval_schedule'] != null + ? new IntervalSchedule.fromJson(json['interval_schedule']) + : null; + _type = json['type']; + _whitelist = json['whitelist']; + _blacklist = json['blacklist']; + _status = json['status']; + _dependences = json['dependences']; + _extensions = json['extensions']; + _subBefore = json['sub_before']; + _subAfter = json['sub_after']; + _branch = json['branch']; + _pullType = json['pull_type']; + _pullOption = json['pull_option']; + _pid = json['pid']; + _isDisabled = json['is_disabled']; + _logPath = json['log_path']; + _scheduleType = json['schedule_type']; + _alias = json['alias']; + _createdAt = json['createdAt']; + _updatedAt = json['updatedAt']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this._id; + data['name'] = this._name; + data['url'] = this._url; + data['schedule'] = this._schedule; + if (this._intervalSchedule != null) { + data['interval_schedule'] = this._intervalSchedule!.toJson(); + } + data['type'] = this._type; + data['whitelist'] = this._whitelist; + data['blacklist'] = this._blacklist; + data['status'] = this._status; + data['dependences'] = this._dependences; + data['extensions'] = this._extensions; + data['sub_before'] = this._subBefore; + data['sub_after'] = this._subAfter; + data['branch'] = this._branch; + data['pull_type'] = this._pullType; + data['pull_option'] = this._pullOption; + data['pid'] = this._pid; + data['is_disabled'] = this._isDisabled; + data['log_path'] = this._logPath; + data['schedule_type'] = this._scheduleType; + data['alias'] = this._alias; + data['createdAt'] = this._createdAt; + data['updatedAt'] = this._updatedAt; + return data; + } + static Subscription jsonConversion(Map json) { + return Subscription.fromJson(json); + } +} + +class IntervalSchedule { + String? _type; + int? _value; + + IntervalSchedule({String? type, int? value}) { + if (type != null) { + this._type = type; + } + if (value != null) { + this._value = value; + } + } + + String? get type => _type; + + set type(String? type) => _type = type; + + int? get value => _value; + + set value(int? value) => _value = value; + + IntervalSchedule.fromJson(Map json) { + _type = json['type']; + _value = json['value']; + } + + Map toJson() { + final Map data = new Map(); + data['type'] = this._type; + data['value'] = this._value; + return data; + } + + +} diff --git a/lib/module/others/subscription/subscription_detail_page.dart b/lib/module/others/subscription/subscription_detail_page.dart new file mode 100644 index 0000000..5bf89be --- /dev/null +++ b/lib/module/others/subscription/subscription_detail_page.dart @@ -0,0 +1,338 @@ +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; +import 'package:qinglong_app/base/ql_app_bar.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_bean.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_time_log_page.dart'; + +import '../../../base/http/api.dart'; +import '../../../base/routes.dart'; +import '../../../base/theme.dart'; +import '../../task/task_viewmodel.dart'; + +class SubscriptionDetailPage extends StatefulWidget { + final Subscription bean; + final bool hideAppbar; + + const SubscriptionDetailPage(this.bean, {Key? key, this.hideAppbar = false}) + : super(key: key); + + @override + State createState() => _SubscriptionDetailPageState(); +} + +class _SubscriptionDetailPageState extends State { + + List actions = []; + + + @override + void initState() { + super.initState(); + //actions.clear(); + actions.addAll( + [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () async { + Navigator.of(context).pop(); + if (widget.bean.status! == 1) { + await startCron(context); + } else { + await stopCron(context); + } + }, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 15, + ), + alignment: Alignment.center, + child: Material( + color: Colors.transparent, + child: Text( + widget.bean.status! == 1 ? "运行" : "停止运行", + style: const TextStyle( + fontSize: 16, + ), + ), + ), + ), + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Navigator.of(context).pop(); + showLog(); + }, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 15, + ), + alignment: Alignment.center, + child: const Material( + color: Colors.transparent, + child: Text( + "查看日志", + style: TextStyle( + fontSize: 16, + ), + ), + ), + ), + ), + + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: QlAppBar( + title: "订阅管理", + canBack: true, + actions: [ + InkWell( + onTap: () { + showCupertinoModalPopup( + context: context, + builder: (context) { + return CupertinoActionSheet( + title: Container( + alignment: Alignment.center, + child: const Material( + color: Colors.transparent, + child: Text( + "更多操作", + style: TextStyle( + fontSize: 16, + ), + ), + ), + ), + actions: actions, + cancelButton: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Navigator.pop(context); + }, + child: Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric( + vertical: 10, + ), + child: const Material( + color: Colors.transparent, + child: Text( + "取消", + style: TextStyle( + color: Colors.red, + fontSize: 16, + ), + ), + ), + ), + ), + ); + }); + }, + child: const Padding( + padding: EdgeInsets.symmetric( + horizontal: 15, + ), + child: Center( + child: Icon( + Icons.more_horiz, + color: Colors.white, + size: 26, + ), + ), + ), + ) + ], + ), + body: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TaskDetailCell( + title: "名称", + desc: widget.bean.name, + ), + TaskDetailCell( + title: "仓库类型", + desc: widget.bean.type == "public-repo" + ? "公开仓库" + : widget.bean.type == "private-repo" + ? "私有仓库" + : "单文件", + ), + TaskDetailCell( + title: "链接", + desc: widget.bean.url ?? "", + ), + TaskDetailCell( + title: "分支", + desc: widget.bean.branch ?? "", + ), + TaskDetailCell( + title: "定时类型", + desc: widget.bean.scheduleType ?? "", + ), + TaskDetailCell( + title: "定时规则", + desc: widget.bean.scheduleType == "crontab" + ? widget.bean.schedule ?? "" + : widget.bean.intervalSchedule!.type == "days" + ? "每" + widget.bean.intervalSchedule!.value.toString() + "天" + : widget.bean.intervalSchedule!.type == "hours" + ? "每" + + widget.bean.intervalSchedule!.value.toString() + + "小时" + : widget.bean.intervalSchedule!.type == "minutes" + ? "每" + + widget.bean.intervalSchedule!.value.toString() + + "分钟" + : "每" + + widget.bean.intervalSchedule!.value.toString() + + "秒", + ), + TaskDetailCell( + title: "白名单", + desc: widget.bean.whitelist ?? "", + ), + TaskDetailCell( + title: "黑名单", + desc: widget.bean.blacklist ?? "", + ), + TaskDetailCell( + title: "依赖文件", + desc: widget.bean.dependences ?? "", + ), + TaskDetailCell( + title: "文件后缀", + desc: widget.bean.extensions ?? "", + ), + TaskDetailCell( + title: "执行前", + desc: widget.bean.subAfter ?? "", + ), + TaskDetailCell( + title: "执行后", + desc: widget.bean.subBefore ?? "", + ), + ], + ), + ); + } + + startCron(BuildContext context) async { + await Api.startSubscription([widget.bean.id!]); + setState(() {}); + showLog(); + } + + stopCron(BuildContext context) async { + await Api.stopSubscription([widget.bean.id!]); + setState(() {}); + } + + void showLog() { + showCupertinoModalBottomSheet( + expand: true, + context: context, + backgroundColor: Colors.transparent, + builder: (context) => + SubscriptionTimeLogPage( + widget.bean.id!, + true, + widget.bean.name ?? "", + ), + ); + } +} + +class TaskDetailCell extends ConsumerWidget { + final String title; + final String? desc; + final Widget? icon; + final bool hideDivide; + final Function? taped; + + const TaskDetailCell({ + Key? key, + required this.title, + this.desc, + this.icon, + this.hideDivide = false, + this.taped, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + left: 15, + right: 10, + bottom: 10, + ), + child: Row( + children: [ + Text( + title, + style: TextStyle( + color: ref.watch(themeProvider).themeColor.titleColor(), + fontSize: 16, + ), + ), + const SizedBox( + width: 30, + ), + desc != null + ? Expanded( + child: Align( + alignment: Alignment.centerRight, + child: SelectableText( + desc!, + textAlign: TextAlign.right, + selectionHeightStyle: BoxHeightStyle.max, + selectionWidthStyle: BoxWidthStyle.max, + onTap: () { + if (taped != null) { + taped!(); + } + }, + style: TextStyle( + color: + ref.watch(themeProvider).themeColor.descColor(), + fontSize: 14, + ), + ), + ), + ) + : Expanded( + child: + Align(alignment: Alignment.centerRight, child: icon!), + ), + ], + ), + ), + hideDivide + ? const SizedBox.shrink() + : const Divider( + indent: 15, + ), + ], + ); + } + + +} diff --git a/lib/module/others/subscription/subscription_page.dart b/lib/module/others/subscription/subscription_page.dart new file mode 100644 index 0000000..d7b5926 --- /dev/null +++ b/lib/module/others/subscription/subscription_page.dart @@ -0,0 +1,351 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:highlight/languages/routeros.dart'; +import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; +import 'package:qinglong_app/base/http/http.dart'; +import 'package:qinglong_app/base/ql_app_bar.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_bean.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_time_log_page.dart'; +import 'package:qinglong_app/utils/extension.dart'; + +import '../../../base/http/api.dart'; +import '../../../base/routes.dart'; +import '../../../base/theme.dart'; +import '../../../base/ui/empty_widget.dart'; +import '../../../base/ui/lazy_load_state.dart'; + +class SubscriptionPage extends ConsumerStatefulWidget { + const SubscriptionPage({Key? key}) : super(key: key); + + @override + _ScriptPageState createState() => _ScriptPageState(); +} + +class _ScriptPageState extends ConsumerState + with LazyLoadState { + List list = []; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: QlAppBar( + title: "订阅管理", + canBack: true, + actions: [ + InkWell( + onTap: () { + Navigator.of(context).pushNamed( + Routes.routerSubscriptionAdd, + ); + }, + child: const Padding( + padding: EdgeInsets.symmetric( + horizontal: 15, + ), + child: Center( + child: Icon( + CupertinoIcons.add, + size: 24, + color: Colors.white, + ), + ), + ), + ), + ], + ), + body: RefreshIndicator( + color: Theme.of(context).primaryColor, + onRefresh: () async { + return loadData(); + }, + child: list.isEmpty + ? const EmptyWidget() + : ListView.builder( + keyboardDismissBehavior: + ScrollViewKeyboardDismissBehavior.onDrag, + itemBuilder: (context, index) { + Subscription item = list[index]; + return SubscriptionCell(list[index], index, ref); + }, + itemCount: list.length, + ), + )); + } + + Future loadData() async { + HttpResponse> response = await Api.getSubscription(); + + if (response.success) { + if (response.bean == null || response.bean!.isEmpty) { + "暂无数据".toast(); + } + list.clear(); + list.addAll(response.bean ?? []); + setState(() {}); + } else { + response.message?.toast(); + } + } + + @override + void onLazyLoad() { + loadData(); + } +} + +class SubscriptionCell extends StatelessWidget { + final Subscription bean; + final int index; + final WidgetRef ref; + + const SubscriptionCell(this.bean, this.index, this.ref, {Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ColoredBox( + color: ref.watch(themeProvider).themeColor.settingBgColor(), + child: Slidable( + key: ValueKey(bean.id), + endActionPane: ActionPane( + motion: const StretchMotion(), + extentRatio: 0.7, + children: [ + SlidableAction( + backgroundColor: const Color(0xff5D5E70), + onPressed: (_) { + WidgetsBinding.instance.endOfFrame.then((timeStamp) { + Navigator.of(context).pushNamed( + Routes.routerSubscriptionAdd, + arguments: bean); + }); + }, + foregroundColor: Colors.white, + icon: CupertinoIcons.pencil_outline, + ), + SlidableAction( + backgroundColor: const Color(0xffA356D6), + onPressed: (_) { + changeSubscriptionEnable(); + }, + foregroundColor: Colors.white, + icon: bean.isDisabled == null || bean.isDisabled == 0 + ? Icons.dnd_forwardslash + : Icons.check_circle_outline_sharp, + ), + SlidableAction( + backgroundColor: const Color(0xffEA4D3E), + onPressed: (_) { + Api.deleteSubscription([bean.id!]); + }, + foregroundColor: Colors.white, + icon: CupertinoIcons.delete, + ), + ], + ), + startActionPane: ActionPane( + motion: const StretchMotion(), + extentRatio: 0.4, + children: [ + SlidableAction( + backgroundColor: const Color(0xffD25535), + onPressed: (_) { + changeSubscriptionStatus(); + }, + foregroundColor: Colors.white, + icon: bean.status! == 1 + ? CupertinoIcons.memories + : CupertinoIcons.stop_circle, + ), + SlidableAction( + backgroundColor: const Color(0xff606467), + onPressed: (_) { + Future.delayed( + const Duration( + milliseconds: 250, + ), () { + showLog(context); + }); + }, + foregroundColor: Colors.white, + icon: CupertinoIcons.text_justifyleft, + ), + ], + ), + child: InkWell( + onTap: (){ + Navigator.of(context).pushNamed( + Routes.routerSubscriptionDetail, + arguments: bean); + }, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: MediaQuery.of(context).size.width, + color: Colors.transparent, + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 8, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + bean.status == 1 + ? const SizedBox.shrink() + : SizedBox( + width: 15, + height: 15, + child: CircularProgressIndicator( + strokeWidth: 2, + color: ref + .watch(themeProvider) + .primaryColor, + ), + ), + SizedBox( + width: bean.status == 1 ? 0 : 5, + ), + Expanded( + child: Material( + color: Colors.transparent, + child: Text( + bean.name ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + overflow: TextOverflow.ellipsis, + color: ref + .watch(themeProvider) + .themeColor + .titleColor(), + fontSize: 18, + ), + ), + ), + ), + ], + ), + ), + Material( + color: Colors.transparent, + child: Text( + bean.branch!, + maxLines: 1, + style: TextStyle( + overflow: TextOverflow.ellipsis, + color: ref + .watch(themeProvider) + .themeColor + .descColor(), + fontSize: 12, + ), + ), + ), + ], + ), + const SizedBox( + height: 8, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + bean.isDisabled == 1 + ? const Icon( + Icons.dnd_forwardslash, + size: 12, + color: Colors.red, + ) + : const SizedBox.shrink(), + const SizedBox( + width: 5, + ), + Material( + color: Colors.transparent, + child: Text( + bean.schedule ?? "", + maxLines: 1, + style: TextStyle( + overflow: TextOverflow.ellipsis, + color: ref + .watch(themeProvider) + .themeColor + .descColor(), + fontSize: 12, + ), + ), + ), + ], + ), + const SizedBox( + height: 8, + ), + Material( + color: Colors.transparent, + child: Text( + bean.url ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + overflow: TextOverflow.ellipsis, + color: + ref.watch(themeProvider).themeColor.descColor(), + fontSize: 12, + ), + ), + ), + ], + ), + ), + const Divider( + height: 1, + indent: 15, + ), + ], + ), + )), + ); + } + + void showLog(BuildContext context) { + showCupertinoModalBottomSheet( + expand: true, + context: context, + backgroundColor: Colors.transparent, + builder: (context) => SubscriptionTimeLogPage( + bean.id!, + true, + bean.name ?? "", + ), + ); + } + + Future changeSubscriptionStatus() async { + if (bean.status == null || bean.status == 0) { + await Api.stopSubscription([bean.id!]); + } else { + await Api.startSubscription([bean.id!]); + } + } + + Future changeSubscriptionEnable() async { + if (bean.isDisabled == null || bean.isDisabled == 0) { + await Api.disableSubscription([bean.id!]); + } else { + await Api.enableSubscription([bean.id!]); + } + } +} diff --git a/lib/module/others/subscription/subscription_time_log_page.dart b/lib/module/others/subscription/subscription_time_log_page.dart new file mode 100644 index 0000000..fc47d1e --- /dev/null +++ b/lib/module/others/subscription/subscription_time_log_page.dart @@ -0,0 +1,181 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:qinglong_app/base/http/api.dart'; +import 'package:qinglong_app/base/http/http.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_page.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_page.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_page.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_page.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_page.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_page.dart'; +import 'package:qinglong_app/module/others/subscription/subscription_page.dart'; +import 'package:share_plus/share_plus.dart'; + +import '../../../base/ui/lazy_load_state.dart'; + +class SubscriptionTimeLogPage extends StatefulWidget { + final int subId; + final bool needTimer; + final String title; + + const SubscriptionTimeLogPage(this.subId, this.needTimer, this.title, {Key? key}) + : super(key: key); + + @override + _SubscriptionTimeLogPageState createState() => _SubscriptionTimeLogPageState(); +} + +class _SubscriptionTimeLogPageState extends State + with LazyLoadState { + Timer? _timer; + + String? content; + + @override + void initState() { + super.initState(); + } + + bool isRequest = false; + bool canRequest = true; + + getLogData() async { + if (!canRequest) return; + if (isRequest) return; + isRequest = true; + HttpResponse response = await Api.subTimeLog(widget.subId); + if (response.success) { + content = response.bean; + setState(() {}); + } + isRequest = false; + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Material( + child: SafeArea( + child: Container( + color: Colors.white, + padding: const EdgeInsets.symmetric( + vertical: 10, + ), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Row( + children: [ + const SizedBox( + width: 15, + ), + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: const Icon( + CupertinoIcons.chevron_down, + size: 18, + ), + ), + Expanded( + child: Center( + child: Text( + widget.title, + style: const TextStyle( + fontSize: 16, + ), + ), + ), + ), + InkWell( + onTap: () { + Share.share(content ?? ""); + }, + child: const Icon( + CupertinoIcons.share, + size: 16, + ), + ), + const SizedBox( + width: 15, + ), + ], + ), + const SizedBox( + height: 15, + ), + if (content == null) + const Expanded( + child: Center( + child: CupertinoActivityIndicator(), + ), + ) + else + Expanded( + child: SingleChildScrollView( + primary: true, + padding: const EdgeInsets.symmetric(horizontal: 15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText( + content!, + scrollPhysics: const NeverScrollableScrollPhysics(), + onSelectionChanged: (TextSelection selection, + SelectionChangedCause? cause) { + final int newStart = min( + selection.baseOffset, selection.extentOffset); + final int newEnd = max( + selection.baseOffset, selection.extentOffset); + if (newEnd == newStart) { + canRequest = true; + } else { + canRequest = false; + } + }, + selectionHeightStyle: BoxHeightStyle.max, + selectionWidthStyle: BoxWidthStyle.max, + style: const TextStyle( + fontSize: 12, + ), + ), + const SizedBox( + height: 400, + ), + ], + ), + ), + ) + ], + ), + ), + ), + ); + } + + @override + void onLazyLoad() { + if (widget.needTimer) { + _timer = Timer.periodic( + const Duration(seconds: 2), + (timer) { + getLogData(); + }, + ); + } else { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + getLogData(); + }); + } + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index e2a3af9..168bf1e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,154 +5,154 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "22.0.0" analyzer: dependency: transitive description: name: analyzer - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.7.2" archive: dependency: transitive description: name: archive - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.3.0" args: dependency: transitive description: name: args - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.3.1" async: dependency: transitive description: name: async - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.8.2" back_button_interceptor: dependency: "direct main" description: name: back_button_interceptor - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "6.0.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.0" build: dependency: transitive description: name: build - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.3.0" build_config: dependency: transitive description: name: build_config - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.0" build_daemon: dependency: transitive description: name: build_daemon - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.1.0" build_resolvers: dependency: transitive description: name: build_resolvers - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.4" build_runner: dependency: "direct dev" description: name: build_runner - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.11" build_runner_core: dependency: transitive description: name: build_runner_core - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "7.2.3" built_collection: dependency: transitive description: name: built_collection - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "5.1.1" built_value: dependency: transitive description: name: built_value - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "8.3.2" + version: "8.3.3" change_app_package_name: dependency: "direct dev" description: name: change_app_package_name - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.0" characters: dependency: transitive description: name: characters - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.2.0" charcode: dependency: transitive description: name: charcode - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.3.1" checked_yaml: dependency: transitive description: name: checked_yaml - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.1" cli_util: dependency: transitive description: name: cli_util - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.3.5" clock: dependency: transitive description: name: clock - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.1.0" code_builder: dependency: transitive description: name: code_builder - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "4.1.0" code_text_field: @@ -166,98 +166,98 @@ packages: dependency: transitive description: name: collection - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.16.0" convert: dependency: transitive description: name: convert - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" crypto: dependency: transitive description: name: crypto - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.0.2" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.5" dart_style: dependency: transitive description: name: dart_style - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.1" dio: dependency: "direct main" description: name: dio - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "4.0.6" dio_log: dependency: "direct main" description: name: dio_log - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.3" drag_and_drop_lists: dependency: "direct main" description: name: drag_and_drop_lists - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.3.2+2" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.3.0" ffi: dependency: transitive description: name: ffi - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.2.1" file: dependency: transitive description: name: file - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "6.1.2" file_picker: dependency: "direct main" description: name: file_picker - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "4.6.1" fixnum: dependency: transitive description: name: fixnum - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.1" flip_card: dependency: "direct main" description: name: flip_card - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.6.0" flutter: @@ -269,42 +269,42 @@ packages: dependency: "direct main" description: name: flutter_animator - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.2.2" flutter_app_name: dependency: "direct dev" description: name: flutter_app_name - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.1.1" flutter_colorpicker: dependency: "direct main" description: name: flutter_colorpicker - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.3" flutter_highlight: dependency: transitive description: name: flutter_highlight - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.7.0" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "0.9.2" + version: "0.9.3" flutter_lints: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.4" flutter_localizations: @@ -316,35 +316,35 @@ packages: dependency: "direct dev" description: name: flutter_native_splash - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "2.1.6" + version: "2.2.0+1" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.6" flutter_riverpod: dependency: "direct main" description: name: flutter_riverpod - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.4" flutter_slidable: dependency: "direct main" description: name: flutter_slidable - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.3.0" flutter_speed_dial: dependency: transitive description: name: flutter_speed_dial - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.0.5" flutter_test: @@ -361,478 +361,478 @@ packages: dependency: "direct main" description: name: fluttertoast - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "8.0.9" frontend_server_client: dependency: transitive description: name: frontend_server_client - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.3" get_it: dependency: "direct main" description: name: get_it - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "7.2.0" glob: dependency: transitive description: name: glob - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.1.0" google_fonts: dependency: transitive description: name: google_fonts - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.3.3" graphs: dependency: transitive description: name: graphs - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.0" highlight: dependency: "direct main" description: name: highlight - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.7.0" http: dependency: transitive description: name: http - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.13.4" http_multi_server: dependency: transitive description: name: http_multi_server - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "3.2.0" + version: "3.2.1" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "4.0.1" image: dependency: transitive description: name: image - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.1.3" intl: dependency: "direct main" description: name: intl - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.17.0" io: dependency: transitive description: name: io - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.3" js: dependency: transitive description: name: js - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.6.4" json_annotation: dependency: transitive description: name: json_annotation - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "4.5.0" json_conversion: dependency: "direct dev" description: name: json_conversion - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.0.4" json_conversion_annotation: dependency: "direct main" description: name: json_conversion_annotation - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.0.4" linked_scroll_controller: dependency: transitive description: name: linked_scroll_controller - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.2.0" lint: dependency: transitive description: name: lint - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.8.2" lints: dependency: transitive description: name: lints - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.1" logger: dependency: "direct main" description: name: logger - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.1.0" logging: dependency: transitive description: name: logging - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.2" matcher: dependency: transitive description: name: matcher - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.12.11" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.1.4" meta: dependency: transitive description: name: meta - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.7.0" mime: dependency: transitive description: name: mime - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.2" modal_bottom_sheet: dependency: "direct main" description: name: modal_bottom_sheet - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.1" move_to_background: dependency: "direct main" description: name: move_to_background - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.2" package_config: dependency: transitive description: name: package_config - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.1.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.4.2" package_info_plus_linux: dependency: transitive description: name: package_info_plus_linux - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.5" package_info_plus_macos: dependency: transitive description: name: package_info_plus_macos - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.2" package_info_plus_web: dependency: transitive description: name: package_info_plus_web - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.5" package_info_plus_windows: dependency: transitive description: name: package_info_plus_windows - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.5" path: dependency: transitive description: name: path - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.8.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "2.0.10" + version: "2.0.11" path_provider_android: dependency: transitive description: name: path_provider_android - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "2.0.14" + version: "2.0.15" path_provider_ios: dependency: transitive description: name: path_provider_ios - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "2.0.9" + version: "2.0.10" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "2.1.6" + version: "2.1.7" path_provider_macos: dependency: transitive description: name: path_provider_macos - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.6" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.4" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.7" pedantic: dependency: transitive description: name: pedantic - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.11.1" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "4.4.0" + version: "5.0.0" platform: dependency: transitive description: name: platform - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.2" pool: dependency: transitive description: name: pool - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "1.5.0" + version: "1.5.1" process: dependency: transitive description: name: process - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "4.2.4" pub_semver: dependency: transitive description: name: pub_semver - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.1" pubspec_parse: dependency: transitive description: name: pubspec_parse - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.2.0" riverpod: dependency: transitive description: name: riverpod - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.3" share_plus: dependency: "direct main" description: name: share_plus - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "4.0.7" + version: "4.0.8" share_plus_linux: dependency: transitive description: name: share_plus_linux - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.0.0" share_plus_macos: dependency: transitive description: name: share_plus_macos - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.0.1" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.0.3" share_plus_web: dependency: transitive description: name: share_plus_web - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.0.1" share_plus_windows: dependency: transitive description: name: share_plus_windows - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.0.1" shared_preferences: dependency: "direct main" description: name: shared_preferences - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.15" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.12" shared_preferences_ios: dependency: transitive description: name: shared_preferences_ios - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.1" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.1" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.4" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.4" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.1" shelf: dependency: transitive description: name: shelf - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" sky_engine: dependency: transitive description: flutter @@ -842,205 +842,205 @@ packages: dependency: transitive description: name: source_gen - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.3" source_span: dependency: transitive description: name: source_span - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.8.2" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.10.0" state_notifier: dependency: transitive description: name: state_notifier - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.7.2+1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.0" stream_transform: dependency: transitive description: name: stream_transform - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.1.0" synchronized: dependency: "direct main" description: name: synchronized - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.0.0+2" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.2.0" test_api: dependency: transitive description: name: test_api - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.4.9" timing: dependency: transitive description: name: timing - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.0" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" universal_io: dependency: transitive description: name: universal_io - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.4" url_launcher: dependency: "direct main" description: name: url_launcher - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "6.1.3" url_launcher_android: dependency: transitive description: name: url_launcher_android - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "6.0.17" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "6.0.17" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.0.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.0.1" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.1.0" url_launcher_web: dependency: transitive description: name: url_launcher_web - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "2.0.11" + version: "2.0.12" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.0.1" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.2" watcher: dependency: transitive description: name: watcher - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.2.0" widget_with_codeview: dependency: "direct main" description: name: widget_with_codeview - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.0.1+2" win32: dependency: transitive description: name: win32 - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "2.5.2" + version: "2.6.1" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.2.0+1" xml: dependency: transitive description: name: xml - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted - version: "5.3.1" + version: "5.4.1" yaml: dependency: transitive description: name: yaml - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "3.1.1" sdks: - dart: ">=2.17.0-0 <3.0.0" + dart: ">=2.17.0 <3.0.0" flutter: ">=3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0f31f47..c3087ca 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,7 @@ dependencies: file_picker: ^4.6.1 code_text_field: path: ./pub/code_field-master + path_provider: ^2.0.11 # flutter pub run build_runner build --delete-conflicting-outputs # flutter pub run change_app_package_name:main work.master.qinglongapp