42 Commits
v1.0.9 ... main

Author SHA1 Message Date
05a38a417c Update README.md 2022-09-07 13:18:57 +08:00
ad4c0044a3 Update README.md 2022-08-17 13:41:02 +08:00
e65c1b24e8 Add files via upload 2022-08-17 13:40:45 +08:00
b9f8af2e71 Create version 2022-08-08 08:43:33 +08:00
eb7c4021b9 Update README.md 2022-08-03 16:26:12 +08:00
373b692a23 Update README.md 2022-08-03 16:25:56 +08:00
7409267753 Update README.md 2022-08-02 16:06:27 +08:00
c812c74076 add 2022-08-02 15:38:32 +08:00
ba65d33a53 Update README.md 2022-08-01 14:23:24 +08:00
02fb4a8182 Delete 9.jpg 2022-08-01 14:01:54 +08:00
53f744c350 Delete 8.jpg 2022-08-01 14:01:47 +08:00
39a815baff Delete 7.jpg 2022-08-01 14:01:40 +08:00
2250887e04 Delete 6.jpg 2022-08-01 14:01:32 +08:00
3d12603f0c Delete 5.jpg 2022-08-01 14:01:25 +08:00
ee9739d08f Delete 3.jpg 2022-08-01 14:01:18 +08:00
a53a4fdd19 Delete 14.jpg 2022-08-01 14:01:10 +08:00
b4e9f8a29a Delete 13.jpg 2022-08-01 14:01:03 +08:00
a223d5e633 Delete 2.jpg 2022-08-01 14:00:56 +08:00
1d8944c4b7 Delete 4.jpg 2022-08-01 14:00:49 +08:00
e19ce0a5e4 Delete 12.jpg 2022-08-01 14:00:42 +08:00
0642ad5c33 Delete 11.jpg 2022-08-01 14:00:34 +08:00
9def109426 Delete 10.jpg 2022-08-01 14:00:26 +08:00
280641ee57 Delete 1.jpg 2022-08-01 14:00:18 +08:00
3f65e9312c Update README.md 2022-08-01 13:59:43 +08:00
a5a3494db9 Update README.md 2022-08-01 13:59:20 +08:00
a55cf258ba Add files via upload 2022-08-01 13:58:40 +08:00
b2b8cf5a31 Update README.md 2022-08-01 13:56:51 +08:00
ba5c15d5a9 Update README.md 2022-08-01 13:56:17 +08:00
484e1eeee3 fix bug 2022-06-23 18:55:11 +08:00
e2a2ae55ec 支持高刷 2022-06-23 17:16:04 +08:00
558b780235 Merge pull request #4 from huoxue1/main
新增订阅管理,新增环境变量备份和导入导出
2022-06-23 16:43:12 +08:00
1cd3724805 新增订阅管理
新增环境变量导入和导出
2022-06-23 16:30:39 +08:00
7e3a63169e 依赖管理支持批量添加 2022-06-16 15:54:19 +08:00
1298dba590 支持上传脚本 2022-06-16 14:37:47 +08:00
2005083d2e 新增代码文件支持行号显示 2022-06-15 11:05:49 +08:00
4e3f8a0df9 1.1.0 release 2022-06-08 10:42:26 +08:00
5698277301 1.1.0 release 2022-06-08 10:18:24 +08:00
02be737264 1.1.0 release 2022-06-08 10:11:49 +08:00
9558f3d235 1.1.0 release 2022-06-08 10:11:32 +08:00
14c3b1a965 优化使用体验 2022-06-08 10:06:54 +08:00
b89de1414a 优化使用体验 2022-06-07 19:32:39 +08:00
9759caf9b8 优化搜索框样式,优化任务列表,环境变量等相关页面使用体验 2022-06-07 18:28:38 +08:00
138 changed files with 16 additions and 13140 deletions

46
.gitignore vendored
View File

@ -1,46 +0,0 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@ -1,10 +0,0 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b
channel: stable
project_type: app

View File

@ -1,53 +0,0 @@
## v1.0.7
* 首页任务增加分类
* 增加国际化相关配置
* 登录页状态栏适配
## v1.0.6
* 支持切换账号功能
* 登录页青龙图标随颜色主题变换
* apk大小减少2/3,只支持arm架构,不再支持x86
## v1.0.5
* 优化配置文件编辑功能
* 优化文本选择样式
* 增加页面错误toast
* 修改iOS端app icon
## 1.0.4
* 增加主题切换功能,各种颜色主题随意切换
* 修改无法停止任务的bug
* 修改脚本管理页面json解析失败的bug
* 修改变更配置文件没有及时刷新UI的bug
* 增加版本更新提醒功能(需要手机能联通github)
* 登录页面长按青龙logo可以呼出网络请求日志,再次长按可关闭
* 打开系统最近任务时展示APP的名称
* 优化使用体验
## 1.0.3
* 解决2.10.12及以下版本无法登录的问题
* 适配最新版qinglong 相关接口字段
* 其他页面排版优化
## 1.0.2
* 支持修改用户名密码(仅限使用用户名密码登录用户)
* 支持脚本的编辑删除
* 支持client_id登录之后只启用部分模块
* 支持两步验证
## 1.0.1
* 增加任务详情页
* 增加环境变量详情页
* 环境变量列表使用体验优化
* 部分页面进入卡顿优化
* 扩大任务的搜索范围
* 优化暗黑主题
* 支持client_id登录
* 修改bug
## 1.0.0
* 完成基础功能

View File

