diff --git a/lib/base/theme.dart b/lib/base/theme.dart index 54c96ff..4ba94ff 100644 --- a/lib/base/theme.dart +++ b/lib/base/theme.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:qinglong_app/utils/qinglong_theme.dart'; var themeProvider = ChangeNotifierProvider((ref) => ThemeViewModel()); const Color _primaryColor = Color(0xFF299343); @@ -48,7 +49,19 @@ ThemeData lightTheme = ThemeData.light().copyWith( progressIndicatorTheme: const ProgressIndicatorThemeData( color: _primaryColor, ), - + tabBarTheme: const TabBarTheme( + labelStyle: TextStyle( + fontSize: 16, + ), + unselectedLabelStyle: TextStyle( + fontSize: 16, + ), + labelColor: _primaryColor, + unselectedLabelColor: Color(0xff999999), + indicator: UnderlineTabIndicator( + borderSide: BorderSide(color: _primaryColor), + ), + ), ); abstract class ThemeColors { @@ -57,6 +70,8 @@ abstract class ThemeColors { Color searchBarBg(); Color pinColor(); + + Map codeEditorTheme(); } class LightartThemeColors extends ThemeColors { @@ -74,6 +89,11 @@ class LightartThemeColors extends ThemeColors { Color pinColor() { return const Color(0xffF7F7F7); } + + @override + Map codeEditorTheme() { + return qinglongLightTheme; + } } class DartThemeColors extends ThemeColors { @@ -91,4 +111,9 @@ class DartThemeColors extends ThemeColors { Color pinColor() { return Colors.black12; } + + @override + Map codeEditorTheme() { + return qinglongDarkTheme; + } } diff --git a/lib/base/ui/abs_underline_tabindicator.dart b/lib/base/ui/abs_underline_tabindicator.dart new file mode 100644 index 0000000..e3139ee --- /dev/null +++ b/lib/base/ui/abs_underline_tabindicator.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; + + +///tabbar样式下方横条固定宽度 +class AbsUnderlineTabIndicator extends Decoration { + const AbsUnderlineTabIndicator({ + this.wantWidth, + this.borderSide = const BorderSide(width: 2.0, color: Colors.white), + this.insets = EdgeInsets.zero, + }) : assert(borderSide != null), + assert(insets != null); + + final BorderSide? borderSide; + final double? wantWidth; + + final EdgeInsetsGeometry? insets; + + @override + Decoration? lerpFrom(Decoration? a, double t) { + if (a is UnderlineTabIndicator) { + return UnderlineTabIndicator( + borderSide: BorderSide.lerp(a.borderSide, borderSide!, t), + insets: EdgeInsetsGeometry.lerp(a.insets, insets, t)!, + ); + } + return super.lerpFrom(a, t); + } + + @override + Decoration? lerpTo(Decoration? b, double t) { + if (b is UnderlineTabIndicator) { + return UnderlineTabIndicator( + borderSide: BorderSide.lerp(borderSide!, b.borderSide, t), + insets: EdgeInsetsGeometry.lerp(insets, b.insets, t)!, + ); + } + return super.lerpTo(b, t); + } + + @override + _UnderlinePainter createBoxPainter([VoidCallback? onChanged]) { + return _UnderlinePainter(this, onChanged); + } +} + +class _UnderlinePainter extends BoxPainter { + _UnderlinePainter(this.decoration, VoidCallback? onChanged) : super(onChanged); + + final AbsUnderlineTabIndicator decoration; + + BorderSide? get borderSide => decoration.borderSide; + + EdgeInsetsGeometry? get insets => decoration.insets; + + Rect _indicatorRectFor(Rect rect, TextDirection textDirection) { + final Rect indicator = insets!.resolve(textDirection).deflateRect(rect); + //取中间坐标 + double cw = (indicator.left + indicator.right) / 2; + return Rect.fromLTWH(cw - decoration.wantWidth! / 2, indicator.bottom - borderSide!.width, decoration.wantWidth!, borderSide!.width); + } + + @override + void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { + assert(configuration.size != null); + final Rect rect = offset & configuration.size!; + final TextDirection textDirection = configuration.textDirection!; + final Rect indicator = _indicatorRectFor(rect, textDirection).deflate(borderSide!.width / 2.0); + final Paint paint = borderSide!.toPaint()..strokeCap = StrokeCap.round; + canvas.drawLine(indicator.bottomLeft, indicator.bottomRight, paint); + } +} \ No newline at end of file diff --git a/lib/module/config/config_edit_page.dart b/lib/module/config/config_edit_page.dart index db225cf..53ed1a1 100644 --- a/lib/module/config/config_edit_page.dart +++ b/lib/module/config/config_edit_page.dart @@ -6,6 +6,7 @@ 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'; +import 'package:qinglong_app/utils/qinglong_theme.dart'; class ConfigEditPage extends ConsumerStatefulWidget { final String content; @@ -33,7 +34,13 @@ class _ConfigEditPageState extends ConsumerState { files: files, styleOptions: EditorModelStyleOptions( fontSize: 13, + heightOfContainer: MediaQuery.of(context).size.height - kToolbarHeight - kBottomNavigationBarHeight - 150, + editorBorderColor: Theme.of(context).scaffoldBackgroundColor, + editButtonBackgroundColor: Theme.of(context).scaffoldBackgroundColor, + editorColor: Theme.of(context).scaffoldBackgroundColor, + theme: qinglongLightTheme, ), + ); return Scaffold( appBar: QlAppBar( diff --git a/lib/module/config/config_page.dart b/lib/module/config/config_page.dart index 4db4278..413b7a2 100644 --- a/lib/module/config/config_page.dart +++ b/lib/module/config/config_page.dart @@ -1,9 +1,8 @@ -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 'package:qinglong_app/utils/qinglong_theme.dart'; import 'config_viewmodel.dart'; @@ -15,76 +14,35 @@ class ConfigPage extends StatefulWidget { } class _ConfigPageState extends State { - final myController = TextEditingController(); - @override Widget build(BuildContext context) { return BaseStateWidget( builder: (ref, model, child) { - List files = [ - FileEditor( - name: model.title, - language: "sh", - code: model.content, // [code] needs a string - ), - ]; + List files = model.list + .map( + (e) => FileEditor( + name: e.title, + language: "sh", + code: model.content[e.title], // [code] needs a string + ), + ) + .toList(); EditorModel editMode = EditorModel( files: files, styleOptions: EditorModelStyleOptions( fontSize: 13, - heightOfContainer: MediaQuery.of(context).size.height - kToolbarHeight - kBottomNavigationBarHeight - 150, + heightOfContainer: MediaQuery.of(context).size.height - kToolbarHeight - kBottomNavigationBarHeight - 90, editorBorderColor: Theme.of(context).scaffoldBackgroundColor, editButtonBackgroundColor: Theme.of(context).scaffoldBackgroundColor, editorColor: Theme.of(context).scaffoldBackgroundColor, + editorFilenameColor: Theme.of(context).primaryColor, + theme: ref.watch(themeProvider).themeColor.codeEditorTheme(), ), ); - 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: Text( - "编辑", - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: 16, - ), - ), - 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, - ), - ), - ], + return CodeEditor( + model: editMode, + edit: false, + disableNavigationbar: false, ); }, model: configProvider, diff --git a/lib/module/config/config_viewmodel.dart b/lib/module/config/config_viewmodel.dart index 60b7bfe..27aa10f 100644 --- a/lib/module/config/config_viewmodel.dart +++ b/lib/module/config/config_viewmodel.dart @@ -9,8 +9,7 @@ var configProvider = ChangeNotifierProvider((ref) => ConfigViewModel()); class ConfigViewModel extends BaseViewModel { List list = []; - String content = ""; - String title = ""; + Map content = {}; Future loadData([isLoading = true]) async { if (isLoading) { @@ -22,9 +21,12 @@ class ConfigViewModel extends BaseViewModel { if (result.success && result.bean != null) { list.clear(); list.addAll(result.bean!); - title = list[0].value!; + + for (var element in list) { + await loadContent(element.value!); + } + success(); - loadContent(list[0].value!); } else { list.clear(); failed(result.message, notify: true); @@ -32,15 +34,10 @@ class ConfigViewModel extends BaseViewModel { } 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); + content[name] = result.bean!; } } } diff --git a/lib/module/task/task_page.dart b/lib/module/task/task_page.dart index 2efba9a..fa14d38 100644 --- a/lib/module/task/task_page.dart +++ b/lib/module/task/task_page.dart @@ -5,6 +5,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:qinglong_app/base/base_state_widget.dart'; import 'package:qinglong_app/base/routes.dart'; import 'package:qinglong_app/base/theme.dart'; +import 'package:qinglong_app/base/ui/abs_underline_tabindicator.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_viewmodel.dart'; @@ -30,27 +31,41 @@ class _TaskPageState extends State { Widget build(BuildContext context) { return BaseStateWidget( builder: (ref, model, child) { - return RefreshIndicator( - color: Theme.of(context).primaryColor, - onRefresh: () async { - return model.loadData(false); - }, - child: ListView.builder( - keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, - itemBuilder: (context, index) { - if (index == 0) { - return searchCell(ref); - } - - TaskBean item = model.list[index - 1]; - - if ((item.name == null || item.name!.contains(_searchKey ?? "")) || (item.command == null || item.command!.contains(_searchKey ?? ""))) { - return TaskItemCell(item, ref); - } else { - return const SizedBox.shrink(); - } - }, - itemCount: model.list.length + 1, + return DefaultTabController( + length: 3, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TabBar( + tabs: const [ + Tab( + text: "全部", + ), + Tab( + text: "正在运行", + ), + Tab( + text: "已禁用", + ), + ], + isScrollable: true, + indicator: AbsUnderlineTabIndicator( + wantWidth: 20, + borderSide: BorderSide( + color: Theme.of(context).primaryColor, + width: 2, + )), + ), + Expanded( + child: TabBarView( + children: [ + body(model, model.list, ref), + body(model, model.running, ref), + body(model, model.disabled, ref), + ], + ), + ), + ], ), ); }, @@ -61,6 +76,32 @@ class _TaskPageState extends State { ); } + Widget body(TaskViewModel model, List list, WidgetRef ref) { + return RefreshIndicator( + color: Theme.of(context).primaryColor, + onRefresh: () async { + return model.loadData(false); + }, + child: ListView.builder( + keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, + itemBuilder: (context, index) { + // if (index == 0) { + // return searchCell(ref); + // } + + TaskBean item = list[index ]; + + if ((item.name == null || item.name!.contains(_searchKey ?? "")) || (item.command == null || item.command!.contains(_searchKey ?? ""))) { + return TaskItemCell(item, ref); + } else { + return const SizedBox.shrink(); + } + }, + itemCount: list.length, + ), + ); + } + Widget searchCell(WidgetRef context) { return Container( color: context.watch(themeProvider).themeColor.searchBarBg(), @@ -112,6 +153,7 @@ class TaskItemCell extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Slidable( + enabled: false, key: const ValueKey(0), startActionPane: ActionPane( motion: const ScrollMotion(), @@ -220,7 +262,10 @@ class TaskItemCell extends StatelessWidget { ), ), ), - const Divider(height: 1,indent: 15,), + const Divider( + height: 1, + indent: 15, + ), ], ); } diff --git a/lib/module/task/task_viewmodel.dart b/lib/module/task/task_viewmodel.dart index 9630204..be66a50 100644 --- a/lib/module/task/task_viewmodel.dart +++ b/lib/module/task/task_viewmodel.dart @@ -8,6 +8,8 @@ var taskProvider = ChangeNotifierProvider((ref) => TaskViewModel()); class TaskViewModel extends BaseViewModel { List list = []; + List running = []; + List disabled = []; Future loadData([isLoading = true]) async { if (isLoading) { @@ -31,14 +33,10 @@ class TaskViewModel extends BaseViewModel { list.sort((a, b) { return b.created!.compareTo(a.created!); }); - list.sort((a, b) { - return a.isDisabled!.compareTo(b.isDisabled!); - }); - - list.sort((a, b) { - return a.status!.compareTo(b.status!); - }); - + running.clear(); + running.addAll(list.where((element) => element.status == 0)); + disabled.clear(); + disabled.addAll(list.where((element) => element.isDisabled == 1)); } Future runCrons(String cron) async { diff --git a/lib/utils/qinglong_theme.dart b/lib/utils/qinglong_theme.dart new file mode 100644 index 0000000..955341a --- /dev/null +++ b/lib/utils/qinglong_theme.dart @@ -0,0 +1,76 @@ + +import 'package:flutter/material.dart'; + +const qinglongLightTheme = { + 'root': + TextStyle(color: Color(0xff333333), backgroundColor: Color(0xffffffff)), + 'comment': TextStyle(color: Color(0xff999988), fontStyle: FontStyle.italic), + 'quote': TextStyle(color: Color(0xff999988), fontStyle: FontStyle.italic), + 'keyword': TextStyle(color: Color(0xff333333), fontWeight: FontWeight.bold), + 'selector-tag': + TextStyle(color: Color(0xff333333), fontWeight: FontWeight.bold), + 'subst': TextStyle(color: Color(0xff333333), fontWeight: FontWeight.normal), + 'number': TextStyle(color: Color(0xff008080)), + 'literal': TextStyle(color: Color(0xff008080)), + 'variable': TextStyle(color: Color(0xff008080)), + 'template-variable': TextStyle(color: Color(0xff008080)), + 'string': TextStyle(color: Color(0xffdd1144)), + 'doctag': TextStyle(color: Color(0xffdd1144)), + 'title': TextStyle(color: Color(0xff990000), fontWeight: FontWeight.bold), + 'section': TextStyle(color: Color(0xff990000), fontWeight: FontWeight.bold), + 'selector-id': + TextStyle(color: Color(0xff990000), fontWeight: FontWeight.bold), + 'type': TextStyle(color: Color(0xff445588), fontWeight: FontWeight.bold), + 'tag': TextStyle(color: Color(0xff000080), fontWeight: FontWeight.normal), + 'name': TextStyle(color: Color(0xff000080), fontWeight: FontWeight.normal), + 'attribute': + TextStyle(color: Color(0xff000080), fontWeight: FontWeight.normal), + 'regexp': TextStyle(color: Color(0xff009926)), + 'link': TextStyle(color: Color(0xff009926)), + 'symbol': TextStyle(color: Color(0xff990073)), + 'bullet': TextStyle(color: Color(0xff990073)), + 'built_in': TextStyle(color: Color(0xff0086b3)), + 'builtin-name': TextStyle(color: Color(0xff0086b3)), + 'meta': TextStyle(color: Color(0xff999999), fontWeight: FontWeight.bold), + 'deletion': TextStyle(backgroundColor: Color(0xffffdddd)), + 'addition': TextStyle(backgroundColor: Color(0xffddffdd)), + 'emphasis': TextStyle(fontStyle: FontStyle.italic), + 'strong': TextStyle(fontWeight: FontWeight.bold), +}; + +const qinglongDarkTheme = { + 'root': + TextStyle(color: Color(0xff333333), backgroundColor: Color(0xff2E312E)), + 'comment': TextStyle(color: Color(0xff999988), fontStyle: FontStyle.italic), + 'quote': TextStyle(color: Color(0xff999988), fontStyle: FontStyle.italic), + 'keyword': TextStyle(color: Color(0xff333333), fontWeight: FontWeight.bold), + 'selector-tag': + TextStyle(color: Color(0xff333333), fontWeight: FontWeight.bold), + 'subst': TextStyle(color: Color(0xff333333), fontWeight: FontWeight.normal), + 'number': TextStyle(color: Color(0xff008080)), + 'literal': TextStyle(color: Color(0xff008080)), + 'variable': TextStyle(color: Color(0xff008080)), + 'template-variable': TextStyle(color: Color(0xff008080)), + 'string': TextStyle(color: Color(0xffdd1144)), + 'doctag': TextStyle(color: Color(0xffdd1144)), + 'title': TextStyle(color: Color(0xff990000), fontWeight: FontWeight.bold), + 'section': TextStyle(color: Color(0xff990000), fontWeight: FontWeight.bold), + 'selector-id': + TextStyle(color: Color(0xff990000), fontWeight: FontWeight.bold), + 'type': TextStyle(color: Color(0xff445588), fontWeight: FontWeight.bold), + 'tag': TextStyle(color: Color(0xff000080), fontWeight: FontWeight.normal), + 'name': TextStyle(color: Color(0xff000080), fontWeight: FontWeight.normal), + 'attribute': + TextStyle(color: Color(0xff000080), fontWeight: FontWeight.normal), + 'regexp': TextStyle(color: Color(0xff009926)), + 'link': TextStyle(color: Color(0xff009926)), + 'symbol': TextStyle(color: Color(0xff990073)), + 'bullet': TextStyle(color: Color(0xff990073)), + 'built_in': TextStyle(color: Color(0xff0086b3)), + 'builtin-name': TextStyle(color: Color(0xff0086b3)), + 'meta': TextStyle(color: Color(0xff999999), fontWeight: FontWeight.bold), + 'deletion': TextStyle(backgroundColor: Color(0xffffdddd)), + 'addition': TextStyle(backgroundColor: Color(0xffddffdd)), + 'emphasis': TextStyle(fontStyle: FontStyle.italic), + 'strong': TextStyle(fontWeight: FontWeight.bold), +};