Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05a38a417c | |||
| ad4c0044a3 | |||
| e65c1b24e8 | |||
| b9f8af2e71 | |||
| eb7c4021b9 | |||
| 373b692a23 | |||
| 7409267753 | |||
| c812c74076 | |||
| ba65d33a53 | |||
| 02fb4a8182 | |||
| 53f744c350 | |||
| 39a815baff | |||
| 2250887e04 | |||
| 3d12603f0c | |||
| ee9739d08f | |||
| a53a4fdd19 | |||
| b4e9f8a29a | |||
| a223d5e633 | |||
| 1d8944c4b7 | |||
| e19ce0a5e4 | |||
| 0642ad5c33 | |||
| 9def109426 | |||
| 280641ee57 | |||
| 3f65e9312c | |||
| a5a3494db9 | |||
| a55cf258ba | |||
| b2b8cf5a31 | |||
| ba5c15d5a9 | |||
| 484e1eeee3 | |||
| e2a2ae55ec | |||
| 558b780235 | |||
| 1cd3724805 | |||
| 7e3a63169e | |||
| 1298dba590 | |||
| 2005083d2e | |||
| 4e3f8a0df9 | |||
| 5698277301 | |||
| 02be737264 | |||
| 9558f3d235 | |||
| 14c3b1a965 | |||
| b89de1414a | |||
| 9759caf9b8 |
46
.gitignore
vendored
@ -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
|
||||
10
.metadata
@ -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
|
||||
53
CHANGELOG.md
@ -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
|
||||
|
||||
* 完成基础功能
|
||||
68
README.md
@ -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>
|
||||
|
||||
|
||||
@ -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
@ -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
|
||||
@ -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"
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -1,6 +0,0 @@
|
||||
package work.master.qinglong_app
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
package work.master.qinglongapp
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 70 B |
@ -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>
|
||||
|
Before Width: | Height: | Size: 70 B |
@ -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>
|
||||
|
Before Width: | Height: | Size: 68 B |
@ -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>
|
||||
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 68 B |
@ -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>
|
||||
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 29 KiB |
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
@ -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
|
||||
@ -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"
|
||||
BIN
art/10.jpg
|
Before Width: | Height: | Size: 200 KiB |
BIN
art/11.jpg
|
Before Width: | Height: | Size: 190 KiB |
BIN
art/12.jpg
|
Before Width: | Height: | Size: 301 KiB |
BIN
art/13.jpg
|
Before Width: | Height: | Size: 111 KiB |
BIN
art/14.jpg
|
Before Width: | Height: | Size: 168 KiB |
BIN
art/6.jpg
|
Before Width: | Height: | Size: 606 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 20 KiB |
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
@ -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],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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 {}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
import 'package:json_conversion_annotation/json_conversion_annotation.dart';
|
||||
|
||||
@JsonConversionTarget()
|
||||
class Json {}
|
||||
115
lib/json.jc.dart
@ -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");
|
||||
}
|
||||
}
|
||||
@ -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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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!;
|
||||
}
|
||||
}
|
||||
}
|
||||
200
lib/module/env/add_env_page.dart
vendored
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
46
lib/module/env/env_bean.dart
vendored
@ -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);
|
||||
}
|
||||
}
|
||||
345
lib/module/env/env_detail_page.dart
vendored
@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
366
lib/module/env/env_page.dart
vendored
@ -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;
|
||||
}
|
||||
}
|
||||