@ -1,63 +1,25 @@
# qinglong_app
版本更新通知 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/14.jpg" width="200" />
<img src="./art/1.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" />
<img src="./art/8.jpg" width="200" />
</p>
## [更新记录](./CHANGELOG.md)
### 不支持的功能
>* 应用设置
>* 通知设置
AppStore搜索 『青龙客户端』
版本更新通知 https://t.me/qinglongapp
用于生成app的图标
基于[qinglong](https://github.com/whyour/qinglong)API实现的三方客户端,采用Flutter编写 (API最低支持2.10.0)
flutter pub run flutter_launcher_icons:main
生成原生的启动页面
Android端下载地址:[Release](https://github.com/qinglong-app/qinglong_app/releases)
flutter pub run flutter_native_splash:create
修改app名称
iOS端下载地址: [AppStore](https://apps.apple.com/cn/app/id1625871665)
flutter pub run flutter_app_name
不再开放源码, 之前的源码在这 https://github.com/qinglong-app/qinglong_app/tags
生成json.jc.dart文件
flutter pub run build_runner build --delete-conflicting-outputs
<p float="left">
<img src="./art/1.png" width="250" />
<img src="./art/2.png" width="250" />
<img src="./art/3.png" width="250" />
</p>
<p float="left">
<img src="./art/4.png" width="250" />
<img src="./art/5.png" width="250" />
<img src="./art/6.jpg" width="250" />
</p>

View File

@ -1,29 +0,0 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

13
android/.gitignore vendored
View File

@ -1,13 +0,0 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

View File

@ -1,82 +0,0 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion flutter.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "work.master.qinglongapp"
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
signingConfigs {
release {
storeFile file("qinglong.keystore")
storePassword "jiangyuesong"
keyAlias "jiangyuesong"
keyPassword "jiangyuesong"
// Optional, specify signing versions used
v1SigningEnabled true
v2SigningEnabled true
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

View File

@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="work.master.qinglongapp">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -1,36 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="work.master.qinglongapp">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="青龙客户端"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<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:hardwareAccelerated="true"
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"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@ -1,6 +0,0 @@
package work.master.qinglong_app
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -1,6 +0,0 @@
package work.master.qinglongapp
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 B

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 B

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowSplashScreenBackground">#000000</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowSplashScreenBackground">#ffffff</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="work.master.qinglongapp">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -1,31 +0,0 @@
buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -1,3 +0,0 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View File

@ -1,6 +0,0 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

View File

@ -1,11 +0,0 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

Binary file not shown.

Binary file not shown.

BIN
art/1.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 KiB

BIN
art/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

BIN
art/2.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

BIN
art/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
art/3.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 606 KiB

BIN
art/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

BIN
art/4.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

BIN
art/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
art/5.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

BIN
art/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
art/6.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 606 KiB

After

Width:  |  Height:  |  Size: 137 KiB

BIN
art/7.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

BIN
art/8.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

BIN
art/9.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,81 +0,0 @@
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';
class BaseStateWidget<T extends BaseViewModel> extends ConsumerStatefulWidget {
final Widget Function(WidgetRef context, T value, Widget? child) builder;
final ProviderBase<T> model;
final Widget? child;
final Function(T)? onReady;
final bool lazyLoad;
const BaseStateWidget({
Key? key,
required this.builder,
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>> with LazyLoadState<BaseStateWidget<T>> {
@override
Widget build(BuildContext context) {
var viewModel = ref.watch<T>(widget.model);
if (viewModel.failedToast != null) {
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
(viewModel.failedToast ?? "").toast();
viewModel.clearToast();
});
}
if (viewModel.currentState == PageState.CONTENT) {
return widget.builder(ref, viewModel, widget.child);
}
if (viewModel.currentState == PageState.LOADING) {
return Container(
alignment: Alignment.center,
child: const CupertinoActivityIndicator(),
);
}
if (viewModel.currentState == PageState.FAILED) {
return Container(
alignment: Alignment.center,
child: Text(viewModel.failReason ?? ""),
);
}
if (viewModel.currentState == PageState.EMPTY) {
return Container(
alignment: Alignment.center,
child: const Text("暂无数据"),
);
}
return Container();
}
@override
void onLazyLoad() {
if (widget.onReady != null && widget.lazyLoad) {
widget.onReady!(ref.read<T>(widget.model));
}
}
@override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
if (widget.onReady != null && !widget.lazyLoad) {
widget.onReady!(ref.read<T>(widget.model));
}
});
}
}

View File

@ -1,66 +0,0 @@
import 'package:flutter/cupertino.dart';
class ViewModel extends ChangeNotifier {}
class BaseViewModel extends ViewModel {
PageState currentState = PageState.START;
String? failReason;
String? failedToast;
void loading({bool notify = false}) {
failReason = null;
failedToast = null;
currentState = PageState.LOADING;
if (notify) {
notifyListeners();
}
}
void success({bool notify = true}) {
failReason = null;
failedToast = null;
currentState = PageState.CONTENT;
if (notify) {
notifyListeners();
}
}
void failed(String? reason, {bool notify = false}) {
currentState = PageState.FAILED;
failReason = reason;
failedToast = null;
if (notify) {
notifyListeners();
}
}
void failToast(String? reason, {bool notify = false}) {
currentState = PageState.CONTENT;
failedToast = reason;
failReason = reason;
if (notify) {
notifyListeners();
}
}
void clearToast() {
failedToast = null;
}
void empty({bool notify = false}) {
failReason = null;
failedToast = null;
currentState = PageState.EMPTY;
if (notify) {
notifyListeners();
}
}
}
enum PageState {
START,
LOADING,
EMPTY,
CONTENT,
FAILED,
}

View File

@ -1,385 +0,0 @@
import 'package:qinglong_app/base/http/http.dart';
import 'package:qinglong_app/base/http/url.dart';
import 'package:qinglong_app/main.dart';
import 'package:qinglong_app/module/config/config_bean.dart';
import 'package:qinglong_app/module/env/env_bean.dart';
import 'package:qinglong_app/module/home/system_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/utils/utils.dart';
class Api {
static Future<HttpResponse<SystemBean>> system() async {
return await Http.get<SystemBean>(
Url.system,
{},
);
}
static Future<HttpResponse<LoginBean>> login(
String userName,
String passWord,
) async {
return await Http.post<LoginBean>(
Url.login,
{
"username": userName,
"password": passWord,
},
);
}
static Future<HttpResponse<LoginBean>> loginOld(
String userName,
String passWord,
) async {
return await Http.post<LoginBean>(
Url.loginOld,
{
"username": userName,
"password": passWord,
},
);
}
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,
{"searchValue": ""},
);
}
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 {
return await Http.put<NullResponse>(
Url.stopTasks,
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),
null,
);
}
static Future<HttpResponse<NullResponse>> addTask(
String name,
String command,
String cron, {
int? id,
String? nId,
}) async {
var data = <String, dynamic>{"name": name, "command": command, "schedule": cron};
if (id != null || nId != null) {
if (id != null) {
data["id"] = id;
} else if (nId != null) {
data["_id"] = nId;
}
return await Http.put<NullResponse>(
Url.addTask,
data,
);
}
return await Http.post<NullResponse>(
Url.addTask,
data,
);
}
static Future<HttpResponse<NullResponse>> delTask(String cron) async {
return await Http.delete<NullResponse>(
Url.addTask,
[cron],
);
}
static Future<HttpResponse<NullResponse>> pinTask(String cron) async {
return await Http.put<NullResponse>(
Url.pinTask,
[cron],
);
}
static Future<HttpResponse<NullResponse>> unpinTask(String cron) async {
return await Http.put<NullResponse>(
Url.unpinTask,
[cron],
);
}
static Future<HttpResponse<NullResponse>> enableTask(String cron) async {
return await Http.put<NullResponse>(
Url.enableTask,
[cron],
);
}
static Future<HttpResponse<NullResponse>> disableTask(String cron) async {
return await Http.put<NullResponse>(
Url.disableTask,
[cron],
);
}
static Future<HttpResponse<List<ConfigBean>>> files() async {
return await Http.get<List<ConfigBean>>(
Url.files,
null,
);
}
static Future<HttpResponse<String>> content(String name) async {
return await Http.get<String>(
Url.configContent + name,
null,
);
}
static Future<HttpResponse<NullResponse>> saveFile(String name, String content) async {
return await Http.post<NullResponse>(
Url.saveFile,
{"content": content, "name": name},
);
}
static Future<HttpResponse<List<EnvBean>>> envs(String search) async {
return await Http.get<List<EnvBean>>(
Url.envs,
{"searchValue": search},
);
}
static Future<HttpResponse<NullResponse>> enableEnv(String id) async {
return await Http.put<NullResponse>(
Url.enableEnvs,
[id],
);
}
static Future<HttpResponse<NullResponse>> disableEnv(String id) async {
return await Http.put<NullResponse>(
Url.disableEnvs,
[id],
);
}
static Future<HttpResponse<NullResponse>> delEnv(String id) async {
return await Http.delete<NullResponse>(
Url.delEnv,
[id],
);
}
static Future<HttpResponse<NullResponse>> addEnv(
String name,
String value,
String remarks, {
int? id,
String? nId,
}) async {
var data = <String, dynamic>{
"value": value,
"remarks": remarks,
"name": name,
};
if (id != null || nId != null) {
if (id != null) {
data["id"] = id;
} else if (nId != null) {
data["_id"] = nId;
}
return await Http.put<NullResponse>(
Url.addEnv,
data,
);
}
return await Http.post<NullResponse>(
Url.addEnv,
[data],
);
}
static Future<HttpResponse<NullResponse>> moveEnv(String id, int fromIndex, int toIndex) async {
return await Http.put<NullResponse>(
Url.envMove(id),
{"fromIndex": fromIndex, "toIndex": toIndex},
);
}
static Future<HttpResponse<List<LoginLogBean>>> loginLog() async {
return await Http.get<List<LoginLogBean>>(
Url.loginLog,
null,
);
}
static Future<HttpResponse<List<TaskLogBean>>> taskLog() async {
return await Http.get<List<TaskLogBean>>(Url.taskLog, null, serializationName: Utils.isUpperVersion2_12_2() ? "data" : "dirs");
}
static Future<HttpResponse<String>> taskLogDetail(String name, String path) async {
if (Utils.isUpperVersion2_13_0()) {
return await Http.get<String>(
Url.taskLogDetail + name + "?path=" + path,
null,
);
} else {
return await Http.get<String>(
Url.taskLogDetail + path + "/" + name,
null,
);
}
}
static Future<HttpResponse<List<ScriptBean>>> scripts() async {
//2.12.2以及以上版本,脚本路径url做了修改
return await Http.get<List<ScriptBean>>(
Utils.isUpperVersion2_13_0() ? Url.scripts2 : Url.scripts,
null,
);
}
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,
{
"path": path,
},
);
}
static Future<HttpResponse<List<DependencyBean>>> dependencies(String type) async {
return await Http.get<List<DependencyBean>>(
Url.dependencies,
{
"type": type.toString(),
},
);
}
static Future<HttpResponse<NullResponse>> dependencyReinstall(String id) async {
return await Http.put<NullResponse>(
Url.dependencies,
[id],
);
}
static Future<HttpResponse<String>> dependencyLog(String id) async {
return await Http.get<String>(
Url.dependencies + "/" + id,
null,
);
}
static Future<HttpResponse<NullResponse>> addDependency(String name, int type) async {
return await Http.post<NullResponse>(
Url.dependencies,
[
{
"name": name,
"type": type,
}
],
);
}
static Future<HttpResponse<NullResponse>> addScript(String name, String path, String content) async {
return await Http.post<NullResponse>(
Url.addScript,
{
"filename": name,
"path": path,
"content": content,
},
);
}
static Future<HttpResponse<NullResponse>> delDependency(String id) async {
return await Http.delete<NullResponse>(
Url.dependencies,
[id],
);
}
}

View File

@ -1,258 +0,0 @@
import 'dart:convert';
import 'dart:core';
import 'package:dio/dio.dart';
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/http/url.dart';
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
import 'package:qinglong_app/utils/extension.dart';
import '../../json.jc.dart';
import '../../main.dart';
import '../routes.dart';
class Http {
static Dio? _dio;
static bool pushedLoginPage = false;
static void initDioConfig(
String host,
) {
_dio = Dio(
BaseOptions(
baseUrl: host,
connectTimeout: 50000,
receiveTimeout: 50000,
sendTimeout: 50000,
contentType: "application/json",
),
);
_dio?.interceptors.add(DioLogInterceptor());
_dio?.interceptors.add(TokenInterceptor());
}
static void _init() {
if (_dio == null) {
initDioConfig(getIt<UserInfoViewModel>().host!);
}
}
static void clear() {
_dio = null;
}
static Future<HttpResponse<T>> get<T>(
String uri,
Map<String, String?>? json, {
bool compute = true,
String serializationName = "data",
}) async {
try {
_init();
var response = await _dio!.get(uri, queryParameters: json);
return decodeResponse<T>(response, serializationName, compute);
} on DioError catch (e) {
return exceptionHandler<T>(e, uri);
}
}
static Future<HttpResponse<T>> post<T>(
String uri,
dynamic json, {
bool compute = true,
String serializationName = "data",
}) async {
try {
_init();
var response = await _dio!.post(uri, data: json);
return decodeResponse<T>(
response,
serializationName,
compute,
);
} on DioError catch (e) {
return exceptionHandler<T>(e, uri);
}
}
static Future<HttpResponse<T>> delete<T>(
String uri,
dynamic json, {
bool compute = true,
String serializationName = "data",
}) async {
try {
_init();
var response = await _dio!.delete(uri, data: json);
return decodeResponse<T>(
response,
serializationName,
compute,
);
} on DioError catch (e) {
return exceptionHandler<T>(e, uri);
}
}
static Future<HttpResponse<T>> put<T>(
String uri,
dynamic json, {
bool compute = true,
String serializationName = "data",
}) async {
try {
_init();
var response = await _dio!.put(uri, data: json);
return decodeResponse<T>(
response,
serializationName,
compute,
);
} on DioError catch (e) {
return exceptionHandler<T>(e, uri);
}
}
static void exitLogin() {
if (!pushedLoginPage) {
"身份已过期,请重新登录".toast();
pushedLoginPage = true;
navigatorState.currentState?.pushNamedAndRemoveUntil(Routes.routeLogin, (route) => false);
}
}
static HttpResponse<T> exceptionHandler<T>(DioError e, String path) {
logger.e(e);
if (e.response?.statusCode == 401 && !Url.inWhiteList(path)) {
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);
}
}
static HttpResponse<T> decodeResponse<T>(
Response<dynamic> response,
String serializationName,
bool compute,
) {
int code = 0;
if (response.statusCode == 200) {
try {
if (response.data["code"] == 200) {
if (response.data[serializationName] != null) {
if (T == NullResponse) {
return HttpResponse<T>(
success: true,
code: 200,
);
}
dynamic data = response.data[serializationName];
T t;
if (T == String) {
if (data is String) {
t = data as T;
} else {
t = jsonEncode(data) as T;
}
return HttpResponse<T>(
success: true,
code: 200,
bean: t,
);
} else {
T bean;
if (compute) {
bean = DeserializeAction.invokeJson(DeserializeAction<T>(data));
} else {
bean = JsonConversion$Json.fromJson<T>(data);
}
return HttpResponse<T>(
success: true,
code: 200,
bean: bean,
);
}
} else {
return HttpResponse<T>(
success: true,
code: 200,
);
}
} else {
return HttpResponse<T>(
success: false,
code: response.data["code"],
message: response.data["message"],
);
}
} catch (e) {
logger.e(e);
return HttpResponse<T>(
success: false,
code: -1000,
message: "json解析失败",
);
}
} else {
code = response.statusCode ?? 0;
return HttpResponse(
success: false,
code: code,
message: response.statusMessage,
);
}
}
}
class HttpResponse<T> {
late bool success;
String? message;
late int code;
T? bean;
HttpResponse({required this.success, this.message, required this.code, this.bean});
}
class DeserializeAction<T> {
final dynamic json;
DeserializeAction(this.json);
T invoke() {
return JsonConversion$Json.fromJson<T>(json);
}
static dynamic invokeJson(DeserializeAction a) => a.invoke();
}
mixin BaseBean<T> {
T fromJson(Map<String, dynamic> json);
}
class CronBean with BaseBean<CronBean> {
@override
CronBean fromJson(Map<String, dynamic> json) {
return CronBean();
}
}
void decode<T>() async {
compute(DeserializeAction.invokeJson, DeserializeAction<T>({}));
}
class NullResponse {}
class NotLoginException implements Exception {}

