mirror of
https://github.com/qinglong-app/qinglong_app.git
synced 2025-10-09 16:48:19 +08:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4e8dec698 | |||
| a97aa1f347 | |||
| 130459d646 | |||
| 4f8285fcf1 | |||
| 25bc0d4101 | |||
| 282695cc93 | |||
| df9aa8e72f | |||
| 747b15fdea | |||
| 22f66d36a2 | |||
| 89ad8e38e5 | |||
| fb802040ca | |||
| 72b9829dbe | |||
| b177298e07 | |||
| 364c0da236 | |||
| e8694643d8 | |||
| 6dbed6cf49 | |||
| fe3e0f1d50 | |||
| 4fe730d414 | |||
| 346ebd68cf | |||
| 4bf2a1b61f | |||
| 0bdf267b36 | |||
| 1981edfafe | |||
| 3959155c1b | |||
| 16f4f27f17 | |||
| 6185a92759 | |||
| 38a6790c51 | |||
| 8a85a838e7 | |||
| 51da0abb84 | |||
| fffa930eb1 | |||
| d72f5bfd5e | |||
| 2dfcf35191 | |||
| b048fdaad8 | |||
| 9ce85a34e9 | |||
| c51ebc16d2 | |||
| e457f3c4c9 | |||
| 884ee84f8a | |||
| 7bd9e823b3 | |||
| 901944983f | |||
| ca6f1db16f | |||
| 0a2e9a3c09 | |||
| 5ba83212af | |||
| e8a3a43e4a | |||
| 00bfee5827 | |||
| 5d56d9b1a8 | |||
| 58f6a5d964 | |||
| f66e0d2c1c | |||
| 98f807589b | |||
| 0426041288 | |||
| da8fae1b6d | |||
| 708ee91d08 | |||
| a211eb19cc | |||
| ca0a54c3ab |
21
CHANGELOG.md
Normal file
21
CHANGELOG.md
Normal file
@ -0,0 +1,21 @@
|
||||
## 1.0.2
|
||||
|
||||
* 支持修改用户名密码(仅限使用用户名密码登录用户)
|
||||
* 支持脚本的编辑删除
|
||||
* 支持client_id登录之后只启用部分模块
|
||||
* 支持两步验证
|
||||
|
||||
## 1.0.1
|
||||
|
||||
* 增加任务详情页
|
||||
* 增加环境变量详情页
|
||||
* 环境变量列表使用体验优化
|
||||
* 部分页面进入卡顿优化
|
||||
* 扩大任务的搜索范围
|
||||
* 优化暗黑主题
|
||||
* 支持client_id登录
|
||||
* 修改bug
|
||||
|
||||
## 1.0.0
|
||||
|
||||
* 完成基础功能
|
||||
44
README.md
44
README.md
@ -1,3 +1,45 @@
|
||||
# qinglong_app
|
||||
|
||||
基于[qinglong](https://github.com/whyour/qinglong)API的三方客户端
|
||||
版本更新通知 https://t.me/qinglongapp
|
||||
|
||||
仅供学习交流, 禁止用于任何商业用途
|
||||
|
||||
基于[qinglong](https://github.com/whyour/qinglong)API实现的三方客户端,采用Flutter编写 (API基于v2.10.13)
|
||||
|
||||
Android端去[release](https://github.com/qinglong-app/qinglong_app/releases)下载
|
||||
|
||||
iOS端暂无上架打算,用户自行下载main分支源码编译安装
|
||||
|
||||
|
||||
<p float="left">
|
||||
<img src="./art/1.jpg" width="200" />
|
||||
<img src="./art/8.jpg" width="200" />
|
||||
<img src="./art/9.jpg" width="200" />
|
||||
</p>
|
||||
<p float="left">
|
||||
<img src="./art/10.jpg" width="200" />
|
||||
<img src="./art/11.jpg" width="200" />
|
||||
<img src="./art/12.jpg" width="200" />
|
||||
</p>
|
||||
<p float="left">
|
||||
<img src="./art/2.jpg" width="200" />
|
||||
<img src="./art/3.jpg" width="200" />
|
||||
<img src="./art/4.jpg" width="200" />
|
||||
</p>
|
||||
|
||||
<p float="left">
|
||||
<img src="./art/5.jpg" width="200" />
|
||||
<img src="./art/6.jpg" width="200" />
|
||||
<img src="./art/7.jpg" width="200" />
|
||||
</p>
|
||||
|
||||
<p float="left">
|
||||
<img src="./art/13.jpg" width="200" />
|
||||
</p>
|
||||
|
||||
### 尚未完成的功能
|
||||
>* 修改用户名密码
|
||||
>* 两步验证
|
||||
>* 应用设置
|
||||
>* 通知设置
|
||||
>* 脚本管理中的增删改和调试
|
||||
|
||||
@ -47,9 +47,6 @@ android {
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
@ -1,30 +1,37 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="work.master.qinglongapp">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
android:label="青龙"
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
|
||||
</queries>
|
||||
<application
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="青龙">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:exported="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
|
||||
BIN
art/10.jpg
Normal file
BIN
art/10.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 KiB |
BIN
art/11.jpg
Normal file
BIN
art/11.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 190 KiB |
BIN
art/12.jpg
Normal file
BIN
art/12.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 301 KiB |
BIN
art/13.jpg
Normal file
BIN
art/13.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
41
ios/Podfile.lock
Normal file
41
ios/Podfile.lock
Normal file
@ -0,0 +1,41 @@
|
||||
PODS:
|
||||
- Flutter (1.0.0)
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
- Toast
|
||||
- move_to_background (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_ios (0.0.1):
|
||||
- Flutter
|
||||
- Toast (4.0.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
|
||||
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- Toast
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
move_to_background:
|
||||
:path: ".symlinks/plugins/move_to_background/ios"
|
||||
shared_preferences_ios:
|
||||
:path: ".symlinks/plugins/shared_preferences_ios/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
||||
fluttertoast: 6122fa75143e992b1d3470f61000f591a798cc58
|
||||
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
|
||||
shared_preferences_ios: aef470a42dc4675a1cdd50e3158b42e3d1232b32
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
|
||||
PODFILE CHECKSUM: aea2ed8a4b05dec076f6f7cea39202b1133ed51c
|
||||
|
||||
COCOAPODS: 1.11.2
|
||||
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1320"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@ -28,6 +28,11 @@
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>https</string>
|
||||
<string>http</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:qinglong_app/base/ui/lazy_load_state.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
import 'base_viewmodel.dart';
|
||||
|
||||
@ -8,6 +9,7 @@ class BaseStateWidget<T extends BaseViewModel> extends ConsumerStatefulWidget {
|
||||
final ProviderBase<T> model;
|
||||
final Widget? child;
|
||||
final Function(T)? onReady;
|
||||
final bool lazyLoad;
|
||||
|
||||
const BaseStateWidget({
|
||||
Key? key,
|
||||
@ -15,26 +17,14 @@ class BaseStateWidget<T extends BaseViewModel> extends ConsumerStatefulWidget {
|
||||
required this.model,
|
||||
this.child,
|
||||
this.onReady,
|
||||
this.lazyLoad = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_BaseStateWidgetState<T> createState() => _BaseStateWidgetState<T>();
|
||||
}
|
||||
|
||||
class _BaseStateWidgetState<T extends BaseViewModel>
|
||||
extends ConsumerState<BaseStateWidget<T>> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance?.addPostFrameCallback(
|
||||
(timeStamp) {
|
||||
if (widget.onReady != null) {
|
||||
widget.onReady!(ref.read<T>(widget.model));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class _BaseStateWidgetState<T extends BaseViewModel> extends ConsumerState<BaseStateWidget<T>> with LazyLoadState<BaseStateWidget<T>> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var viewModel = ref.watch<T>(widget.model);
|
||||
@ -65,32 +55,27 @@ class _BaseStateWidgetState<T extends BaseViewModel>
|
||||
if (viewModel.currentState == PageState.EMPTY) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text("暂无数据"),
|
||||
child: const Text("暂无数据"),
|
||||
);
|
||||
}
|
||||
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BaseState<T extends StatefulWidget> extends State {
|
||||
late T parent;
|
||||
@override
|
||||
void onLazyLoad() {
|
||||
if (widget.onReady != null && widget.lazyLoad) {
|
||||
widget.onReady!(ref.read<T>(widget.model));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
parent = widget as T;
|
||||
super.initState();
|
||||
WidgetsBinding.instance?.endOfFrame.then((_) {
|
||||
firstFrameCalled();
|
||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||
if (widget.onReady != null && !widget.lazyLoad) {
|
||||
widget.onReady!(ref.read<T>(widget.model));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void firstFrameCalled() {}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return buildWidgets(context);
|
||||
}
|
||||
|
||||
Widget buildWidgets(BuildContext context);
|
||||
}
|
||||
|
||||
@ -2,18 +2,20 @@ import 'package:qinglong_app/base/http/http.dart';
|
||||
import 'package:qinglong_app/module/config/config_bean.dart';
|
||||
import 'package:qinglong_app/module/env/env_bean.dart';
|
||||
import 'package:qinglong_app/module/login/login_bean.dart';
|
||||
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/task_log/task_log_bean.dart';
|
||||
import 'package:qinglong_app/module/task/task_bean.dart';
|
||||
import 'package:qinglong_app/module/task/task_detail/task_detail_bean.dart';
|
||||
|
||||
import 'url.dart';
|
||||
|
||||
class Api {
|
||||
static Future<HttpResponse<LoginBean>> login(
|
||||
String userName, String passWord) async {
|
||||
String userName,
|
||||
String passWord,
|
||||
) async {
|
||||
return await Http.post<LoginBean>(
|
||||
Url.login,
|
||||
{
|
||||
@ -23,6 +25,41 @@ class Api {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<LoginBean>> loginTwo(
|
||||
String userName,
|
||||
String passWord,
|
||||
String code,
|
||||
) async {
|
||||
return await Http.put<LoginBean>(
|
||||
Url.loginTwo,
|
||||
{
|
||||
"username": userName,
|
||||
"password": passWord,
|
||||
"code": code,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<LoginBean>> loginByClientId(
|
||||
String id,
|
||||
String secret,
|
||||
) async {
|
||||
return await Http.get<LoginBean>(
|
||||
Url.loginByClientId,
|
||||
{
|
||||
"client_id": id,
|
||||
"client_secret": secret,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<UserBean>> user() async {
|
||||
return await Http.get<UserBean>(
|
||||
Url.user,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<List<TaskBean>>> crons() async {
|
||||
return await Http.get<List<TaskBean>>(
|
||||
Url.tasks,
|
||||
@ -30,22 +67,30 @@ class Api {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<NullResponse>> startTasks(
|
||||
List<String> crons) async {
|
||||
static Future<HttpResponse<NullResponse>> startTasks(List<String> crons) async {
|
||||
return await Http.put<NullResponse>(
|
||||
Url.runTasks,
|
||||
crons,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<NullResponse>> stopTasks(
|
||||
List<String> crons) async {
|
||||
static Future<HttpResponse<NullResponse>> stopTasks(List<String> crons) async {
|
||||
return await Http.put<NullResponse>(
|
||||
Url.runTasks,
|
||||
crons,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<NullResponse>> updatePassword(String name, String password) async {
|
||||
return await Http.put<NullResponse>(
|
||||
Url.updatePassword,
|
||||
{
|
||||
"username": name,
|
||||
"password": password,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<String>> inTimeLog(String cron) async {
|
||||
return await Http.get<String>(
|
||||
Url.intimeLog(cron),
|
||||
@ -53,26 +98,17 @@ class Api {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<TaskDetailBean>> taskDetail(String cron) async {
|
||||
return await Http.get<TaskDetailBean>(
|
||||
Url.taskDetail + cron,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<TaskDetailBean>> addTask(
|
||||
String name, String command, String cron,
|
||||
{String? id}) async {
|
||||
static Future<HttpResponse<NullResponse>> addTask(String name, String command, String cron, {String? id}) async {
|
||||
var data = {"name": name, "command": command, "schedule": cron};
|
||||
|
||||
if (id != null) {
|
||||
data["_id"] = id;
|
||||
return await Http.put<TaskDetailBean>(
|
||||
return await Http.put<NullResponse>(
|
||||
Url.addTask,
|
||||
data,
|
||||
);
|
||||
}
|
||||
return await Http.post<TaskDetailBean>(
|
||||
return await Http.post<NullResponse>(
|
||||
Url.addTask,
|
||||
data,
|
||||
);
|
||||
@ -127,8 +163,7 @@ class Api {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<NullResponse>> saveFile(
|
||||
String name, String content) async {
|
||||
static Future<HttpResponse<NullResponse>> saveFile(String name, String content) async {
|
||||
return await Http.post<NullResponse>(
|
||||
Url.saveFile,
|
||||
{"content": content, "name": name},
|
||||
@ -163,9 +198,7 @@ class Api {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<NullResponse>> addEnv(
|
||||
String name, String value, String remarks,
|
||||
{String? id}) async {
|
||||
static Future<HttpResponse<NullResponse>> addEnv(String name, String value, String remarks, {String? id}) async {
|
||||
var data = {
|
||||
"value": value,
|
||||
"remarks": remarks,
|
||||
@ -185,8 +218,7 @@ class Api {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<NullResponse>> moveEnv(
|
||||
String id, int fromIndex, int toIndex) async {
|
||||
static Future<HttpResponse<NullResponse>> moveEnv(String id, int fromIndex, int toIndex) async {
|
||||
return await Http.put<NullResponse>(
|
||||
Url.envMove(id),
|
||||
{"fromIndex": fromIndex, "toIndex": toIndex},
|
||||
@ -201,8 +233,7 @@ class Api {
|
||||
}
|
||||
|
||||
static Future<HttpResponse<List<TaskLogBean>>> taskLog() async {
|
||||
return await Http.get<List<TaskLogBean>>(Url.taskLog, null,
|
||||
serializationName: "dirs");
|
||||
return await Http.get<List<TaskLogBean>>(Url.taskLog, null, serializationName: "dirs");
|
||||
}
|
||||
|
||||
static Future<HttpResponse<String>> taskLogDetail(String name) async {
|
||||
@ -219,8 +250,28 @@ class Api {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<String>> scriptDetail(
|
||||
String name, String? path) async {
|
||||
static Future<HttpResponse<NullResponse>> updateScript(String name, String path, String content) async {
|
||||
return await Http.put<NullResponse>(
|
||||
Url.scriptDetail,
|
||||
{
|
||||
"filename": name,
|
||||
"path": path,
|
||||
"content": content,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<NullResponse>> delScript(String name, String path) async {
|
||||
return await Http.delete<NullResponse>(
|
||||
Url.scriptDetail,
|
||||
{
|
||||
"filename": name,
|
||||
"path": path,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<String>> scriptDetail(String name, String? path) async {
|
||||
return await Http.get<String>(
|
||||
Url.scriptDetail + name,
|
||||
{
|
||||
@ -229,8 +280,7 @@ class Api {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<List<DependencyBean>>> dependencies(
|
||||
String type) async {
|
||||
static Future<HttpResponse<List<DependencyBean>>> dependencies(String type) async {
|
||||
return await Http.get<List<DependencyBean>>(
|
||||
Url.dependencies,
|
||||
{
|
||||
@ -239,8 +289,7 @@ class Api {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<NullResponse>> dependencyReinstall(
|
||||
String id) async {
|
||||
static Future<HttpResponse<NullResponse>> dependencyReinstall(String id) async {
|
||||
return await Http.put<NullResponse>(
|
||||
Url.dependencies,
|
||||
[id],
|
||||
@ -254,8 +303,7 @@ class Api {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<HttpResponse<NullResponse>> addDependency(
|
||||
String name, int type) async {
|
||||
static Future<HttpResponse<NullResponse>> addDependency(String name, int type) async {
|
||||
return await Http.post<NullResponse>(
|
||||
Url.dependencies,
|
||||
[
|
||||
|
||||
@ -6,15 +6,15 @@ import 'package:dio_log/dio_log.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:qinglong_app/base/http/token_interceptor.dart';
|
||||
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||
import 'package:qinglong_app/utils/QlNavigatorObserver.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
|
||||
import '../../json.jc.dart';
|
||||
import '../../main.dart';
|
||||
import '../routes.dart';
|
||||
|
||||
class Http {
|
||||
static const int NOT_LOGIN = 1000;
|
||||
static Dio? _dio;
|
||||
static bool pushedLoginPage = false;
|
||||
|
||||
static void initDioConfig(
|
||||
String host,
|
||||
@ -22,9 +22,9 @@ class Http {
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: host,
|
||||
connectTimeout: 5000,
|
||||
receiveTimeout: 5000,
|
||||
sendTimeout: 5000,
|
||||
connectTimeout: 50000,
|
||||
receiveTimeout: 50000,
|
||||
sendTimeout: 50000,
|
||||
contentType: "application/json",
|
||||
),
|
||||
);
|
||||
@ -38,6 +38,10 @@ class Http {
|
||||
}
|
||||
}
|
||||
|
||||
static void clear() {
|
||||
_dio = null;
|
||||
}
|
||||
|
||||
static Future<HttpResponse<T>> get<T>(
|
||||
String uri,
|
||||
Map<String, String?>? json, {
|
||||
@ -50,13 +54,7 @@ class Http {
|
||||
|
||||
return decodeResponse<T>(response, serializationName, compute);
|
||||
} on DioError catch (e) {
|
||||
if (e.response?.statusCode == 401) {
|
||||
exitLogin();
|
||||
}
|
||||
return HttpResponse(
|
||||
success: false,
|
||||
message: e.response?.statusMessage ?? e.message,
|
||||
code: 0);
|
||||
return exceptionHandler<T>(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,13 +74,7 @@ class Http {
|
||||
compute,
|
||||
);
|
||||
} on DioError catch (e) {
|
||||
if (e.response?.statusCode == 401) {
|
||||
exitLogin();
|
||||
}
|
||||
return HttpResponse(
|
||||
success: false,
|
||||
message: e.response?.statusMessage ?? e.message,
|
||||
code: 0);
|
||||
return exceptionHandler<T>(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,13 +94,7 @@ class Http {
|
||||
compute,
|
||||
);
|
||||
} on DioError catch (e) {
|
||||
if (e.response?.statusCode == 401) {
|
||||
exitLogin();
|
||||
}
|
||||
return HttpResponse(
|
||||
success: false,
|
||||
message: e.response?.statusMessage ?? e.message,
|
||||
code: 0);
|
||||
return exceptionHandler<T>(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,22 +113,30 @@ class Http {
|
||||
compute,
|
||||
);
|
||||
} on DioError catch (e) {
|
||||
if (e.response?.statusCode == 401) {
|
||||
exitLogin();
|
||||
}
|
||||
return HttpResponse(
|
||||
success: false,
|
||||
message: e.response?.statusMessage ?? e.message,
|
||||
code: 0);
|
||||
return exceptionHandler<T>(e);
|
||||
}
|
||||
}
|
||||
|
||||
static bool pushedLoginPage = false;
|
||||
|
||||
static void exitLogin() {
|
||||
if (!pushedLoginPage) {
|
||||
"身份已过期,请重新登录".toast();
|
||||
pushedLoginPage = true;
|
||||
navigatorState.currentState?.pushReplacementNamed(Routes.routeLogin);
|
||||
navigatorState.currentState?.pushNamedAndRemoveUntil(Routes.routeLogin, (route) => false);
|
||||
}
|
||||
}
|
||||
|
||||
static HttpResponse<T> exceptionHandler<T>(DioError e) {
|
||||
if (e.response?.statusCode == 401) {
|
||||
if (!getIt<UserInfoViewModel>().useSecretLogined) {
|
||||
exitLogin();
|
||||
}
|
||||
return HttpResponse(success: false, message: "没有该模块的访问权限", code: 401);
|
||||
}
|
||||
|
||||
if (e.response != null && e.response!.data != null) {
|
||||
return HttpResponse(success: false, message: e.response?.data["message"] ?? e.message, code: e.response?.data["code"] ?? 0);
|
||||
} else {
|
||||
return HttpResponse(success: false, message: e.message, code: e.response?.statusCode ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,7 +192,7 @@ class Http {
|
||||
} else {
|
||||
return HttpResponse<T>(
|
||||
success: false,
|
||||
code: -1000,
|
||||
code: response.data["code"],
|
||||
message: response.data["message"],
|
||||
);
|
||||
}
|
||||
@ -227,8 +221,7 @@ class HttpResponse<T> {
|
||||
late int code;
|
||||
T? bean;
|
||||
|
||||
HttpResponse(
|
||||
{required this.success, this.message, required this.code, this.bean});
|
||||
HttpResponse({required this.success, this.message, required this.code, this.bean});
|
||||
}
|
||||
|
||||
class DeserializeAction<T> {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:qinglong_app/base/http/url.dart';
|
||||
import 'package:qinglong_app/main.dart';
|
||||
|
||||
import '../userinfo_viewmodel.dart';
|
||||
@ -12,11 +13,13 @@ class TokenInterceptor extends Interceptor {
|
||||
"Bearer " + getIt<UserInfoViewModel>().token!;
|
||||
}
|
||||
|
||||
options.headers["User-Agent"] =
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1";
|
||||
options.headers["User-Agent"] = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1";
|
||||
|
||||
options.headers["Content-Type"] = "application/json;charset=UTF-8";
|
||||
options.queryParameters["t"] =
|
||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000).toString();
|
||||
if (options.path != Url.loginByClientId && options.path!= Url.loginTwo) {
|
||||
options.queryParameters["t"] =
|
||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000).toString();
|
||||
}
|
||||
return handler.next(options);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,35 +1,71 @@
|
||||
import '../../main.dart';
|
||||
import '../userinfo_viewmodel.dart';
|
||||
|
||||
class Url {
|
||||
static const login = "/api/user/login";
|
||||
static const tasks = "/api/crons";
|
||||
static const runTasks = "/api/crons/run";
|
||||
static const stopTasks = "/api/crons/stop";
|
||||
static const taskDetail = "/api/crons/";
|
||||
static const addTask = "/api/crons";
|
||||
static const pinTask = "/api/crons/pin";
|
||||
static const unpinTask = "/api/crons/unpin";
|
||||
static const enableTask = "/api/crons/enable";
|
||||
static const disableTask = "/api/crons/disable";
|
||||
static const files = "/api/configs/files";
|
||||
static const configContent = "/api/configs/";
|
||||
static const saveFile = "/api/configs/save";
|
||||
static const envs = "/api/envs";
|
||||
static const addEnv = "/api/envs";
|
||||
static const delEnv = "/api/envs";
|
||||
static const disableEnvs = "/api/envs/disable";
|
||||
static const enableEnvs = "/api/envs/enable";
|
||||
static const loginLog = "/api/user/login-log";
|
||||
static const taskLog = "/api/logs";
|
||||
static const taskLogDetail = "/api/logs/";
|
||||
static const scripts = "/api/scripts/files";
|
||||
static const scriptDetail = "/api/scripts/";
|
||||
static const dependencies = "/api/dependencies";
|
||||
static const dependencyReinstall = "/api/dependencies/reinstall";
|
||||
static get login => "/api/user/login";
|
||||
static get loginTwo => "/api/user/two-factor/login";
|
||||
static const loginByClientId = "/open/auth/token";
|
||||
static const user = "/api/user";
|
||||
|
||||
static const updatePassword = "/api/user";
|
||||
|
||||
static get tasks => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons" : "/api/crons";
|
||||
|
||||
static get runTasks => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/run" : "/api/crons/run";
|
||||
|
||||
static get stopTasks => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/stop" : "/api/crons/stop";
|
||||
|
||||
static get taskDetail => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/" : "/api/crons/";
|
||||
|
||||
static get addTask => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons" : "/api/crons";
|
||||
|
||||
static get pinTask => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/pin" : "/api/crons/pin";
|
||||
|
||||
static get unpinTask => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/unpin" : "/api/crons/unpin";
|
||||
|
||||
static get enableTask => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/enable" : "/api/crons/enable";
|
||||
|
||||
static get disableTask => getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/disable" : "/api/crons/disable";
|
||||
|
||||
static get files => getIt<UserInfoViewModel>().useSecretLogined ? "/open/configs/files" : "/api/configs/files";
|
||||
|
||||
static get configContent => getIt<UserInfoViewModel>().useSecretLogined ? "/open/configs/" : "/api/configs/";
|
||||
|
||||
static get saveFile => getIt<UserInfoViewModel>().useSecretLogined ? "/open/configs/save" : "/api/configs/save";
|
||||
|
||||
static get envs => getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs" : "/api/envs";
|
||||
|
||||
static get addEnv => getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs" : "/api/envs";
|
||||
|
||||
static get delEnv => getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs" : "/api/envs";
|
||||
|
||||
static get disableEnvs => getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs/disable" : "/api/envs/disable";
|
||||
|
||||
static get enableEnvs => getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs/enable" : "/api/envs/enable";
|
||||
|
||||
static get loginLog => getIt<UserInfoViewModel>().useSecretLogined ? "/open/user/login-log" : "/api/user/login-log";
|
||||
|
||||
static get taskLog => getIt<UserInfoViewModel>().useSecretLogined ? "/open/logs" : "/api/logs";
|
||||
|
||||
static get taskLogDetail => getIt<UserInfoViewModel>().useSecretLogined ? "/open/logs/" : "/api/logs/";
|
||||
|
||||
static get scripts => getIt<UserInfoViewModel>().useSecretLogined ? "/open/scripts/files" : "/api/scripts/files";
|
||||
static get scriptUpdate => getIt<UserInfoViewModel>().useSecretLogined ? "/open/scripts" : "/api/scripts";
|
||||
|
||||
static get scriptDetail => getIt<UserInfoViewModel>().useSecretLogined ? "/open/scripts/" : "/api/scripts/";
|
||||
|
||||
static get dependencies => getIt<UserInfoViewModel>().useSecretLogined ? "/open/dependencies" : "/api/dependencies";
|
||||
|
||||
static get dependencyReinstall => getIt<UserInfoViewModel>().useSecretLogined ? "/open/dependencies/reinstall" : "/api/dependencies/reinstall";
|
||||
|
||||
|
||||
|
||||
|
||||
static intimeLog(String cronId) {
|
||||
return "/api/crons/$cronId/log";
|
||||
return getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/$cronId/log" : "/api/crons/$cronId/log";
|
||||
}
|
||||
|
||||
static envMove(String envId) {
|
||||
return "/api/envs/$envId/move";
|
||||
return getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs/$envId/move" : "/api/envs/$envId/move";
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,16 +20,23 @@ class QlAppBar extends StatelessWidget with PreferredSizeWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget back = backWidget ??
|
||||
GestureDetector(
|
||||
InkWell(
|
||||
onTap: () {
|
||||
if (backCall != null) {
|
||||
backCall!();
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
CupertinoIcons.left_chevron,
|
||||
color: Colors.white,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
CupertinoIcons.left_chevron,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -3,22 +3,29 @@ import 'package:flutter/material.dart';
|
||||
import 'package:qinglong_app/module/config/config_edit_page.dart';
|
||||
import 'package:qinglong_app/module/env/add_env_page.dart';
|
||||
import 'package:qinglong_app/module/env/env_bean.dart';
|
||||
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/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/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/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/update_password_page.dart';
|
||||
import 'package:qinglong_app/module/task/add_task_page.dart';
|
||||
import 'package:qinglong_app/module/task/task_bean.dart';
|
||||
import 'package:qinglong_app/module/task/task_detail/task_detail_page.dart';
|
||||
|
||||
class Routes {
|
||||
static const String routeHomePage = "/home/homepage";
|
||||
static const String routeLogin = "/login";
|
||||
static const String routeAddTask = "/task/add";
|
||||
static const String routeTaskDetail = "/task/detail";
|
||||
static const String routeEnvDetail = "/env/detail";
|
||||
static const String routeAddDependency = "/task/dependency";
|
||||
static const String routeAddEnv = "/env/add";
|
||||
static const String routeConfigEdit = "/config/edit";
|
||||
@ -27,7 +34,11 @@ class Routes {
|
||||
static const String routeTaskLogDetail = "/log/taskDetail";
|
||||
static const String routeScript = "/script";
|
||||
static const String routeScriptDetail = "/script/detail";
|
||||
static const String routeScriptUpdate = "/script/update";
|
||||
static const String routeScriptAdd = "/script/add";
|
||||
static const String routeDependency = "/Dependency";
|
||||
static const String routeUpdatePassword = "/updatePassword";
|
||||
static const String routeAbout = "/about";
|
||||
|
||||
static Route<dynamic>? generateRoute(RouteSettings settings) {
|
||||
switch (settings.name) {
|
||||
@ -45,12 +56,12 @@ class Routes {
|
||||
return CupertinoPageRoute(builder: (context) => const AddTaskPage());
|
||||
}
|
||||
case routeAddDependency:
|
||||
return CupertinoPageRoute(builder: (context) => const AddDependenyPage());
|
||||
return CupertinoPageRoute(builder: (context) => const AddDependencyPage());
|
||||
case routeAddEnv:
|
||||
if (settings.arguments != null) {
|
||||
return CupertinoPageRoute(
|
||||
builder: (context) => AddEnvPage(
|
||||
taskBean: settings.arguments as EnvBean,
|
||||
envBean: settings.arguments as EnvBean,
|
||||
));
|
||||
} else {
|
||||
return CupertinoPageRoute(builder: (context) => const AddEnvPage());
|
||||
@ -91,6 +102,34 @@ class Routes {
|
||||
path: (settings.arguments as Map)["path"],
|
||||
),
|
||||
);
|
||||
case routeTaskDetail:
|
||||
return CupertinoPageRoute(
|
||||
builder: (context) => TaskDetailPage(
|
||||
settings.arguments as TaskBean,
|
||||
),
|
||||
);
|
||||
case routeEnvDetail:
|
||||
return CupertinoPageRoute(
|
||||
builder: (context) => EnvDetailPage(
|
||||
settings.arguments as EnvBean,
|
||||
),
|
||||
);
|
||||
case routeUpdatePassword:
|
||||
return CupertinoPageRoute(
|
||||
builder: (context) => const UpdatePasswordPage(),
|
||||
);
|
||||
case routeAbout:
|
||||
return CupertinoPageRoute(
|
||||
builder: (context) => const AboutPage(),
|
||||
);
|
||||
case routeScriptUpdate:
|
||||
return CupertinoPageRoute(
|
||||
builder: (context) => ScriptEditPage(
|
||||
(settings.arguments as Map)["title"],
|
||||
(settings.arguments as Map)["path"],
|
||||
(settings.arguments as Map)["content"],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@ -3,3 +3,4 @@ String spHost = "host";
|
||||
String spUserName = "username";
|
||||
String spPassWord = "password";
|
||||
String spTheme = "dart_mode";
|
||||
String spSecretLogined = "secret_logined";
|
||||
|
||||
@ -13,13 +13,14 @@ get primaryColor => _primaryColor;
|
||||
|
||||
class ThemeViewModel extends ChangeNotifier {
|
||||
ThemeData currentTheme = lightTheme;
|
||||
bool _isInDarkMode = false;
|
||||
|
||||
ThemeColors themeColor = LightThemeColors();
|
||||
|
||||
ThemeViewModel() {
|
||||
var brightness = SchedulerBinding.instance!.window.platformBrightness;
|
||||
bool isDarkMode = brightness == Brightness.dark;
|
||||
changeThemeReal(isDarkMode, false);
|
||||
_isInDarkMode = brightness == Brightness.dark;
|
||||
changeThemeReal(_isInDarkMode, false);
|
||||
}
|
||||
|
||||
bool isInDartMode() {
|
||||
@ -27,6 +28,7 @@ class ThemeViewModel extends ChangeNotifier {
|
||||
}
|
||||
|
||||
void changeThemeReal(bool dark, [bool notify = true]) {
|
||||
_isInDarkMode = dark;
|
||||
SpUtil.putBool(spTheme, dark);
|
||||
if (!dark) {
|
||||
currentTheme = lightTheme;
|
||||
@ -40,6 +42,8 @@ class ThemeViewModel extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
get darkMode => _isInDarkMode;
|
||||
|
||||
void changeTheme() {
|
||||
changeThemeReal(!SpUtil.getBool(spTheme, defValue: false), true);
|
||||
}
|
||||
@ -63,6 +67,16 @@ ThemeData darkTheme = ThemeData.dark().copyWith(
|
||||
color: _primaryColor,
|
||||
),
|
||||
),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xff999999),
|
||||
),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xff999999),
|
||||
),
|
||||
),
|
||||
),
|
||||
tabBarTheme: const TabBarTheme(
|
||||
labelStyle: TextStyle(
|
||||
@ -92,6 +106,11 @@ ThemeData darkTheme = ThemeData.dark().copyWith(
|
||||
},
|
||||
),
|
||||
),
|
||||
cupertinoOverrideTheme: const NoDefaultCupertinoThemeData(
|
||||
brightness: Brightness.dark,
|
||||
primaryColor: Color(0xffffffff),
|
||||
scaffoldBackgroundColor: Colors.black,
|
||||
),
|
||||
);
|
||||
ThemeData lightTheme = ThemeData.light().copyWith(
|
||||
brightness: Brightness.light,
|
||||
@ -108,6 +127,16 @@ ThemeData lightTheme = ThemeData.light().copyWith(
|
||||
color: _primaryColor,
|
||||
),
|
||||
),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xff999999),
|
||||
),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xff999999),
|
||||
),
|
||||
),
|
||||
),
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: _primaryColor,
|
||||
@ -152,12 +181,17 @@ ThemeData lightTheme = ThemeData.light().copyWith(
|
||||
},
|
||||
),
|
||||
),
|
||||
cupertinoOverrideTheme: const NoDefaultCupertinoThemeData(
|
||||
brightness: Brightness.light,
|
||||
primaryColor: _primaryColor,
|
||||
scaffoldBackgroundColor: Color(0xfff5f5f5),
|
||||
),
|
||||
);
|
||||
|
||||
abstract class ThemeColors {
|
||||
Color settingBgColor();
|
||||
|
||||
Color taskTitleColor();
|
||||
Color titleColor();
|
||||
|
||||
Color descColor();
|
||||
|
||||
@ -170,7 +204,7 @@ abstract class ThemeColors {
|
||||
|
||||
class LightThemeColors extends ThemeColors {
|
||||
@override
|
||||
Color taskTitleColor() {
|
||||
Color titleColor() {
|
||||
return const Color(0xff333333);
|
||||
}
|
||||
|
||||
@ -202,7 +236,7 @@ class LightThemeColors extends ThemeColors {
|
||||
|
||||
class DartThemeColors extends ThemeColors {
|
||||
@override
|
||||
Color taskTitleColor() {
|
||||
Color titleColor() {
|
||||
return Colors.white;
|
||||
}
|
||||
|
||||
@ -223,7 +257,7 @@ class DartThemeColors extends ThemeColors {
|
||||
|
||||
@override
|
||||
Color settingBgColor() {
|
||||
return Colors.black12;
|
||||
return Colors.black;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
1216
lib/base/ui/ql_context_menu.dart
Normal file
1216
lib/base/ui/ql_context_menu.dart
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,12 +7,16 @@ class UserInfoViewModel {
|
||||
String? _host = "";
|
||||
String? _userName;
|
||||
String? _passWord;
|
||||
bool _useSecertLogined = false;
|
||||
|
||||
UserInfoViewModel() {
|
||||
String userInfoJson = SpUtil.getString(spUserInfo);
|
||||
_userName = SpUtil.getString(spUserName);
|
||||
_passWord = SpUtil.getString(spPassWord);
|
||||
|
||||
_useSecertLogined = SpUtil.getBool(spSecretLogined, defValue: false);
|
||||
_host = SpUtil.getString(spHost, defValue: '');
|
||||
|
||||
if (userInfoJson.isNotEmpty) {
|
||||
_token = userInfoJson;
|
||||
}
|
||||
@ -30,6 +34,11 @@ class UserInfoViewModel {
|
||||
SpUtil.putString(spPassWord, password);
|
||||
}
|
||||
|
||||
void useSecretLogin(bool use) {
|
||||
_useSecertLogined = use;
|
||||
SpUtil.putBool(spSecretLogined, _useSecertLogined);
|
||||
}
|
||||
|
||||
void updateHost(String host) {
|
||||
_host = host;
|
||||
SpUtil.putString(spHost, host);
|
||||
@ -43,6 +52,8 @@ class UserInfoViewModel {
|
||||
|
||||
String? get passWord => _passWord;
|
||||
|
||||
bool get useSecretLogined => _useSecertLogined;
|
||||
|
||||
bool isLogined() {
|
||||
return token != null && token!.isNotEmpty;
|
||||
}
|
||||
|
||||
121
lib/json.jc.dart
121
lib/json.jc.dart
@ -3,15 +3,17 @@
|
||||
import 'package:qinglong_app/module/config/config_bean.dart';
|
||||
import 'package:qinglong_app/module/env/env_bean.dart';
|
||||
import 'package:qinglong_app/module/login/login_bean.dart';
|
||||
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/task_log/task_log_bean.dart';
|
||||
import 'package:qinglong_app/module/task/task_bean.dart';
|
||||
import 'package:qinglong_app/module/task/task_detail/task_detail_bean.dart';
|
||||
|
||||
|
||||
class JsonConversion$Json {
|
||||
static M fromJson<M>(dynamic json) {
|
||||
|
||||
static M fromJson<M>(dynamic json) {
|
||||
if (json is List) {
|
||||
return _getListChildType<M>(json);
|
||||
} else {
|
||||
@ -20,94 +22,83 @@ class JsonConversion$Json {
|
||||
}
|
||||
|
||||
static M _fromJsonSingle<M>(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 == (EnvBean).toString()){
|
||||
return EnvBean.jsonConversion(json) as M;
|
||||
}
|
||||
|
||||
if (type == (LoginBean).toString()) {
|
||||
return LoginBean.jsonConversion(json) as M;
|
||||
|
||||
if(type == (LoginBean).toString()){
|
||||
return LoginBean.jsonConversion(json) as M;
|
||||
}
|
||||
|
||||
if (type == (DependencyBean).toString()) {
|
||||
return DependencyBean.jsonConversion(json) as M;
|
||||
|
||||
if(type == (UserBean).toString()){
|
||||
return UserBean.jsonConversion(json) as M;
|
||||
}
|
||||
|
||||
if (type == (LoginLogBean).toString()) {
|
||||
return LoginLogBean.jsonConversion(json) as M;
|
||||
|
||||
if(type == (DependencyBean).toString()){
|
||||
return DependencyBean.jsonConversion(json) as M;
|
||||
}
|
||||
|
||||
if (type == (ScriptBean).toString()) {
|
||||
return ScriptBean.jsonConversion(json) as M;
|
||||
|
||||
if(type == (LoginLogBean).toString()){
|
||||
return LoginLogBean.jsonConversion(json) as M;
|
||||
}
|
||||
|
||||
if (type == (TaskLogBean).toString()) {
|
||||
return TaskLogBean.jsonConversion(json) as M;
|
||||
|
||||
if(type == (ScriptBean).toString()){
|
||||
return ScriptBean.jsonConversion(json) as M;
|
||||
}
|
||||
|
||||
if (type == (TaskBean).toString()) {
|
||||
return TaskBean.jsonConversion(json) as M;
|
||||
|
||||
if(type == (TaskLogBean).toString()){
|
||||
return TaskLogBean.jsonConversion(json) as M;
|
||||
}
|
||||
|
||||
if (type == (TaskDetailBean).toString()) {
|
||||
return TaskDetailBean.jsonConversion(json) as M;
|
||||
|
||||
if(type == (TaskBean).toString()){
|
||||
return TaskBean.jsonConversion(json) as M;
|
||||
}
|
||||
|
||||
throw Exception("not found");
|
||||
}
|
||||
|
||||
static M _getListChildType<M>(List<dynamic> data) {
|
||||
if (<ConfigBean>[] is M) {
|
||||
return data.map<ConfigBean>((e) => ConfigBean.jsonConversion(e)).toList()
|
||||
as M;
|
||||
if(<ConfigBean>[] is M){
|
||||
return data.map<ConfigBean>((e) => ConfigBean.jsonConversion(e)).toList() as M;
|
||||
}
|
||||
|
||||
if (<EnvBean>[] is M) {
|
||||
|
||||
if(<EnvBean>[] is M){
|
||||
return data.map<EnvBean>((e) => EnvBean.jsonConversion(e)).toList() as M;
|
||||
}
|
||||
|
||||
if (<LoginBean>[] is M) {
|
||||
return data.map<LoginBean>((e) => LoginBean.jsonConversion(e)).toList()
|
||||
as M;
|
||||
|
||||
if(<LoginBean>[] is M){
|
||||
return data.map<LoginBean>((e) => LoginBean.jsonConversion(e)).toList() as M;
|
||||
}
|
||||
|
||||
if (<DependencyBean>[] is M) {
|
||||
return data
|
||||
.map<DependencyBean>((e) => DependencyBean.jsonConversion(e))
|
||||
.toList() as M;
|
||||
|
||||
if(<UserBean>[] is M){
|
||||
return data.map<UserBean>((e) => UserBean.jsonConversion(e)).toList() as M;
|
||||
}
|
||||
|
||||
if (<LoginLogBean>[] is M) {
|
||||
return data
|
||||
.map<LoginLogBean>((e) => LoginLogBean.jsonConversion(e))
|
||||
.toList() as M;
|
||||
|
||||
if(<DependencyBean>[] is M){
|
||||
return data.map<DependencyBean>((e) => DependencyBean.jsonConversion(e)).toList() as M;
|
||||
}
|
||||
|
||||
if (<ScriptBean>[] is M) {
|
||||
return data.map<ScriptBean>((e) => ScriptBean.jsonConversion(e)).toList()
|
||||
as M;
|
||||
|
||||
if(<LoginLogBean>[] is M){
|
||||
return data.map<LoginLogBean>((e) => LoginLogBean.jsonConversion(e)).toList() as M;
|
||||
}
|
||||
|
||||
if (<TaskLogBean>[] is M) {
|
||||
return data
|
||||
.map<TaskLogBean>((e) => TaskLogBean.jsonConversion(e))
|
||||
.toList() as M;
|
||||
|
||||
if(<ScriptBean>[] is M){
|
||||
return data.map<ScriptBean>((e) => ScriptBean.jsonConversion(e)).toList() as M;
|
||||
}
|
||||
|
||||
if (<TaskBean>[] is M) {
|
||||
return data.map<TaskBean>((e) => TaskBean.jsonConversion(e)).toList()
|
||||
as M;
|
||||
|
||||
if(<TaskLogBean>[] is M){
|
||||
return data.map<TaskLogBean>((e) => TaskLogBean.jsonConversion(e)).toList() as M;
|
||||
}
|
||||
|
||||
if (<TaskDetailBean>[] is M) {
|
||||
return data
|
||||
.map<TaskDetailBean>((e) => TaskDetailBean.jsonConversion(e))
|
||||
.toList() as M;
|
||||
|
||||
if(<TaskBean>[] is M){
|
||||
return data.map<TaskBean>((e) => TaskBean.jsonConversion(e)).toList() as M;
|
||||
}
|
||||
|
||||
throw Exception("not found");
|
||||
|
||||
@ -9,7 +9,6 @@ import 'package:get_it/get_it.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:qinglong_app/base/theme.dart';
|
||||
import 'package:qinglong_app/module/login/login_page.dart';
|
||||
import 'package:qinglong_app/utils/QlNavigatorObserver.dart';
|
||||
import 'package:qinglong_app/utils/sp_utils.dart';
|
||||
|
||||
import 'base/routes.dart';
|
||||
@ -25,14 +24,13 @@ void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await SpUtil.getInstance();
|
||||
getIt.registerSingleton<UserInfoViewModel>(UserInfoViewModel());
|
||||
getIt.registerSingleton<QlNavigatorObserver>(QlNavigatorObserver());
|
||||
|
||||
runApp(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
themeProvider,
|
||||
],
|
||||
child: const MyApp(),
|
||||
child: const QlApp(),
|
||||
),
|
||||
);
|
||||
if (Platform.isAndroid) {
|
||||
@ -41,33 +39,40 @@ void main() async {
|
||||
}
|
||||
}
|
||||
|
||||
class MyApp extends ConsumerWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
class QlApp extends ConsumerStatefulWidget {
|
||||
const QlApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
ConsumerState<QlApp> createState() => QlAppState();
|
||||
}
|
||||
|
||||
class QlAppState extends ConsumerState<QlApp> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
},
|
||||
child: MaterialApp(
|
||||
locale: const Locale('zh', 'cn'),
|
||||
navigatorObservers: [getIt<QlNavigatorObserver>()],
|
||||
navigatorKey: navigatorState,
|
||||
theme: ref.watch<ThemeViewModel>(themeProvider).currentTheme,
|
||||
onGenerateRoute: (setting) {
|
||||
return Routes.generateRoute(setting);
|
||||
},
|
||||
home: Builder(
|
||||
builder: (context) {
|
||||
if (!kReleaseMode) {
|
||||
showDebugBtn(context, btnColor: Colors.blue);
|
||||
}
|
||||
return getIt<UserInfoViewModel>().isLogined() ? const HomePage() : LoginPage();
|
||||
child: MediaQuery(
|
||||
data: MediaQueryData.fromWindow(WidgetsBinding.instance!.window).copyWith(textScaleFactor: 1,),
|
||||
child: MaterialApp(
|
||||
locale: const Locale('zh', 'cn'),
|
||||
navigatorKey: navigatorState,
|
||||
theme: ref.watch<ThemeViewModel>(themeProvider).currentTheme,
|
||||
onGenerateRoute: (setting) {
|
||||
return Routes.generateRoute(setting);
|
||||
},
|
||||
home: Builder(
|
||||
builder: (context) {
|
||||
if (!kReleaseMode) {
|
||||
showDebugBtn(context, btnColor: Colors.blue);
|
||||
}
|
||||
return getIt<UserInfoViewModel>().isLogined() ? const HomePage() : const LoginPage();
|
||||
},
|
||||
),
|
||||
// home: LoginPage(),
|
||||
),
|
||||
// home: LoginPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -18,14 +18,18 @@ class ConfigEditPage extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
|
||||
String? value;
|
||||
|
||||
late TextEditingController _controller;
|
||||
FocusNode node = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_controller = TextEditingController(text: widget.content);
|
||||
super.initState();
|
||||
WidgetsBinding.instance?.addPostFrameCallback(
|
||||
(timeStamp) {
|
||||
node.requestFocus();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -40,13 +44,10 @@ class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
|
||||
actions: [
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
if (value == null) {
|
||||
"请先点击保存".toast();
|
||||
return;
|
||||
}
|
||||
HttpResponse<NullResponse> response =
|
||||
await Api.saveFile(widget.title, _controller.text);
|
||||
if (response.success) {
|
||||
"提交成功".toast();
|
||||
ref.read(configProvider).loadContent(widget.title);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
@ -58,7 +59,13 @@ class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Center(
|
||||
child: Text("提交"),
|
||||
child: Text(
|
||||
"提交",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -71,7 +78,7 @@ class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: TextField(
|
||||
focusNode: FocusNode(),
|
||||
focusNode: node,
|
||||
style: TextStyle(
|
||||
color: ref.read(themeProvider).themeColor.descColor(),
|
||||
fontSize: 14,
|
||||
|
||||
35
lib/module/env/add_env_page.dart
vendored
35
lib/module/env/add_env_page.dart
vendored
@ -8,9 +8,9 @@ import 'package:qinglong_app/module/env/env_viewmodel.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
|
||||
class AddEnvPage extends ConsumerStatefulWidget {
|
||||
final EnvBean? taskBean;
|
||||
final EnvBean? envBean;
|
||||
|
||||
const AddEnvPage({Key? key, this.taskBean}) : super(key: key);
|
||||
const AddEnvPage({Key? key, this.envBean}) : super(key: key);
|
||||
|
||||
@override
|
||||
ConsumerState<AddEnvPage> createState() => _AddEnvPageState();
|
||||
@ -22,18 +22,24 @@ class _AddEnvPageState extends ConsumerState<AddEnvPage> {
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _valueController = TextEditingController();
|
||||
final TextEditingController _remarkController = TextEditingController();
|
||||
FocusNode focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.taskBean != null) {
|
||||
envBean = widget.taskBean!;
|
||||
if (widget.envBean != null) {
|
||||
envBean = widget.envBean!;
|
||||
_nameController.text = envBean.name ?? "";
|
||||
_valueController.text = envBean.value ?? "";
|
||||
_remarkController.text = envBean.remarks ?? "";
|
||||
} else {
|
||||
envBean = EnvBean();
|
||||
}
|
||||
WidgetsBinding.instance?.addPostFrameCallback(
|
||||
(timeStamp) {
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -82,7 +88,7 @@ class _AddEnvPageState extends ConsumerState<AddEnvPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
height: 15,
|
||||
),
|
||||
const Text(
|
||||
"名称:",
|
||||
@ -91,13 +97,10 @@ class _AddEnvPageState extends ConsumerState<AddEnvPage> {
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
TextField(
|
||||
focusNode: focusNode,
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入名称",
|
||||
),
|
||||
autofocus: false,
|
||||
@ -114,7 +117,7 @@ class _AddEnvPageState extends ConsumerState<AddEnvPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
height: 15,
|
||||
),
|
||||
const Text(
|
||||
"值:",
|
||||
@ -123,15 +126,11 @@ class _AddEnvPageState extends ConsumerState<AddEnvPage> {
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
TextField(
|
||||
controller: _valueController,
|
||||
maxLines: 8,
|
||||
minLines: 1,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入值",
|
||||
),
|
||||
autofocus: false,
|
||||
@ -148,7 +147,7 @@ class _AddEnvPageState extends ConsumerState<AddEnvPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
height: 15,
|
||||
),
|
||||
const Text(
|
||||
"备注:",
|
||||
@ -157,13 +156,9 @@ class _AddEnvPageState extends ConsumerState<AddEnvPage> {
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
TextField(
|
||||
controller: _remarkController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入备注",
|
||||
),
|
||||
autofocus: false,
|
||||
@ -195,7 +190,7 @@ class _AddEnvPageState extends ConsumerState<AddEnvPage> {
|
||||
id: envBean.sId);
|
||||
|
||||
if (response.success) {
|
||||
"操作成功".toast();
|
||||
(envBean.sId == null) ? "新增成功" : "修改成功".toast();
|
||||
ref.read(envProvider).updateEnv(envBean);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
|
||||
341
lib/module/env/env_detail_page.dart
vendored
Normal file
341
lib/module/env/env_detail_page.dart
vendored
Normal file
@ -0,0 +1,341 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||
import 'package:qinglong_app/base/routes.dart';
|
||||
import 'package:qinglong_app/base/theme.dart';
|
||||
import 'package:qinglong_app/module/env/env_bean.dart';
|
||||
import 'package:qinglong_app/module/env/env_viewmodel.dart';
|
||||
import 'package:qinglong_app/utils/utils.dart';
|
||||
|
||||
class EnvDetailPage extends ConsumerStatefulWidget {
|
||||
final EnvBean envBean;
|
||||
|
||||
const EnvDetailPage(this.envBean, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_TaskDetailPageState createState() => _TaskDetailPageState();
|
||||
}
|
||||
|
||||
class _TaskDetailPageState extends ConsumerState<EnvDetailPage> {
|
||||
List<Widget> actions = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
actions.clear();
|
||||
actions.addAll(
|
||||
[
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pushNamed(Routes.routeAddEnv, arguments: widget.envBean);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: const Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
"编辑",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
enableTask();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
widget.envBean.status! == 0 ? "禁用" : "启用",
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
return Scaffold(
|
||||
appBar: QlAppBar(
|
||||
canBack: true,
|
||||
backCall: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
title: widget.envBean.name ?? "",
|
||||
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: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
vertical: 15,
|
||||
),
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
EnvDetailCell(
|
||||
title: "ID",
|
||||
desc: widget.envBean.sId ?? "",
|
||||
),
|
||||
EnvDetailCell(
|
||||
title: "变量名称",
|
||||
desc: widget.envBean.name ?? "",
|
||||
),
|
||||
EnvDetailCell(
|
||||
title: "创建时间",
|
||||
desc: Utils.formatMessageTime(widget.envBean.created ?? 0),
|
||||
),
|
||||
EnvDetailCell(
|
||||
title: "更新时间",
|
||||
desc: Utils.formatGMTTime(widget.envBean.timestamp ?? ""),
|
||||
),
|
||||
EnvDetailCell(
|
||||
title: "值",
|
||||
desc: widget.envBean.value ?? "",
|
||||
),
|
||||
EnvDetailCell(
|
||||
title: "备注",
|
||||
desc: widget.envBean.remarks ?? "",
|
||||
),
|
||||
EnvDetailCell(
|
||||
title: "变量状态",
|
||||
desc: widget.envBean.status == 1 ? "已禁用" : "已启用",
|
||||
hideDivide: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width - 80,
|
||||
child: CupertinoButton(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 5,
|
||||
),
|
||||
color: Colors.red,
|
||||
child: const Text(
|
||||
"删 除",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
delTask(context, ref);
|
||||
}),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void enableTask() async {
|
||||
await ref.read(envProvider).enableEnv(widget.envBean.sId!, widget.envBean.status!);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void delTask(BuildContext context, WidgetRef ref) {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: const Text("确认删除"),
|
||||
content: Text("确认删除环境变量 ${widget.envBean.name ?? ""} 吗"),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text(
|
||||
"取消",
|
||||
style: TextStyle(
|
||||
color: Color(0xff999999),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: Text(
|
||||
"确定",
|
||||
style: TextStyle(
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
await ref.read(envProvider).delEnv(widget.envBean.sId!);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EnvDetailCell extends ConsumerWidget {
|
||||
final String title;
|
||||
final String? desc;
|
||||
final Widget? icon;
|
||||
final bool hideDivide;
|
||||
final Function? taped;
|
||||
|
||||
const EnvDetailCell({
|
||||
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,
|
||||
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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
287
lib/module/env/env_page.dart
vendored
287
lib/module/env/env_page.dart
vendored
@ -11,6 +11,7 @@ import 'package:qinglong_app/base/ui/empty_widget.dart';
|
||||
import 'package:qinglong_app/module/env/env_bean.dart';
|
||||
import 'package:qinglong_app/module/env/env_viewmodel.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
import 'package:qinglong_app/utils/utils.dart';
|
||||
|
||||
class EnvPage extends StatefulWidget {
|
||||
const EnvPage({Key? key}) : super(key: key);
|
||||
@ -28,13 +29,15 @@ class _EnvPageState extends State<EnvPage> {
|
||||
builder: (ref, model, child) {
|
||||
List<EnvItemCell> list = [];
|
||||
|
||||
for (var value in model.list) {
|
||||
for (int i = 0; i < model.list.length; i++) {
|
||||
EnvBean value = model.list[i];
|
||||
if (_searchController.text.isEmpty ||
|
||||
(value.name?.contains(_searchController.text) ?? false) ||
|
||||
(value.value?.contains(_searchController.text) ?? false) ||
|
||||
(value.remarks?.contains(_searchController.text) ?? false)) {
|
||||
list.add(EnvItemCell(
|
||||
value,
|
||||
i + 1,
|
||||
ref,
|
||||
key: ValueKey(value.sId),
|
||||
));
|
||||
@ -49,8 +52,7 @@ class _EnvPageState extends State<EnvPage> {
|
||||
return model.loadData(false);
|
||||
},
|
||||
child: ReorderableListView(
|
||||
keyboardDismissBehavior:
|
||||
ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
header: searchCell(ref),
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
if (list.length != model.list.length) {
|
||||
@ -124,134 +126,179 @@ class _EnvPageState extends State<EnvPage> {
|
||||
|
||||
class EnvItemCell extends StatelessWidget {
|
||||
final EnvBean bean;
|
||||
final int index;
|
||||
final WidgetRef ref;
|
||||
|
||||
const EnvItemCell(this.bean, this.ref, {Key? key}) : super(key: key);
|
||||
const EnvItemCell(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.sId),
|
||||
endActionPane: ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
extentRatio: 0.45,
|
||||
children: [
|
||||
SlidableAction(
|
||||
backgroundColor: Colors.grey,
|
||||
flex: 1,
|
||||
onPressed: (_) {
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.routeAddEnv, arguments: bean);
|
||||
},
|
||||
foregroundColor: Colors.white,
|
||||
icon: CupertinoIcons.pencil,
|
||||
),
|
||||
SlidableAction(
|
||||
backgroundColor: Colors.orange,
|
||||
flex: 1,
|
||||
onPressed: (_) {
|
||||
enableEnv();
|
||||
},
|
||||
foregroundColor: Colors.white,
|
||||
icon: bean.status == 0
|
||||
? Icons.dnd_forwardslash
|
||||
: Icons.check_circle_outline_sharp,
|
||||
),
|
||||
SlidableAction(
|
||||
backgroundColor: Colors.red,
|
||||
flex: 1,
|
||||
onPressed: (_) {
|
||||
delEnv(context, ref);
|
||||
},
|
||||
foregroundColor: Colors.white,
|
||||
icon: CupertinoIcons.delete,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.routeEnvDetail, arguments: bean);
|
||||
},
|
||||
child: ColoredBox(
|
||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
||||
child: Slidable(
|
||||
key: ValueKey(bean.sId),
|
||||
endActionPane: ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
extentRatio: 0.45,
|
||||
children: [
|
||||
Container(
|
||||
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: Material(
|
||||
SlidableAction(
|
||||
backgroundColor: Colors.grey,
|
||||
flex: 1,
|
||||
onPressed: (_) {
|
||||
Navigator.of(context).pushNamed(Routes.routeAddEnv, arguments: bean);
|
||||
},
|
||||
foregroundColor: Colors.white,
|
||||
icon: CupertinoIcons.pencil,
|
||||
),
|
||||
SlidableAction(
|
||||
backgroundColor: Colors.orange,
|
||||
flex: 1,
|
||||
onPressed: (_) {
|
||||
enableEnv();
|
||||
},
|
||||
foregroundColor: Colors.white,
|
||||
icon: bean.status == 0 ? Icons.dnd_forwardslash : Icons.check_circle_outline_sharp,
|
||||
),
|
||||
SlidableAction(
|
||||
backgroundColor: Colors.red,
|
||||
flex: 1,
|
||||
onPressed: (_) {
|
||||
delEnv(context, ref);
|
||||
},
|
||||
foregroundColor: Colors.white,
|
||||
icon: CupertinoIcons.delete,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
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(
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
bean.name ?? "",
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
bean.name ?? "",
|
||||
Utils.formatGMTTime(bean.timestamp ?? ""),
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: bean.status == 1
|
||||
? const Color(0xffF85152)
|
||||
: ref
|
||||
.watch(themeProvider)
|
||||
.themeColor
|
||||
.taskTitleColor(),
|
||||
fontSize: 18,
|
||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
bean.remarks ?? "-",
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: ref
|
||||
.watch(themeProvider)
|
||||
.themeColor
|
||||
.descColor(),
|
||||
fontSize: 12,
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 5,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
border: Border.all(color: primaryColor, width: 1),
|
||||
),
|
||||
child: Text(
|
||||
"$index",
|
||||
style: TextStyle(color: primaryColor, fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
bean.value ?? "",
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color:
|
||||
ref.watch(themeProvider).themeColor.descColor(),
|
||||
fontSize: 12,
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
bean.remarks ?? "-",
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
bean.status == 1
|
||||
? const Icon(
|
||||
Icons.dnd_forwardslash,
|
||||
size: 12,
|
||||
color: Colors.red,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
bean.value ?? "",
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(
|
||||
height: 1,
|
||||
indent: 15,
|
||||
),
|
||||
],
|
||||
const Divider(
|
||||
height: 1,
|
||||
indent: 15,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -270,13 +317,23 @@ class EnvItemCell extends StatelessWidget {
|
||||
content: Text("确认删除环境变量 ${bean.name ?? ""} 吗"),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text("取消"),
|
||||
child: const Text(
|
||||
"取消",
|
||||
style: TextStyle(
|
||||
color: Color(0xff999999),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: const Text("确定"),
|
||||
child: Text(
|
||||
"确定",
|
||||
style: TextStyle(
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
ref.read(envProvider).delEnv(bean.sId!);
|
||||
|
||||
6
lib/module/env/env_viewmodel.dart
vendored
6
lib/module/env/env_viewmodel.dart
vendored
@ -3,6 +3,7 @@ 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/env/env_bean.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
|
||||
var envProvider = ChangeNotifierProvider((ref) => EnvViewModel());
|
||||
|
||||
@ -29,6 +30,7 @@ class EnvViewModel extends BaseViewModel {
|
||||
Future<void> delEnv(String id) async {
|
||||
HttpResponse<NullResponse> result = await Api.delEnv(id);
|
||||
if (result.success) {
|
||||
"删除成功".toast();
|
||||
list.removeWhere((element) => element.sId == id);
|
||||
notifyListeners();
|
||||
} else {
|
||||
@ -48,11 +50,12 @@ class EnvViewModel extends BaseViewModel {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void enableEnv(String sId, int status) async {
|
||||
Future<void> enableEnv(String sId, int status) async {
|
||||
if (status == 1) {
|
||||
HttpResponse<NullResponse> response = await Api.enableEnv(sId);
|
||||
|
||||
if (response.success) {
|
||||
"启用成功".toast();
|
||||
list.firstWhere((element) => element.sId == sId).status = 0;
|
||||
success();
|
||||
} else {
|
||||
@ -62,6 +65,7 @@ class EnvViewModel extends BaseViewModel {
|
||||
HttpResponse<NullResponse> response = await Api.disableEnv(sId);
|
||||
|
||||
if (response.success) {
|
||||
"禁用成功".toast();
|
||||
list.firstWhere((element) => element.sId == sId).status = 1;
|
||||
success();
|
||||
} else {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
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/ql_app_bar.dart';
|
||||
import 'package:qinglong_app/base/routes.dart';
|
||||
@ -7,6 +8,7 @@ import 'package:qinglong_app/module/config/config_page.dart';
|
||||
import 'package:qinglong_app/module/env/env_page.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';
|
||||
|
||||
class HomePage extends ConsumerStatefulWidget {
|
||||
const HomePage({Key? key}) : super(key: key);
|
||||
@ -35,45 +37,49 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
List<Widget> actions = [];
|
||||
|
||||
if (_index == 0) {
|
||||
actions.add(InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeAddTask,
|
||||
);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
CupertinoIcons.add,
|
||||
size: 20,
|
||||
color: Colors.white,
|
||||
actions.add(
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeAddTask,
|
||||
);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
CupertinoIcons.add,
|
||||
size: 24,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
);
|
||||
} else if (_index == 1) {
|
||||
actions.add(InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeAddEnv,
|
||||
);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
CupertinoIcons.add,
|
||||
size: 20,
|
||||
color: Colors.white,
|
||||
actions.add(
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeAddEnv,
|
||||
);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
CupertinoIcons.add,
|
||||
size: 24,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
);
|
||||
} else if (_index == 2) {
|
||||
actions.add(InkWell(
|
||||
onTap: () {
|
||||
@ -96,48 +102,54 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
));
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: QlAppBar(
|
||||
canBack: false,
|
||||
title: _title,
|
||||
actions: actions,
|
||||
),
|
||||
body: IndexedStack(
|
||||
index: _index,
|
||||
children: [
|
||||
const Positioned.fill(
|
||||
child: TaskPage(),
|
||||
),
|
||||
const Positioned.fill(
|
||||
child: EnvPage(),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: ConfigPage(
|
||||
key: configKey,
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
await MoveToBackground.moveTaskToBack();
|
||||
return false;
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: QlAppBar(
|
||||
canBack: false,
|
||||
title: _title,
|
||||
actions: actions,
|
||||
),
|
||||
body: IndexedStack(
|
||||
index: _index,
|
||||
children: [
|
||||
const Positioned.fill(
|
||||
child: TaskPage(),
|
||||
),
|
||||
),
|
||||
const Positioned.fill(
|
||||
child: OtherPage(),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
items: titles
|
||||
.map(
|
||||
(e) => BottomNavigationBarItem(
|
||||
icon: Icon(e.icon),
|
||||
activeIcon: Icon(e.checkedIcon),
|
||||
label: e.title,
|
||||
const Positioned.fill(
|
||||
child: EnvPage(),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: ConfigPage(
|
||||
key: configKey,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
currentIndex: _index,
|
||||
onTap: (index) {
|
||||
_index = index;
|
||||
_title = titles[index].title;
|
||||
setState(() {});
|
||||
},
|
||||
type: BottomNavigationBarType.fixed,
|
||||
),
|
||||
const Positioned.fill(
|
||||
child: OtherPage(),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
items: titles
|
||||
.map(
|
||||
(e) => BottomNavigationBarItem(
|
||||
icon: Icon(e.icon),
|
||||
activeIcon: Icon(e.checkedIcon),
|
||||
label: e.title,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
currentIndex: _index,
|
||||
onTap: (index) {
|
||||
_index = index;
|
||||
_title = titles[index].title;
|
||||
setState(() {});
|
||||
},
|
||||
type: BottomNavigationBarType.fixed,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -7,8 +7,10 @@ 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/utils.dart';
|
||||
import 'package:flip_card/flip_card.dart';
|
||||
|
||||
import 'login_bean.dart';
|
||||
|
||||
@ -20,29 +22,45 @@ class LoginPage extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
final TextEditingController _hostController =
|
||||
TextEditingController(text: getIt<UserInfoViewModel>().host);
|
||||
final TextEditingController _hostController = TextEditingController(text: getIt<UserInfoViewModel>().host);
|
||||
final TextEditingController _userNameController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
final TextEditingController _cIdController = TextEditingController();
|
||||
final TextEditingController _cSecretController = TextEditingController();
|
||||
GlobalKey<FlipCardState> cardKey = GlobalKey<FlipCardState>();
|
||||
|
||||
bool rememberPassword = false;
|
||||
|
||||
bool useSecretLogin = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
useSecretLogin = getIt<UserInfoViewModel>().useSecretLogined;
|
||||
|
||||
if (getIt<UserInfoViewModel>().userName != null &&
|
||||
getIt<UserInfoViewModel>().userName!.isNotEmpty) {
|
||||
_userNameController.text = getIt<UserInfoViewModel>().userName!;
|
||||
if (getIt<UserInfoViewModel>().userName != null && getIt<UserInfoViewModel>().userName!.isNotEmpty) {
|
||||
if (getIt<UserInfoViewModel>().useSecretLogined) {
|
||||
_cIdController.text = getIt<UserInfoViewModel>().userName!;
|
||||
} else {
|
||||
_userNameController.text = getIt<UserInfoViewModel>().userName!;
|
||||
}
|
||||
rememberPassword = true;
|
||||
} else {
|
||||
rememberPassword = false;
|
||||
}
|
||||
if (getIt<UserInfoViewModel>().passWord != null &&
|
||||
getIt<UserInfoViewModel>().passWord!.isNotEmpty) {
|
||||
_passwordController.text = getIt<UserInfoViewModel>().passWord!;
|
||||
if (getIt<UserInfoViewModel>().passWord != null && getIt<UserInfoViewModel>().passWord!.isNotEmpty) {
|
||||
if (getIt<UserInfoViewModel>().useSecretLogined) {
|
||||
_cSecretController.text = getIt<UserInfoViewModel>().passWord!;
|
||||
} else {
|
||||
_passwordController.text = getIt<UserInfoViewModel>().passWord!;
|
||||
}
|
||||
}
|
||||
getIt<UserInfoViewModel>().updateToken("");
|
||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||
if (useSecretLogin) {
|
||||
cardKey.currentState?.toggleCard();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@ -63,120 +81,203 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 40,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 10,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 40,
|
||||
),
|
||||
SizedBox(
|
||||
height: 50,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
child: Image.asset(
|
||||
"assets/images/login_tip.png",
|
||||
height: 45,
|
||||
),
|
||||
),
|
||||
const Positioned(
|
||||
top: 10,
|
||||
left: 0,
|
||||
child: Text(
|
||||
"登录",
|
||||
style: TextStyle(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.bold,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 10,
|
||||
),
|
||||
SizedBox(
|
||||
height: 50,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
child: Image.asset(
|
||||
"assets/images/login_tip.png",
|
||||
height: 45,
|
||||
),
|
||||
),
|
||||
const Positioned(
|
||||
top: 10,
|
||||
left: 0,
|
||||
child: Text(
|
||||
"登录",
|
||||
style: TextStyle(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 5,
|
||||
right: 0,
|
||||
child: Image.asset(
|
||||
"assets/images/ql.png",
|
||||
height: 45,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 15,
|
||||
),
|
||||
const Text(
|
||||
"域名:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
onChanged: (_) {
|
||||
setState(() {});
|
||||
},
|
||||
controller: _hostController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "http://1.1.1.1:5700",
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
FlipCard(
|
||||
key: cardKey,
|
||||
onFlipDone: (back) {
|
||||
useSecretLogin = back;
|
||||
setState(() {});
|
||||
},
|
||||
direction: FlipDirection.HORIZONTAL,
|
||||
front: SizedBox(
|
||||
height: 200,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
const Text(
|
||||
"用户名:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
onChanged: (_) {
|
||||
setState(() {});
|
||||
},
|
||||
controller: _userNameController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入用户名",
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
const Text(
|
||||
"密码:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
onChanged: (_) {
|
||||
setState(() {});
|
||||
},
|
||||
controller: _passwordController,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入密码",
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 5,
|
||||
right: 0,
|
||||
child: Image.asset(
|
||||
"assets/images/ql.png",
|
||||
height: 45,
|
||||
back: SizedBox(
|
||||
height: 200,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
const Text(
|
||||
"client_id:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
onChanged: (_) {
|
||||
setState(() {});
|
||||
},
|
||||
controller: _cIdController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入client_id",
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
const Text(
|
||||
"client_secret:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
onChanged: (_) {
|
||||
setState(() {});
|
||||
},
|
||||
controller: _cSecretController,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入client_secret",
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 15,
|
||||
),
|
||||
const Text(
|
||||
"域名:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
onChanged: (_) {
|
||||
setState(() {});
|
||||
},
|
||||
controller: _hostController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "http://1.1.1.1:5700",
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
const Text(
|
||||
"用户名:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
onChanged: (_) {
|
||||
setState(() {});
|
||||
},
|
||||
controller: _userNameController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入用户名",
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
const Text(
|
||||
"密码:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
onChanged: (_) {
|
||||
setState(() {});
|
||||
},
|
||||
controller: _passwordController,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入密码",
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Row(
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 25,),
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: rememberPassword,
|
||||
@ -186,64 +287,69 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
},
|
||||
),
|
||||
const Text(
|
||||
"记住密码",
|
||||
"记住密码/client",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
cardKey.currentState?.toggleCard();
|
||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
loginByUserName() ? "client_id登录" : "用户名密码登录",
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10,),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
SizedBox(
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40,),
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width - 80,
|
||||
child: IgnorePointer(
|
||||
ignoring: _hostController.text.isEmpty ||
|
||||
_userNameController.text.isEmpty ||
|
||||
_passwordController.text.isEmpty ||
|
||||
isLoading,
|
||||
ignoring: !canClickLoginBtn(),
|
||||
child: CupertinoButton(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 5,
|
||||
),
|
||||
color: (_hostController.text.isNotEmpty &&
|
||||
_userNameController.text.isNotEmpty &&
|
||||
_passwordController.text.isNotEmpty &&
|
||||
!isLoading)
|
||||
? ref
|
||||
.watch(themeProvider)
|
||||
.themeColor
|
||||
.buttonBgColor()
|
||||
: Theme.of(context)
|
||||
.primaryColor
|
||||
.withOpacity(0.4),
|
||||
child: isLoading
|
||||
? const CupertinoActivityIndicator()
|
||||
: const Text(
|
||||
"登 录",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 5,
|
||||
),
|
||||
color: canClickLoginBtn() ? primaryColor : primaryColor.withOpacity(0.4),
|
||||
child: isLoading
|
||||
? const CupertinoActivityIndicator()
|
||||
: const Text(
|
||||
"登 录",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
onPressed: () {
|
||||
if (rememberPassword) {
|
||||
getIt<UserInfoViewModel>().updateUserName(
|
||||
_userNameController.text,
|
||||
_passwordController.text);
|
||||
}
|
||||
|
||||
Http.pushedLoginPage = false;
|
||||
Utils.hideKeyBoard(context);
|
||||
getIt<UserInfoViewModel>()
|
||||
.updateHost(_hostController.text);
|
||||
login(_userNameController.text,
|
||||
_passwordController.text);
|
||||
}),
|
||||
),
|
||||
onPressed: () {
|
||||
Http.pushedLoginPage = false;
|
||||
Utils.hideKeyBoard(context);
|
||||
getIt<UserInfoViewModel>().updateHost(_hostController.text);
|
||||
Http.clear();
|
||||
if (loginByUserName()) {
|
||||
login(_userNameController.text, _passwordController.text);
|
||||
} else {
|
||||
login(_cIdController.text, _cSecretController.text);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -255,17 +361,118 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
|
||||
bool isLoading = false;
|
||||
|
||||
bool loginByUserName() {
|
||||
return !useSecretLogin;
|
||||
}
|
||||
|
||||
Future<void> login(String userName, String password) async {
|
||||
isLoading = true;
|
||||
setState(() {});
|
||||
HttpResponse<LoginBean> response = await Api.login(userName, password);
|
||||
if (response.success) {
|
||||
getIt<UserInfoViewModel>().updateToken(response.bean?.token ?? "");
|
||||
Navigator.of(context).pushReplacementNamed(Routes.routeHomePage);
|
||||
HttpResponse<LoginBean> response;
|
||||
|
||||
if (loginByUserName()) {
|
||||
response = await Api.login(userName, password);
|
||||
} else {
|
||||
response = await Api.loginByClientId(userName, password);
|
||||
}
|
||||
if (response.success) {
|
||||
loginSuccess(response, userName, password);
|
||||
} else {
|
||||
print(response.code);
|
||||
(response.message ?? "请检查网络情况").toast();
|
||||
isLoading = false;
|
||||
setState(() {});
|
||||
//420代表需要2步验证
|
||||
if (response.code == 420) {
|
||||
String twoFact = "";
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (_) => CupertinoAlertDialog(
|
||||
title: const Text("两步验证"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: TextField(
|
||||
onChanged: (value) {
|
||||
twoFact = value;
|
||||
},
|
||||
maxLines: 1,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入code",
|
||||
),
|
||||
autofocus: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text(
|
||||
"取消",
|
||||
style: TextStyle(
|
||||
color: Color(0xff999999),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: Text(
|
||||
"确定",
|
||||
style: TextStyle(
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop(true);
|
||||
HttpResponse<LoginBean> response = await Api.loginTwo(userName, password, twoFact);
|
||||
if (response.success) {
|
||||
loginSuccess(response, userName, password);
|
||||
} else {
|
||||
loginFailed(response);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
)).then((value) {
|
||||
if (value == null) {
|
||||
isLoading = false;
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
isLoading = false;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loginFailed(HttpResponse<LoginBean> response) {
|
||||
(response.message ?? "请检查网络情况").toast();
|
||||
isLoading = false;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void loginSuccess(HttpResponse<LoginBean> response, String userName, String password) {
|
||||
getIt<UserInfoViewModel>().updateToken(response.bean?.token ?? "");
|
||||
getIt<UserInfoViewModel>().useSecretLogin(!loginByUserName());
|
||||
if (rememberPassword) {
|
||||
getIt<UserInfoViewModel>().updateUserName(userName, password);
|
||||
}
|
||||
Navigator.of(context).pushReplacementNamed(Routes.routeHomePage);
|
||||
}
|
||||
|
||||
bool canClickLoginBtn() {
|
||||
if (isLoading) return false;
|
||||
|
||||
if (_hostController.text.isEmpty) return false;
|
||||
if (!loginByUserName()) {
|
||||
return _cIdController.text.isNotEmpty && _cSecretController.text.isNotEmpty;
|
||||
} else {
|
||||
return _userNameController.text.isNotEmpty && _passwordController.text.isNotEmpty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
lib/module/login/user_bean.dart
Normal file
25
lib/module/login/user_bean.dart
Normal file
@ -0,0 +1,25 @@
|
||||
import 'package:json_conversion_annotation/json_conversion_annotation.dart';
|
||||
|
||||
/// @author NewTab
|
||||
@JsonConversion()
|
||||
class UserBean {
|
||||
String? username;
|
||||
bool? twoFactorActivated;
|
||||
|
||||
UserBean({this.username, this.twoFactorActivated});
|
||||
|
||||
UserBean.fromJson(Map<String, dynamic> json) {
|
||||
username = json['username'];
|
||||
twoFactorActivated = json['twoFactorActivated'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['username'] = this.username;
|
||||
data['twoFactorActivated'] = this.twoFactorActivated;
|
||||
return data;
|
||||
}
|
||||
static UserBean jsonConversion(Map<String, dynamic> json) {
|
||||
return UserBean.fromJson(json);
|
||||
}
|
||||
}
|
||||
128
lib/module/others/about_page.dart
Normal file
128
lib/module/others/about_page.dart
Normal file
@ -0,0 +1,128 @@
|
||||
import 'package:flutter/material.dart';
|
||||
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:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../main.dart';
|
||||
|
||||
/// @author NewTab
|
||||
|
||||
class AboutPage extends ConsumerStatefulWidget {
|
||||
const AboutPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
ConsumerState createState() => _AboutPageState();
|
||||
}
|
||||
|
||||
class _AboutPageState extends ConsumerState<AboutPage> {
|
||||
String desc = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getInfo();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: QlAppBar(
|
||||
canBack: true,
|
||||
title: "关于",
|
||||
),
|
||||
body: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 10,
|
||||
),
|
||||
Image.asset(
|
||||
"assets/images/ql.png",
|
||||
height: 50,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
Text(
|
||||
"青龙",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Text(
|
||||
"Version ${desc}",
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
_launchURL("https://github.com/qinglong-app/qinglong_app/releases");
|
||||
},
|
||||
child: Text(
|
||||
"版本更新",
|
||||
style: TextStyle(
|
||||
color: primaryColor,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
_launchURL("https://github.com/qinglong-app/qinglong_app");
|
||||
},
|
||||
child: Text(
|
||||
"项目地址",
|
||||
style: TextStyle(
|
||||
color: primaryColor,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _launchURL(String _url) async {
|
||||
try {
|
||||
await launch(_url);
|
||||
} catch (e) {
|
||||
logger.e(e);
|
||||
}
|
||||
}
|
||||
|
||||
void getInfo() async {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
|
||||
String version = packageInfo.version;
|
||||
|
||||
desc = version;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
@ -1,24 +1,22 @@
|
||||
import 'package:dio_log/dio_log.dart';
|
||||
import 'package:flutter/material.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/ql_app_bar.dart';
|
||||
import 'package:qinglong_app/module/others/dependencies/dependency_viewmodel.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/extension.dart';
|
||||
|
||||
class AddDependenyPage extends ConsumerStatefulWidget {
|
||||
const AddDependenyPage({
|
||||
class AddDependencyPage extends ConsumerStatefulWidget {
|
||||
const AddDependencyPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
ConsumerState<AddDependenyPage> createState() => _AddDependencyPageState();
|
||||
ConsumerState<AddDependencyPage> createState() => _AddDependencyPageState();
|
||||
}
|
||||
|
||||
class _AddDependencyPageState extends ConsumerState<AddDependenyPage> {
|
||||
class _AddDependencyPageState extends ConsumerState<AddDependencyPage> {
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
|
||||
DepedencyEnum depedencyType = DepedencyEnum.NodeJS;
|
||||
@ -39,6 +37,10 @@ class _AddDependencyPageState extends ConsumerState<AddDependenyPage> {
|
||||
title: "新增依赖",
|
||||
actions: [
|
||||
InkWell(
|
||||
onLongPress: () {
|
||||
showDebugBtn(context, btnColor: Colors.blue);
|
||||
WidgetsBinding.instance?.endOfFrame;
|
||||
},
|
||||
onTap: () {
|
||||
submit();
|
||||
},
|
||||
@ -59,90 +61,88 @@ class _AddDependencyPageState extends ConsumerState<AddDependenyPage> {
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Container(
|
||||
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: 10,
|
||||
),
|
||||
const Text(
|
||||
"依赖类型:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
DropdownButtonFormField<DepedencyEnum>(
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: DepedencyEnum.NodeJS,
|
||||
child: Text(DepedencyEnum.NodeJS.name),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: DepedencyEnum.Python3,
|
||||
child: Text(DepedencyEnum.Python3.name),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: DepedencyEnum.Linux,
|
||||
child: Text(DepedencyEnum.Linux.name),
|
||||
),
|
||||
],
|
||||
value: DepedencyEnum.NodeJS,
|
||||
onChanged: (value) {
|
||||
depedencyType = value!;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const Text(
|
||||
"依赖类型:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
const Text(
|
||||
"名称:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
DropdownButtonFormField<DepedencyEnum>(
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: DepedencyEnum.NodeJS,
|
||||
child: Text(DepedencyEnum.NodeJS.name),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入名称",
|
||||
DropdownMenuItem(
|
||||
value: DepedencyEnum.Python3,
|
||||
child: Text(DepedencyEnum.Python3.name),
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: DepedencyEnum.Linux,
|
||||
child: Text(DepedencyEnum.Linux.name),
|
||||
),
|
||||
],
|
||||
value: DepedencyEnum.NodeJS,
|
||||
onChanged: (value) {
|
||||
depedencyType = value!;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const Text(
|
||||
"名称:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入名称",
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -159,7 +159,7 @@ class _AddDependencyPageState extends ConsumerState<AddDependenyPage> {
|
||||
);
|
||||
|
||||
if (response.success) {
|
||||
"操作成功".toast();
|
||||
"新增成功".toast();
|
||||
ref.read(dependencyProvider).loadData(
|
||||
depedencyType.name.toLowerCase(),
|
||||
);
|
||||
|
||||
@ -63,7 +63,7 @@ class _DependcyPageState extends State<DependencyPage> with TickerProviderStateM
|
||||
child: Center(
|
||||
child: Icon(
|
||||
CupertinoIcons.add,
|
||||
size: 20,
|
||||
size: 24,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
@ -71,68 +71,68 @@ class _DependcyPageState extends State<DependencyPage> with TickerProviderStateM
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
TabBar(
|
||||
controller: _tabController,
|
||||
tabs: types
|
||||
.map((e) => Tab(
|
||||
text: e.name,
|
||||
))
|
||||
.toList(),
|
||||
isScrollable: true,
|
||||
indicator: AbsUnderlineTabIndicator(
|
||||
wantWidth: 20,
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).primaryColor,
|
||||
width: 2,
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
TabBar(
|
||||
controller: _tabController,
|
||||
tabs: types
|
||||
.map(
|
||||
(e) => Tab(
|
||||
text: e.name,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
isScrollable: true,
|
||||
indicator: AbsUnderlineTabIndicator(
|
||||
wantWidth: 20,
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).primaryColor,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: BaseStateWidget<DependencyViewModel>(
|
||||
onReady: (model) {
|
||||
model.loadData(types[0].name.toLowerCase());
|
||||
model.loadData(types[1].name.toLowerCase());
|
||||
model.loadData(types[2].name.toLowerCase());
|
||||
},
|
||||
model: dependencyProvider,
|
||||
builder: (context, model, child) {
|
||||
return TabBarView(
|
||||
controller: _tabController,
|
||||
children: types.map(
|
||||
(e) {
|
||||
List<DependencyBean> list;
|
||||
if (e.index == 0) {
|
||||
list = model.nodeJsList;
|
||||
} else if (e.index == 1) {
|
||||
list = model.python3List;
|
||||
} else {
|
||||
list = model.linuxList;
|
||||
}
|
||||
),
|
||||
Expanded(
|
||||
child: BaseStateWidget<DependencyViewModel>(
|
||||
onReady: (model) {
|
||||
model.loadData(types[0].name.toLowerCase());
|
||||
model.loadData(types[1].name.toLowerCase());
|
||||
model.loadData(types[2].name.toLowerCase());
|
||||
},
|
||||
model: dependencyProvider,
|
||||
builder: (context, model, child) {
|
||||
return TabBarView(
|
||||
controller: _tabController,
|
||||
children: types.map(
|
||||
(e) {
|
||||
List<DependencyBean> list;
|
||||
if (e.index == 0) {
|
||||
list = model.nodeJsList;
|
||||
} else if (e.index == 1) {
|
||||
list = model.python3List;
|
||||
} else {
|
||||
list = model.linuxList;
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
return DependencyCell(e, list[index]);
|
||||
},
|
||||
itemCount: list.length,
|
||||
),
|
||||
onRefresh: () {
|
||||
return model.loadData(types[_tabController!.index].name.toLowerCase(), false);
|
||||
return RefreshIndicator(
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
return DependencyCell(e, list[index]);
|
||||
},
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
itemCount: list.length,
|
||||
),
|
||||
onRefresh: () {
|
||||
return model.loadData(types[_tabController!.index].name.toLowerCase(), false);
|
||||
},
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -187,7 +187,7 @@ class DependencyCell extends ConsumerWidget {
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
@ -220,7 +220,7 @@ class DependencyCell extends ConsumerWidget {
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
@ -334,13 +334,23 @@ class DependencyCell extends ConsumerWidget {
|
||||
content: Text("确认删除依赖 ${bean.name ?? ""} 吗"),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text("取消"),
|
||||
child: const Text(
|
||||
"取消",
|
||||
style: TextStyle(
|
||||
color: Color(0xff999999),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: const Text("确定"),
|
||||
child: Text(
|
||||
"确定",
|
||||
style: TextStyle(
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
ref.read(dependencyProvider).del(type.name.toLowerCase(), sId ?? "");
|
||||
|
||||
@ -32,73 +32,73 @@ class _LoginLogPageState extends ConsumerState<LoginLogPage> with LazyLoadState<
|
||||
backCall: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
title: "任务日志",
|
||||
title: "登录日志",
|
||||
),
|
||||
body: list.isEmpty
|
||||
? const Center(
|
||||
child: CupertinoActivityIndicator(),
|
||||
)
|
||||
child: CupertinoActivityIndicator(),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
LoginLogBean item = list[index];
|
||||
itemBuilder: (context, index) {
|
||||
LoginLogBean item = list[index];
|
||||
|
||||
return ColoredBox(
|
||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
||||
child: ListTile(
|
||||
isThreeLine: true,
|
||||
title: Text(
|
||||
Utils.formatMessageTime(item.timestamp ?? 0),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||
),
|
||||
),
|
||||
subtitle: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
SelectableText(
|
||||
"${item.address}",
|
||||
selectionWidthStyle: BoxWidthStyle.max,
|
||||
selectionHeightStyle: BoxHeightStyle.max,
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||
fontSize: 14,
|
||||
return ColoredBox(
|
||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
||||
child: ListTile(
|
||||
isThreeLine: true,
|
||||
title: Text(
|
||||
Utils.formatMessageTime(item.timestamp ?? 0),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
SelectableText(
|
||||
"${item.ip}",
|
||||
selectionWidthStyle: BoxWidthStyle.max,
|
||||
selectionHeightStyle: BoxHeightStyle.max,
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||
fontSize: 14,
|
||||
subtitle: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
SelectableText(
|
||||
"${item.address}",
|
||||
selectionWidthStyle: BoxWidthStyle.max,
|
||||
selectionHeightStyle: BoxHeightStyle.max,
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
SelectableText(
|
||||
"${item.ip}",
|
||||
selectionWidthStyle: BoxWidthStyle.max,
|
||||
selectionHeightStyle: BoxHeightStyle.max,
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: item.status == 0
|
||||
? Icon(
|
||||
CupertinoIcons.checkmark_circle,
|
||||
color: primaryColor,
|
||||
size: 16,
|
||||
)
|
||||
: const Icon(
|
||||
CupertinoIcons.clear_circled,
|
||||
color: Colors.red,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: item.status == 0
|
||||
? Icon(
|
||||
CupertinoIcons.checkmark_circle,
|
||||
color: primaryColor,
|
||||
size: 16,
|
||||
)
|
||||
: const Icon(
|
||||
CupertinoIcons.clear_circled,
|
||||
color: Colors.red,
|
||||
size: 16,
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: list.length,
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: list.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:dio_log/dio_log.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@ -5,6 +6,7 @@ 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/utils/extension.dart';
|
||||
|
||||
class OtherPage extends ConsumerStatefulWidget {
|
||||
const OtherPage({Key? key}) : super(key: key);
|
||||
@ -26,7 +28,7 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
vertical: 15,
|
||||
vertical: 30,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
||||
@ -57,7 +59,7 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
||||
Text(
|
||||
"脚本管理",
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
@ -90,7 +92,7 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
||||
Text(
|
||||
"依赖管理",
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
@ -123,7 +125,60 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
||||
Text(
|
||||
"任务日志",
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const Icon(
|
||||
CupertinoIcons.right_chevron,
|
||||
size: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
vertical: 15,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(15),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (getIt<UserInfoViewModel>().useSecretLogined) {
|
||||
"使用client_id方式登录无法获取登录日志".toast();
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeLoginLog,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
"登录日志",
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
@ -140,24 +195,29 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
||||
indent: 15,
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeLoginLog,
|
||||
);
|
||||
if (getIt<UserInfoViewModel>().useSecretLogined) {
|
||||
"使用client_id方式登录无法修改密码".toast();
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeUpdatePassword,
|
||||
);
|
||||
}
|
||||
},
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"登录日志",
|
||||
"修改密码",
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
@ -190,10 +250,9 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 5,
|
||||
bottom: 5,
|
||||
left: 15,
|
||||
right: 15,
|
||||
top: 5,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
@ -202,22 +261,63 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
||||
Text(
|
||||
"夜间模式",
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
CupertinoSwitch(
|
||||
value: ref.watch(themeProvider).isInDartMode(),
|
||||
onChanged: (open) {
|
||||
ref.watch(themeProvider).changeThemeReal(open);
|
||||
}),
|
||||
value: ref.watch(themeProvider).isInDartMode(),
|
||||
onChanged: (open) {
|
||||
ref.watch(themeProvider).changeThemeReal(open);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(
|
||||
indent: 15,
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeAbout,
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 15,
|
||||
right: 15,
|
||||
top: 5,
|
||||
bottom: 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 SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
Center(
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width - 40,
|
||||
@ -236,16 +336,26 @@ class _OtherPageState extends ConsumerState<OtherPage> {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: const Text("确定退出登录吗?"),
|
||||
title: const Text("确认退出登录吗?"),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text("取消"),
|
||||
child: const Text(
|
||||
"取消",
|
||||
style: TextStyle(
|
||||
color: Color(0xff999999),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: const Text("确定"),
|
||||
child: Text(
|
||||
"确定",
|
||||
style: TextStyle(
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
getIt<UserInfoViewModel>().updateToken("");
|
||||
Navigator.of(context).pushReplacementNamed(Routes.routeLogin);
|
||||
|
||||
@ -5,6 +5,7 @@ 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/ql_app_bar.dart';
|
||||
import 'package:qinglong_app/base/routes.dart';
|
||||
import 'package:qinglong_app/base/theme.dart';
|
||||
import 'package:qinglong_app/base/ui/lazy_load_state.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
@ -27,6 +28,115 @@ class ScriptDetailPage extends ConsumerStatefulWidget {
|
||||
class _ScriptDetailPageState extends ConsumerState<ScriptDetailPage> with LazyLoadState<ScriptDetailPage> {
|
||||
String? content;
|
||||
|
||||
List<Widget> actions = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
actions.addAll(
|
||||
[
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
if (content == null || content!.isEmpty) {
|
||||
"未获取到脚本内容,请稍候重试".toast();
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.routeScriptUpdate,
|
||||
arguments: {
|
||||
"title": widget.title,
|
||||
"path": widget.path,
|
||||
"content": content,
|
||||
},
|
||||
).then((value) {
|
||||
if (value != null && value == true) {
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: const Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
"编辑",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: const Text("确认删除"),
|
||||
content: const Text("确认删除该脚本吗"),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text(
|
||||
"取消",
|
||||
style: TextStyle(
|
||||
color: Color(0xff999999),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: Text(
|
||||
"确定",
|
||||
style: TextStyle(
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
HttpResponse<NullResponse> result = await Api.delScript(widget.title, widget.path ?? "");
|
||||
if (result.success) {
|
||||
"删除成功".toast();
|
||||
Navigator.of(context).pop(true);
|
||||
} else {
|
||||
result.message?.toast();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: const Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
"删除",
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -36,22 +146,82 @@ class _ScriptDetailPageState extends ConsumerState<ScriptDetailPage> with LazyLo
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
title: "脚本详情",
|
||||
),
|
||||
body:content == null
|
||||
? const Center(
|
||||
child: CupertinoActivityIndicator(),
|
||||
)
|
||||
: SingleChildScrollView(
|
||||
child: HighlightView(
|
||||
content ?? "",
|
||||
language: getLanguageType(widget.title),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
theme: ref.watch(themeProvider).themeColor.codeEditorTheme(),
|
||||
tabSize: 14,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: content == null
|
||||
? const Center(
|
||||
child: CupertinoActivityIndicator(),
|
||||
)
|
||||
: SingleChildScrollView(
|
||||
child: HighlightView(
|
||||
content ?? "",
|
||||
language: getLanguageType(widget.title),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
theme: ref.watch(themeProvider).themeColor.codeEditorTheme(),
|
||||
tabSize: 14,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
93
lib/module/others/scripts/script_edit_page.dart
Normal file
93
lib/module/others/scripts/script_edit_page.dart
Normal file
@ -0,0 +1,93 @@
|
||||
import 'package:flutter/material.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/ql_app_bar.dart';
|
||||
import 'package:qinglong_app/base/theme.dart';
|
||||
import 'package:qinglong_app/module/config/config_viewmodel.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
|
||||
class ScriptEditPage extends ConsumerStatefulWidget {
|
||||
final String content;
|
||||
final String title;
|
||||
final String path;
|
||||
|
||||
const ScriptEditPage(this.title, this.path, this.content, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ScriptEditPageState createState() => _ScriptEditPageState();
|
||||
}
|
||||
|
||||
class _ScriptEditPageState extends ConsumerState<ScriptEditPage> {
|
||||
late TextEditingController _controller;
|
||||
FocusNode node = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_controller = TextEditingController(text: widget.content);
|
||||
super.initState();
|
||||
WidgetsBinding.instance?.addPostFrameCallback(
|
||||
(timeStamp) {
|
||||
node.requestFocus();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: QlAppBar(
|
||||
canBack: true,
|
||||
backCall: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
title: '编辑${widget.title}',
|
||||
actions: [
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
HttpResponse<NullResponse> response = await Api.updateScript(widget.title, widget.path, _controller.text);
|
||||
if (response.success) {
|
||||
"提交成功".toast();
|
||||
Navigator.of(context).pop(true);
|
||||
} else {
|
||||
(response.message ?? "").toast();
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"提交",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 15,
|
||||
right: 15,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: TextField(
|
||||
focusNode: node,
|
||||
style: TextStyle(
|
||||
color: ref.read(themeProvider).themeColor.descColor(),
|
||||
fontSize: 14,
|
||||
),
|
||||
controller: _controller,
|
||||
minLines: 1,
|
||||
maxLines: 100,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ import 'package:qinglong_app/base/http/http.dart';
|
||||
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||
import 'package:qinglong_app/base/routes.dart';
|
||||
import 'package:qinglong_app/base/theme.dart';
|
||||
import 'package:qinglong_app/base/ui/lazy_load_state.dart';
|
||||
import 'package:qinglong_app/module/others/scripts/script_bean.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
|
||||
@ -17,15 +18,9 @@ class ScriptPage extends ConsumerStatefulWidget {
|
||||
_ScriptPageState createState() => _ScriptPageState();
|
||||
}
|
||||
|
||||
class _ScriptPageState extends ConsumerState<ScriptPage> {
|
||||
class _ScriptPageState extends ConsumerState<ScriptPage> with LazyLoadState<ScriptPage> {
|
||||
List<ScriptBean> list = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
loadData();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -51,9 +46,8 @@ class _ScriptPageState extends ConsumerState<ScriptPage> {
|
||||
title: Text(
|
||||
item.title ?? "",
|
||||
style: TextStyle(
|
||||
color: (item.disabled ?? false)
|
||||
? ref.watch(themeProvider).themeColor.descColor()
|
||||
: ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||
color:
|
||||
(item.disabled ?? false) ? ref.watch(themeProvider).themeColor.descColor() : ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
@ -66,14 +60,18 @@ class _ScriptPageState extends ConsumerState<ScriptPage> {
|
||||
"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.taskTitleColor(),
|
||||
: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
@ -88,14 +86,19 @@ class _ScriptPageState extends ConsumerState<ScriptPage> {
|
||||
"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.taskTitleColor(),
|
||||
color:
|
||||
(item.disabled ?? false) ? ref.watch(themeProvider).themeColor.descColor() : ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
@ -118,4 +121,9 @@ class _ScriptPageState extends ConsumerState<ScriptPage> {
|
||||
response.message?.toast();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onLazyLoad() {
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ class _TaskLogPageState extends ConsumerState<TaskLogPage> with LazyLoadState<Ta
|
||||
title: Text(
|
||||
item.name ?? "",
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
@ -58,7 +58,7 @@ class _TaskLogPageState extends ConsumerState<TaskLogPage> with LazyLoadState<Ta
|
||||
title: Text(
|
||||
e,
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
@ -72,7 +72,7 @@ class _TaskLogPageState extends ConsumerState<TaskLogPage> with LazyLoadState<Ta
|
||||
title: Text(
|
||||
item.name ?? "",
|
||||
style: TextStyle(
|
||||
color: ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
|
||||
207
lib/module/others/update_password_page.dart
Normal file
207
lib/module/others/update_password_page.dart
Normal file
@ -0,0 +1,207 @@
|
||||
import 'package:flutter/material.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/ql_app_bar.dart';
|
||||
import 'package:qinglong_app/base/routes.dart';
|
||||
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||
import 'package:qinglong_app/module/task/task_bean.dart';
|
||||
import 'package:qinglong_app/module/task/task_viewmodel.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
|
||||
import '../../main.dart';
|
||||
|
||||
class UpdatePasswordPage extends ConsumerStatefulWidget {
|
||||
const UpdatePasswordPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
ConsumerState<UpdatePasswordPage> createState() => _UpdatePasswordPageState();
|
||||
}
|
||||
|
||||
class _UpdatePasswordPageState extends ConsumerState<UpdatePasswordPage> {
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
final TextEditingController _passwordAgainController = TextEditingController();
|
||||
|
||||
FocusNode focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance?.addPostFrameCallback(
|
||||
(timeStamp) {
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: QlAppBar(
|
||||
canBack: true,
|
||||
backCall: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
title: "修改用户名密码",
|
||||
actions: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
submit();
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"提交",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: 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(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
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(
|
||||
obscureText: true,
|
||||
controller: _passwordController,
|
||||
maxLines: 1,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
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(
|
||||
obscureText: true,
|
||||
maxLines: 1,
|
||||
controller: _passwordAgainController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "再次输入新密码",
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void submit() async {
|
||||
if (_nameController.text.isEmpty) {
|
||||
"用户名不能为空".toast();
|
||||
return;
|
||||
}
|
||||
if (_passwordController.text.isEmpty || _passwordAgainController.text.isEmpty) {
|
||||
"密码不能为空".toast();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_passwordAgainController.text != _passwordController.text) {
|
||||
"两次输入的密码不一致".toast();
|
||||
return;
|
||||
}
|
||||
|
||||
commitReal();
|
||||
}
|
||||
|
||||
void commitReal() async {
|
||||
String name = _nameController.text;
|
||||
String password = _passwordController.text;
|
||||
HttpResponse<NullResponse> response = await Api.updatePassword(name, password);
|
||||
|
||||
if (response.success) {
|
||||
"更新成功".toast();
|
||||
|
||||
if (!getIt<UserInfoViewModel>().useSecretLogined) {
|
||||
getIt<UserInfoViewModel>().updateUserName(name, password);
|
||||
}
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(Routes.routeLogin, (route) => false);
|
||||
} else {
|
||||
response.message.toast();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,6 @@ 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/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/extension.dart';
|
||||
|
||||
@ -24,6 +23,8 @@ class _AddTaskPageState extends ConsumerState<AddTaskPage> {
|
||||
final TextEditingController _commandController = TextEditingController();
|
||||
final TextEditingController _cronController = TextEditingController();
|
||||
|
||||
FocusNode focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -35,6 +36,11 @@ class _AddTaskPageState extends ConsumerState<AddTaskPage> {
|
||||
} else {
|
||||
taskBean = TaskBean();
|
||||
}
|
||||
WidgetsBinding.instance?.addPostFrameCallback(
|
||||
(timeStamp) {
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -68,112 +74,111 @@ class _AddTaskPageState extends ConsumerState<AddTaskPage> {
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Container(
|
||||
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: 10,
|
||||
),
|
||||
const Text(
|
||||
"名称:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入名称",
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
const Text(
|
||||
"名称:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
const Text(
|
||||
"命令:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
focusNode: focusNode,
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入名称",
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
TextField(
|
||||
controller: _commandController,
|
||||
maxLines: 4,
|
||||
minLines: 1,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入命令",
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const Text(
|
||||
"定时:",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
TextField(
|
||||
controller: _cronController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
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: _commandController,
|
||||
maxLines: 4,
|
||||
minLines: 1,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
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: _cronController,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
|
||||
hintText: "请输入定时",
|
||||
),
|
||||
autofocus: false,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
const Text(
|
||||
"定时的cron不校验是否正确",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -192,15 +197,19 @@ class _AddTaskPageState extends ConsumerState<AddTaskPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
commitReal();
|
||||
}
|
||||
|
||||
void commitReal() async {
|
||||
taskBean.name = _nameController.text;
|
||||
taskBean.command = _commandController.text;
|
||||
taskBean.schedule = _cronController.text;
|
||||
HttpResponse<TaskDetailBean> response = await Api.addTask(
|
||||
HttpResponse<NullResponse> response = await Api.addTask(
|
||||
_nameController.text, _commandController.text, _cronController.text,
|
||||
id: taskBean.sId);
|
||||
|
||||
if (response.success) {
|
||||
"操作成功".toast();
|
||||
(widget.taskBean?.sId == null) ? "新增成功" : "修改成功".toast();
|
||||
ref.read(taskProvider).updateBean(taskBean);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
import 'package:json_conversion_annotation/json_conversion_annotation.dart';
|
||||
|
||||
@JsonConversion()
|
||||
class TaskDetailBean {
|
||||
String? name;
|
||||
String? command;
|
||||
String? schedule;
|
||||
bool? saved;
|
||||
String? sId;
|
||||
int? created;
|
||||
int? status;
|
||||
String? timestamp;
|
||||
int? isSystem;
|
||||
int? isDisabled;
|
||||
String? logPath;
|
||||
int? isPinned;
|
||||
|
||||
TaskDetailBean(
|
||||
{this.name,
|
||||
this.command,
|
||||
this.schedule,
|
||||
this.saved,
|
||||
this.sId,
|
||||
this.created,
|
||||
this.status,
|
||||
this.timestamp,
|
||||
this.isSystem,
|
||||
this.isDisabled,
|
||||
this.logPath,
|
||||
this.isPinned});
|
||||
|
||||
TaskDetailBean.fromJson(Map<String, dynamic> json) {
|
||||
name = json['name'];
|
||||
command = json['command'];
|
||||
schedule = json['schedule'];
|
||||
saved = json['saved'];
|
||||
sId = json['_id'];
|
||||
created = json['created'];
|
||||
status = json['status'];
|
||||
timestamp = json['timestamp'];
|
||||
isSystem = json['isSystem'];
|
||||
isDisabled = json['isDisabled'];
|
||||
logPath = json['log_path'];
|
||||
isPinned = json['isPinned'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['name'] = this.name;
|
||||
data['command'] = this.command;
|
||||
data['schedule'] = this.schedule;
|
||||
data['saved'] = this.saved;
|
||||
data['_id'] = this.sId;
|
||||
data['created'] = this.created;
|
||||
data['status'] = this.status;
|
||||
data['timestamp'] = this.timestamp;
|
||||
data['isSystem'] = this.isSystem;
|
||||
data['isDisabled'] = this.isDisabled;
|
||||
data['log_path'] = this.logPath;
|
||||
data['isPinned'] = this.isPinned;
|
||||
return data;
|
||||
}
|
||||
|
||||
static TaskDetailBean jsonConversion(Map<String, dynamic> json) {
|
||||
return TaskDetailBean.fromJson(json);
|
||||
}
|
||||
}
|
||||
@ -1,36 +1,504 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:qinglong_app/base/base_state_widget.dart';
|
||||
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||
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_viewmodel.dart';
|
||||
import 'package:qinglong_app/utils/utils.dart';
|
||||
|
||||
import '../task_viewmodel.dart';
|
||||
|
||||
class TaskDetailPage extends ConsumerStatefulWidget {
|
||||
final TaskBean taskBean;
|
||||
final bool hideAppbar;
|
||||
|
||||
const TaskDetailPage(this.taskBean, {Key? key}) : super(key: key);
|
||||
const TaskDetailPage(this.taskBean, {Key? key, this.hideAppbar = false}) : super(key: key);
|
||||
|
||||
@override
|
||||
_TaskDetailPageState createState() => _TaskDetailPageState();
|
||||
}
|
||||
|
||||
class _TaskDetailPageState extends ConsumerState<TaskDetailPage> {
|
||||
List<Widget> actions = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget body = Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
vertical: 15,
|
||||
),
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TaskDetailCell(
|
||||
title: "名称",
|
||||
desc: widget.taskBean.name ?? "",
|
||||
),
|
||||
TaskDetailCell(
|
||||
title: "ID",
|
||||
desc: widget.taskBean.sId ?? "",
|
||||
),
|
||||
TaskDetailCell(
|
||||
title: "任务",
|
||||
desc: widget.taskBean.command ?? "",
|
||||
),
|
||||
TaskDetailCell(
|
||||
title: "创建时间",
|
||||
desc: Utils.formatMessageTime(widget.taskBean.created ?? 0),
|
||||
),
|
||||
TaskDetailCell(
|
||||
title: "更新时间",
|
||||
desc: Utils.formatGMTTime(widget.taskBean.timestamp ?? ""),
|
||||
),
|
||||
TaskDetailCell(
|
||||
title: "任务定时",
|
||||
desc: widget.taskBean.schedule ?? "",
|
||||
),
|
||||
TaskDetailCell(
|
||||
title: "最后运行时间",
|
||||
desc: Utils.formatMessageTime(widget.taskBean.lastExecutionTime ?? 0),
|
||||
),
|
||||
TaskDetailCell(
|
||||
title: "最后运行时长",
|
||||
desc: widget.taskBean.lastRunningTime == null ? "-" : "${widget.taskBean.lastRunningTime ?? "-"}秒",
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
showLog();
|
||||
},
|
||||
child: TaskDetailCell(
|
||||
title: "日志路径",
|
||||
desc: widget.taskBean.logPath ?? "-",
|
||||
taped: () {
|
||||
showLog();
|
||||
},
|
||||
),
|
||||
),
|
||||
TaskDetailCell(
|
||||
title: "运行状态",
|
||||
desc: widget.taskBean.status == 0 ? "正在运行" : "空闲",
|
||||
),
|
||||
TaskDetailCell(
|
||||
title: "脚本状态",
|
||||
desc: widget.taskBean.isDisabled == 1 ? "已禁用" : "已启用",
|
||||
),
|
||||
TaskDetailCell(
|
||||
title: "是否置顶",
|
||||
desc: widget.taskBean.isPinned == 1 ? "已置顶" : "未置顶",
|
||||
hideDivide: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
widget.hideAppbar
|
||||
? const SizedBox.shrink()
|
||||
: SizedBox(
|
||||
width: MediaQuery.of(context).size.width - 80,
|
||||
child: CupertinoButton(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 5,
|
||||
),
|
||||
color: Colors.red,
|
||||
child: const Text(
|
||||
"删 除",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
delTask(context, ref);
|
||||
}),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (widget.hideAppbar) {
|
||||
return body;
|
||||
}
|
||||
|
||||
actions.clear();
|
||||
actions.addAll(
|
||||
[
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () async{
|
||||
Navigator.of(context).pop();
|
||||
await startCron(context, ref);
|
||||
setState(() {
|
||||
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
widget.taskBean.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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pushNamed(Routes.routeAddTask, arguments: widget.taskBean);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: const Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
"编辑",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
pinTask();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15,
|
||||
),
|
||||
color: Colors.transparent,
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
widget.taskBean.isPinned! == 0 ? "置顶" : "取消置顶",
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
enableTask();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
widget.taskBean.isDisabled! == 0 ? "禁用" : "启用",
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: QlAppBar(
|
||||
canBack: true,
|
||||
backCall: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
title: widget.taskBean.name ?? "",
|
||||
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: BaseStateWidget<TaskDetailViewModel>(
|
||||
model: taskDetailProvider,
|
||||
builder: (WidgetRef context, TaskDetailViewModel value, Widget? child) {
|
||||
return Container();
|
||||
},
|
||||
onReady: (model) {
|
||||
model.loadDetail(widget.taskBean.sId!);
|
||||
},
|
||||
body: body,
|
||||
);
|
||||
}
|
||||
|
||||
startCron(BuildContext context, WidgetRef ref) async {
|
||||
await ref.read(taskProvider).runCrons(widget.taskBean.sId!);
|
||||
setState(() {});
|
||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||
showLog();
|
||||
});
|
||||
}
|
||||
|
||||
stopCron(BuildContext context, WidgetRef ref) async {
|
||||
await ref.read(taskProvider).stopCrons(widget.taskBean.sId!);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void enableTask() async {
|
||||
await ref.read(taskProvider).enableTask(widget.taskBean.sId!, widget.taskBean.isDisabled!);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void pinTask() async {
|
||||
await ref.read(taskProvider).pinTask(widget.taskBean.sId!, widget.taskBean.isPinned!);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void delTask(BuildContext context, WidgetRef ref) {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: const Text("确认删除"),
|
||||
content: Text("确认删除定时任务 ${widget.taskBean.name ?? ""} 吗"),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text(
|
||||
"取消",
|
||||
style: TextStyle(
|
||||
color: Color(0xff999999),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: Text(
|
||||
"确定",
|
||||
style: TextStyle(
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
await ref.read(taskProvider).delCron(widget.taskBean.sId!);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void showLog() {
|
||||
showCupertinoDialog(
|
||||
builder: (BuildContext context) {
|
||||
return CupertinoAlertDialog(
|
||||
title: Text(
|
||||
"${widget.taskBean.name}运行日志",
|
||||
maxLines: 1,
|
||||
style: const TextStyle(overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
content: InTimeLogPage(widget.taskBean.sId!, widget.taskBean.status == 0),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text(
|
||||
"知道了",
|
||||
style: TextStyle(color: Theme.of(context).primaryColor),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
ref.read(taskProvider).loadData(false);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
context: context);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
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/task/task_detail/task_detail_bean.dart';
|
||||
|
||||
var taskDetailProvider =
|
||||
AutoDisposeChangeNotifierProvider<TaskDetailViewModel>((ref) {
|
||||
return TaskDetailViewModel();
|
||||
});
|
||||
|
||||
class TaskDetailViewModel extends BaseViewModel {
|
||||
TaskDetailBean? bean;
|
||||
|
||||
TaskDetailViewModel();
|
||||
|
||||
Future<void> loadDetail(String id) async {
|
||||
loading(notify: true);
|
||||
|
||||
HttpResponse<TaskDetailBean> response = await Api.taskDetail(id);
|
||||
|
||||
if (response.success) {
|
||||
bean = response.bean;
|
||||
success();
|
||||
} else {
|
||||
failed(response.message, notify: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,8 +9,10 @@ import 'package:qinglong_app/base/theme.dart';
|
||||
import 'package:qinglong_app/base/ui/abs_underline_tabindicator.dart';
|
||||
import 'package:qinglong_app/base/ui/empty_widget.dart';
|
||||
import 'package:qinglong_app/base/ui/menu.dart';
|
||||
import 'package:qinglong_app/base/ui/ql_context_menu.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_page.dart';
|
||||
import 'package:qinglong_app/module/task/task_viewmodel.dart';
|
||||
import 'package:qinglong_app/utils/utils.dart';
|
||||
|
||||
@ -22,7 +24,6 @@ class TaskPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _TaskPageState extends State<TaskPage> {
|
||||
String? _searchKey;
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
@ -101,10 +102,9 @@ class _TaskPageState extends State<TaskPage> {
|
||||
TaskBean item = list[index];
|
||||
|
||||
if (_searchController.text.isEmpty ||
|
||||
(item.name?.contains(_searchController.text) ?? false) ||
|
||||
(item.command?.contains(_searchController.text) ?? false) ||
|
||||
(item.schedule?.contains(_searchController.text) ??
|
||||
false)) {
|
||||
(item.name?.toLowerCase().contains(_searchController.text.toLowerCase()) ?? false) ||
|
||||
(item.command?.toLowerCase().contains(_searchController.text.toLowerCase()) ?? false) ||
|
||||
(item.schedule?.contains(_searchController.text.toLowerCase()) ?? false)) {
|
||||
return TaskItemCell(item, ref);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
@ -168,15 +168,14 @@ class TaskItemCell extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return ColoredBox(
|
||||
color: ref.watch(themeProvider).themeColor.settingBgColor(),
|
||||
child: CupertinoContextMenu(
|
||||
child: QlCupertinoContextMenu(
|
||||
bean: bean,
|
||||
actions: [
|
||||
QLCupertinoContextMenuAction(
|
||||
child: Text(
|
||||
bean.status! == 1 ? "运行" : "停止运行",
|
||||
),
|
||||
trailingIcon: bean.status! == 1
|
||||
? CupertinoIcons.memories
|
||||
: CupertinoIcons.stop_circle,
|
||||
trailingIcon: bean.status! == 1 ? CupertinoIcons.memories : CupertinoIcons.stop_circle,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
startCron(context, ref);
|
||||
@ -194,8 +193,7 @@ class TaskItemCell extends StatelessWidget {
|
||||
child: const Text("编辑"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.routeAddTask, arguments: bean);
|
||||
Navigator.of(context).pushNamed(Routes.routeAddTask, arguments: bean);
|
||||
},
|
||||
trailingIcon: CupertinoIcons.pencil_outline,
|
||||
),
|
||||
@ -205,9 +203,7 @@ class TaskItemCell extends StatelessWidget {
|
||||
Navigator.of(context).pop();
|
||||
pinTask();
|
||||
},
|
||||
trailingIcon: bean.isPinned! == 0
|
||||
? CupertinoIcons.pin
|
||||
: CupertinoIcons.pin_slash,
|
||||
trailingIcon: bean.isPinned! == 0 ? CupertinoIcons.pin : CupertinoIcons.pin_slash,
|
||||
),
|
||||
QLCupertinoContextMenuAction(
|
||||
child: Text(bean.isDisabled! == 0 ? "禁用" : "启用"),
|
||||
@ -216,9 +212,7 @@ class TaskItemCell extends StatelessWidget {
|
||||
enableTask();
|
||||
},
|
||||
isDestructiveAction: true,
|
||||
trailingIcon: bean.isDisabled! == 0
|
||||
? Icons.dnd_forwardslash
|
||||
: Icons.check_circle_outline_sharp,
|
||||
trailingIcon: bean.isDisabled! == 0 ? Icons.dnd_forwardslash : Icons.check_circle_outline_sharp,
|
||||
),
|
||||
QLCupertinoContextMenuAction(
|
||||
child: const Text("删除"),
|
||||
@ -231,34 +225,24 @@ class TaskItemCell extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
previewBuilder: (context, anima, child) {
|
||||
return IntrinsicWidth(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
bean.name ?? "",
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color:
|
||||
ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
bool isDark = ref.watch(themeProvider).darkMode;
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: Container(
|
||||
color: isDark ? const Color(0xff333333) : Theme.of(context).scaffoldBackgroundColor,
|
||||
padding: EdgeInsets.only(
|
||||
left: 5,
|
||||
right: 5,
|
||||
top: 5,
|
||||
bottom: isDark ? 5 : 20,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: TaskDetailPage(
|
||||
bean,
|
||||
hideAppbar: true,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
bean.isDisabled == 1
|
||||
? const Icon(
|
||||
Icons.dnd_forwardslash,
|
||||
size: 16,
|
||||
color: Colors.red,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -267,9 +251,8 @@ class TaskItemCell extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
color: bean.isPinned == 1
|
||||
? ref.watch(themeProvider).themeColor.pinColor()
|
||||
: Colors.transparent,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
color: bean.isPinned == 1 ? ref.watch(themeProvider).themeColor.pinColor() : Colors.transparent,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
vertical: 8,
|
||||
@ -289,10 +272,7 @@ class TaskItemCell extends StatelessWidget {
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: ref
|
||||
.watch(themeProvider)
|
||||
.themeColor
|
||||
.taskTitleColor(),
|
||||
color: ref.watch(themeProvider).themeColor.titleColor(),
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
@ -313,16 +293,11 @@ class TaskItemCell extends StatelessWidget {
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
(bean.lastExecutionTime == null ||
|
||||
bean.lastExecutionTime == 0)
|
||||
? "-"
|
||||
: Utils.formatMessageTime(
|
||||
bean.lastExecutionTime!),
|
||||
(bean.lastExecutionTime == null || bean.lastExecutionTime == 0) ? "-" : Utils.formatMessageTime(bean.lastExecutionTime!),
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color:
|
||||
ref.watch(themeProvider).themeColor.descColor(),
|
||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
@ -333,6 +308,7 @@ class TaskItemCell extends StatelessWidget {
|
||||
height: 8,
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
@ -341,8 +317,7 @@ class TaskItemCell extends StatelessWidget {
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color:
|
||||
ref.watch(themeProvider).themeColor.descColor(),
|
||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
@ -359,6 +334,22 @@ class TaskItemCell extends StatelessWidget {
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
bean.command ?? "",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -372,54 +363,15 @@ class TaskItemCell extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
startCron(BuildContext context, WidgetRef ref) {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: const Text("确认运行"),
|
||||
content: Text("确认运行定时任务 ${bean.name ?? ""} 吗"),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text("取消"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: const Text("确定"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
ref.read(taskProvider).runCrons(bean.sId!);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
startCron(BuildContext context, WidgetRef ref) async {
|
||||
await ref.read(taskProvider).runCrons(bean.sId!);
|
||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||
logCron(context, ref);
|
||||
});
|
||||
}
|
||||
|
||||
stopCron(BuildContext context, WidgetRef ref) {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: const Text("确认停止"),
|
||||
content: Text("确认停止定时任务 ${bean.name ?? ""} 吗"),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text("取消"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: const Text("确定"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
ref.read(taskProvider).stopCrons(bean.sId!);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
ref.read(taskProvider).stopCrons(bean.sId!);
|
||||
}
|
||||
|
||||
logCron(BuildContext context, WidgetRef ref) {
|
||||
@ -441,6 +393,7 @@ class TaskItemCell extends StatelessWidget {
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
ref.read(taskProvider).loadData(false);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -466,13 +419,23 @@ class TaskItemCell extends StatelessWidget {
|
||||
content: Text("确认删除定时任务 ${bean.name ?? ""} 吗"),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text("取消"),
|
||||
child: const Text(
|
||||
"取消",
|
||||
style: TextStyle(
|
||||
color: Color(0xff999999),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: const Text("确定"),
|
||||
child: Text(
|
||||
"确定",
|
||||
style: TextStyle(
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
ref.read(taskProvider).delCron(bean.sId!);
|
||||
|
||||
@ -3,6 +3,7 @@ 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/task/task_bean.dart';
|
||||
import 'package:qinglong_app/utils/extension.dart';
|
||||
|
||||
var taskProvider = ChangeNotifierProvider((ref) => TaskViewModel());
|
||||
|
||||
@ -30,10 +31,6 @@ class TaskViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
void sortList() {
|
||||
list.sort((a, b) {
|
||||
return b.created!.compareTo(a.created!);
|
||||
});
|
||||
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
if (list[i].isPinned == 1) {
|
||||
final TaskBean item = list.removeAt(i);
|
||||
@ -71,6 +68,7 @@ class TaskViewModel extends BaseViewModel {
|
||||
HttpResponse<NullResponse> result = await Api.delTask(id);
|
||||
if (result.success) {
|
||||
list.removeWhere((element) => element.sId == id);
|
||||
"删除成功".toast();
|
||||
notifyListeners();
|
||||
} else {
|
||||
failToast(result.message, notify: true);
|
||||
@ -89,11 +87,12 @@ class TaskViewModel extends BaseViewModel {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void pinTask(String sId, int isPinned) async {
|
||||
Future<void> pinTask(String sId, int isPinned) async {
|
||||
if (isPinned == 1) {
|
||||
HttpResponse<NullResponse> response = await Api.unpinTask(sId);
|
||||
|
||||
if (response.success) {
|
||||
"取消置顶成功".toast();
|
||||
list.firstWhere((element) => element.sId == sId).isPinned = 0;
|
||||
sortList();
|
||||
success();
|
||||
@ -104,6 +103,7 @@ class TaskViewModel extends BaseViewModel {
|
||||
HttpResponse<NullResponse> response = await Api.pinTask(sId);
|
||||
|
||||
if (response.success) {
|
||||
"置顶成功".toast();
|
||||
list.firstWhere((element) => element.sId == sId).isPinned = 1;
|
||||
sortList();
|
||||
success();
|
||||
@ -113,11 +113,12 @@ class TaskViewModel extends BaseViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
void enableTask(String sId, int isDisabled) async {
|
||||
Future<void> enableTask(String sId, int isDisabled) async {
|
||||
if (isDisabled == 0) {
|
||||
HttpResponse<NullResponse> response = await Api.disableTask(sId);
|
||||
|
||||
if (response.success) {
|
||||
"禁用成功".toast();
|
||||
list.firstWhere((element) => element.sId == sId).isDisabled = 1;
|
||||
sortList();
|
||||
success();
|
||||
@ -128,6 +129,7 @@ class TaskViewModel extends BaseViewModel {
|
||||
HttpResponse<NullResponse> response = await Api.enableTask(sId);
|
||||
|
||||
if (response.success) {
|
||||
"启用成功".toast();
|
||||
list.firstWhere((element) => element.sId == sId).isDisabled = 0;
|
||||
sortList();
|
||||
success();
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:qinglong_app/base/routes.dart';
|
||||
|
||||
class QlNavigatorObserver extends NavigatorObserver {
|
||||
bool isInLoginPage = false;
|
||||
|
||||
@override
|
||||
void didPush(Route route, Route? previousRoute) {
|
||||
super.didPush(route, previousRoute);
|
||||
if (Routes.routeLogin == route.settings.name) {
|
||||
isInLoginPage = true;
|
||||
} else {
|
||||
isInLoginPage = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,11 +4,7 @@ import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
extension ContextExt on BuildContext {
|
||||
T read<T>(ProviderBase<T> provider) {
|
||||
return (this as WidgetRef).read<T>(provider);
|
||||
}
|
||||
|
||||
T watch<T>(ProviderBase<T> provider) {
|
||||
return (this as WidgetRef).watch<T>(provider);
|
||||
return ProviderScope.containerOf(this, listen: false).read(provider);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,74 @@ class Utils {
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
}
|
||||
|
||||
static String formatGMTTime(String gmtTime) {
|
||||
// "Wed Jan 12 2022 20:33:39 GMT+0800 (中国标准时间)"
|
||||
try {
|
||||
if (gmtTime.isEmpty) return "-";
|
||||
List<String> splitedStr = gmtTime.split(" ");
|
||||
|
||||
int year = int.parse(splitedStr[3]);
|
||||
String time = splitedStr[4];
|
||||
List<String> splitedTime = time.split(":");
|
||||
int hour = int.parse(splitedTime[0]);
|
||||
int min = int.parse(splitedTime[1]);
|
||||
int second = int.parse(splitedTime[2]);
|
||||
|
||||
int day = int.parse(splitedStr[2]);
|
||||
|
||||
String month = "01";
|
||||
|
||||
switch (splitedStr[1]) {
|
||||
case "Jan":
|
||||
month = "01";
|
||||
break;
|
||||
case "Feb":
|
||||
month = "02";
|
||||
break;
|
||||
case "Mar":
|
||||
month = "03";
|
||||
break;
|
||||
case "Apr":
|
||||
month = "04";
|
||||
break;
|
||||
case "May":
|
||||
month = "05";
|
||||
break;
|
||||
case "Jun":
|
||||
month = "06";
|
||||
break;
|
||||
case "Jul":
|
||||
month = "07";
|
||||
break;
|
||||
case "Aug":
|
||||
month = "08";
|
||||
break;
|
||||
case "Sep":
|
||||
month = "09";
|
||||
break;
|
||||
case "Oct":
|
||||
month = "10";
|
||||
break;
|
||||
case "Nov":
|
||||
month = "11";
|
||||
break;
|
||||
case "Dec":
|
||||
month = "12";
|
||||
break;
|
||||
}
|
||||
|
||||
var date = DateTime(year, int.parse(month), day, hour, min, second);
|
||||
|
||||
return formatMessageTime(date.millisecondsSinceEpoch);
|
||||
} catch (e) {
|
||||
return "-";
|
||||
}
|
||||
}
|
||||
|
||||
static String formatMessageTime(int time) {
|
||||
if(time == 0){
|
||||
return "-";
|
||||
}
|
||||
DateTime current = DateTime.now();
|
||||
DateTime chatTime;
|
||||
if (time.toString().length == 10) {
|
||||
|
||||
119
pubspec.lock
119
pubspec.lock
@ -239,6 +239,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
flip_card:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flip_card
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -345,6 +352,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.13.4"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -450,6 +464,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
move_to_background:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: move_to_background
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -457,6 +478,48 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
package_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
package_info_plus_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_linux
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
package_info_plus_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_macos
|
||||
url: "https://pub.flutter-io.cn"
|
||||
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"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
package_info_plus_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_web
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
package_info_plus_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_windows
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -714,6 +777,62 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.0.18"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.0.14"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.0.14"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.6"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
name: qinglong_app
|
||||
description: A new Flutter project.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
version: 1.0.0+1
|
||||
version: 1.0.2+3
|
||||
|
||||
environment:
|
||||
sdk: ">=2.15.1 <3.0.0"
|
||||
@ -26,6 +26,10 @@ dependencies:
|
||||
flutter_highlight: ^0.7.0
|
||||
drag_and_drop_lists: ^0.3.2+2
|
||||
fluttertoast: ^8.0.8
|
||||
move_to_background: ^1.0.2
|
||||
flip_card: ^0.6.0
|
||||
package_info_plus: ^1.3.0
|
||||
url_launcher: ^6.0.18
|
||||
|
||||
dev_dependencies:
|
||||
flutter_native_splash: ^1.3.3
|
||||
|
||||
@ -5,13 +5,9 @@
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:qinglong_app/base/http/http.dart';
|
||||
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||
void main() {
|
||||
String time = "Wed Jan 12 2022 20:33:39 GMT+0800 (中国标准时间)";
|
||||
|
||||
import 'package:qinglong_app/main.dart';
|
||||
import 'package:qinglong_app/module/login/login_bean.dart';
|
||||
import 'package:qinglong_app/module/login/login_viewmodel.dart';
|
||||
|
||||
void main() {}
|
||||
var result = DateTime.tryParse(time);
|
||||
print(result);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user