mirror of
https://github.com/qinglong-app/qinglong_app.git
synced 2025-10-09 16:48:19 +08:00
同步ios的功能
This commit is contained in:
@ -105,7 +105,8 @@ class Routes {
|
||||
case routeTaskLogDetail:
|
||||
return CupertinoPageRoute(
|
||||
builder: (context) => TaskLogDetailPage(
|
||||
title: settings.arguments as String,
|
||||
title: (settings.arguments as Map)['title'],
|
||||
path: (settings.arguments as Map)['path'],
|
||||
),
|
||||
);
|
||||
case routeScriptDetail:
|
||||
|
||||
@ -10,7 +10,7 @@ import 'package:qinglong_app/utils/sp_utils.dart';
|
||||
|
||||
var themeProvider = ChangeNotifierProvider((ref) => ThemeViewModel());
|
||||
|
||||
const commonColor = Color(0xFF299343);
|
||||
Color commonColor = const Color(0xFF299343);
|
||||
Color _primaryColor = commonColor;
|
||||
|
||||
class ThemeViewModel extends ChangeNotifier {
|
||||
@ -71,7 +71,10 @@ class ThemeViewModel extends ChangeNotifier {
|
||||
),
|
||||
scaffoldBackgroundColor: const Color(0xfff5f5f5),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
labelStyle: TextStyle(color: _primaryColor),
|
||||
labelStyle: TextStyle(
|
||||
color: _primaryColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: _primaryColor,
|
||||
@ -152,7 +155,10 @@ class ThemeViewModel extends ChangeNotifier {
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
labelStyle: TextStyle(color: _primaryColor),
|
||||
labelStyle: TextStyle(
|
||||
color: _primaryColor,
|
||||
fontSize: 14,
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: _primaryColor,
|
||||
@ -208,12 +214,15 @@ class ThemeViewModel extends ChangeNotifier {
|
||||
|
||||
abstract class ThemeColors {
|
||||
Color settingBgColor();
|
||||
|
||||
Color settingBordorColor();
|
||||
|
||||
Color titleColor();
|
||||
|
||||
Color descColor();
|
||||
|
||||
Color tabBarColor();
|
||||
|
||||
Color pinColor();
|
||||
|
||||
Color buttonBgColor();
|
||||
@ -256,6 +265,11 @@ class LightThemeColors extends ThemeColors {
|
||||
Color settingBordorColor() {
|
||||
return Colors.white;
|
||||
}
|
||||
|
||||
@override
|
||||
Color tabBarColor() {
|
||||
return const Color(0xffF7F7F7);
|
||||
}
|
||||
}
|
||||
|
||||
class DartThemeColors extends ThemeColors {
|
||||
@ -293,4 +307,9 @@ class DartThemeColors extends ThemeColors {
|
||||
Color settingBordorColor() {
|
||||
return Color(0xff333333);
|
||||
}
|
||||
|
||||
@override
|
||||
Color tabBarColor() {
|
||||
return Colors.black;
|
||||
}
|
||||
}
|
||||
|
||||
2
lib/module/env/add_env_page.dart
vendored
2
lib/module/env/add_env_page.dart
vendored
@ -187,7 +187,7 @@ class _AddEnvPageState extends ConsumerState<AddEnvPage> {
|
||||
envBean.remarks = _remarkController.text;
|
||||
HttpResponse<NullResponse> response = await Api.addEnv(
|
||||
_nameController.text, _valueController.text, _remarkController.text,
|
||||
id: envBean.sId);
|
||||
id: envBean.id,nId: envBean.nId,);
|
||||
|
||||
if (response.success) {
|
||||
(envBean.sId == null) ? "新增成功" : "修改成功".toast();
|
||||
|
||||
6
lib/module/env/env_bean.dart
vendored
6
lib/module/env/env_bean.dart
vendored
@ -4,6 +4,8 @@ import 'package:json_conversion_annotation/json_conversion_annotation.dart';
|
||||
class EnvBean {
|
||||
String? value;
|
||||
String? sId;
|
||||
String? _id;
|
||||
int? id;
|
||||
int? created;
|
||||
int? status;
|
||||
String? timestamp;
|
||||
@ -12,8 +14,12 @@ class EnvBean {
|
||||
|
||||
EnvBean({this.value, this.sId, this.created, this.status, this.timestamp, this.name, this.remarks});
|
||||
|
||||
get nId => _id;
|
||||
|
||||
EnvBean.fromJson(Map<String, dynamic> json) {
|
||||
value = json['value'];
|
||||
id = json['id'];
|
||||
_id = json['_id'];
|
||||
sId = json.containsKey('_id') ? json['_id'].toString() : (json.containsKey('id') ? json['id'].toString() : "");
|
||||
created = int.tryParse(json['created'].toString());
|
||||
status = json['status'];
|
||||
|
||||
@ -8,11 +8,9 @@ import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||
import 'package:qinglong_app/base/routes.dart';
|
||||
import 'package:qinglong_app/module/config/config_page.dart';
|
||||
import 'package:qinglong_app/module/env/env_page.dart';
|
||||
import 'package:qinglong_app/module/home/system_bean.dart';
|
||||
import 'package:qinglong_app/module/others/other_page.dart';
|
||||
import 'package:qinglong_app/module/task/task_page.dart';
|
||||
import 'package:move_to_background/move_to_background.dart';
|
||||
import 'package:qinglong_app/utils/update_utils.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import '../../utils/utils.dart';
|
||||
@ -47,7 +45,6 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
_title = titles[0].title;
|
||||
super.initState();
|
||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||
update();
|
||||
getSystemBean();
|
||||
});
|
||||
}
|
||||
@ -243,17 +240,6 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
);
|
||||
}
|
||||
|
||||
void update() async {
|
||||
String? result = await UpdateUtils().checkUpdate();
|
||||
if (result != null && result.isNotEmpty) {
|
||||
UpdateDialog updateDialog = UpdateDialog(context,
|
||||
title: "发现新版本", updateContent: "版本号:v$result", onUpdate: () {
|
||||
UpdateUtils.launchURL(result);
|
||||
});
|
||||
updateDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
void isNewYearDuration() {
|
||||
DateTime date = DateTime.now();
|
||||
DateTime dateYear1 = DateTime(2022, 1, 29);
|
||||
|
||||
@ -3,16 +3,13 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:qinglong_app/base/http/api.dart';
|
||||
import 'package:qinglong_app/base/http/http.dart';
|
||||
import 'package:qinglong_app/base/routes.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/module/login/user_bean.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
import 'package:qinglong_app/utils/login_helper.dart';
|
||||
import 'package:qinglong_app/utils/update_utils.dart';
|
||||
import 'package:qinglong_app/utils/utils.dart';
|
||||
import 'package:flip_card/flip_card.dart';
|
||||
|
||||
@ -66,7 +63,6 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
}
|
||||
getIt<UserInfoViewModel>().updateToken("");
|
||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||
update();
|
||||
if (useSecretLogin) {
|
||||
cardKey.currentState?.toggleCard();
|
||||
}
|
||||
@ -522,16 +518,6 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
});
|
||||
}
|
||||
|
||||
void update() async {
|
||||
String? result = await UpdateUtils().checkUpdate();
|
||||
if (result != null && result.isNotEmpty) {
|
||||
UpdateDialog updateDialog = UpdateDialog(context, title: "发现新版本", updateContent: "版本号:v${result}", onUpdate: () {
|
||||
UpdateUtils.launchURL(result);
|
||||
});
|
||||
updateDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildCell(UserInfoBean bean) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
|
||||
@ -3,7 +3,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:qinglong_app/base/theme.dart';
|
||||
import 'package:qinglong_app/utils/update_utils.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../main.dart';
|
||||
@ -25,19 +24,6 @@ class _AboutPageState extends ConsumerState<AboutPage> {
|
||||
super.initState();
|
||||
getInfo();
|
||||
|
||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
void update() async {
|
||||
String? result = await UpdateUtils().checkUpdate(true);
|
||||
if (result != null && result.isNotEmpty) {
|
||||
UpdateDialog updateDialog = UpdateDialog(context, title: "发现新版本", updateContent: "版本号:v${result}", onUpdate: () {
|
||||
UpdateUtils.launchURL(result);
|
||||
});
|
||||
updateDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -2,6 +2,11 @@ import 'package:code_text_field/code_text_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:highlight/languages/javascript.dart';
|
||||
import 'package:highlight/languages/json.dart';
|
||||
import 'package:highlight/languages/powershell.dart';
|
||||
import 'package:highlight/languages/python.dart';
|
||||
import 'package:highlight/languages/vbscript-html.dart';
|
||||
import 'package:highlight/languages/yaml.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';
|
||||
@ -41,11 +46,32 @@ class _ScriptEditPageState extends ConsumerState<ScriptEditPage> {
|
||||
});
|
||||
}
|
||||
|
||||
getLanguageType(String title) {
|
||||
if (title.endsWith(".js")) {
|
||||
return javascript;
|
||||
}
|
||||
|
||||
if (title.endsWith(".sh")) {
|
||||
return powershell;
|
||||
}
|
||||
|
||||
if (title.endsWith(".py")) {
|
||||
return python;
|
||||
}
|
||||
if (title.endsWith(".json")) {
|
||||
return json;
|
||||
}
|
||||
if (title.endsWith(".yaml")) {
|
||||
return yaml;
|
||||
}
|
||||
return vbscriptHtml;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_codeController ??= CodeController(
|
||||
text: widget.content,
|
||||
language: javascript,
|
||||
language: getLanguageType(widget.title),
|
||||
onChange: (value) {
|
||||
result = value;
|
||||
},
|
||||
@ -91,10 +117,24 @@ class _ScriptEditPageState extends ConsumerState<ScriptEditPage> {
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: CodeField(
|
||||
controller: _codeController!,
|
||||
expands: true,
|
||||
background: ref.watch(themeProvider).themeColor.settingBgColor(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 10,
|
||||
),
|
||||
child: CodeField(
|
||||
controller: _codeController!,
|
||||
expands: true,
|
||||
wrap: true,
|
||||
lineNumberStyle: LineNumberStyle(
|
||||
width: 0,
|
||||
margin: 0,
|
||||
textStyle: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||
),
|
||||
),
|
||||
background: ref.watch(themeProvider).themeColor.tabBarColor(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -20,6 +20,18 @@ class ScriptPage extends ConsumerStatefulWidget {
|
||||
|
||||
class _ScriptPageState extends ConsumerState<ScriptPage> with LazyLoadState<ScriptPage> {
|
||||
List<ScriptBean> list = [];
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
String? path;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_searchController.addListener(() {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -30,83 +42,167 @@ class _ScriptPageState extends ConsumerState<ScriptPage> with LazyLoadState<Scri
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
title: "脚本管理",
|
||||
actions: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
addScript();
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
CupertinoIcons.add,
|
||||
size: 24,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: list.isEmpty
|
||||
? const Center(
|
||||
child: CupertinoActivityIndicator(),
|
||||
)
|
||||
child: CupertinoActivityIndicator(),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
ScriptBean item = list[index];
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return searchCell(ref);
|
||||
}
|
||||
|
||||
return ColoredBox(
|
||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
||||
child: (item.children != null && item.children!.isNotEmpty)
|
||||
? ExpansionTile(
|
||||
title: Text(
|
||||
item.title ?? "",
|
||||
style: TextStyle(
|
||||
color:
|
||||
(item.disabled ?? false) ? ref.watch(themeProvider).themeColor.descColor() : ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
children: item.children!
|
||||
.map((e) => ListTile(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeScriptDetail,
|
||||
arguments: {
|
||||
"title": e.title,
|
||||
"path": e.parent,
|
||||
},
|
||||
).then((value) {
|
||||
if (value != null && value == true) {
|
||||
loadData();
|
||||
}
|
||||
});
|
||||
},
|
||||
title: Text(
|
||||
e.title ?? "",
|
||||
style: TextStyle(
|
||||
color: (item.disabled ?? false)
|
||||
? ref.watch(themeProvider).themeColor.descColor()
|
||||
: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
)
|
||||
: ListTile(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeScriptDetail,
|
||||
arguments: {
|
||||
"title": item.title,
|
||||
"path": "",
|
||||
},
|
||||
).then(
|
||||
(value) {
|
||||
if (value != null && value == true) {
|
||||
loadData();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
title: Text(
|
||||
item.title ?? "",
|
||||
style: TextStyle(
|
||||
color:
|
||||
(item.disabled ?? false) ? ref.watch(themeProvider).themeColor.descColor() : ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: list.length,
|
||||
),
|
||||
ScriptBean item = list[index - 1];
|
||||
|
||||
if (_searchController.text.isEmpty ||
|
||||
(item.title?.contains(_searchController.text) ?? false) ||
|
||||
(item.value?.contains(_searchController.text) ?? false) ||
|
||||
((item.children?.where((e) {
|
||||
return (e.title?.contains(_searchController.text) ?? false) || (e.value?.contains(_searchController.text) ?? false);
|
||||
}).isNotEmpty ??
|
||||
false))) {
|
||||
return ColoredBox(
|
||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
||||
child: (item.children != null && item.children!.isNotEmpty)
|
||||
? ExpansionTile(
|
||||
title: Text(
|
||||
item.title ?? "",
|
||||
style: TextStyle(
|
||||
color: (item.disabled ?? false)
|
||||
? ref.watch(themeProvider).themeColor.descColor()
|
||||
: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
children: item.children!
|
||||
.where((element) {
|
||||
if (_searchController.text.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
return (element.title?.contains(_searchController.text) ?? false) ||
|
||||
(element.value?.contains(_searchController.text) ?? false);
|
||||
})
|
||||
.map((e) => ListTile(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeScriptDetail,
|
||||
arguments: {
|
||||
"title": e.title,
|
||||
"path": e.parent,
|
||||
},
|
||||
).then((value) {
|
||||
if (value != null && value == true) {
|
||||
loadData();
|
||||
}
|
||||
});
|
||||
},
|
||||
title: Text(
|
||||
e.title ?? "",
|
||||
style: TextStyle(
|
||||
color: (item.disabled ?? false)
|
||||
? ref.watch(themeProvider).themeColor.descColor()
|
||||
: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
)
|
||||
: ListTile(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeScriptDetail,
|
||||
arguments: {
|
||||
"title": item.title,
|
||||
"path": "",
|
||||
},
|
||||
).then(
|
||||
(value) {
|
||||
if (value != null && value == true) {
|
||||
loadData();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
title: Text(
|
||||
item.title ?? "",
|
||||
style: TextStyle(
|
||||
color: (item.disabled ?? false)
|
||||
? ref.watch(themeProvider).themeColor.descColor()
|
||||
: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
itemCount: list.length + 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget searchCell(WidgetRef context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
vertical: 10,
|
||||
),
|
||||
child: CupertinoSearchTextField(
|
||||
onSubmitted: (value) {
|
||||
setState(() {});
|
||||
},
|
||||
onSuffixTap: () {
|
||||
_searchController.text = "";
|
||||
setState(() {});
|
||||
},
|
||||
controller: _searchController,
|
||||
borderRadius: BorderRadius.circular(
|
||||
30,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 5,
|
||||
vertical: 5,
|
||||
),
|
||||
suffixInsets: const EdgeInsets.only(
|
||||
right: 15,
|
||||
),
|
||||
prefixInsets: const EdgeInsets.only(
|
||||
top: 6,
|
||||
bottom: 6,
|
||||
left: 15,
|
||||
),
|
||||
placeholderStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
color: context.watch(themeProvider).themeColor.descColor(),
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
placeholder: "搜索",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -129,4 +225,139 @@ class _ScriptPageState extends ConsumerState<ScriptPage> with LazyLoadState<Scri
|
||||
void onLazyLoad() {
|
||||
loadData();
|
||||
}
|
||||
|
||||
String scriptPath = "";
|
||||
|
||||
void addScript() {
|
||||
showCupertinoDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: const Text("新增脚本"),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const Text(
|
||||
"脚本名称:",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
),
|
||||
child: TextField(
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.all(4),
|
||||
hintText: "请输入脚本名称",
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const Text(
|
||||
"脚本所属文件夹:",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
DropdownButtonFormField<String>(
|
||||
items: list
|
||||
.where((element) => element.children?.isNotEmpty ?? false)
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e.value,
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 2,
|
||||
child: Text(
|
||||
e.value ?? "",
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
))
|
||||
.toList()
|
||||
..insert(
|
||||
0,
|
||||
DropdownMenuItem(
|
||||
value: "",
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 2,
|
||||
child: const Text(
|
||||
"根目录",
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
)),
|
||||
value: scriptPath,
|
||||
onChanged: (value) {
|
||||
scriptPath = value ?? "";
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text(
|
||||
"取消",
|
||||
style: TextStyle(
|
||||
color: Color(0xff999999),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: Text(
|
||||
"确定",
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).primaryColor,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
"提交中...".toast();
|
||||
HttpResponse<NullResponse> response = await Api.addScript(
|
||||
_nameController.text,
|
||||
scriptPath,
|
||||
"## created by 青龙客户端 ${DateTime.now()}\n\n",
|
||||
);
|
||||
if (response.success) {
|
||||
"提交成功".toast();
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeScriptUpdate,
|
||||
arguments: {
|
||||
"title": _nameController.text,
|
||||
"path": scriptPath,
|
||||
"content": "## created by 青龙客户端 ${DateTime.now()}\n\n",
|
||||
},
|
||||
).then((value) {
|
||||
if (value != null && value == true) {
|
||||
_nameController.text = "";
|
||||
loadData();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
(response.message ?? "").toast();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,10 +12,12 @@ import 'package:qinglong_app/utils/extension.dart';
|
||||
/// @author NewTab
|
||||
class TaskLogDetailPage extends ConsumerStatefulWidget {
|
||||
final String title;
|
||||
final String path;
|
||||
|
||||
const TaskLogDetailPage({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.path,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -38,21 +40,22 @@ class _TaskLogDetailPageState extends ConsumerState<TaskLogDetailPage> with Lazy
|
||||
body: content == null
|
||||
? const Center(child: CupertinoActivityIndicator())
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: SelectableText(
|
||||
(content == null || content!.isEmpty) ? "暂无数据" : content!,
|
||||
selectionHeightStyle: BoxHeightStyle.max,
|
||||
selectionWidthStyle: BoxWidthStyle.max,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: SelectableText(
|
||||
(content == null || content!.isEmpty) ? "暂无数据" : content!,
|
||||
selectionHeightStyle: BoxHeightStyle.max,
|
||||
selectionWidthStyle: BoxWidthStyle.max,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> loadData() async {
|
||||
HttpResponse<String> response = await Api.taskLogDetail(
|
||||
widget.title,
|
||||
widget.path,
|
||||
);
|
||||
|
||||
if (response.success) {
|
||||
|
||||
@ -206,7 +206,7 @@ class _AddTaskPageState extends ConsumerState<AddTaskPage> {
|
||||
taskBean.schedule = _cronController.text;
|
||||
HttpResponse<NullResponse> response = await Api.addTask(
|
||||
_nameController.text, _commandController.text, _cronController.text,
|
||||
id: taskBean.sId);
|
||||
id: taskBean.id,);
|
||||
|
||||
if (response.success) {
|
||||
(widget.taskBean?.sId == null) ? "新增成功" : "修改成功".toast();
|
||||
|
||||
@ -11,6 +11,8 @@ class TaskBean {
|
||||
String? schedule;
|
||||
bool? saved;
|
||||
String? sId;
|
||||
int? id;
|
||||
String? _id;
|
||||
int? created;
|
||||
int? status;
|
||||
String? timestamp;
|
||||
@ -24,20 +26,22 @@ class TaskBean {
|
||||
|
||||
TaskBean(
|
||||
{this.name,
|
||||
this.command,
|
||||
this.schedule,
|
||||
this.saved,
|
||||
this.sId,
|
||||
this.created,
|
||||
this.status,
|
||||
this.timestamp,
|
||||
this.isSystem,
|
||||
this.isDisabled,
|
||||
this.logPath,
|
||||
this.isPinned,
|
||||
this.lastExecutionTime,
|
||||
this.lastRunningTime,
|
||||
this.pid});
|
||||
this.command,
|
||||
this.schedule,
|
||||
this.saved,
|
||||
this.sId,
|
||||
this.created,
|
||||
this.status,
|
||||
this.timestamp,
|
||||
this.isSystem,
|
||||
this.isDisabled,
|
||||
this.logPath,
|
||||
this.isPinned,
|
||||
this.lastExecutionTime,
|
||||
this.lastRunningTime,
|
||||
this.pid});
|
||||
|
||||
get nId => _id;
|
||||
|
||||
TaskBean.fromJson(Map<String, dynamic> json) {
|
||||
try {
|
||||
@ -45,6 +49,8 @@ class TaskBean {
|
||||
command = json['command'].toString();
|
||||
schedule = json['schedule'].toString();
|
||||
saved = json['saved'];
|
||||
id = json['id'];
|
||||
_id = json['_id'];
|
||||
sId = json.containsKey('_id') ? json['_id'].toString() : (json.containsKey('id') ? json['id'].toString() : "");
|
||||
created = int.tryParse(json['created'].toString());
|
||||
status = json['status'];
|
||||
|
||||
@ -6,7 +6,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
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/single_account_page.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';
|
||||
|
||||
@ -1,399 +0,0 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:qinglong_app/base/http/url.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../main.dart';
|
||||
|
||||
/// @author NewTab
|
||||
|
||||
class UpdateUtils {
|
||||
Future<String?> checkUpdate([bool remind = false]) async {
|
||||
try {
|
||||
if (!Platform.isAndroid) {
|
||||
return null;
|
||||
}
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
|
||||
String version = packageInfo.version;
|
||||
String url = Url.checkUpdateUrl;
|
||||
Dio dio = Dio(BaseOptions(
|
||||
connectTimeout: 50000,
|
||||
receiveTimeout: 50000,
|
||||
sendTimeout: 50000,
|
||||
));
|
||||
var response = await dio.get(url);
|
||||
if (response.statusCode == 200) {
|
||||
if (version == response.data as String) {
|
||||
return null;
|
||||
}
|
||||
return response.data as String;
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
if (remind) {
|
||||
"无法连接到github服务器,版本更新检测失败".toast();
|
||||
}
|
||||
logger.e(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static void launchURL(String version) async {
|
||||
try {
|
||||
await launch("https://github.com/qinglong-app/qinglong_app/releases/download/v$version/app-release-v$version.apk");
|
||||
} catch (e) {
|
||||
logger.e(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///版本更新加提示框
|
||||
class UpdateDialog {
|
||||
bool _isShowing = false;
|
||||
late BuildContext _context;
|
||||
late UpdateWidget _widget;
|
||||
|
||||
UpdateDialog(BuildContext context,
|
||||
{double width = 0.0,
|
||||
required String title,
|
||||
required String updateContent,
|
||||
required VoidCallback onUpdate,
|
||||
double titleTextSize = 16.0,
|
||||
double contentTextSize = 14.0,
|
||||
double buttonTextSize = 14.0,
|
||||
double progress = -1.0,
|
||||
Color progressBackgroundColor = const Color(0xFFFFCDD2),
|
||||
Image? topImage,
|
||||
double extraHeight = 5.0,
|
||||
double radius = 4.0,
|
||||
Color themeColor = Colors.red,
|
||||
bool enableIgnore = false,
|
||||
VoidCallback? onIgnore,
|
||||
bool isForce = false,
|
||||
String? updateButtonText,
|
||||
String? ignoreButtonText,
|
||||
VoidCallback? onClose}) {
|
||||
_context = context;
|
||||
_widget = UpdateWidget(
|
||||
width: width,
|
||||
title: title,
|
||||
updateContent: updateContent,
|
||||
onUpdate: onUpdate,
|
||||
titleTextSize: titleTextSize,
|
||||
contentTextSize: contentTextSize,
|
||||
buttonTextSize: buttonTextSize,
|
||||
progress: progress,
|
||||
topImage: topImage,
|
||||
extraHeight: extraHeight,
|
||||
radius: radius,
|
||||
themeColor: themeColor,
|
||||
progressBackgroundColor: progressBackgroundColor,
|
||||
enableIgnore: enableIgnore,
|
||||
onIgnore: onIgnore,
|
||||
isForce: isForce,
|
||||
updateButtonText: updateButtonText ?? '更新',
|
||||
ignoreButtonText: ignoreButtonText ?? '忽略此版本',
|
||||
onClose: onClose ?? () => dismiss());
|
||||
}
|
||||
|
||||
/// 显示弹窗
|
||||
Future<bool> show() {
|
||||
try {
|
||||
if (isShowing()) {
|
||||
return Future<bool>.value(false);
|
||||
}
|
||||
showDialog<bool>(
|
||||
context: _context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return WillPopScope(onWillPop: () => Future<bool>.value(false), child: _widget);
|
||||
});
|
||||
_isShowing = true;
|
||||
return Future<bool>.value(true);
|
||||
} catch (err) {
|
||||
_isShowing = false;
|
||||
return Future<bool>.value(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// 隐藏弹窗
|
||||
Future<bool> dismiss() {
|
||||
try {
|
||||
if (_isShowing) {
|
||||
_isShowing = false;
|
||||
Navigator.pop(_context);
|
||||
return Future<bool>.value(true);
|
||||
} else {
|
||||
return Future<bool>.value(false);
|
||||
}
|
||||
} catch (err) {
|
||||
return Future<bool>.value(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// 是否显示
|
||||
bool isShowing() {
|
||||
return _isShowing;
|
||||
}
|
||||
|
||||
/// 更新进度
|
||||
void update(double progress) {
|
||||
if (isShowing()) {
|
||||
_widget.update(progress);
|
||||
}
|
||||
}
|
||||
|
||||
/// 显示版本更新提示框
|
||||
static UpdateDialog showUpdate(BuildContext context,
|
||||
{double width = 0.0,
|
||||
required String title,
|
||||
required String updateContent,
|
||||
required VoidCallback onUpdate,
|
||||
double titleTextSize = 16.0,
|
||||
double contentTextSize = 14.0,
|
||||
double buttonTextSize = 14.0,
|
||||
double progress = -1.0,
|
||||
Color progressBackgroundColor = const Color(0xFFFFCDD2),
|
||||
Image? topImage,
|
||||
double extraHeight = 5.0,
|
||||
double radius = 4.0,
|
||||
Color themeColor = Colors.red,
|
||||
bool enableIgnore = false,
|
||||
VoidCallback? onIgnore,
|
||||
String? updateButtonText,
|
||||
String? ignoreButtonText,
|
||||
bool isForce = false}) {
|
||||
final UpdateDialog dialog = UpdateDialog(context,
|
||||
width: width,
|
||||
title: title,
|
||||
updateContent: updateContent,
|
||||
onUpdate: onUpdate,
|
||||
titleTextSize: titleTextSize,
|
||||
contentTextSize: contentTextSize,
|
||||
buttonTextSize: buttonTextSize,
|
||||
progress: progress,
|
||||
topImage: topImage,
|
||||
extraHeight: extraHeight,
|
||||
radius: radius,
|
||||
themeColor: themeColor,
|
||||
progressBackgroundColor: progressBackgroundColor,
|
||||
enableIgnore: enableIgnore,
|
||||
isForce: isForce,
|
||||
updateButtonText: updateButtonText,
|
||||
ignoreButtonText: ignoreButtonText,
|
||||
onIgnore: onIgnore);
|
||||
dialog.show();
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class UpdateWidget extends StatefulWidget {
|
||||
/// 对话框的宽度
|
||||
final double width;
|
||||
|
||||
/// 升级标题
|
||||
final String title;
|
||||
|
||||
/// 更新内容
|
||||
final String updateContent;
|
||||
|
||||
/// 标题文字的大小
|
||||
final double titleTextSize;
|
||||
|
||||
/// 更新文字内容的大小
|
||||
final double contentTextSize;
|
||||
|
||||
/// 按钮文字的大小
|
||||
final double buttonTextSize;
|
||||
|
||||
/// 顶部图片
|
||||
final Widget? topImage;
|
||||
|
||||
/// 拓展高度(适配顶部图片高度不一致的情况)
|
||||
final double extraHeight;
|
||||
|
||||
/// 边框圆角大小
|
||||
final double radius;
|
||||
|
||||
/// 主题颜色
|
||||
final Color themeColor;
|
||||
|
||||
/// 更新事件
|
||||
final VoidCallback onUpdate;
|
||||
|
||||
/// 可忽略更新
|
||||
final bool enableIgnore;
|
||||
|
||||
/// 更新事件
|
||||
final VoidCallback? onIgnore;
|
||||
|
||||
double progress;
|
||||
|
||||
/// 进度条的背景颜色
|
||||
final Color progressBackgroundColor;
|
||||
|
||||
/// 更新事件
|
||||
final VoidCallback? onClose;
|
||||
|
||||
/// 是否是强制更新
|
||||
final bool isForce;
|
||||
|
||||
/// 更新按钮内容
|
||||
final String updateButtonText;
|
||||
|
||||
/// 忽略按钮内容
|
||||
final String ignoreButtonText;
|
||||
|
||||
UpdateWidget(
|
||||
{Key? key,
|
||||
this.width = 0.0,
|
||||
required this.title,
|
||||
required this.updateContent,
|
||||
required this.onUpdate,
|
||||
this.titleTextSize = 16.0,
|
||||
this.contentTextSize = 14.0,
|
||||
this.buttonTextSize = 14.0,
|
||||
this.progress = -1.0,
|
||||
this.progressBackgroundColor = const Color(0xFFFFCDD2),
|
||||
this.topImage,
|
||||
this.extraHeight = 5.0,
|
||||
this.radius = 4.0,
|
||||
this.themeColor = Colors.red,
|
||||
this.enableIgnore = false,
|
||||
this.onIgnore,
|
||||
this.isForce = false,
|
||||
this.updateButtonText = '更新',
|
||||
this.ignoreButtonText = '忽略此版本',
|
||||
this.onClose})
|
||||
: super(key: key);
|
||||
|
||||
final _UpdateWidgetState _state = _UpdateWidgetState();
|
||||
|
||||
void update(double progress) {
|
||||
_state.update(progress);
|
||||
}
|
||||
|
||||
@override
|
||||
_UpdateWidgetState createState() => _state;
|
||||
}
|
||||
|
||||
class _UpdateWidgetState extends State<UpdateWidget> {
|
||||
void update(double progress) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
widget.progress = progress;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double dialogWidth = widget.width <= 0 ? getFitWidth(context) * 0.618 : widget.width;
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: SizedBox(
|
||||
width: dialogWidth,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
width: dialogWidth,
|
||||
child: widget.topImage ?? Image.asset('assets/images/update_bg_app_top.png', fit: BoxFit.fill),
|
||||
),
|
||||
Container(
|
||||
width: dialogWidth,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8),
|
||||
decoration: ShapeDecoration(
|
||||
color: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(widget.radius), bottomRight: Radius.circular(widget.radius)),
|
||||
),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: widget.extraHeight),
|
||||
child: Text(widget.title, style: TextStyle(fontSize: widget.titleTextSize, color: Colors.black)),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text(widget.updateContent, style: TextStyle(fontSize: widget.contentTextSize, color: const Color(0xFF666666))),
|
||||
),
|
||||
Column(children: <Widget>[
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1,
|
||||
child: ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
textStyle: MaterialStateProperty.all(TextStyle(fontSize: widget.buttonTextSize)),
|
||||
foregroundColor: MaterialStateProperty.all(Colors.white),
|
||||
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5))),
|
||||
elevation: MaterialStateProperty.all(0),
|
||||
backgroundColor: MaterialStateProperty.all(widget.themeColor),
|
||||
),
|
||||
child: Text(widget.updateButtonText),
|
||||
onPressed: widget.onUpdate,
|
||||
),
|
||||
),
|
||||
if (widget.enableIgnore && widget.onIgnore != null)
|
||||
FractionallySizedBox(
|
||||
widthFactor: 1,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
textStyle: MaterialStateProperty.all(TextStyle(fontSize: widget.buttonTextSize)),
|
||||
foregroundColor: MaterialStateProperty.all(const Color(0xFF666666)),
|
||||
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5))),
|
||||
),
|
||||
child: Text(widget.ignoreButtonText),
|
||||
onPressed: widget.onIgnore,
|
||||
))
|
||||
else
|
||||
const SizedBox()
|
||||
]),
|
||||
],
|
||||
)),
|
||||
),
|
||||
if (!widget.isForce)
|
||||
Column(children: <Widget>[
|
||||
const SizedBox(width: 1.5, height: 50, child: DecoratedBox(decoration: BoxDecoration(color: Colors.white))),
|
||||
IconButton(
|
||||
iconSize: 30,
|
||||
constraints: const BoxConstraints(maxHeight: 30, maxWidth: 30),
|
||||
padding: EdgeInsets.zero,
|
||||
icon: Image.asset(
|
||||
'assets/images/update_ic_close.png',
|
||||
),
|
||||
onPressed: widget.onClose,
|
||||
)
|
||||
])
|
||||
else
|
||||
const SizedBox()
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
double getFitWidth(BuildContext context) {
|
||||
return min(getScreenHeight(context), getScreenWidth(context));
|
||||
}
|
||||
|
||||
double getScreenHeight(BuildContext context) {
|
||||
return MediaQuery.of(context).size.height;
|
||||
}
|
||||
|
||||
double getScreenWidth(BuildContext context) {
|
||||
return MediaQuery.of(context).size.width;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user