View File

@ -1,29 +0,0 @@
import 'package:dio/dio.dart';
import 'package:qinglong_app/base/http/url.dart';
import 'package:qinglong_app/main.dart';
import '../userinfo_viewmodel.dart';
class TokenInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
options.headers["User-Agent"] =
"qinglong_client";
options.headers["Content-Type"] = "application/json;charset=UTF-8";
if (!Url.inWhiteList(options.path)) {
options.queryParameters["t"] =
(DateTime.now().millisecondsSinceEpoch ~/ 1000).toString();
}
if (!Url.inLoginList(options.path)) {
if (getIt<UserInfoViewModel>().token != null &&
getIt<UserInfoViewModel>().token!.isNotEmpty) {
options.headers["Authorization"] =
"Bearer " + getIt<UserInfoViewModel>().token!;
}
}
return handler.next(options);
}
}

View File

@ -1,92 +0,0 @@
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
import 'package:qinglong_app/main.dart';
class Url {
static get login => "/api/user/login";
static get system => "/api/system";
static get loginOld => "/api/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 scripts2 => getIt<UserInfoViewModel>().useSecretLogined ? "/open/scripts" : "/api/scripts";
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 addScript => getIt<UserInfoViewModel>().useSecretLogined ? "/open/scripts" : "/api/scripts";
static get dependencyReinstall => getIt<UserInfoViewModel>().useSecretLogined ? "/open/dependencies/reinstall" : "/api/dependencies/reinstall";
static intimeLog(String cronId) {
return getIt<UserInfoViewModel>().useSecretLogined ? "/open/crons/$cronId/log" : "/api/crons/$cronId/log";
}
static envMove(String envId) {
return getIt<UserInfoViewModel>().useSecretLogined ? "/open/envs/$envId/move" : "/api/envs/$envId/move";
}
static bool inWhiteList(String path) {
if (path == login || path == loginByClientId || path == loginTwo || path == loginOld) {
return true;
}
return false;
}
static bool inLoginList(String path) {
if (path == login || path == loginByClientId || path == loginOld) {
return true;
}
return false;
}
}

View File

@ -1,56 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class QlAppBar extends StatelessWidget with PreferredSizeWidget {
final String title;
final List<Widget>? actions;
final VoidCallback? backCall;
final bool canBack;
final Widget? backWidget;
QlAppBar({
Key? key,
required this.title,
this.actions,
this.backCall,
this.canBack = true,
this.backWidget,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget back = backWidget ??
InkWell(
onTap: () {
if (backCall != null) {
backCall!();
} else {
Navigator.of(context).pop();
}
},
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 15,
),
child: Center(
child: Icon(
CupertinoIcons.left_chevron,
color: Colors.white,
),
),
),
);
return AppBar(
elevation: 0,
leading: canBack ? back : null,
automaticallyImplyLeading: canBack,
title: Text(title),
centerTitle: true,
actions: [...?actions],
);
}
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}

View File

@ -1,153 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:qinglong_app/module/config/config_edit_page.dart';
import 'package:qinglong_app/module/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/theme_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";
static const String routeLoginLog = "/log/login";
static const String routeTaskLog = "/log/task";
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 const String routeTheme = "/theme";
static Route<dynamic>? generateRoute(RouteSettings settings) {
switch (settings.name) {
case routeHomePage:
return MaterialPageRoute(builder: (context) => const HomePage());
case routeLogin:
if (settings.arguments != null) {
return MaterialPageRoute(
builder: (context) => const LoginPage(
doNotLoadLocalData: true,
));
} else {
return MaterialPageRoute(builder: (context) => const LoginPage());
}
case routeAddTask:
if (settings.arguments != null) {
return CupertinoPageRoute(
builder: (context) => AddTaskPage(
taskBean: settings.arguments as TaskBean,
));
} else {
return CupertinoPageRoute(builder: (context) => const AddTaskPage());
}
case routeAddDependency:
return CupertinoPageRoute(
builder: (context) => const AddDependencyPage());
case routeAddEnv:
if (settings.arguments != null) {
return CupertinoPageRoute(
builder: (context) => AddEnvPage(
envBean: settings.arguments as EnvBean,
));
} else {
return CupertinoPageRoute(builder: (context) => const AddEnvPage());
}
case routeConfigEdit:
return CupertinoPageRoute(
builder: (context) => ConfigEditPage(
(settings.arguments as Map)["title"],
(settings.arguments as Map)["content"],
),
);
case routeLoginLog:
return CupertinoPageRoute(
builder: (context) => const LoginLogPage(),
);
case routeTaskLog:
return CupertinoPageRoute(
builder: (context) => const TaskLogPage(),
);
case routeScript:
return CupertinoPageRoute(
builder: (context) => const ScriptPage(),
);
case routeDependency:
return CupertinoPageRoute(
builder: (context) => const DependencyPage(),
);
case routeTaskLogDetail:
return CupertinoPageRoute(
builder: (context) => TaskLogDetailPage(
title: (settings.arguments as Map)['title'],
path: (settings.arguments as Map)['path'],
),
);
case routeScriptDetail:
return CupertinoPageRoute(
builder: (context) => ScriptDetailPage(
title: (settings.arguments as Map)["title"],
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 routeTheme:
return CupertinoPageRoute(
builder: (context) => const ThemePage(),
);
case routeScriptUpdate:
return CupertinoPageRoute(
builder: (context) => ScriptEditPage(
(settings.arguments as Map)["title"],
(settings.arguments as Map)["path"],
(settings.arguments as Map)["content"],
),
);
}
return null;
}
}

View File

@ -1,8 +0,0 @@
String spUserInfo = "userinfo";
String spHost = "host";
String spUserName = "username";
String spPassWord = "password";
String spTheme = "dart_mode";
String spSecretLogined = "secret_logined";
String spCustomColor = "customColor";
String spLoginHistory = "loginHistory";

View File

@ -1,315 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qinglong_app/base/sp_const.dart';
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
import 'package:qinglong_app/main.dart';
import 'package:qinglong_app/utils/codeeditor_theme.dart';
import 'package:qinglong_app/utils/sp_utils.dart';
var themeProvider = ChangeNotifierProvider((ref) => ThemeViewModel());
Color commonColor = const Color(0xFF299343);
Color _primaryColor = commonColor;
class ThemeViewModel extends ChangeNotifier {
late ThemeData currentTheme;
bool _isInDarkMode = false;
Color primaryColor = commonColor;
ThemeColors themeColor = LightThemeColors();
ThemeViewModel() {
_primaryColor = Color(getIt<UserInfoViewModel>().primaryColor);
primaryColor = Color(getIt<UserInfoViewModel>().primaryColor);
var brightness = SchedulerBinding.instance!.window.platformBrightness;
_isInDarkMode = brightness == Brightness.dark;
changeThemeReal(_isInDarkMode, false);
}
bool isInDartMode() {
return SpUtil.getBool(spTheme, defValue: false);
}
void changeThemeReal(bool dark, [bool notify = true]) {
_isInDarkMode = dark;
SpUtil.putBool(spTheme, dark);
if (!dark) {
currentTheme = getLightTheme();
themeColor = LightThemeColors();
} else {
currentTheme = getDartTheme();
themeColor = DartThemeColors();
}
if (notify) {
notifyListeners();
}
}
get darkMode => _isInDarkMode;
void changePrimaryColor(Color color) {
_primaryColor = color;
primaryColor = color;
getIt<UserInfoViewModel>().updateCustomColor(color.value);
changeThemeReal(SpUtil.getBool(spTheme, defValue: false), true);
}
void changeTheme() {
changeThemeReal(!SpUtil.getBool(spTheme, defValue: false), true);
}
ThemeData getLightTheme() {
return ThemeData.light().copyWith(
brightness: Brightness.light,
primaryColor: _primaryColor,
colorScheme: ColorScheme.light(
secondary: _primaryColor,
primary: _primaryColor,
),
scaffoldBackgroundColor: const Color(0xfff5f5f5),
inputDecorationTheme: InputDecorationTheme(
labelStyle: TextStyle(
color: _primaryColor,
fontSize: 14,
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: _primaryColor,
),
),
border: const UnderlineInputBorder(
borderSide: BorderSide(
color: Color(0xff999999),
),
),
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Color(0xff999999),
),
),
),
appBarTheme: AppBarTheme(
backgroundColor: _primaryColor,
titleTextStyle: const TextStyle(
color: Colors.white,
fontSize: 18,
),
),
bottomNavigationBarTheme: BottomNavigationBarThemeData(
selectedItemColor: _primaryColor,
),
buttonTheme: ButtonThemeData(
buttonColor: _primaryColor,
),
progressIndicatorTheme: ProgressIndicatorThemeData(
color: _primaryColor,
),
tabBarTheme: TabBarTheme(
labelStyle: const TextStyle(
fontSize: 14,
),
unselectedLabelStyle: const TextStyle(
fontSize: 14,
),
labelColor: _primaryColor,
unselectedLabelColor: const Color(0xff999999),
indicator: UnderlineTabIndicator(
borderSide: BorderSide(color: _primaryColor),
),
),
toggleableActiveColor: _primaryColor,
checkboxTheme: CheckboxThemeData(
checkColor: MaterialStateProperty.resolveWith(
(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return Colors.transparent;
}
if (states.contains(MaterialState.selected)) {
return Colors.white;
}
return Colors.black;
},
),
),
cupertinoOverrideTheme: NoDefaultCupertinoThemeData(
brightness: Brightness.light,
primaryColor: _primaryColor,
scaffoldBackgroundColor: const Color(0xfff5f5f5),
),
);
}
ThemeData getDartTheme() {
return ThemeData.dark().copyWith(
brightness: Brightness.dark,
primaryColor: const Color(0xffffffff),
scaffoldBackgroundColor: Colors.black,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.black,
titleTextStyle: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
inputDecorationTheme: InputDecorationTheme(
labelStyle: TextStyle(
color: _primaryColor,
fontSize: 14,
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: _primaryColor,
),
),
border: const UnderlineInputBorder(
borderSide: BorderSide(
color: Color(0xff999999),
),
),
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Color(0xff999999),
),
),
),
tabBarTheme: const TabBarTheme(
labelStyle: TextStyle(
fontSize: 14,
),
unselectedLabelStyle: TextStyle(
fontSize: 14,
),
labelColor: Color(0xffffffff),
unselectedLabelColor: Color(0xff999999),
),
colorScheme: ColorScheme.light(
secondary: _primaryColor,
primary: _primaryColor,
),
toggleableActiveColor: _primaryColor,
checkboxTheme: CheckboxThemeData(
checkColor: MaterialStateProperty.resolveWith(
(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return Colors.transparent;
}
if (states.contains(MaterialState.selected)) {
return Colors.white;
}
return Colors.white;
},
),
),
cupertinoOverrideTheme: const NoDefaultCupertinoThemeData(
brightness: Brightness.dark,
primaryColor: Color(0xffffffff),
scaffoldBackgroundColor: Colors.black,
),
);
}
}
abstract class ThemeColors {
Color settingBgColor();
Color settingBordorColor();
Color titleColor();
Color descColor();
Color tabBarColor();
Color pinColor();
Color buttonBgColor();
Map<String, TextStyle> codeEditorTheme();
}
class LightThemeColors extends ThemeColors {
@override
Color titleColor() {
return const Color(0xff333333);
}
@override
Color pinColor() {
return const Color(0xffF7F7F7);
}
@override
Map<String, TextStyle> codeEditorTheme() {
return qinglongLightTheme;
}
@override
Color descColor() {
return const Color(0xff999999);
}
@override
Color settingBgColor() {
return Colors.white;
}
@override
Color buttonBgColor() {
return _primaryColor;
}
@override
Color settingBordorColor() {
return Colors.white;
}
@override
Color tabBarColor() {
return const Color(0xffF7F7F7);
}
}
class DartThemeColors extends ThemeColors {
@override
Color titleColor() {
return Colors.white;
}
@override
Color pinColor() {
return Colors.black12;
}
@override
Map<String, TextStyle> codeEditorTheme() {
return qinglongDarkTheme;
}
@override
Color descColor() {
return const Color(0xff999999);
}
@override
Color settingBgColor() {
return Colors.black;
}
@override
Color buttonBgColor() {
return const Color(0xff333333);
}
@override
Color settingBordorColor() {
return Color(0xff333333);
}
@override
Color tabBarColor() {
return Colors.black;
}
}

