diff --git a/lib/base/http/api.dart b/lib/base/http/api.dart index 27820bd..ba38761 100644 --- a/lib/base/http/api.dart +++ b/lib/base/http/api.dart @@ -1,6 +1,7 @@ import 'dart:ffi'; import 'package:qinglong_app/base/http/http.dart'; +import 'package:qinglong_app/module/config/config_bean.dart'; import 'package:qinglong_app/module/login/login_bean.dart'; import 'package:qinglong_app/module/task/task_bean.dart'; import 'package:qinglong_app/module/task/task_detail/task_detail_bean.dart'; @@ -67,4 +68,15 @@ class Api { static Future> disableTask(String cron) async { return await Http.put(Url.DISABLE_TASK, [cron]); } + + static Future>> files() async { + return await Http.get>(Url.FILES, null); + } + + static Future> content(String name) async { + return await Http.get(Url.CONFIG_CONTENT+name, null); + } + static Future> saveFile(String name,String content) async { + return await Http.post(Url.SAVE_FILE, {"content":content,"name": name}); + } } diff --git a/lib/base/http/url.dart b/lib/base/http/url.dart index 67d8633..83e3db0 100644 --- a/lib/base/http/url.dart +++ b/lib/base/http/url.dart @@ -9,6 +9,9 @@ class Url { static const UNPIN_TASK = "/api/crons/unpin"; static const ENABLE_TASK = "/api/crons/enable"; static const DISABLE_TASK = "/api/crons/disable"; + static const FILES = "/api/configs/files"; + static const CONFIG_CONTENT = "/api/configs/"; + static const SAVE_FILE = "/api/configs/save"; static INTIME_LOG(String cronId) { return "/api/crons/$cronId/log"; diff --git a/lib/base/routes.dart b/lib/base/routes.dart index 60f8c3e..8bac20c 100644 --- a/lib/base/routes.dart +++ b/lib/base/routes.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:qinglong_app/module/config/config_edit_page.dart'; import 'package:qinglong_app/module/home/home_page.dart'; import 'package:qinglong_app/module/task/add_task_page.dart'; import 'package:qinglong_app/module/task/task_bean.dart'; @@ -7,6 +8,7 @@ import 'package:qinglong_app/module/task/task_bean.dart'; class Routes { static const String route_HomePage = "/home/homepage"; static const String route_AddTask = "/task/add"; + static const String route_ConfigEdit = "/config/edit"; static Route? generateRoute(RouteSettings settings) { switch (settings.name) { @@ -19,8 +21,15 @@ class Routes { taskBean: settings.arguments as TaskBean, )); } else { - return CupertinoPageRoute(builder: (context) => AddTaskPage()); + return CupertinoPageRoute(builder: (context) => const AddTaskPage()); } + case route_ConfigEdit: + return CupertinoPageRoute( + builder: (context) => ConfigEditPage( + (settings.arguments as Map)["title"], + (settings.arguments as Map)["content"], + ), + ); } return null; diff --git a/lib/json.jc.dart b/lib/json.jc.dart index d35bd35..b35a6a3 100644 --- a/lib/json.jc.dart +++ b/lib/json.jc.dart @@ -1,5 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND +import 'package:qinglong_app/module/config/config_bean.dart'; import 'package:qinglong_app/module/login/login_bean.dart'; import 'package:qinglong_app/module/task/task_bean.dart'; import 'package:qinglong_app/module/task/task_detail/task_detail_bean.dart'; @@ -19,6 +20,10 @@ class JsonConversion$Json { String type = M.toString(); + if(type == (ConfigBean).toString()){ + return ConfigBean.jsonConversion(json) as M; + } + if(type == (LoginBean).toString()){ return LoginBean.jsonConversion(json) as M; } @@ -35,7 +40,11 @@ class JsonConversion$Json { } static M _getListChildType(List data) { - if([] is M){ + if([] is M){ + return data.map((e) => ConfigBean.jsonConversion(e)).toList() as M; + } + + if([] is M){ return data.map((e) => LoginBean.jsonConversion(e)).toList() as M; } diff --git a/lib/module/config/config_bean.dart b/lib/module/config/config_bean.dart new file mode 100644 index 0000000..6d2d905 --- /dev/null +++ b/lib/module/config/config_bean.dart @@ -0,0 +1,25 @@ +import 'package:json_conversion_annotation/json_conversion_annotation.dart'; + +@JsonConversion() +class ConfigBean { + + String? title; + String? value; + + ConfigBean({this.title, this.value}); + + ConfigBean.fromJson(Map json) { + title = json['title']; + value = json['value']; + } + + Map toJson() { + final Map data = new Map(); + data['title'] = this.title; + data['value'] = this.value; + return data; + } + static ConfigBean jsonConversion(Map json) { + return ConfigBean.fromJson(json); + } +} diff --git a/lib/module/config/config_edit_page.dart b/lib/module/config/config_edit_page.dart new file mode 100644 index 0000000..db225cf --- /dev/null +++ b/lib/module/config/config_edit_page.dart @@ -0,0 +1,83 @@ +import 'package:code_editor/code_editor.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:qinglong_app/base/common_dialog.dart'; +import 'package:qinglong_app/base/http/api.dart'; +import 'package:qinglong_app/base/http/http.dart'; +import 'package:qinglong_app/base/ql_app_bar.dart'; +import 'package:qinglong_app/module/config/config_viewmodel.dart'; + +class ConfigEditPage extends ConsumerStatefulWidget { + final String content; + final String title; + + const ConfigEditPage(this.title, this.content, {Key? key}) : super(key: key); + + @override + _ConfigEditPageState createState() => _ConfigEditPageState(); +} + +class _ConfigEditPageState extends ConsumerState { + String? value; + + @override + Widget build(BuildContext context) { + List files = [ + FileEditor( + name: widget.title, + language: "sh", + code: widget.content, // [code] needs a string + ), + ]; + EditorModel editMode = EditorModel( + files: files, + styleOptions: EditorModelStyleOptions( + fontSize: 13, + ), + ); + return Scaffold( + appBar: QlAppBar( + canBack: true, + backCall: () { + Navigator.of(context).pop(); + }, + title: '编辑文件', + actions: [ + InkWell( + onTap: () async { + if (value == null) { + failDialog(context, "请先点击保存"); + return; + } + HttpResponse response = await Api.saveFile(widget.title, value!); + if (response.success) { + ref.read(configProvider).loadContent(widget.title); + Navigator.of(context).pop(); + } else { + failDialog(context, response.message ?? ""); + } + }, + child: const Padding( + padding: EdgeInsets.symmetric( + horizontal: 15, + ), + child: Center( + child: Text("提交"), + ), + ), + ) + ], + ), + body: Container( + child: CodeEditor( + model: editMode, + edit: true, + onSubmit: (title, v) { + value = v; + }, + disableNavigationbar: false, + ), + ), + ); + } +} diff --git a/lib/module/config/config_page.dart b/lib/module/config/config_page.dart index 756ff7a..751747e 100644 --- a/lib/module/config/config_page.dart +++ b/lib/module/config/config_page.dart @@ -1,4 +1,11 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:qinglong_app/base/base_state_widget.dart'; +import 'package:code_editor/code_editor.dart'; +import 'package:qinglong_app/base/routes.dart'; +import 'package:qinglong_app/base/theme.dart'; + +import 'config_viewmodel.dart'; class ConfigPage extends StatefulWidget { const ConfigPage({Key? key}) : super(key: key); @@ -8,8 +15,72 @@ class ConfigPage extends StatefulWidget { } class _ConfigPageState extends State { + final myController = TextEditingController(); + @override Widget build(BuildContext context) { - return Scaffold(); + return BaseStateWidget( + builder: (ref, model, child) { + List files = [ + FileEditor( + name: model.title, + language: "sh", + code: model.content, // [code] needs a string + ), + ]; + EditorModel editMode = EditorModel( + files: files, + styleOptions: EditorModelStyleOptions( + fontSize: 13, + heightOfContainer: MediaQuery.of(context).size.height - kToolbarHeight - kBottomNavigationBarHeight - 150, + ), + ); + myController.text = model.content; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + DropdownButton( + value: model.title, + items: model.list + .map( + (e) => DropdownMenuItem( + value: e.value ?? "", + child: Text(e.value ?? ""), + ), + ) + .toList(), + onChanged: (value) { + model.loadContent(value!); + }, + ), + const Spacer(), + CupertinoButton( + child: const Text("编辑"), + onPressed: () { + Navigator.of(context).pushNamed(Routes.route_ConfigEdit, arguments: { + "title": model.title, + "content": model.content, + }); + }), + ], + ), + Expanded( + child: CodeEditor( + model: editMode, + edit: false, + textEditingController: myController, + disableNavigationbar: false, + ), + ), + ], + ); + }, + model: configProvider, + onReady: (viewModel) { + viewModel.loadData(); + }, + ); } } diff --git a/lib/module/config/config_viewmodel.dart b/lib/module/config/config_viewmodel.dart new file mode 100644 index 0000000..60b7bfe --- /dev/null +++ b/lib/module/config/config_viewmodel.dart @@ -0,0 +1,46 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:qinglong_app/base/base_viewmodel.dart'; +import 'package:qinglong_app/base/http/api.dart'; +import 'package:qinglong_app/base/http/http.dart'; +import 'package:qinglong_app/module/config/config_bean.dart'; + +var configProvider = ChangeNotifierProvider((ref) => ConfigViewModel()); + +class ConfigViewModel extends BaseViewModel { + List list = []; + + String content = ""; + String title = ""; + + Future loadData([isLoading = true]) async { + if (isLoading) { + loading(notify: true); + } + + HttpResponse> result = await Api.files(); + + if (result.success && result.bean != null) { + list.clear(); + list.addAll(result.bean!); + title = list[0].value!; + success(); + loadContent(list[0].value!); + } else { + list.clear(); + failed(result.message, notify: true); + } + } + + Future loadContent(String name) async { + title = name; + notifyListeners(); + HttpResponse result = await Api.content(name); + + if (result.success && result.bean != null) { + content = result.bean!; + success(); + } else { + failToast(result.message, notify: false); + } + } +} diff --git a/lib/module/task/task_page.dart b/lib/module/task/task_page.dart index f6ed325..9f6c02f 100644 --- a/lib/module/task/task_page.dart +++ b/lib/module/task/task_page.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -9,7 +7,6 @@ import 'package:qinglong_app/base/routes.dart'; import 'package:qinglong_app/base/theme.dart'; import 'package:qinglong_app/module/task/intime_log/intime_log_page.dart'; import 'package:qinglong_app/module/task/task_bean.dart'; -import 'package:qinglong_app/module/task/task_detail/task_detail_bean.dart'; import 'package:qinglong_app/module/task/task_viewmodel.dart'; import 'package:qinglong_app/utils/utils.dart'; @@ -21,8 +18,8 @@ class TaskPage extends StatefulWidget { } class _TaskPageState extends State { - String? _searchKey = null; - TextEditingController _searchController = TextEditingController(); + String? _searchKey; + final TextEditingController _searchController = TextEditingController(); @override void initState() { diff --git a/pubspec.lock b/pubspec.lock index 9e7be8e..b064602 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -141,6 +141,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.1.0" + code_editor: + dependency: "direct main" + description: + name: code_editor + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.1" collection: dependency: transitive description: @@ -223,6 +230,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_highlight: + dependency: transitive + description: + name: flutter_highlight + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.0" flutter_lints: dependency: "direct dev" description: @@ -254,6 +268,13 @@ packages: description: flutter source: sdk version: "0.0.0" + font_awesome_flutter: + dependency: transitive + description: + name: font_awesome_flutter + url: "https://pub.flutter-io.cn" + source: hosted + version: "9.2.0" frontend_server_client: dependency: transitive description: @@ -275,6 +296,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" + highlight: + dependency: transitive + description: + name: highlight + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.0" http_multi_server: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index aa954a4..7f472e6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: json_conversion_annotation: ^0.0.4 logger: ^1.1.0 intl: ^0.17.0 + code_editor: ^1.3.1 dev_dependencies: build_runner: ^2.0.0