View File

@ -1,76 +0,0 @@
import 'package:flutter/material.dart';
///tabbar样式下方横条固定宽度
class AbsUnderlineTabIndicator extends Decoration {
const AbsUnderlineTabIndicator({
this.wantWidth,
this.borderSide = const BorderSide(width: 2.0, color: Colors.white),
this.insets = EdgeInsets.zero,
}) : assert(borderSide != null),
assert(insets != null);
final BorderSide? borderSide;
final double? wantWidth;
final EdgeInsetsGeometry? insets;
@override
Decoration? lerpFrom(Decoration? a, double t) {
if (a is UnderlineTabIndicator) {
return UnderlineTabIndicator(
borderSide: BorderSide.lerp(a.borderSide, borderSide!, t),
insets: EdgeInsetsGeometry.lerp(a.insets, insets, t)!,
);
}
return super.lerpFrom(a, t);
}
@override
Decoration? lerpTo(Decoration? b, double t) {
if (b is UnderlineTabIndicator) {
return UnderlineTabIndicator(
borderSide: BorderSide.lerp(borderSide!, b.borderSide, t),
insets: EdgeInsetsGeometry.lerp(insets, b.insets, t)!,
);
}
return super.lerpTo(b, t);
}
@override
_UnderlinePainter createBoxPainter([VoidCallback? onChanged]) {
return _UnderlinePainter(this, onChanged);
}
}
class _UnderlinePainter extends BoxPainter {
_UnderlinePainter(this.decoration, VoidCallback? onChanged)
: super(onChanged);
final AbsUnderlineTabIndicator decoration;
BorderSide? get borderSide => decoration.borderSide;
EdgeInsetsGeometry? get insets => decoration.insets;
Rect _indicatorRectFor(Rect rect, TextDirection textDirection) {
final Rect indicator = insets!.resolve(textDirection).deflateRect(rect);
//取中间坐标
double cw = (indicator.left + indicator.right) / 2;
return Rect.fromLTWH(
cw - decoration.wantWidth! / 2,
indicator.bottom - borderSide!.width,
decoration.wantWidth!,
borderSide!.width);
}
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
assert(configuration.size != null);
final Rect rect = offset & configuration.size!;
final TextDirection textDirection = configuration.textDirection!;
final Rect indicator =
_indicatorRectFor(rect, textDirection).deflate(borderSide!.width / 2.0);
final Paint paint = borderSide!.toPaint()..strokeCap = StrokeCap.round;
canvas.drawLine(indicator.bottomLeft, indicator.bottomRight, paint);
}
}

View File

@ -1,26 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qinglong_app/base/theme.dart';
class EmptyWidget extends ConsumerWidget {
const EmptyWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"暂无数据",
style: TextStyle(
fontSize: 14,
color: ref.watch(themeProvider).themeColor.descColor(),
),
),
],
),
);
}
}

View File

@ -1,29 +0,0 @@
import 'package:flutter/material.dart';
/// @author NewTab
mixin LazyLoadState<T extends StatefulWidget> on State<T> {
@override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
var route = ModalRoute.of(context);
void handler(status) {
if (status == AnimationStatus.completed) {
route?.animation?.removeStatusListener(handler);
onLazyLoad();
}
}
if (route == null ||
route.animation == null ||
route.animation!.status == AnimationStatus.completed) {
onLazyLoad();
} else {
route.animation!.addStatusListener(handler);
}
});
}
void onLazyLoad();
}

View File

@ -1,149 +0,0 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
/// A button in a _ContextMenuSheet.
///
/// A typical use case is to pass a [Text] as the [child] here, but be sure to
/// use [TextOverflow.ellipsis] for the [Text.overflow] field if the text may be
/// long, as without it the text will wrap to the next line.
class QLCupertinoContextMenuAction extends StatefulWidget {
/// Construct a CupertinoContextMenuAction.
const QLCupertinoContextMenuAction({
Key? key,
required this.child,
this.isDefaultAction = false,
this.isDestructiveAction = false,
this.onPressed,
this.trailingIcon,
}) : assert(child != null),
assert(isDefaultAction != null),
assert(isDestructiveAction != null),
super(key: key);
/// The widget that will be placed inside the action.
final Widget child;
/// Indicates whether this action should receive the style of an emphasized,
/// default action.
final bool isDefaultAction;
/// Indicates whether this action should receive the style of a destructive
/// action.
final bool isDestructiveAction;
/// Called when the action is pressed.
final VoidCallback? onPressed;
/// An optional icon to display to the right of the child.
///
/// Will be colored in the same way as the [TextStyle] used for [child] (for
/// example, if using [isDestructiveAction]).
final IconData? trailingIcon;
@override
State<QLCupertinoContextMenuAction> createState() =>
_QLCupertinoContextMenuActionState();
}
class _QLCupertinoContextMenuActionState
extends State<QLCupertinoContextMenuAction> {
static const Color _kBackgroundColor = Color(0xFFEEEEEE);
static const Color _kBackgroundColorPressed = Color(0xFFDDDDDD);
static const double _kButtonHeight = 30.0;
static const TextStyle _kActionSheetActionStyle = TextStyle(
fontFamily: '.SF UI Text',
inherit: false,
fontSize: 14.0,
fontWeight: FontWeight.w400,
color: CupertinoColors.black,
textBaseline: TextBaseline.alphabetic,
);
final GlobalKey _globalKey = GlobalKey();
bool _isPressed = false;
void onTapDown(TapDownDetails details) {
setState(() {
_isPressed = true;
});
}
void onTapUp(TapUpDetails details) {
setState(() {
_isPressed = false;
});
}
void onTapCancel() {
setState(() {
_isPressed = false;
});
}
TextStyle get _textStyle {
if (widget.isDefaultAction) {
return _kActionSheetActionStyle.copyWith(
fontWeight: FontWeight.w600,
);
}
if (widget.isDestructiveAction) {
return _kActionSheetActionStyle.copyWith(
color: CupertinoColors.destructiveRed,
);
}
return _kActionSheetActionStyle;
}
@override
Widget build(BuildContext context) {
return GestureDetector(
key: _globalKey,
onTapDown: onTapDown,
onTapUp: onTapUp,
onTapCancel: onTapCancel,
onTap: widget.onPressed,
behavior: HitTestBehavior.opaque,
child: ConstrainedBox(
constraints: const BoxConstraints(
minHeight: _kButtonHeight,
),
child: Semantics(
button: true,
child: Container(
decoration: BoxDecoration(
color: _isPressed ? _kBackgroundColorPressed : _kBackgroundColor,
border: const Border(
bottom: BorderSide(color: _kBackgroundColorPressed),
),
),
padding: const EdgeInsets.symmetric(
vertical: 10.0,
horizontal: 10.0,
),
child: DefaultTextStyle(
style: _textStyle,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: widget.child,
),
if (widget.trailingIcon != null)
Icon(
widget.trailingIcon,
color: _textStyle.color,
size: 14,
),
],
),
),
),
),
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,170 +0,0 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:url_launcher/url_launcher.dart' as url_launcher;
import 'syntax_highlighter.dart';
class SourceCodeView extends StatefulWidget {
final String filePath;
final String? codeLinkPrefix;
final bool showLabelText;
final Color? iconBackgroundColor;
final Color? iconForegroundColor;
final Color? labelBackgroundColor;
final TextStyle? labelTextStyle;
final SyntaxHighlighterStyle? syntaxHighlighterStyle;
const SourceCodeView({
Key? key,
required this.filePath,
this.codeLinkPrefix,
this.showLabelText = false,
this.iconBackgroundColor,
this.iconForegroundColor,
this.labelBackgroundColor,
this.labelTextStyle,
this.syntaxHighlighterStyle,
}) : super(key: key);
String? get codeLink => codeLinkPrefix == null
? null
: '$codeLinkPrefix/$filePath';
@override
_SourceCodeViewState createState() {
return _SourceCodeViewState();
}
}
class _SourceCodeViewState extends State<SourceCodeView> {
double _textScaleFactor = 1.0;
Widget _getCodeView(String codeContent, BuildContext context) {
codeContent = codeContent.replaceAll('\r\n', '\n');
final SyntaxHighlighterStyle style = widget.syntaxHighlighterStyle ??
(Theme.of(context).brightness == Brightness.dark
? SyntaxHighlighterStyle.darkThemeStyle()
: SyntaxHighlighterStyle.lightThemeStyle());
return Container(
constraints: BoxConstraints.expand(),
child: Scrollbar(
child: SingleChildScrollView(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SelectableText.rich(
TextSpan(
style: GoogleFonts.droidSansMono(fontSize: 12)
.apply(fontSizeFactor: this._textScaleFactor),
children: <TextSpan>[
DartSyntaxHighlighter(style).format(codeContent)
],
),
style: DefaultTextStyle.of(context)
.style
.apply(fontSizeFactor: this._textScaleFactor),
),
),
),
),
);
}
List<SpeedDialChild> _buildFloatingButtons({
TextStyle? labelTextStyle,
Color? iconBackgroundColor,
Color? iconForegroundColor,
Color? labelBackgroundColor,
required bool showLabelText,
}) =>
[
if (this.widget.codeLink != null)
SpeedDialChild(
child: Icon(Icons.content_copy),
labelWidget: showLabelText ? Text('Copy code to clipboard') : null,
backgroundColor: iconBackgroundColor,
foregroundColor: iconForegroundColor,
labelBackgroundColor: labelBackgroundColor,
labelStyle: labelTextStyle,
onTap: () async {
Clipboard.setData(ClipboardData(
text: await DefaultAssetBundle.of(context)
.loadString(widget.filePath)));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Code copied to clipboard!'),
));
},
),
if (this.widget.codeLink != null)
SpeedDialChild(
child: Icon(Icons.open_in_new),
labelWidget: showLabelText ? Text('View code in browser') : null,
backgroundColor: iconBackgroundColor,
foregroundColor: iconForegroundColor,
labelBackgroundColor: labelBackgroundColor,
labelStyle: labelTextStyle,
onTap: () => url_launcher.launch(this.widget.codeLink!),
),
SpeedDialChild(
child: Icon(Icons.zoom_out),
label: showLabelText ? 'Zoom out' : null,
// labelWidget: showLabelText ? Text('Zoom out') : null,
backgroundColor: iconBackgroundColor,
foregroundColor: iconForegroundColor,
labelBackgroundColor: labelBackgroundColor,
labelStyle: labelTextStyle,
onTap: () => setState(() {
this._textScaleFactor = max(0.8, this._textScaleFactor - 0.1);
}),
),
SpeedDialChild(
child: Icon(Icons.zoom_in),
labelWidget: showLabelText ? Text('Zoom in') : null,
backgroundColor: iconBackgroundColor,
foregroundColor: iconForegroundColor,
labelBackgroundColor: labelBackgroundColor,
labelStyle: labelTextStyle,
onTap: () => setState(() {
this._textScaleFactor += 0.1;
}),
),
];
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: DefaultAssetBundle.of(context).loadString(widget.filePath),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Scaffold(
body: Padding(
padding: EdgeInsets.all(4.0),
child: _getCodeView(snapshot.data!, context),
),
floatingActionButton: SpeedDial(
renderOverlay: false,
overlayOpacity: 0,
children: _buildFloatingButtons(
labelTextStyle: widget.labelTextStyle,
iconBackgroundColor: widget.iconBackgroundColor,
iconForegroundColor: widget.iconForegroundColor,
labelBackgroundColor: widget.labelBackgroundColor,
showLabelText: widget.showLabelText,
),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
activeBackgroundColor: Colors.red,
activeForegroundColor: Colors.white,
icon: Icons.menu,
activeIcon: Icons.close,
),
);
} else {
return Center(child: CircularProgressIndicator());
}
},
);
}
}

View File

@ -1,384 +0,0 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:string_scanner/string_scanner.dart';
class SyntaxHighlighterStyle {
SyntaxHighlighterStyle(
{this.baseStyle,
this.numberStyle,
this.commentStyle,
this.keywordStyle,
this.stringStyle,
this.punctuationStyle,
this.classStyle,
this.constantStyle});
static SyntaxHighlighterStyle lightThemeStyle() => SyntaxHighlighterStyle(
baseStyle: const TextStyle(color: const Color(0xFF000000),height: 1,),
numberStyle: const TextStyle(color: const Color(0xFF1565C0)),
commentStyle: const TextStyle(color: const Color(0xFF9E9E9E)),
keywordStyle: const TextStyle(color: const Color(0xFF9C27B0)),
stringStyle: const TextStyle(color: const Color(0xFF43A047)),
punctuationStyle: const TextStyle(color: const Color(0xFF000000)),
classStyle: const TextStyle(color: const Color(0xFF512DA8)),
constantStyle: const TextStyle(color: const Color(0xFF795548)),
);
static SyntaxHighlighterStyle darkThemeStyle() => SyntaxHighlighterStyle(
baseStyle: const TextStyle(color: const Color(0xFFFFFFFF)),
numberStyle: const TextStyle(color: const Color(0xFF1565C0)),
commentStyle: const TextStyle(color: const Color(0xFF9E9E9E)),
keywordStyle: const TextStyle(color: const Color(0xFF80CBC4)),
stringStyle: const TextStyle(color: const Color(0xFF009688)),
punctuationStyle: const TextStyle(color: const Color(0xFFFFFFFF)),
classStyle: const TextStyle(color: const Color(0xFF009688)),
constantStyle: const TextStyle(color: const Color(0xFF795548)),
);
SyntaxHighlighterStyle copyWith({
TextStyle? baseStyle,
TextStyle? numberStyle,
TextStyle? commentStyle,
TextStyle? keywordStyle,
TextStyle? stringStyle,
TextStyle? punctuationStyle,
TextStyle? classStyle,
TextStyle? constantStyle,
}) =>
SyntaxHighlighterStyle(
baseStyle: baseStyle ?? this.baseStyle,
numberStyle: numberStyle ?? this.numberStyle,
commentStyle: commentStyle ?? this.commentStyle,
keywordStyle: keywordStyle ?? this.keywordStyle,
stringStyle: stringStyle ?? this.stringStyle,
punctuationStyle: punctuationStyle ?? this.punctuationStyle,
classStyle: classStyle ?? this.classStyle,
constantStyle: constantStyle ?? this.constantStyle,
);
final TextStyle? baseStyle;
final TextStyle? numberStyle;
final TextStyle? commentStyle;
final TextStyle? keywordStyle;
final TextStyle? stringStyle;
final TextStyle? punctuationStyle;
final TextStyle? classStyle;
final TextStyle? constantStyle;
}
abstract class SyntaxHighlighter {
// ignore: one_member_abstracts
TextSpan format(String src);
}
class DartSyntaxHighlighter extends SyntaxHighlighter {
DartSyntaxHighlighter([this._style]) {
_spans = <_HighlightSpan>[];
_style ??= SyntaxHighlighterStyle.darkThemeStyle();
}
SyntaxHighlighterStyle? _style;
static const List<String> _keywords = const <String>[
'abstract',
'as',
'assert',
'async',
'await',
'break',
'case',
'catch',
'class',
'const',
'continue',
'default',
'deferred',
'do',
'dynamic',
'else',
'enum',
'export',
'external',
'extends',
'factory',
'false',
'final',
'finally',
'for',
'get',
'if',
'implements',
'import',
'in',
'is',
'library',
'new',
'null',
'operator',
'part',
'rethrow',
'return',
'set',
'static',
'super',
'switch',
'sync',
'this',
'throw',
'true',
'try',
'typedef',
'var',
'void',
'while',
'with',
'yield'
];
static const List<String> _builtInTypes = const <String>[
'int',
'double',
'num',
'bool'
];
late String _src;
late StringScanner _scanner;
late List<_HighlightSpan> _spans;
@override
TextSpan format(String src) {
_src = src;
_scanner = StringScanner(_src);
if (_generateSpans()) {
// Successfully parsed the code
final List<TextSpan> formattedText = <TextSpan>[];
int currentPosition = 0;
for (_HighlightSpan span in _spans) {
if (currentPosition != span.start)
formattedText
.add(TextSpan(text: _src.substring(currentPosition, span.start)));
formattedText.add(TextSpan(
style: span.textStyle(_style), text: span.textForSpan(_src)));
currentPosition = span.end;
}
if (currentPosition != _src.length)
formattedText
.add(TextSpan(text: _src.substring(currentPosition, _src.length)));
return TextSpan(style: _style!.baseStyle, children: formattedText);
} else {
// Parsing failed, return with only basic formatting
return TextSpan(style: _style!.baseStyle, text: src);
}
}
bool _generateSpans() {
int lastLoopPosition = _scanner.position;
while (!_scanner.isDone) {
// Skip White space
_scanner.scan(RegExp(r'\s+'));
// Block comments
if (_scanner.scan(RegExp(r'/\*(.|\n)*\*/'))) {
_spans.add(_HighlightSpan(_HighlightType.comment,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Line comments
if (_scanner.scan('##')) {
final int startComment = _scanner.lastMatch!.start;
bool eof = false;
int endComment;
if (_scanner.scan(RegExp(r'.*\n'))) {
endComment = _scanner.lastMatch!.end - 1;
} else {
eof = true;
endComment = _src.length;
}
_spans.add(
_HighlightSpan(_HighlightType.comment, startComment, endComment));
if (eof) break;
continue;
}
// Raw r"String"
if (_scanner.scan(RegExp(r'r".*"'))) {
_spans.add(_HighlightSpan(_HighlightType.string,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Raw r'String'
if (_scanner.scan(RegExp(r"r'.*'"))) {
_spans.add(_HighlightSpan(_HighlightType.string,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Multiline """String"""
if (_scanner.scan(RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) {
_spans.add(_HighlightSpan(_HighlightType.string,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Multiline '''String'''
if (_scanner.scan(RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) {
_spans.add(_HighlightSpan(_HighlightType.string,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// "String"
if (_scanner.scan(RegExp(r'"(?:[^"\\]|\\.)*"'))) {
_spans.add(_HighlightSpan(_HighlightType.string,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// 'String'
if (_scanner.scan(RegExp(r"'(?:[^'\\]|\\.)*'"))) {
_spans.add(_HighlightSpan(_HighlightType.string,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Double
if (_scanner.scan(RegExp(r'\d+\.\d+'))) {
_spans.add(_HighlightSpan(_HighlightType.number,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Integer
if (_scanner.scan(RegExp(r'\d+'))) {
_spans.add(_HighlightSpan(_HighlightType.number,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Punctuation
if (_scanner.scan(RegExp(r'[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]'))) {
_spans.add(_HighlightSpan(_HighlightType.punctuation,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Meta data
if (_scanner.scan(RegExp(r'@\w+'))) {
_spans.add(_HighlightSpan(_HighlightType.keyword,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Words
if (_scanner.scan(RegExp(r'\w+'))) {
_HighlightType? type;
String word = _scanner.lastMatch![0]!;
if (word.startsWith('_')) word = word.substring(1);
if (_keywords.contains(word))
type = _HighlightType.keyword;
else if (_builtInTypes.contains(word))
type = _HighlightType.keyword;
else if (_firstLetterIsUpperCase(word))
type = _HighlightType.klass;
else if (word.length >= 2 &&
word.startsWith('k') &&
_firstLetterIsUpperCase(word.substring(1)))
type = _HighlightType.constant;
if (type != null) {
_spans.add(_HighlightSpan(
type, _scanner.lastMatch!.start, _scanner.lastMatch!.end));
}
}
// Check if this loop did anything
if (lastLoopPosition == _scanner.position) {
// Failed to parse this file, abort gracefully
return false;
}
lastLoopPosition = _scanner.position;
}
_simplify();
return true;
}
void _simplify() {
for (int i = _spans.length - 2; i >= 0; i -= 1) {
if (_spans[i].type == _spans[i + 1].type &&
_spans[i].end == _spans[i + 1].start) {
_spans[i] =
_HighlightSpan(_spans[i].type, _spans[i].start, _spans[i + 1].end);
_spans.removeAt(i + 1);
}
}
}
bool _firstLetterIsUpperCase(String str) {
if (str.isNotEmpty) {
final String first = str.substring(0, 1);
return first == first.toUpperCase();
}
return false;
}
}
enum _HighlightType {
number,
comment,
keyword,
string,
punctuation,
klass,
constant
}
class _HighlightSpan {
_HighlightSpan(this.type, this.start, this.end);
final _HighlightType type;
final int start;
final int end;
String textForSpan(String src) {
return src.substring(start, end);
}
TextStyle? textStyle(SyntaxHighlighterStyle? style) {
if (type == _HighlightType.number)
return style!.numberStyle;
else if (type == _HighlightType.comment)
return style!.commentStyle;
else if (type == _HighlightType.keyword)
return style!.keywordStyle;
else if (type == _HighlightType.string)
return style!.stringStyle;
else if (type == _HighlightType.punctuation)
return style!.punctuationStyle;
else if (type == _HighlightType.klass)
return style!.classStyle;
else if (type == _HighlightType.constant)
return style!.constantStyle;
else
return style!.baseStyle;
}
}

View File

@ -1,137 +0,0 @@
import 'dart:convert';
import 'package:qinglong_app/utils/extension.dart';
import 'package:qinglong_app/utils/sp_utils.dart';
import '../main.dart';
import 'sp_const.dart';
import 'theme.dart';
class UserInfoViewModel {
int _primaryColor = commonColor.value;
String? _token;
String? _host = "";
String? _userName;
String? _passWord;
bool _useSecertLogined = false;
List<UserInfoBean> historyAccounts = [];
UserInfoViewModel() {
_token = SpUtil.getString(spUserInfo);
_userName = SpUtil.getString(spUserName);
_passWord = SpUtil.getString(spPassWord);
_primaryColor = SpUtil.getInt(spCustomColor, defValue: commonColor.value);
_useSecertLogined = SpUtil.getBool(spSecretLogined, defValue: false);
_host = SpUtil.getString(spHost, defValue: '');
List<dynamic>? tempList = jsonDecode(SpUtil.getString(spLoginHistory, defValue: '[]'));
if (tempList != null && tempList.isNotEmpty) {
for (Map<String, dynamic> value in tempList) {
historyAccounts.add(UserInfoBean.fromJson(value));
}
}
}
void updateToken(String token) {
_token = token;
SpUtil.putString(spUserInfo, token);
}
void updateUserName(String host, String userName, String password, bool secretLogin) {
updateHost(host);
_useSecretLogin(secretLogin);
_userName = userName;
_passWord = password;
SpUtil.putString(spUserName, userName);
SpUtil.putString(spPassWord, password);
save2HistoryAccount();
}
void _useSecretLogin(bool use) {
_useSecertLogined = use;
SpUtil.putBool(spSecretLogined, _useSecertLogined);
}
void updateCustomColor(int color) {
_primaryColor = color;
SpUtil.putInt(spCustomColor, color);
}
void updateHost(String host) {
_host = host;
SpUtil.putString(spHost, host);
}
String? get token => _token;
String? get host => _host;
String? get userName => _userName;
String? get passWord => _passWord;
bool get useSecretLogined => _useSecertLogined;
int get primaryColor => _primaryColor;
bool isLogined() {
return token != null && token!.isNotEmpty;
}
void save2HistoryAccount() {
if (_host == null || _host!.isEmpty) return;
if (_userName == null || _userName!.isEmpty) return;
if (_passWord == null || _passWord!.isEmpty) return;
//如果已经存在host那就更新
"login success $_host $userName $passWord".log();
historyAccounts.removeWhere((element) => element.host == _host);
historyAccounts.insert(0, UserInfoBean(userName: _userName, password: _passWord, useSecretLogined: _useSecertLogined, host: _host));
while (historyAccounts.length > 3) {
historyAccounts.removeLast();
}
SpUtil.putString(spLoginHistory, jsonEncode(historyAccounts));
}
void removeHistoryAccount(String? host) {
if (host == null || host.isEmpty) return;
historyAccounts.removeWhere((element) => element.host == host);
SpUtil.putString(spLoginHistory, jsonEncode(historyAccounts));
}
}
class UserInfoBean {
String? userName;
String? password;
bool useSecretLogined = false;
String? host;
UserInfoBean({this.userName, this.password, this.useSecretLogined = false, this.host});
UserInfoBean.fromJson(Map<String, dynamic> json) {
userName = json['userName'];
password = json['password'];
useSecretLogined = json['useSecretLogined'] ?? false;
host = json['host'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['userName'] = this.userName;
data['password'] = this.password;
data['useSecretLogined'] = this.useSecretLogined;
data['host'] = this.host;
return data;
}
}

View File

@ -1,4 +0,0 @@
import 'package:json_conversion_annotation/json_conversion_annotation.dart';
@JsonConversionTarget()
class Json {}

View File

@ -1,115 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
import 'package:qinglong_app/module/config/config_bean.dart';
import 'package:qinglong_app/module/env/env_bean.dart';
import 'package:qinglong_app/module/home/system_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';
class JsonConversion$Json {
static M fromJson<M>(dynamic json) {
if (json is List) {
return _getListChildType<M>(json);
} else {
return _fromJsonSingle<M>(json);
}
}
static M _fromJsonSingle<M>(dynamic json) {
String type = M.toString();
if(type == (ConfigBean).toString()){
return ConfigBean.jsonConversion(json) as M;
}
if(type == (EnvBean).toString()){
return EnvBean.jsonConversion(json) as M;
}
if(type == (SystemBean).toString()){
return SystemBean.jsonConversion(json) as M;
}
if(type == (LoginBean).toString()){
return LoginBean.jsonConversion(json) as M;
}
if(type == (UserBean).toString()){
return UserBean.jsonConversion(json) as M;
}
if(type == (DependencyBean).toString()){
return DependencyBean.jsonConversion(json) as M;
}
if(type == (LoginLogBean).toString()){
return LoginLogBean.jsonConversion(json) as M;
}
if(type == (ScriptBean).toString()){
return ScriptBean.jsonConversion(json) as M;
}
if(type == (TaskLogBean).toString()){
return TaskLogBean.jsonConversion(json) as M;
}
if(type == (TaskBean).toString()){
return TaskBean.jsonConversion(json) as M;
}
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(<EnvBean>[] is M){
return data.map<EnvBean>((e) => EnvBean.jsonConversion(e)).toList() as M;
}
if(<SystemBean>[] is M){
return data.map<SystemBean>((e) => SystemBean.jsonConversion(e)).toList() as M;
}
if(<LoginBean>[] is M){
return data.map<LoginBean>((e) => LoginBean.jsonConversion(e)).toList() as M;
}
if(<UserBean>[] is M){
return data.map<UserBean>((e) => UserBean.jsonConversion(e)).toList() as M;
}
if(<DependencyBean>[] is M){
return data.map<DependencyBean>((e) => DependencyBean.jsonConversion(e)).toList() as M;
}
if(<LoginLogBean>[] is M){
return data.map<LoginLogBean>((e) => LoginLogBean.jsonConversion(e)).toList() as M;
}
if(<ScriptBean>[] is M){
return data.map<ScriptBean>((e) => ScriptBean.jsonConversion(e)).toList() as M;
}
if(<TaskLogBean>[] is M){
return data.map<TaskLogBean>((e) => TaskLogBean.jsonConversion(e)).toList() as M;
}
if(<TaskBean>[] is M){
return data.map<TaskBean>((e) => TaskBean.jsonConversion(e)).toList() as M;
}
throw Exception("not found");
}
}

View File

@ -1,98 +0,0 @@
import 'dart:io';
import 'package:dio_log/overlay_draggable_button.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
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/sp_utils.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'base/routes.dart';
import 'base/userinfo_viewmodel.dart';
import 'module/home/home_page.dart';
final getIt = GetIt.instance;
var navigatorState = GlobalKey<NavigatorState>();
var logger = Logger();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await SpUtil.getInstance();
getIt.registerSingleton<UserInfoViewModel>(UserInfoViewModel());
await SystemChrome.setPreferredOrientations(
[
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
],
);
runApp(
ProviderScope(
overrides: [
themeProvider,
],
child: const QlApp(),
),
);
if (Platform.isAndroid) {
SystemUiOverlayStyle style = const SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(style);
}
}
class QlApp extends ConsumerStatefulWidget {
const QlApp({Key? key}) : super(key: key);
@override
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: MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance!.window).copyWith(
textScaleFactor: 1,
),
child: MaterialApp(
title: "青龙",
locale: const Locale('zh', 'CN'),
navigatorKey: navigatorState,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('zh', 'CN'),
Locale('en', 'US'),
],
theme: ref.watch<ThemeViewModel>(themeProvider).currentTheme,
onGenerateRoute: (setting) {
return Routes.generateRoute(setting);
},
home: Builder(
builder: (context) {
if (!kReleaseMode) {
showDebugBtn(context);
}
return getIt<UserInfoViewModel>().isLogined() ? const HomePage() : const LoginPage();
},
),
// home: LoginPage(),
),
),
);
}
}

View File

@ -1,25 +0,0 @@
import 'package:json_conversion_annotation/json_conversion_annotation.dart';
@JsonConversion()
class ConfigBean {
String? title;
String? value;
ConfigBean({this.title, this.value});
ConfigBean.fromJson(Map<String, dynamic> json) {
title = json['title'];
value = json['value'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['title'] = this.title;
data['value'] = this.value;
return data;
}
static ConfigBean jsonConversion(Map<String, dynamic> json) {
return ConfigBean.fromJson(json);
}
}

View File

@ -1,101 +0,0 @@
import 'package:code_text_field/code_text_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:highlight/languages/powershell.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/utils/extension.dart';
class ConfigEditPage extends ConsumerStatefulWidget {
final String content;
final String title;
const ConfigEditPage(this.title, this.content, {Key? key}) : super(key: key);
@override
_ConfigEditPageState createState() => _ConfigEditPageState();
}
class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
CodeController? _codeController;
late String result;
@override
void dispose() {
_codeController?.dispose();
super.dispose();
}
@override
void initState() {
result = widget.content;
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
focusNode.requestFocus();
});
}
@override
Widget build(BuildContext context) {
_codeController ??= CodeController(
text: widget.content,
language: powershell,
onChange: (value) {
result = value;
},
theme: ref.watch(themeProvider).themeColor.codeEditorTheme(),
stringMap: {
"export": const TextStyle(fontWeight: FontWeight.normal, color: Color(0xff6B2375)),
},
);
return Scaffold(
appBar: QlAppBar(
canBack: true,
backCall: () {
Navigator.of(context).pop();
},
title: '编辑${widget.title}',
actions: [
InkWell(
onTap: () async {
HttpResponse<NullResponse> response = await Api.saveFile(widget.title, result);
if (response.success) {
"提交成功".toast();
Navigator.of(context).pop(widget.title);
} else {
(response.message ?? "").toast();
}
},
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 15,
),
child: Center(
child: Text(
"提交",
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
),
),
),
)
],
),
body: SafeArea(
top: false,
child: CodeField(
controller: _codeController!,
expands: true,
background: ref.watch(themeProvider).themeColor.settingBgColor(),
),
),
);
}
FocusNode focusNode = FocusNode();
}

View File

@ -1,145 +0,0 @@
import 'dart:ui';
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/routes.dart';
import 'package:qinglong_app/base/ui/abs_underline_tabindicator.dart';
import 'package:qinglong_app/base/ui/empty_widget.dart';
import 'package:qinglong_app/main.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../base/ui/syntax_highlighter.dart';
import 'config_viewmodel.dart';
class ConfigPage extends StatefulWidget {
const ConfigPage({Key? key}) : super(key: key);
@override
ConfigPageState createState() => ConfigPageState();
}
class ConfigPageState extends State<ConfigPage>
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
int _initIndex = 0;
BuildContext? childContext;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return BaseStateWidget<ConfigViewModel>(
builder: (ref, model, child) {
if (model.list.isEmpty) {
return const EmptyWidget();
}
return DefaultTabController(
length: model.list.length,
initialIndex: _initIndex,
child: Builder(builder: (context) {
childContext = context;
return Column(
children: [
TabBar(
tabs: model.list
.map((e) => Tab(
text: e.title,
))
.toList(),
isScrollable: true,
indicator: AbsUnderlineTabIndicator(
wantWidth: 20,
borderSide: BorderSide(
color: Theme.of(context).primaryColor,
width: 2,
),
),
),
Expanded(
child: TabBarView(
children: model.list
.map(
(e) => CodeWidget(
content: model.content[e.title] ?? "",
),
)
.toList(),
),
),
],
);
}),
);
},
model: configProvider,
onReady: (viewModel) {
viewModel.loadData();
},
);
}
void editMe(WidgetRef ref) {
if (childContext == null) return;
navigatorState.currentState?.pushNamed(Routes.routeConfigEdit, arguments: {
"title": ref
.read(configProvider)
.list[DefaultTabController.of(childContext!)?.index ?? 0]
.title,
"content": ref.read(configProvider).content[ref
.read(configProvider)
.list[DefaultTabController.of(childContext!)?.index ?? 0]
.title]
}).then((value) async {
if (value != null && (value as String).isNotEmpty) {
await ref.read(configProvider).loadContent(value);
setState(() {});
}
});
}
@override
bool get wantKeepAlive => true;
}
class CodeWidget extends StatefulWidget {
final String content;
const CodeWidget({
Key? key,
required this.content,
}) : super(key: key);
@override
State<CodeWidget> createState() => _CodeWidgetState();
}
class _CodeWidgetState extends State<CodeWidget>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
return SelectableText.rich(
TextSpan(
style: GoogleFonts.droidSansMono(fontSize: 14).apply(
fontSizeFactor: 1,
),
children: <TextSpan>[
DartSyntaxHighlighter(SyntaxHighlighterStyle.lightThemeStyle())
.format(widget.content)
],
),
style: DefaultTextStyle.of(context).style.apply(
fontSizeFactor: 1,
),
selectionWidthStyle: BoxWidthStyle.max,
selectionHeightStyle: BoxHeightStyle.max,
autofocus: true,
);
}
@override
bool get wantKeepAlive => true;
}

View File

@ -1,43 +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/config/config_bean.dart';
var configProvider = ChangeNotifierProvider((ref) => ConfigViewModel());
class ConfigViewModel extends BaseViewModel {
List<ConfigBean> list = [];
Map<String, String> content = {};
Future<void> loadData([isLoading = true]) async {
if (isLoading) {
loading(notify: true);
}
HttpResponse<List<ConfigBean>> result = await Api.files();
if (result.success && result.bean != null) {
list.clear();
list.addAll(result.bean!);
for (var element in list) {
await loadContent(element.value!);
}
success();
} else {
list.clear();
failed(result.message, notify: true);
}
}
Future<void> loadContent(String name) async {
HttpResponse<String> result = await Api.content(name);
if (result.success && result.bean != null) {
content[name] = result.bean!;
}
}
}

View File

@ -1,200 +0,0 @@
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/env/env_bean.dart';
import 'package:qinglong_app/module/env/env_viewmodel.dart';
import 'package:qinglong_app/utils/extension.dart';
class AddEnvPage extends ConsumerStatefulWidget {
final EnvBean? envBean;
const AddEnvPage({Key? key, this.envBean}) : super(key: key);
@override
ConsumerState<AddEnvPage> createState() => _AddEnvPageState();
}
class _AddEnvPageState extends ConsumerState<AddEnvPage> {
late EnvBean envBean;
final TextEditingController _nameController = TextEditingController();
final TextEditingController _valueController = TextEditingController();
final TextEditingController _remarkController = TextEditingController();
FocusNode focusNode = FocusNode();
@override
void initState() {
super.initState();
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
Widget build(BuildContext context) {
return Scaffold(
appBar: QlAppBar(
canBack: true,
backCall: () {
Navigator.of(context).pop();
},
title: envBean.name == null ? "新增环境变量" : "编辑环境变量",
actions: [
InkWell(
onTap: () {
submit();
},
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 15,
),
child: Center(
child: Text(
"提交",
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
),
),
),
)
],
),
body: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 15,
),
const Text(
"名称:",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
TextField(
focusNode: focusNode,
controller: _nameController,
decoration: const InputDecoration(
hintText: "请输入名称",
),
autofocus: false,
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 15,
),
const Text(
"值:",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
TextField(
controller: _valueController,
maxLines: 8,
minLines: 1,
decoration: const InputDecoration(
hintText: "请输入值",
),
autofocus: false,
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 15,
),
const Text(
"备注:",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
TextField(
controller: _remarkController,
decoration: const InputDecoration(
hintText: "请输入备注",
),
autofocus: false,
),
],
),
),
],
),
),
);
}
void submit() async {
if (_nameController.text.isEmpty) {
"名称不能为空".toast();
return;
}
if (_valueController.text.isEmpty) {
"值不能为空".toast();
return;
}
envBean.name = _nameController.text;
envBean.value = _valueController.text;
envBean.remarks = _remarkController.text;
HttpResponse<NullResponse> response = await Api.addEnv(
_nameController.text, _valueController.text, _remarkController.text,
id: envBean.id,nId: envBean.nId,);
if (response.success) {
(envBean.sId == null) ? "新增成功" : "修改成功".toast();
ref.read(envProvider).updateEnv(envBean);
Navigator.of(context).pop();
} else {
(response.message ?? "").toast();
}
}
}

View File

@ -1,46 +0,0 @@
import 'package:json_conversion_annotation/json_conversion_annotation.dart';
@JsonConversion()
class EnvBean {
String? value;
String? sId;
String? _id;
int? id;
int? created;
int? status;
String? timestamp;
String? name;
String? remarks;
EnvBean({this.value, this.sId, this.created, this.status, this.timestamp, this.name, this.remarks});
get nId => _id;
EnvBean.fromJson(Map<String, dynamic> json) {
value = json['value'];
id = json['id'];
_id = json['_id'];
sId = json.containsKey('_id') ? json['_id'].toString() : (json.containsKey('id') ? json['id'].toString() : "");
created = int.tryParse(json['created'].toString());
status = json['status'];
timestamp = json['timestamp'];
name = json['name'];
remarks = json['remarks'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['value'] = this.value;
data['_id'] = this.sId;
data['created'] = this.created;
data['status'] = this.status;
data['timestamp'] = this.timestamp;
data['name'] = this.name;
data['remarks'] = this.remarks;
return data;
}
static EnvBean jsonConversion(Map<String, dynamic> json) {
return EnvBean.fromJson(json);
}
}

View File

@ -1,345 +0,0 @@
import 'dart:ui';
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: ref.watch(themeProvider).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,
selectionHeightStyle: BoxHeightStyle.max,
selectionWidthStyle: BoxWidthStyle.max,
onTap: () {
if (taped != null) {
taped!();
}
},
style: TextStyle(
color: ref.watch(themeProvider).themeColor.descColor(),
fontSize: 14,
),
),
),
)
: Expanded(
child: Align(alignment: Alignment.centerRight, child: icon!),
),
],
),
),
hideDivide
? const SizedBox.shrink()
: const Divider(
indent: 15,
),
],
);
}
}

View File

@ -1,366 +0,0 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:qinglong_app/base/base_state_widget.dart';
import 'package:qinglong_app/base/routes.dart';
import 'package:qinglong_app/base/theme.dart';
import 'package:qinglong_app/base/ui/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);
@override
_EnvPageState createState() => _EnvPageState();
}
class _EnvPageState extends State<EnvPage> {
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
_searchController.addListener(() {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return BaseStateWidget<EnvViewModel>(
builder: (ref, model, child) {
List<EnvItemCell> 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,
ref,
key: ValueKey(value.sId),
));
}
}
return model.list.isEmpty
? const EmptyWidget()
: RefreshIndicator(
color: Theme.of(context).primaryColor,
onRefresh: () async {
return model.loadData(false);
},
child: SlidableAutoCloseBehavior(
child: ReorderableListView(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
header: searchCell(ref),
onReorder: (int oldIndex, int newIndex) {
if (list.length != model.list.length) {
"请先清空搜索关键词".toast();
return;
}
setState(() {
//交换数据
if (newIndex > oldIndex) {
newIndex -= 1;
}
final EnvBean item = model.list.removeAt(oldIndex);
model.list.insert(newIndex, item);
model.update(item.sId ?? "", newIndex, oldIndex);
});
},
children: list,
),
),
);
},
model: envProvider,
onReady: (viewModel) {
viewModel.loadData();
},
);
}
Widget searchCell(WidgetRef context) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 10,
),
child: CupertinoSearchTextField(
onSubmitted: (value) {
setState(() {});
},
onSuffixTap: () {
_searchController.text = "";
setState(() {});
},
controller: _searchController,
borderRadius: BorderRadius.circular(
30,
),
padding: const EdgeInsets.symmetric(
horizontal: 5,
vertical: 5,
),
suffixInsets: const EdgeInsets.only(
right: 15,
),
prefixInsets: EdgeInsets.only(
top: Platform.isAndroid ? 10 : 6,
bottom: 6,
left: 15,
),
placeholderStyle: TextStyle(
fontSize: 16,
color: context.watch(themeProvider).themeColor.descColor(),
),
style: const TextStyle(
fontSize: 16,
),
placeholder: "搜索",
),
);
}
}
class EnvItemCell extends StatelessWidget {
final EnvBean bean;
final int index;
final WidgetRef ref;
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 StretchMotion(),
extentRatio: 0.5,
children: [
SlidableAction(
backgroundColor: const Color(0xff5D5E70),
onPressed: (_) {
Navigator.of(context).pushNamed(Routes.routeAddEnv, arguments: bean);
},
foregroundColor: Colors.white,
icon: CupertinoIcons.pencil_outline,
),
SlidableAction(
backgroundColor: const Color(0xffA356D6),
onPressed: (_) {
enableEnv(context);
},
foregroundColor: Colors.white,
icon: bean.status == 0 ? Icons.dnd_forwardslash : Icons.check_circle_outline_sharp,
),
SlidableAction(
backgroundColor: const Color(0xffEA4D3E),
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: [
Material(
color: ref.watch(themeProvider).themeColor.settingBgColor(),
child: InkWell(
onTap: () {
Navigator.of(context).pushNamed(Routes.routeEnvDetail, arguments: bean);
},
child: 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: [
bean.status == 1
? const Icon(
Icons.dnd_forwardslash,
size: 18,
color: Color(0xffEA4D3E),
)
: Container(
padding: const EdgeInsets.symmetric(
horizontal: 5,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: ref.watch(themeProvider).primaryColor, width: 1),
),
child: Text(
"${getIndexByIndex(context, index)}",
style: TextStyle(color: ref.watch(themeProvider).primaryColor, fontSize: 12),
),
),
const SizedBox(
width: 5,
),
Material(
color: Colors.transparent,
child: Text(
bean.name ?? "",
maxLines: 1,
style: TextStyle(
overflow: TextOverflow.ellipsis,
color: ref.watch(themeProvider).themeColor.titleColor(),
fontSize: 16,
),
),
),
const SizedBox(
width: 5,
),
Expanded(
child: Visibility(
visible: bean.remarks != null && bean.remarks!.isNotEmpty,
child: 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: 15,
),
Material(
color: Colors.transparent,
child: Text(
Utils.formatGMTTime(bean.timestamp ?? ""),
maxLines: 1,
style: TextStyle(
overflow: TextOverflow.ellipsis,
color: ref.watch(themeProvider).themeColor.descColor(),
fontSize: 12,
),
),
),
],
),
const SizedBox(
height: 15,
),
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,
),
],
),
),
),
);
}
void enableEnv(BuildContext context) {
ref.read(envProvider).enableEnv(bean.sId!, bean.status!);
}
void delEnv(BuildContext context, WidgetRef ref) {
showCupertinoDialog(
useRootNavigator: false,
context: context,
builder: (context) => CupertinoAlertDialog(
title: const Text("确认删除"),
content: Text("确认删除环境变量 ${bean.name ?? ""}"),
actions: [
CupertinoDialogAction(
child: const Text(
"取消",
style: TextStyle(
color: Color(0xff999999),
),
),
onPressed: () {
Navigator.of(context).pop();
},
),
CupertinoDialogAction(
child: Text(
"确定",
style: TextStyle(
color: ref.watch(themeProvider).primaryColor,
),
),
onPressed: () {
Navigator.of(context).pop();
ref.read(envProvider).delEnv(bean.sId!);
},
),
],
),
);
}
int getIndexByIndex(BuildContext context, int index) {
var list = ref.watch(envProvider.notifier).list;
int result = 0;
for (int i = 0; i <= index; i++) {
if (list.length > index && list[i].status == 0) {
result++;
}
}
return result;
}
}

Some files were not shown because too many files have changed in this diff Show More