Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
196a50aaf7 |
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@@ -136,25 +136,18 @@ jobs:
|
||||
gitchangelog "${pre}.." >> release.md 2>&1 || echo "Error in gitchangelog"
|
||||
echo -e "\n\n</details>" >> release.md
|
||||
fi
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ./dist/*
|
||||
body_path: './release.md'
|
||||
|
||||
- name: Create Fdroid Source Dir
|
||||
run: |
|
||||
mkdir -p ./tmp
|
||||
cp ./dist/*android-arm64-v8a* ./tmp/ || true
|
||||
echo "Files copied successfully"
|
||||
|
||||
- name: Push to fdroid repo
|
||||
uses: cpina/github-action-push-to-another-repository@v1.7.2
|
||||
env:
|
||||
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
|
||||
with:
|
||||
source-directory: ./tmp/
|
||||
source-directory: ./dist/
|
||||
destination-github-username: chen08209
|
||||
destination-repository-name: FlClash-fdroid-repo
|
||||
user-name: 'github-actions[bot]'
|
||||
|
||||
@@ -102,9 +102,6 @@ flutter {
|
||||
dependencies {
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
implementation 'com.google.code.gson:gson:2.10'
|
||||
implementation("com.android.tools.smali:smali-dexlib2:3.0.7") {
|
||||
exclude group: "com.google.guava", module: "guava"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.follow.clash.models
|
||||
|
||||
import java.util.Date
|
||||
|
||||
data class Package(
|
||||
val packageName: String,
|
||||
val label: String,
|
||||
val isSystem: Boolean,
|
||||
val firstInstallTime: Long,
|
||||
val isSystem:Boolean
|
||||
)
|
||||
|
||||
@@ -6,13 +6,11 @@ import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.ComponentInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import com.android.tools.smali.dexlib2.dexbacked.DexBackedDexFile
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.content.getSystemService
|
||||
import com.follow.clash.GlobalState
|
||||
@@ -34,7 +32,6 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
|
||||
class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware {
|
||||
@@ -52,62 +49,6 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
private var connectivity: ConnectivityManager? = null
|
||||
|
||||
private val iconMap = mutableMapOf<String, String?>()
|
||||
private val packages = mutableListOf<Package>()
|
||||
|
||||
private val skipPrefixList = listOf(
|
||||
"com.google",
|
||||
"com.android.chrome",
|
||||
"com.android.vending",
|
||||
"com.microsoft",
|
||||
"com.apple",
|
||||
"com.zhiliaoapp.musically", // Banned by China
|
||||
)
|
||||
|
||||
private val chinaAppPrefixList = listOf(
|
||||
"com.tencent",
|
||||
"com.alibaba",
|
||||
"com.umeng",
|
||||
"com.qihoo",
|
||||
"com.ali",
|
||||
"com.alipay",
|
||||
"com.amap",
|
||||
"com.sina",
|
||||
"com.weibo",
|
||||
"com.vivo",
|
||||
"com.xiaomi",
|
||||
"com.huawei",
|
||||
"com.taobao",
|
||||
"com.secneo",
|
||||
"s.h.e.l.l",
|
||||
"com.stub",
|
||||
"com.kiwisec",
|
||||
"com.secshell",
|
||||
"com.wrapper",
|
||||
"cn.securitystack",
|
||||
"com.mogosec",
|
||||
"com.secoen",
|
||||
"com.netease",
|
||||
"com.mx",
|
||||
"com.qq.e",
|
||||
"com.baidu",
|
||||
"com.bytedance",
|
||||
"com.bugly",
|
||||
"com.miui",
|
||||
"com.oppo",
|
||||
"com.coloros",
|
||||
"com.iqoo",
|
||||
"com.meizu",
|
||||
"com.gionee",
|
||||
"cn.nubia",
|
||||
"com.oplus",
|
||||
"andes.oplus",
|
||||
"com.unionpay",
|
||||
"cn.wps"
|
||||
)
|
||||
|
||||
private val chinaAppRegex by lazy {
|
||||
("(" + chinaAppPrefixList.joinToString("|").replace(".", "\\.") + ").*").toRegex()
|
||||
}
|
||||
|
||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
scope = CoroutineScope(Dispatchers.Default)
|
||||
@@ -147,13 +88,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
|
||||
"getPackages" -> {
|
||||
scope.launch {
|
||||
result.success(getPackagesToJson())
|
||||
}
|
||||
}
|
||||
|
||||
"getChinaPackageNames" -> {
|
||||
scope.launch {
|
||||
result.success(getChinaPackageNames())
|
||||
result.success(getPackages())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,106 +248,26 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
||||
return iconMap[packageName]
|
||||
}
|
||||
|
||||
private fun getPackages(): List<Package> {
|
||||
val packageManager = context?.packageManager
|
||||
if (packages.isNotEmpty()) return packages;
|
||||
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter {
|
||||
it.packageName != context?.packageName
|
||||
|| it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|
||||
|| it.packageName == "android"
|
||||
|
||||
}?.map {
|
||||
Package(
|
||||
packageName = it.packageName,
|
||||
label = it.applicationInfo.loadLabel(packageManager).toString(),
|
||||
isSystem = (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 1,
|
||||
firstInstallTime = it.firstInstallTime
|
||||
)
|
||||
}?.let { packages.addAll(it) }
|
||||
return packages;
|
||||
}
|
||||
|
||||
private suspend fun getPackagesToJson(): String {
|
||||
private suspend fun getPackages(): String {
|
||||
return withContext(Dispatchers.Default) {
|
||||
Gson().toJson(getPackages())
|
||||
}
|
||||
}
|
||||
val packageManager = context?.packageManager
|
||||
val packages: List<Package>? =
|
||||
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter {
|
||||
it.packageName != context?.packageName
|
||||
|| it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|
||||
|| it.packageName == "android"
|
||||
|
||||
private suspend fun getChinaPackageNames(): String {
|
||||
return withContext(Dispatchers.Default) {
|
||||
val packages: List<String> =
|
||||
getPackages().map { it.packageName }.filter { isChinaPackage(it) }
|
||||
}?.map {
|
||||
Package(
|
||||
packageName = it.packageName,
|
||||
label = it.applicationInfo.loadLabel(packageManager).toString(),
|
||||
isSystem = (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 1
|
||||
)
|
||||
}
|
||||
Gson().toJson(packages)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isChinaPackage(packageName: String): Boolean {
|
||||
val packageManager = context?.packageManager ?: return false
|
||||
skipPrefixList.forEach {
|
||||
if (packageName == it || packageName.startsWith("$it.")) return false
|
||||
}
|
||||
val packageManagerFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
PackageManager.MATCH_UNINSTALLED_PACKAGES or PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
PackageManager.GET_UNINSTALLED_PACKAGES or PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES or PackageManager.GET_RECEIVERS or PackageManager.GET_PROVIDERS
|
||||
}
|
||||
if (packageName.matches(chinaAppRegex)) {
|
||||
return true
|
||||
}
|
||||
try {
|
||||
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getPackageInfo(
|
||||
packageName,
|
||||
PackageManager.PackageInfoFlags.of(packageManagerFlags.toLong())
|
||||
)
|
||||
} else {
|
||||
@Suppress("DEPRECATION") packageManager.getPackageInfo(
|
||||
packageName, packageManagerFlags
|
||||
)
|
||||
}
|
||||
mutableListOf<ComponentInfo>().apply {
|
||||
packageInfo.services?.let { addAll(it) }
|
||||
packageInfo.activities?.let { addAll(it) }
|
||||
packageInfo.receivers?.let { addAll(it) }
|
||||
packageInfo.providers?.let { addAll(it) }
|
||||
}.forEach {
|
||||
if (it.name.matches(chinaAppRegex)) return true
|
||||
}
|
||||
ZipFile(File(packageInfo.applicationInfo.publicSourceDir)).use {
|
||||
for (packageEntry in it.entries()) {
|
||||
if (packageEntry.name.startsWith("firebase-")) return false
|
||||
}
|
||||
for (packageEntry in it.entries()) {
|
||||
if (!(packageEntry.name.startsWith("classes") && packageEntry.name.endsWith(
|
||||
".dex"
|
||||
))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
if (packageEntry.size > 15000000) {
|
||||
return true
|
||||
}
|
||||
val input = it.getInputStream(packageEntry).buffered()
|
||||
val dexFile = try {
|
||||
DexBackedDexFile.fromInputStream(null, input)
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
for (clazz in dexFile.classes) {
|
||||
val clazzName =
|
||||
clazz.type.substring(1, clazz.type.length - 1).replace("/", ".")
|
||||
.replace("$", ".")
|
||||
if (clazzName.matches(chinaAppRegex)) return true
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun requestGc() {
|
||||
channel.invokeMethod("gc", null)
|
||||
}
|
||||
|
||||
Submodule core/Clash.Meta updated: 44d4b6dab2...3d773d7fa5
201
core/common.go
201
core/common.go
@@ -2,24 +2,19 @@ package main
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
"github.com/metacubex/mihomo/adapter/provider"
|
||||
"github.com/metacubex/mihomo/common/batch"
|
||||
ap "github.com/metacubex/mihomo/adapter/provider"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
cp "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/hub"
|
||||
"github.com/metacubex/mihomo/hub/executor"
|
||||
"github.com/metacubex/mihomo/hub/route"
|
||||
"github.com/metacubex/mihomo/listener"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
rp "github.com/metacubex/mihomo/rules/provider"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -31,40 +26,40 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//type healthCheckSchema struct {
|
||||
// Enable bool `provider:"enable"`
|
||||
// URL string `provider:"url"`
|
||||
// Interval int `provider:"interval"`
|
||||
// TestTimeout int `provider:"timeout,omitempty"`
|
||||
// Lazy bool `provider:"lazy,omitempty"`
|
||||
// ExpectedStatus string `provider:"expected-status,omitempty"`
|
||||
//}
|
||||
type healthCheckSchema struct {
|
||||
Enable bool `provider:"enable"`
|
||||
URL string `provider:"url"`
|
||||
Interval int `provider:"interval"`
|
||||
TestTimeout int `provider:"timeout,omitempty"`
|
||||
Lazy bool `provider:"lazy,omitempty"`
|
||||
ExpectedStatus string `provider:"expected-status,omitempty"`
|
||||
}
|
||||
|
||||
//type proxyProviderSchema struct {
|
||||
// Type string `provider:"type"`
|
||||
// Path string `provider:"path,omitempty"`
|
||||
// URL string `provider:"url,omitempty"`
|
||||
// Proxy string `provider:"proxy,omitempty"`
|
||||
// Interval int `provider:"interval,omitempty"`
|
||||
// Filter string `provider:"filter,omitempty"`
|
||||
// ExcludeFilter string `provider:"exclude-filter,omitempty"`
|
||||
// ExcludeType string `provider:"exclude-type,omitempty"`
|
||||
// DialerProxy string `provider:"dialer-proxy,omitempty"`
|
||||
//
|
||||
// HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
// Override ap.OverrideSchema `provider:"override,omitempty"`
|
||||
// Header map[string][]string `provider:"header,omitempty"`
|
||||
//}
|
||||
//
|
||||
//type ruleProviderSchema struct {
|
||||
// Type string `provider:"type"`
|
||||
// Behavior string `provider:"behavior"`
|
||||
// Path string `provider:"path,omitempty"`
|
||||
// URL string `provider:"url,omitempty"`
|
||||
// Proxy string `provider:"proxy,omitempty"`
|
||||
// Format string `provider:"format,omitempty"`
|
||||
// Interval int `provider:"interval,omitempty"`
|
||||
//}
|
||||
type proxyProviderSchema struct {
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path,omitempty"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Proxy string `provider:"proxy,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
ExcludeFilter string `provider:"exclude-filter,omitempty"`
|
||||
ExcludeType string `provider:"exclude-type,omitempty"`
|
||||
DialerProxy string `provider:"dialer-proxy,omitempty"`
|
||||
|
||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
Override ap.OverrideSchema `provider:"override,omitempty"`
|
||||
Header map[string][]string `provider:"header,omitempty"`
|
||||
}
|
||||
|
||||
type ruleProviderSchema struct {
|
||||
Type string `provider:"type"`
|
||||
Behavior string `provider:"behavior"`
|
||||
Path string `provider:"path,omitempty"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Proxy string `provider:"proxy,omitempty"`
|
||||
Format string `provider:"format,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
}
|
||||
|
||||
type ConfigExtendedParams struct {
|
||||
IsPatch bool `json:"is-patch"`
|
||||
@@ -74,9 +69,9 @@ type ConfigExtendedParams struct {
|
||||
}
|
||||
|
||||
type GenerateConfigParams struct {
|
||||
ProfileId string `json:"profile-id"`
|
||||
Config config.RawConfig `json:"config" `
|
||||
Params ConfigExtendedParams `json:"params"`
|
||||
ProfilePath *string `json:"profile-path"`
|
||||
Config config.RawConfig `json:"config" `
|
||||
Params ConfigExtendedParams `json:"params"`
|
||||
}
|
||||
|
||||
type ChangeProxyParams struct {
|
||||
@@ -98,19 +93,9 @@ type ExternalProvider struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
VehicleType string `json:"vehicle-type"`
|
||||
Count int `json:"count"`
|
||||
Path string `json:"path"`
|
||||
UpdateAt time.Time `json:"update-at"`
|
||||
}
|
||||
|
||||
type ExternalProviders []ExternalProvider
|
||||
|
||||
func (a ExternalProviders) Len() int { return len(a) }
|
||||
func (a ExternalProviders) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
||||
func (a ExternalProviders) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
var b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
||||
|
||||
func restartExecutable(execPath string) {
|
||||
var err error
|
||||
executor.Shutdown()
|
||||
@@ -160,108 +145,26 @@ func removeFile(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getProfilePath(id string) string {
|
||||
return filepath.Join(constant.Path.HomeDir(), "profiles", id+".yaml")
|
||||
}
|
||||
|
||||
func getProfileProvidersPath(id string) string {
|
||||
return filepath.Join(constant.Path.HomeDir(), "providers", id)
|
||||
}
|
||||
|
||||
func getRawConfigWithId(id string) *config.RawConfig {
|
||||
path := getProfilePath(id)
|
||||
bytes, err := readFile(path)
|
||||
if err != nil {
|
||||
log.Errorln("profile is not exist")
|
||||
func getRawConfigWithPath(path *string) *config.RawConfig {
|
||||
if path == nil {
|
||||
return config.DefaultRawConfig()
|
||||
}
|
||||
prof, err := config.UnmarshalRawConfig(bytes)
|
||||
if err != nil {
|
||||
log.Errorln("unmarshalRawConfig error %v", err)
|
||||
return config.DefaultRawConfig()
|
||||
}
|
||||
for _, mapping := range prof.ProxyProvider {
|
||||
value, exist := mapping["path"].(string)
|
||||
if !exist {
|
||||
continue
|
||||
} else {
|
||||
bytes, err := readFile(*path)
|
||||
if err != nil {
|
||||
log.Errorln("getProfile readFile error %v", err)
|
||||
return config.DefaultRawConfig()
|
||||
}
|
||||
mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
|
||||
}
|
||||
for _, mapping := range prof.RuleProvider {
|
||||
value, exist := mapping["path"].(string)
|
||||
if !exist {
|
||||
continue
|
||||
prof, err := config.UnmarshalRawConfig(bytes)
|
||||
if err != nil {
|
||||
log.Errorln("getProfile UnmarshalRawConfig error %v", err)
|
||||
return config.DefaultRawConfig()
|
||||
}
|
||||
mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
|
||||
}
|
||||
return prof
|
||||
}
|
||||
|
||||
func getExternalProvidersRaw() map[string]cp.Provider {
|
||||
eps := make(map[string]cp.Provider)
|
||||
for n, p := range tunnel.Providers() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
eps[n] = p
|
||||
}
|
||||
}
|
||||
for n, p := range tunnel.RuleProviders() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
eps[n] = p
|
||||
}
|
||||
}
|
||||
return eps
|
||||
}
|
||||
|
||||
func toExternalProvider(p cp.Provider) (*ExternalProvider, error) {
|
||||
switch p.(type) {
|
||||
case *provider.ProxySetProvider:
|
||||
psp := p.(*provider.ProxySetProvider)
|
||||
return &ExternalProvider{
|
||||
Name: psp.Name(),
|
||||
Type: psp.Type().String(),
|
||||
VehicleType: psp.VehicleType().String(),
|
||||
Count: psp.Count(),
|
||||
Path: psp.Vehicle().Path(),
|
||||
UpdateAt: psp.UpdatedAt,
|
||||
}, nil
|
||||
case *rp.RuleSetProvider:
|
||||
rsp := p.(*rp.RuleSetProvider)
|
||||
return &ExternalProvider{
|
||||
Name: rsp.Name(),
|
||||
Type: rsp.Type().String(),
|
||||
VehicleType: rsp.VehicleType().String(),
|
||||
Count: rsp.Count(),
|
||||
Path: rsp.Vehicle().Path(),
|
||||
UpdateAt: rsp.UpdatedAt,
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.New("not external provider")
|
||||
return prof
|
||||
}
|
||||
}
|
||||
|
||||
func sideUpdateExternalProvider(p cp.Provider, bytes []byte) error {
|
||||
switch p.(type) {
|
||||
case *provider.ProxySetProvider:
|
||||
psp := p.(*provider.ProxySetProvider)
|
||||
elm, same, err := psp.SideUpdate(bytes)
|
||||
if err == nil && !same {
|
||||
psp.OnUpdate(elm)
|
||||
}
|
||||
return nil
|
||||
case rp.RuleSetProvider:
|
||||
rsp := p.(*rp.RuleSetProvider)
|
||||
elm, same, err := rsp.SideUpdate(bytes)
|
||||
if err == nil && !same {
|
||||
rsp.OnUpdate(elm)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errors.New("not external provider")
|
||||
}
|
||||
}
|
||||
|
||||
func decorationConfig(profileId string, cfg config.RawConfig) *config.RawConfig {
|
||||
prof := getRawConfigWithId(profileId)
|
||||
func decorationConfig(profilePath *string, cfg config.RawConfig) *config.RawConfig {
|
||||
prof := getRawConfigWithPath(profilePath)
|
||||
overwriteConfig(prof, cfg)
|
||||
return prof
|
||||
}
|
||||
@@ -424,7 +327,6 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
|
||||
targetConfig.LogLevel = patchConfig.LogLevel
|
||||
targetConfig.Port = 0
|
||||
targetConfig.SocksPort = 0
|
||||
targetConfig.KeepAliveInterval = patchConfig.KeepAliveInterval
|
||||
targetConfig.MixedPort = patchConfig.MixedPort
|
||||
targetConfig.FindProcessMode = patchConfig.FindProcessMode
|
||||
targetConfig.AllowLan = patchConfig.AllowLan
|
||||
@@ -526,6 +428,5 @@ func applyConfig() error {
|
||||
hub.UltraApplyConfig(cfg, true)
|
||||
patchSelectGroup()
|
||||
}
|
||||
externalProviders = getExternalProvidersRaw()
|
||||
return err
|
||||
}
|
||||
|
||||
194
core/hub.go
194
core/hub.go
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
"github.com/metacubex/mihomo/adapter/provider"
|
||||
"github.com/metacubex/mihomo/common/structure"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/updater"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
@@ -18,12 +19,12 @@ import (
|
||||
cp "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/hub/executor"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
rp "github.com/metacubex/mihomo/rules/provider"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||
"golang.org/x/net/context"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
@@ -32,10 +33,10 @@ var currentConfig = config.DefaultRawConfig()
|
||||
|
||||
var configParams = ConfigExtendedParams{}
|
||||
|
||||
var externalProviders = map[string]cp.Provider{}
|
||||
|
||||
var isInit = false
|
||||
|
||||
var currentProfileName = ""
|
||||
|
||||
//export initClash
|
||||
func initClash(homeDirStr *C.char) bool {
|
||||
if !isInit {
|
||||
@@ -74,6 +75,16 @@ func forceGc() {
|
||||
}()
|
||||
}
|
||||
|
||||
//export setCurrentProfileName
|
||||
func setCurrentProfileName(s *C.char) {
|
||||
currentProfileName = C.GoString(s)
|
||||
}
|
||||
|
||||
//export getCurrentProfileName
|
||||
func getCurrentProfileName() *C.char {
|
||||
return C.CString(currentProfileName)
|
||||
}
|
||||
|
||||
//export validateConfig
|
||||
func validateConfig(s *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
@@ -100,7 +111,7 @@ func updateConfig(s *C.char, port C.longlong) {
|
||||
return
|
||||
}
|
||||
configParams = params.Params
|
||||
prof := decorationConfig(params.ProfileId, params.Config)
|
||||
prof := decorationConfig(params.ProfilePath, params.Config)
|
||||
currentConfig = prof
|
||||
err = applyConfig()
|
||||
if err != nil {
|
||||
@@ -113,10 +124,34 @@ func updateConfig(s *C.char, port C.longlong) {
|
||||
|
||||
//export clearEffect
|
||||
func clearEffect(s *C.char) {
|
||||
id := C.GoString(s)
|
||||
path := C.GoString(s)
|
||||
go func() {
|
||||
_ = removeFile(getProfilePath(id))
|
||||
_ = removeFile(getProfileProvidersPath(id))
|
||||
rawCfg := getRawConfigWithPath(&path)
|
||||
for _, mapping := range rawCfg.RuleProvider {
|
||||
schema := &ruleProviderSchema{}
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
||||
if err := decoder.Decode(mapping, schema); err != nil {
|
||||
return
|
||||
}
|
||||
if schema.Type == "http" {
|
||||
_ = removeFile(constant.Path.Resolve(schema.Path))
|
||||
}
|
||||
}
|
||||
for _, mapping := range rawCfg.ProxyProvider {
|
||||
schema := &proxyProviderSchema{
|
||||
HealthCheck: healthCheckSchema{
|
||||
Lazy: true,
|
||||
},
|
||||
}
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
||||
if err := decoder.Decode(mapping, schema); err != nil {
|
||||
return
|
||||
}
|
||||
if schema.Type == "http" {
|
||||
_ = removeFile(constant.Path.Resolve(schema.Path))
|
||||
}
|
||||
}
|
||||
_ = removeFile(path)
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -149,13 +184,10 @@ func changeProxy(s *C.char) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if proxyName == "" {
|
||||
selector.ForceSet(proxyName)
|
||||
} else {
|
||||
err = selector.Set(proxyName)
|
||||
}
|
||||
|
||||
err = selector.Set(proxyName)
|
||||
if err == nil {
|
||||
log.Infoln("[SelectAble] %s selected %s", groupName, proxyName)
|
||||
log.Infoln("[Selector] %s selected %s", groupName, proxyName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,16 +230,16 @@ func resetTraffic() {
|
||||
func asyncTestDelay(s *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
paramsString := C.GoString(s)
|
||||
b.Go(paramsString, func() (bool, error) {
|
||||
go func() {
|
||||
var params = &TestDelayParams{}
|
||||
err := json.Unmarshal([]byte(paramsString), params)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
return
|
||||
}
|
||||
|
||||
expectedStatus, err := utils.NewUnsignedRanges[uint16]("")
|
||||
if err != nil {
|
||||
return false, nil
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(params.Timeout))
|
||||
@@ -224,7 +256,7 @@ func asyncTestDelay(s *C.char, port C.longlong) {
|
||||
delayData.Value = -1
|
||||
data, _ := json.Marshal(delayData)
|
||||
bridge.SendToPort(i, string(data))
|
||||
return false, nil
|
||||
return
|
||||
}
|
||||
|
||||
delay, err := proxy.URLTest(ctx, constant.DefaultTestURL, expectedStatus)
|
||||
@@ -232,14 +264,14 @@ func asyncTestDelay(s *C.char, port C.longlong) {
|
||||
delayData.Value = -1
|
||||
data, _ := json.Marshal(delayData)
|
||||
bridge.SendToPort(i, string(data))
|
||||
return false, nil
|
||||
return
|
||||
}
|
||||
|
||||
delayData.Value = int32(delay)
|
||||
data, _ := json.Marshal(delayData)
|
||||
bridge.SendToPort(i, string(data))
|
||||
return false, nil
|
||||
})
|
||||
return
|
||||
}()
|
||||
}
|
||||
|
||||
//export getVersionInfo
|
||||
@@ -313,67 +345,78 @@ func getProvider(name *C.char) *C.char {
|
||||
|
||||
//export getExternalProviders
|
||||
func getExternalProviders() *C.char {
|
||||
eps := make([]ExternalProvider, 0)
|
||||
for _, p := range externalProviders {
|
||||
externalProvider, err := toExternalProvider(p)
|
||||
if err != nil {
|
||||
continue
|
||||
externalProviders := make([]ExternalProvider, 0)
|
||||
providers := tunnel.Providers()
|
||||
for n, p := range providers {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*provider.ProxySetProvider)
|
||||
externalProviders = append(externalProviders, ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
})
|
||||
}
|
||||
eps = append(eps, *externalProvider)
|
||||
}
|
||||
sort.Sort(ExternalProviders(eps))
|
||||
data, err := json.Marshal(eps)
|
||||
for n, p := range tunnel.RuleProviders() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*rp.RuleSetProvider)
|
||||
externalProviders = append(externalProviders, ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
data, err := json.Marshal(externalProviders)
|
||||
if err != nil {
|
||||
return C.CString("")
|
||||
}
|
||||
return C.CString(string(data))
|
||||
}
|
||||
|
||||
//export getExternalProvider
|
||||
func getExternalProvider(name *C.char) *C.char {
|
||||
externalProviderName := C.GoString(name)
|
||||
externalProvider, exist := externalProviders[externalProviderName]
|
||||
if !exist {
|
||||
return C.CString("")
|
||||
}
|
||||
e, err := toExternalProvider(externalProvider)
|
||||
if err != nil {
|
||||
return C.CString("")
|
||||
}
|
||||
data, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return C.CString("")
|
||||
}
|
||||
return C.CString(string(data))
|
||||
}
|
||||
|
||||
//export updateGeoData
|
||||
func updateGeoData(geoType *C.char, geoName *C.char, port C.longlong) {
|
||||
//export updateExternalProvider
|
||||
func updateExternalProvider(providerName *C.char, providerType *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
geoTypeString := C.GoString(geoType)
|
||||
geoNameString := C.GoString(geoName)
|
||||
providerNameString := C.GoString(providerName)
|
||||
providerTypeString := C.GoString(providerType)
|
||||
go func() {
|
||||
switch geoTypeString {
|
||||
switch providerTypeString {
|
||||
case "Proxy":
|
||||
providers := tunnel.Providers()
|
||||
err := providers[providerNameString].Update()
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "Rule":
|
||||
providers := tunnel.RuleProviders()
|
||||
err := providers[providerNameString].Update()
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "MMDB":
|
||||
err := updater.UpdateMMDB(constant.Path.Resolve(geoNameString))
|
||||
err := updater.UpdateMMDB(constant.Path.Resolve(providerNameString))
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "ASN":
|
||||
err := updater.UpdateASN(constant.Path.Resolve(geoNameString))
|
||||
err := updater.UpdateASN(constant.Path.Resolve(providerNameString))
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "GeoIp":
|
||||
err := updater.UpdateGeoIp(constant.Path.Resolve(geoNameString))
|
||||
err := updater.UpdateGeoIp(constant.Path.Resolve(providerNameString))
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "GeoSite":
|
||||
err := updater.UpdateGeoSite(constant.Path.Resolve(geoNameString))
|
||||
err := updater.UpdateGeoSite(constant.Path.Resolve(providerNameString))
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
@@ -383,45 +426,6 @@ func updateGeoData(geoType *C.char, geoName *C.char, port C.longlong) {
|
||||
}()
|
||||
}
|
||||
|
||||
//export updateExternalProvider
|
||||
func updateExternalProvider(providerName *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
providerNameString := C.GoString(providerName)
|
||||
go func() {
|
||||
externalProvider, exist := externalProviders[providerNameString]
|
||||
if !exist {
|
||||
bridge.SendToPort(i, "external provider is not exist")
|
||||
return
|
||||
}
|
||||
err := externalProvider.Update()
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
bridge.SendToPort(i, "")
|
||||
}()
|
||||
}
|
||||
|
||||
//export sideLoadExternalProvider
|
||||
func sideLoadExternalProvider(providerName *C.char, data *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
bytes := []byte(C.GoString(data))
|
||||
providerNameString := C.GoString(providerName)
|
||||
go func() {
|
||||
externalProvider, exist := externalProviders[providerNameString]
|
||||
if !exist {
|
||||
bridge.SendToPort(i, "external provider is not exist")
|
||||
return
|
||||
}
|
||||
err := sideUpdateExternalProvider(externalProvider, bytes)
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
bridge.SendToPort(i, "")
|
||||
}()
|
||||
}
|
||||
|
||||
//export initNativeApiBridge
|
||||
func initNativeApiBridge(api unsafe.Pointer) {
|
||||
bridge.InitDartApi(api)
|
||||
@@ -459,7 +463,7 @@ func init() {
|
||||
Data: c,
|
||||
})
|
||||
}
|
||||
executor.DefaultProviderLoadedHook = func(providerName string) {
|
||||
executor.DefaultProxyProviderLoadedHook = func(providerName string) {
|
||||
SendMessage(Message{
|
||||
Type: LoadedMessage,
|
||||
Data: providerName,
|
||||
|
||||
@@ -21,9 +21,8 @@ type AndroidProps struct {
|
||||
|
||||
type State struct {
|
||||
AndroidProps
|
||||
CurrentProfileName string `json:"currentProfileName"`
|
||||
MixedPort int `json:"mixedPort"`
|
||||
OnlyProxy bool `json:"onlyProxy"`
|
||||
MixedPort int `json:"mixedPort"`
|
||||
OnlyProxy bool `json:"onlyProxy"`
|
||||
}
|
||||
|
||||
var state State
|
||||
|
||||
@@ -88,6 +88,7 @@ class ApplicationState extends State<Application> {
|
||||
}
|
||||
await globalState.appController.init();
|
||||
globalState.appController.initLink();
|
||||
_updateGroups();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,6 +120,19 @@ class ApplicationState extends State<Application> {
|
||||
});
|
||||
}
|
||||
|
||||
_updateGroups() {
|
||||
if (globalState.groupsUpdateTimer != null) {
|
||||
globalState.groupsUpdateTimer?.cancel();
|
||||
globalState.groupsUpdateTimer = null;
|
||||
}
|
||||
globalState.groupsUpdateTimer ??= Timer.periodic(
|
||||
httpTimeoutDuration,
|
||||
(timer) async {
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(context) {
|
||||
return AppStateContainer(
|
||||
|
||||
@@ -100,6 +100,22 @@ class ClashCore {
|
||||
);
|
||||
}
|
||||
|
||||
setProfileName(String profileName) {
|
||||
final profileNameChar = profileName.toNativeUtf8().cast<Char>();
|
||||
clashFFI.setCurrentProfileName(
|
||||
profileNameChar,
|
||||
);
|
||||
malloc.free(profileNameChar);
|
||||
}
|
||||
|
||||
getProfileName() {
|
||||
final currentProfileNameRaw = clashFFI.getCurrentProfileName();
|
||||
final currentProfileName =
|
||||
currentProfileNameRaw.cast<Utf8>().toDartString();
|
||||
clashFFI.freeCString(currentProfileNameRaw);
|
||||
return currentProfileName;
|
||||
}
|
||||
|
||||
Future<List<Group>> getProxiesGroups() {
|
||||
final proxiesRaw = clashFFI.getProxies();
|
||||
final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString();
|
||||
@@ -149,69 +165,9 @@ class ClashCore {
|
||||
});
|
||||
}
|
||||
|
||||
ExternalProvider? getExternalProvider(String externalProviderName) {
|
||||
final externalProviderNameChar =
|
||||
externalProviderName.toNativeUtf8().cast<Char>();
|
||||
final externalProviderRaw =
|
||||
clashFFI.getExternalProvider(externalProviderNameChar);
|
||||
malloc.free(externalProviderNameChar);
|
||||
final externalProviderRawString =
|
||||
externalProviderRaw.cast<Utf8>().toDartString();
|
||||
clashFFI.freeCString(externalProviderRaw);
|
||||
if(externalProviderRawString.isEmpty) return null;
|
||||
return ExternalProvider.fromJson(json.decode(externalProviderRawString));
|
||||
}
|
||||
|
||||
Future<String> updateGeoData({
|
||||
required String geoType,
|
||||
required String geoName,
|
||||
}) {
|
||||
final completer = Completer<String>();
|
||||
final receiver = ReceivePort();
|
||||
receiver.listen((message) {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(message);
|
||||
receiver.close();
|
||||
}
|
||||
});
|
||||
final geoTypeChar = geoType.toNativeUtf8().cast<Char>();
|
||||
final geoNameChar = geoName.toNativeUtf8().cast<Char>();
|
||||
clashFFI.updateGeoData(
|
||||
geoTypeChar,
|
||||
geoNameChar,
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(geoTypeChar);
|
||||
malloc.free(geoNameChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<String> sideLoadExternalProvider({
|
||||
required String providerName,
|
||||
required String data,
|
||||
}) {
|
||||
final completer = Completer<String>();
|
||||
final receiver = ReceivePort();
|
||||
receiver.listen((message) {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(message);
|
||||
receiver.close();
|
||||
}
|
||||
});
|
||||
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
|
||||
final dataChar = data.toNativeUtf8().cast<Char>();
|
||||
clashFFI.sideLoadExternalProvider(
|
||||
providerNameChar,
|
||||
dataChar,
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(providerNameChar);
|
||||
malloc.free(dataChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<String> updateExternalProvider({
|
||||
required String providerName,
|
||||
required String providerType,
|
||||
}) {
|
||||
final completer = Completer<String>();
|
||||
final receiver = ReceivePort();
|
||||
@@ -222,11 +178,14 @@ class ClashCore {
|
||||
}
|
||||
});
|
||||
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
|
||||
final providerTypeChar = providerType.toNativeUtf8().cast<Char>();
|
||||
clashFFI.updateExternalProvider(
|
||||
providerNameChar,
|
||||
providerTypeChar,
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(providerNameChar);
|
||||
malloc.free(providerTypeChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@@ -257,13 +216,21 @@ class ClashCore {
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(delayParamsChar);
|
||||
Future.delayed(httpTimeoutDuration + moreDuration, () {
|
||||
receiver.close();
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(
|
||||
Delay(name: proxyName, value: -1),
|
||||
);
|
||||
}
|
||||
});
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
clearEffect(String profileId) {
|
||||
final profileIdChar = profileId.toNativeUtf8().cast<Char>();
|
||||
clashFFI.clearEffect(profileIdChar);
|
||||
malloc.free(profileIdChar);
|
||||
clearEffect(String path) {
|
||||
final pathChar = path.toNativeUtf8().cast<Char>();
|
||||
clashFFI.clearEffect(pathChar);
|
||||
malloc.free(pathChar);
|
||||
}
|
||||
|
||||
VersionInfo getVersionInfo() {
|
||||
|
||||
@@ -5190,6 +5190,30 @@ class ClashFFI {
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function()>>('forceGc');
|
||||
late final _forceGc = _forceGcPtr.asFunction<void Function()>();
|
||||
|
||||
void setCurrentProfileName(
|
||||
ffi.Pointer<ffi.Char> s,
|
||||
) {
|
||||
return _setCurrentProfileName(
|
||||
s,
|
||||
);
|
||||
}
|
||||
|
||||
late final _setCurrentProfileNamePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
|
||||
'setCurrentProfileName');
|
||||
late final _setCurrentProfileName = _setCurrentProfileNamePtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
||||
|
||||
ffi.Pointer<ffi.Char> getCurrentProfileName() {
|
||||
return _getCurrentProfileName();
|
||||
}
|
||||
|
||||
late final _getCurrentProfileNamePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
|
||||
'getCurrentProfileName');
|
||||
late final _getCurrentProfileName =
|
||||
_getCurrentProfileNamePtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||
|
||||
void validateConfig(
|
||||
ffi.Pointer<ffi.Char> s,
|
||||
int port,
|
||||
@@ -5385,76 +5409,24 @@ class ClashFFI {
|
||||
late final _getExternalProviders =
|
||||
_getExternalProvidersPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||
|
||||
ffi.Pointer<ffi.Char> getExternalProvider(
|
||||
ffi.Pointer<ffi.Char> name,
|
||||
) {
|
||||
return _getExternalProvider(
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
late final _getExternalProviderPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<ffi.Char> Function(
|
||||
ffi.Pointer<ffi.Char>)>>('getExternalProvider');
|
||||
late final _getExternalProvider = _getExternalProviderPtr
|
||||
.asFunction<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
|
||||
|
||||
void updateGeoData(
|
||||
ffi.Pointer<ffi.Char> geoType,
|
||||
ffi.Pointer<ffi.Char> geoName,
|
||||
int port,
|
||||
) {
|
||||
return _updateGeoData(
|
||||
geoType,
|
||||
geoName,
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _updateGeoDataPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
|
||||
ffi.LongLong)>>('updateGeoData');
|
||||
late final _updateGeoData = _updateGeoDataPtr.asFunction<
|
||||
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
||||
|
||||
void updateExternalProvider(
|
||||
ffi.Pointer<ffi.Char> providerName,
|
||||
ffi.Pointer<ffi.Char> providerType,
|
||||
int port,
|
||||
) {
|
||||
return _updateExternalProvider(
|
||||
providerName,
|
||||
providerType,
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _updateExternalProviderPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(
|
||||
ffi.Pointer<ffi.Char>, ffi.LongLong)>>('updateExternalProvider');
|
||||
late final _updateExternalProvider = _updateExternalProviderPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
|
||||
|
||||
void sideLoadExternalProvider(
|
||||
ffi.Pointer<ffi.Char> providerName,
|
||||
ffi.Pointer<ffi.Char> data,
|
||||
int port,
|
||||
) {
|
||||
return _sideLoadExternalProvider(
|
||||
providerName,
|
||||
data,
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _sideLoadExternalProviderPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
|
||||
ffi.LongLong)>>('sideLoadExternalProvider');
|
||||
late final _sideLoadExternalProvider =
|
||||
_sideLoadExternalProviderPtr.asFunction<
|
||||
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
||||
ffi.LongLong)>>('updateExternalProvider');
|
||||
late final _updateExternalProvider = _updateExternalProviderPtr.asFunction<
|
||||
void Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int)>();
|
||||
|
||||
void initNativeApiBridge(
|
||||
ffi.Pointer<ffi.Void> api,
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
extension ArchiveExt on Archive {
|
||||
addDirectoryToArchive(String dirPath, String parentPath) {
|
||||
final dir = Directory(dirPath);
|
||||
final entities = dir.listSync(recursive: false);
|
||||
for (final entity in entities) {
|
||||
final relativePath = relative(entity.path, from: parentPath);
|
||||
if (entity is File) {
|
||||
final data = entity.readAsBytesSync();
|
||||
final archiveFile = ArchiveFile(relativePath, data.length, data);
|
||||
addFile(archiveFile);
|
||||
} else if (entity is Directory) {
|
||||
addDirectoryToArchive(entity.path, parentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add<T>(String name, T raw) {
|
||||
final data = json.encode(raw);
|
||||
addFile(
|
||||
ArchiveFile(name, data.length, data),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,20 +16,13 @@ extension ColorExtension on Color {
|
||||
toLittle() {
|
||||
return withOpacity(0.03);
|
||||
}
|
||||
|
||||
Color darken([double amount = .1]) {
|
||||
assert(amount >= 0 && amount <= 1);
|
||||
final hsl = HSLColor.fromColor(this);
|
||||
final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0));
|
||||
return hslDark.toColor();
|
||||
}
|
||||
}
|
||||
|
||||
extension ColorSchemeExtension on ColorScheme {
|
||||
ColorScheme toPrueBlack(bool isPrueBlack) => isPrueBlack
|
||||
? copyWith(
|
||||
surface: Colors.black,
|
||||
surfaceContainer: surfaceContainer.darken(0.05),
|
||||
background: Colors.black,
|
||||
)
|
||||
: this;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
@@ -16,7 +17,7 @@ const mmdbFileName = "geoip.metadb";
|
||||
const asnFileName = "ASN.mmdb";
|
||||
const geoIpFileName = "GeoIP.dat";
|
||||
const geoSiteFileName = "GeoSite.dat";
|
||||
final double kHeaderHeight = system.isDesktop ? 40 : 0;
|
||||
final double kHeaderHeight = system.isDesktop ? (Platform.isMacOS ? 28 : 40) : 0;
|
||||
const GeoXMap defaultGeoXMap = {
|
||||
"mmdb":
|
||||
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:webdav_client/webdav_client.dart';
|
||||
|
||||
class DAVClient {
|
||||
@@ -30,6 +33,8 @@ class DAVClient {
|
||||
Future<bool> _ping() async {
|
||||
try {
|
||||
await client.ping();
|
||||
await client.mkdir("/$appName");
|
||||
await client.mkdir("/$appName/$profilesDirectoryName");
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
@@ -38,16 +43,65 @@ class DAVClient {
|
||||
|
||||
get root => "/$appName";
|
||||
|
||||
get backupFile => "$root/backup.zip";
|
||||
get remoteConfig => "$root/$configKey.json";
|
||||
|
||||
backup(Uint8List data) async {
|
||||
get remoteClashConfig => "$root/$clashConfigKey.json";
|
||||
|
||||
get remoteProfiles => "$root/$profilesDirectoryName";
|
||||
|
||||
backup() async {
|
||||
final appController = globalState.appController;
|
||||
final config = appController.config;
|
||||
final clashConfig = appController.clashConfig;
|
||||
await client.mkdir("$root");
|
||||
await client.write("$backupFile", data);
|
||||
client.write(
|
||||
remoteConfig,
|
||||
utf8.encode(
|
||||
json.encode(config.toJson()),
|
||||
),
|
||||
);
|
||||
client.write(
|
||||
remoteClashConfig,
|
||||
utf8.encode(
|
||||
json.encode(clashConfig.toJson()),
|
||||
),
|
||||
);
|
||||
await client.remove(remoteProfiles);
|
||||
for (final profile in config.profiles) {
|
||||
final path = await appPath.getProfilePath(profile.id);
|
||||
if (path == null) continue;
|
||||
await client.writeFromFile(
|
||||
path,
|
||||
"$remoteProfiles/${basename(path)}",
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<List<int>> recovery() async {
|
||||
final data = await client.read(backupFile);
|
||||
return data;
|
||||
recovery({required RecoveryOption recoveryOption}) async {
|
||||
final profiles = await client.readDir(remoteProfiles);
|
||||
final profilesPath = await appPath.getProfilesPath();
|
||||
for (final file in profiles) {
|
||||
await client.read2File(
|
||||
"$remoteProfiles/${file.name}",
|
||||
join(
|
||||
profilesPath,
|
||||
file.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
final configRaw = utf8.decode((await client.read(remoteConfig)));
|
||||
final clashConfigRaw = utf8.decode(await client.read(remoteClashConfig));
|
||||
final config = Config.fromJson(json.decode(configRaw));
|
||||
final clashConfig = ClashConfig.fromJson(json.decode(clashConfigRaw));
|
||||
if(recoveryOption == RecoveryOption.onlyProfiles){
|
||||
globalState.appController.config.update(config, RecoveryOption.onlyProfiles);
|
||||
}else{
|
||||
globalState.appController.config.update(config, RecoveryOption.all);
|
||||
globalState.appController.clashConfig.update(clashConfig);
|
||||
}
|
||||
await globalState.appController.applyProfile();
|
||||
globalState.appController.savePreferences();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class Navigation {
|
||||
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
|
||||
),
|
||||
const NavigationItem(
|
||||
icon: Icon(Icons.storage),
|
||||
icon: Icon(Icons.swap_vert_circle),
|
||||
label: "resources",
|
||||
description: "resourcesDesc",
|
||||
keep: false,
|
||||
|
||||
@@ -2,10 +2,10 @@ import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/common/app_localizations.dart';
|
||||
import 'package:fl_clash/common/constant.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
import 'package:zxing2/qrcode.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
|
||||
@@ -83,7 +83,7 @@ class Other {
|
||||
if (charA == charB) {
|
||||
return sortByChar(a.substring(1), b.substring(1));
|
||||
} else {
|
||||
return charA.compareToLower(charB);
|
||||
return charA.compareTo(charB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,8 +199,8 @@ class Other {
|
||||
return targetColumnsArray.first;
|
||||
}
|
||||
|
||||
String getColumnsTextForInt(int number) {
|
||||
return switch (number) {
|
||||
String getColumnsTextForInt(int number){
|
||||
return switch(number){
|
||||
1 => appLocalizations.oneColumn,
|
||||
2 => appLocalizations.twoColumns,
|
||||
3 => appLocalizations.threeColumns,
|
||||
@@ -208,10 +208,6 @@ class Other {
|
||||
int() => throw UnimplementedError(),
|
||||
};
|
||||
}
|
||||
|
||||
String getBackupFileName() {
|
||||
return "${appName}_backup_${DateTime.now().show}.zip";
|
||||
}
|
||||
}
|
||||
|
||||
final other = Other();
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'constant.dart';
|
||||
class AppPath {
|
||||
static AppPath? _instance;
|
||||
Completer<Directory> cacheDir = Completer();
|
||||
Completer<Directory> downloadDir = Completer();
|
||||
|
||||
// Future<Directory> _createDesktopCacheDir() async {
|
||||
// final path = join(dirname(Platform.resolvedExecutable), 'cache');
|
||||
@@ -24,9 +23,6 @@ class AppPath {
|
||||
getApplicationSupportDirectory().then((value) {
|
||||
cacheDir.complete(value);
|
||||
});
|
||||
getDownloadsDirectory().then((value) {
|
||||
downloadDir.complete(value);
|
||||
});
|
||||
// if (Platform.isAndroid) {
|
||||
// getApplicationSupportDirectory().then((value) {
|
||||
// cacheDir.complete(value);
|
||||
@@ -43,11 +39,6 @@ class AppPath {
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
Future<String> getDownloadDirPath() async {
|
||||
final directory = await downloadDir.future;
|
||||
return directory.path;
|
||||
}
|
||||
|
||||
Future<String> getHomeDirPath() async {
|
||||
final directory = await cacheDir.future;
|
||||
return directory.path;
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class Picker {
|
||||
Future<PlatformFile?> pickerFile() async {
|
||||
Future<PlatformFile?> pickerConfigFile() async {
|
||||
final filePickerResult = await FilePicker.platform.pickFiles(
|
||||
withData: true,
|
||||
allowMultiple: false,
|
||||
initialDirectory: await appPath.getDownloadDirPath(),
|
||||
);
|
||||
return filePickerResult?.files.first;
|
||||
}
|
||||
|
||||
Future<String?> saveFile(String fileName,Uint8List bytes) async {
|
||||
final path = await FilePicker.platform.saveFile(
|
||||
fileName: fileName,
|
||||
initialDirectory: await appPath.getDownloadDirPath(),
|
||||
bytes: bytes,
|
||||
Future<PlatformFile?> pickerGeoDataFile() async {
|
||||
final filePickerResult = await FilePicker.platform.pickFiles(
|
||||
withData: true,
|
||||
allowMultiple: false,
|
||||
);
|
||||
return path;
|
||||
return filePickerResult?.files.first;
|
||||
}
|
||||
|
||||
Future<String?> pickerConfigQRCode() async {
|
||||
|
||||
@@ -13,6 +13,9 @@ class Request {
|
||||
|
||||
Request() {
|
||||
_dio = Dio();
|
||||
_dio.options = BaseOptions(
|
||||
headers: {"User-Agent": globalState.appController.clashConfig.globalUa},
|
||||
);
|
||||
_dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onRequest: (options, handler) {
|
||||
@@ -49,12 +52,11 @@ class Request {
|
||||
.get(
|
||||
url,
|
||||
options: Options(
|
||||
headers: {"User-Agent": globalState.appController.clashConfig.globalUa},
|
||||
responseType: ResponseType.bytes,
|
||||
),
|
||||
)
|
||||
.timeout(
|
||||
httpTimeoutDuration * 6,
|
||||
httpTimeoutDuration * 2,
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,4 @@ extension StringExtension on String {
|
||||
bool get isUrl {
|
||||
return RegExp(r'^(http|https|ftp)://').hasMatch(this);
|
||||
}
|
||||
|
||||
int compareToLower(String other) {
|
||||
return toLowerCase().compareTo(
|
||||
other.toLowerCase(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ class Window {
|
||||
WindowOptions windowOptions = WindowOptions(
|
||||
size: Size(props.width, props.height),
|
||||
minimumSize: const Size(380, 500),
|
||||
windowButtonVisibility: false,
|
||||
titleBarStyle: TitleBarStyle.hidden,
|
||||
);
|
||||
if (props.left != null || props.top != null) {
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:fl_clash/common/archive.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
@@ -96,7 +90,9 @@ class AppController {
|
||||
|
||||
deleteProfile(String id) async {
|
||||
config.deleteProfileById(id);
|
||||
clashCore.clearEffect(id);
|
||||
final profilePath = await appPath.getProfilePath(id);
|
||||
if (profilePath == null) return;
|
||||
clashCore.clearEffect(profilePath);
|
||||
if (config.currentProfileId == id) {
|
||||
if (config.profiles.isNotEmpty) {
|
||||
final updateId = config.profiles.first.id;
|
||||
@@ -108,10 +104,8 @@ class AppController {
|
||||
}
|
||||
|
||||
Future<void> updateProfile(Profile profile) async {
|
||||
final newProfile = await profile.update();
|
||||
config.setProfile(
|
||||
newProfile.copyWith(isUpdating: false),
|
||||
);
|
||||
await profile.update();
|
||||
config.setProfile(await profile.update());
|
||||
}
|
||||
|
||||
Future<void> updateClashConfig({bool isPatch = true}) async {
|
||||
@@ -146,6 +140,9 @@ class AppController {
|
||||
changeProfile(String? value) async {
|
||||
if (value == config.currentProfileId) return;
|
||||
config.currentProfileId = value;
|
||||
await applyProfile();
|
||||
appState.delayMap = {};
|
||||
saveConfigPreferences();
|
||||
}
|
||||
|
||||
autoUpdateProfiles() async {
|
||||
@@ -297,6 +294,26 @@ class AppController {
|
||||
if (!config.silentLaunch) {
|
||||
window?.show();
|
||||
}
|
||||
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
|
||||
if (commonScaffoldState?.mounted == true) {
|
||||
await commonScaffoldState?.loadingRun(() async {
|
||||
await globalState.applyProfile(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
}, title: appLocalizations.init);
|
||||
} else {
|
||||
await globalState.applyProfile(
|
||||
appState: appState,
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
}
|
||||
await afterInit();
|
||||
}
|
||||
|
||||
afterInit() async {
|
||||
await proxyManager.updateStartTime();
|
||||
if (proxyManager.isStart) {
|
||||
await updateSystemProxy(true);
|
||||
@@ -386,7 +403,7 @@ class AppController {
|
||||
}
|
||||
|
||||
addProfileFormFile() async {
|
||||
final platformFile = await globalState.safeRun(picker.pickerFile);
|
||||
final platformFile = await globalState.safeRun(picker.pickerConfigFile);
|
||||
final bytes = platformFile?.bytes;
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
@@ -425,10 +442,7 @@ class AppController {
|
||||
List<Proxy> _sortOfName(List<Proxy> proxies) {
|
||||
return List.of(proxies)
|
||||
..sort(
|
||||
(a, b) => other.sortByChar(
|
||||
PinyinHelper.getPinyin(a.name),
|
||||
PinyinHelper.getPinyin(b.name),
|
||||
),
|
||||
(a, b) => other.sortByChar(a.name, b.name),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -466,63 +480,4 @@ class AppController {
|
||||
config.currentSelectedMap[groupName] ?? '') ??
|
||||
'';
|
||||
}
|
||||
|
||||
Future<List<int>> backupData() async {
|
||||
final homeDirPath = await appPath.getHomeDirPath();
|
||||
final profilesPath = await appPath.getProfilesPath();
|
||||
final configJson = config.toJson();
|
||||
final clashConfigJson = clashConfig.toJson();
|
||||
return Isolate.run<List<int>>(() async {
|
||||
final archive = Archive();
|
||||
archive.add("config.json", configJson);
|
||||
archive.add("clashConfig.json", clashConfigJson);
|
||||
await archive.addDirectoryToArchive(profilesPath, homeDirPath);
|
||||
final zipEncoder = ZipEncoder();
|
||||
return zipEncoder.encode(archive) ?? [];
|
||||
});
|
||||
}
|
||||
|
||||
recoveryData(
|
||||
List<int> data,
|
||||
RecoveryOption recoveryOption,
|
||||
) async {
|
||||
final archive = await Isolate.run<Archive>(() {
|
||||
final zipDecoder = ZipDecoder();
|
||||
return zipDecoder.decodeBytes(data);
|
||||
});
|
||||
final homeDirPath = await appPath.getHomeDirPath();
|
||||
final configs =
|
||||
archive.files.where((item) => item.name.endsWith(".json")).toList();
|
||||
final profiles =
|
||||
archive.files.where((item) => !item.name.endsWith(".json"));
|
||||
final configIndex =
|
||||
configs.indexWhere((config) => config.name == "config.json");
|
||||
final clashConfigIndex =
|
||||
configs.indexWhere((config) => config.name == "clashConfig.json");
|
||||
if (configIndex == -1 || clashConfigIndex == -1) throw "invalid backup.zip";
|
||||
final configFile = configs[configIndex];
|
||||
final clashConfigFile = configs[clashConfigIndex];
|
||||
final tempConfig = Config.fromJson(
|
||||
json.decode(
|
||||
utf8.decode(configFile.content),
|
||||
),
|
||||
);
|
||||
final tempClashConfig = ClashConfig.fromJson(
|
||||
json.decode(
|
||||
utf8.decode(clashConfigFile.content),
|
||||
),
|
||||
);
|
||||
for (final profile in profiles) {
|
||||
final filePath = join(homeDirPath, profile.name);
|
||||
final file = File(filePath);
|
||||
await file.create(recursive: true);
|
||||
await file.writeAsBytes(profile.content);
|
||||
}
|
||||
if (recoveryOption == RecoveryOption.onlyProfiles) {
|
||||
config.update(tempConfig, RecoveryOption.onlyProfiles);
|
||||
} else {
|
||||
config.update(tempConfig, RecoveryOption.all);
|
||||
clashConfig.update(tempClashConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,6 @@ enum TunStack { gvisor, system, mixed }
|
||||
|
||||
enum AccessControlMode { acceptSelected, rejectSelected }
|
||||
|
||||
enum AccessSortType { none, name, time }
|
||||
|
||||
enum ProfileType { file, url }
|
||||
|
||||
enum ResultType { success, error }
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
@@ -7,9 +6,15 @@ import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
extension AccessControlExtension on AccessControl {
|
||||
List<String> get currentList => switch (mode) {
|
||||
AccessControlMode.acceptSelected => acceptList,
|
||||
AccessControlMode.rejectSelected => rejectList,
|
||||
};
|
||||
}
|
||||
|
||||
class AccessFragment extends StatefulWidget {
|
||||
const AccessFragment({super.key});
|
||||
|
||||
@@ -18,100 +23,319 @@ class AccessFragment extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AccessFragmentState extends State<AccessFragment> {
|
||||
List<String> acceptList = [];
|
||||
List<String> rejectList = [];
|
||||
final packagesListenable = ValueNotifier<List<Package>>([]);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateInitList();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final appState = globalState.appController.appState;
|
||||
if (appState.packages.isEmpty) {
|
||||
Future.delayed(const Duration(milliseconds: 300), () async {
|
||||
appState.packages = await app?.getPackages() ?? [];
|
||||
});
|
||||
}
|
||||
Future.delayed(const Duration(milliseconds: 300), () async {
|
||||
packagesListenable.value = await app?.getPackages() ?? [];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_updateInitList() {
|
||||
final accessControl = globalState.appController.config.accessControl;
|
||||
acceptList = accessControl.acceptList;
|
||||
rejectList = accessControl.rejectList;
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
packagesListenable.dispose();
|
||||
}
|
||||
|
||||
Widget _buildSearchButton() {
|
||||
Widget _buildAppProxyModePopup() {
|
||||
final items = [
|
||||
CommonPopupMenuItem(
|
||||
action: AccessControlMode.rejectSelected,
|
||||
label: appLocalizations.blacklistMode,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: AccessControlMode.acceptSelected,
|
||||
label: appLocalizations.whitelistMode,
|
||||
),
|
||||
];
|
||||
return Selector<Config, AccessControlMode>(
|
||||
selector: (_, config) => config.accessControl.mode,
|
||||
builder: (context, mode, __) {
|
||||
return CommonPopupMenu<AccessControlMode>.radio(
|
||||
icon: Icon(
|
||||
Icons.mode_standby,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
items: items,
|
||||
onSelected: (value) {
|
||||
final config = context.read<Config>();
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
mode: value,
|
||||
);
|
||||
},
|
||||
selectedValue: mode,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterSystemAppButton() {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.accessControl.isFilterSystemApp,
|
||||
builder: (context, isFilterSystemApp, __) {
|
||||
final tooltip = isFilterSystemApp
|
||||
? appLocalizations.cancelFilterSystemApp
|
||||
: appLocalizations.filterSystemApp;
|
||||
return IconButton(
|
||||
tooltip: tooltip,
|
||||
onPressed: () {
|
||||
final config = context.read<Config>();
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
isFilterSystemApp: !isFilterSystemApp,
|
||||
);
|
||||
},
|
||||
icon: isFilterSystemApp
|
||||
? const Icon(Icons.filter_list_off)
|
||||
: const Icon(Icons.filter_list),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSearchButton(List<Package> packages) {
|
||||
return IconButton(
|
||||
tooltip: appLocalizations.search,
|
||||
onPressed: () {
|
||||
showSearch(
|
||||
context: context,
|
||||
delegate: AccessControlSearchDelegate(
|
||||
acceptList: acceptList,
|
||||
rejectList: rejectList,
|
||||
packages: packages,
|
||||
),
|
||||
).then((_) => setState(() {
|
||||
_updateInitList();
|
||||
}));
|
||||
).then((_) => {setState(() {})});
|
||||
},
|
||||
icon: const Icon(Icons.search),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSelectedAllButton({
|
||||
required bool isSelectedAll,
|
||||
required List<String> allValueList,
|
||||
}) {
|
||||
final tooltip = isSelectedAll
|
||||
? appLocalizations.cancelSelectAll
|
||||
: appLocalizations.selectAll;
|
||||
return IconButton(
|
||||
tooltip: tooltip,
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
final isAccept =
|
||||
config.accessControl.mode == AccessControlMode.acceptSelected;
|
||||
if (isSelectedAll) {
|
||||
config.accessControl = switch (isAccept) {
|
||||
true => config.accessControl.copyWith(
|
||||
acceptList: [],
|
||||
),
|
||||
false => config.accessControl.copyWith(
|
||||
rejectList: [],
|
||||
),
|
||||
};
|
||||
} else {
|
||||
config.accessControl = switch (isAccept) {
|
||||
true => config.accessControl.copyWith(
|
||||
acceptList: allValueList,
|
||||
),
|
||||
false => config.accessControl.copyWith(
|
||||
rejectList: allValueList,
|
||||
),
|
||||
};
|
||||
}
|
||||
},
|
||||
icon: isSelectedAll
|
||||
? const Icon(Icons.deselect)
|
||||
: const Icon(Icons.select_all),
|
||||
);
|
||||
}
|
||||
// Widget _buildSelectedAllButton({
|
||||
// required bool isAccessControl,
|
||||
// required bool isSelectedAll,
|
||||
// required List<String> allValueList,
|
||||
// }) {
|
||||
// final tooltip = isSelectedAll
|
||||
// ? appLocalizations.cancelSelectAll
|
||||
// : appLocalizations.selectAll;
|
||||
// return AbsorbPointer(
|
||||
// absorbing: !isAccessControl,
|
||||
// child: FloatingActionButton(
|
||||
// tooltip: tooltip,
|
||||
// onPressed: () {
|
||||
// final config = globalState.appController.config;
|
||||
// final isAccept =
|
||||
// config.accessControl.mode == AccessControlMode.acceptSelected;
|
||||
//
|
||||
// if (isSelectedAll) {
|
||||
// config.accessControl = switch (isAccept) {
|
||||
// true => config.accessControl.copyWith(
|
||||
// acceptList: [],
|
||||
// ),
|
||||
// false => config.accessControl.copyWith(
|
||||
// rejectList: [],
|
||||
// ),
|
||||
// };
|
||||
// } else {
|
||||
// config.accessControl = switch (isAccept) {
|
||||
// true => config.accessControl.copyWith(
|
||||
// acceptList: allValueList,
|
||||
// ),
|
||||
// false => config.accessControl.copyWith(
|
||||
// rejectList: allValueList,
|
||||
// ),
|
||||
// };
|
||||
// }
|
||||
// },
|
||||
// child: isSelectedAll
|
||||
// ? const Icon(Icons.deselect)
|
||||
// : const Icon(Icons.select_all),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
Widget _buildSettingButton() {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
showSheet(
|
||||
title: appLocalizations.proxiesSetting,
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return AccessControlWidget(
|
||||
context: context,
|
||||
Widget _buildPackageList() {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: packagesListenable,
|
||||
builder: (_, packages, ___) {
|
||||
final accessControl = globalState.appController.config.accessControl;
|
||||
final acceptList = accessControl.acceptList;
|
||||
final rejectList = accessControl.rejectList;
|
||||
final acceptPackages = packages.sorted((a, b) {
|
||||
final isSelectA = acceptList.contains(a.packageName);
|
||||
final isSelectB = acceptList.contains(b.packageName);
|
||||
if (isSelectA && isSelectB) return 0;
|
||||
if (isSelectA) return -1;
|
||||
if (isSelectB) return 1;
|
||||
return 0;
|
||||
});
|
||||
final rejectPackages = packages.sorted((a, b) {
|
||||
final isSelectA = rejectList.contains(a.packageName);
|
||||
final isSelectB = rejectList.contains(b.packageName);
|
||||
if (isSelectA && isSelectB) return 0;
|
||||
if (isSelectA) return -1;
|
||||
if (isSelectB) return 1;
|
||||
return 0;
|
||||
});
|
||||
return Selector<Config, PackageListSelectorState>(
|
||||
selector: (_, config) => PackageListSelectorState(
|
||||
accessControl: config.accessControl,
|
||||
isAccessControl: config.isAccessControl,
|
||||
),
|
||||
builder: (context, state, __) {
|
||||
final accessControl = state.accessControl;
|
||||
final isAccessControl = state.isAccessControl;
|
||||
final isFilterSystemApp = accessControl.isFilterSystemApp;
|
||||
final accessControlMode = accessControl.mode;
|
||||
final packages =
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? acceptPackages
|
||||
: rejectPackages;
|
||||
final currentList = accessControl.currentList;
|
||||
final currentPackages = isFilterSystemApp
|
||||
? packages
|
||||
.where((element) => element.isSystem == false)
|
||||
.toList()
|
||||
: packages;
|
||||
final packageNameList =
|
||||
currentPackages.map((e) => e.packageName).toList();
|
||||
final valueList = currentList.intersection(packageNameList);
|
||||
final describe =
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? appLocalizations.accessControlAllowDesc
|
||||
: appLocalizations.accessControlNotAllowDesc;
|
||||
return DisabledMask(
|
||||
status: !isAccessControl,
|
||||
child: Column(
|
||||
children: [
|
||||
AbsorbPointer(
|
||||
absorbing: !isAccessControl,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
left: 16,
|
||||
right: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
appLocalizations.selected,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Flexible(
|
||||
child: SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
"${valueList.length}",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(describe),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Flexible(
|
||||
child: _buildSearchButton(currentPackages)),
|
||||
Flexible(child: _buildFilterSystemAppButton()),
|
||||
Flexible(child: _buildAppProxyModePopup()),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: FadeBox(
|
||||
key: const Key("fade_box"),
|
||||
child: currentPackages.isEmpty
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: currentPackages.length,
|
||||
itemBuilder: (_, index) {
|
||||
final package = currentPackages[index];
|
||||
return PackageListItem(
|
||||
key: Key(package.packageName),
|
||||
package: package,
|
||||
value:
|
||||
valueList.contains(package.packageName),
|
||||
isActive: isAccessControl,
|
||||
onChanged: (value) {
|
||||
if (value == true) {
|
||||
valueList.add(package.packageName);
|
||||
} else {
|
||||
valueList.remove(package.packageName);
|
||||
}
|
||||
final config =
|
||||
globalState.appController.config;
|
||||
if (accessControlMode ==
|
||||
AccessControlMode.acceptSelected) {
|
||||
config.accessControl =
|
||||
config.accessControl.copyWith(
|
||||
acceptList: valueList,
|
||||
);
|
||||
} else {
|
||||
config.accessControl =
|
||||
config.accessControl.copyWith(
|
||||
rejectList: valueList,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.tune),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -148,170 +372,7 @@ class _AccessFragmentState extends State<AccessFragment> {
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Selector<AppState, List<Package>>(
|
||||
selector: (_, appState) => appState.packages,
|
||||
builder: (_, packages, ___) {
|
||||
return Selector2<AppState, Config, PackageListSelectorState>(
|
||||
selector: (_, appState, config) => PackageListSelectorState(
|
||||
accessControl: config.accessControl,
|
||||
isAccessControl: config.isAccessControl,
|
||||
packages: appState.packages,
|
||||
),
|
||||
builder: (context, state, __) {
|
||||
final accessControl = state.accessControl;
|
||||
final isAccessControl = state.isAccessControl;
|
||||
final accessControlMode = accessControl.mode;
|
||||
final packages = state.getList(
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? acceptList
|
||||
: rejectList,
|
||||
);
|
||||
final currentList = accessControl.currentList;
|
||||
final packageNameList =
|
||||
packages.map((e) => e.packageName).toList();
|
||||
final valueList = currentList.intersection(packageNameList);
|
||||
final describe =
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? appLocalizations.accessControlAllowDesc
|
||||
: appLocalizations.accessControlNotAllowDesc;
|
||||
return DisabledMask(
|
||||
status: !isAccessControl,
|
||||
child: Column(
|
||||
children: [
|
||||
AbsorbPointer(
|
||||
absorbing: !isAccessControl,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
left: 16,
|
||||
right: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
appLocalizations.selected,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Flexible(
|
||||
child: SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
"${valueList.length}",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(describe),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Flexible(
|
||||
child: _buildSearchButton(),
|
||||
),
|
||||
Flexible(
|
||||
child: _buildSelectedAllButton(
|
||||
isSelectedAll: valueList.length ==
|
||||
packageNameList.length,
|
||||
allValueList: packageNameList,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: _buildSettingButton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: packages.isEmpty
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: packages.length,
|
||||
itemBuilder: (_, index) {
|
||||
final package = packages[index];
|
||||
return PackageListItem(
|
||||
key: Key(package.packageName),
|
||||
package: package,
|
||||
value:
|
||||
valueList.contains(package.packageName),
|
||||
isActive: isAccessControl,
|
||||
onChanged: (value) {
|
||||
if (value == true) {
|
||||
valueList.add(package.packageName);
|
||||
} else {
|
||||
valueList.remove(package.packageName);
|
||||
}
|
||||
final config =
|
||||
globalState.appController.config;
|
||||
if (accessControlMode ==
|
||||
AccessControlMode.acceptSelected) {
|
||||
config.accessControl =
|
||||
config.accessControl.copyWith(
|
||||
acceptList: valueList,
|
||||
);
|
||||
} else {
|
||||
config.accessControl =
|
||||
config.accessControl.copyWith(
|
||||
rejectList: valueList,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
child: _buildPackageList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -378,14 +439,23 @@ class PackageListItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
class AccessControlSearchDelegate extends SearchDelegate {
|
||||
List<String> acceptList = [];
|
||||
List<String> rejectList = [];
|
||||
final List<Package> packages;
|
||||
|
||||
AccessControlSearchDelegate({
|
||||
required this.acceptList,
|
||||
required this.rejectList,
|
||||
required this.packages,
|
||||
});
|
||||
|
||||
List<Package> get _results {
|
||||
final lowQuery = query.toLowerCase();
|
||||
return packages
|
||||
.where(
|
||||
(package) =>
|
||||
package.label.toLowerCase().contains(lowQuery) ||
|
||||
package.packageName.contains(lowQuery),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Widget>? buildActions(BuildContext context) {
|
||||
return [
|
||||
@@ -415,39 +485,26 @@ class AccessControlSearchDelegate extends SearchDelegate {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _packageList() {
|
||||
final lowQuery = query.toLowerCase();
|
||||
return Selector2<AppState, Config, PackageListSelectorState>(
|
||||
selector: (_, appState, config) => PackageListSelectorState(
|
||||
packages: appState.packages,
|
||||
Widget _packageList(List<Package> packages) {
|
||||
return Selector<Config, PackageListSelectorState>(
|
||||
selector: (_, config) => PackageListSelectorState(
|
||||
accessControl: config.accessControl,
|
||||
isAccessControl: config.isAccessControl,
|
||||
),
|
||||
builder: (context, state, __) {
|
||||
final accessControl = state.accessControl;
|
||||
final accessControlMode = accessControl.mode;
|
||||
final packages = state.getList(
|
||||
accessControlMode == AccessControlMode.acceptSelected
|
||||
? acceptList
|
||||
: rejectList,
|
||||
);
|
||||
final queryPackages = packages
|
||||
.where(
|
||||
(package) =>
|
||||
package.label.toLowerCase().contains(lowQuery) ||
|
||||
package.packageName.contains(lowQuery),
|
||||
)
|
||||
.toList();
|
||||
final isAccessControl = state.isAccessControl;
|
||||
final accessControlMode = accessControl.mode;
|
||||
final currentList = accessControl.currentList;
|
||||
final packageNameList = packages.map((e) => e.packageName).toList();
|
||||
final packageNameList =
|
||||
this.packages.map((e) => e.packageName).toList();
|
||||
final valueList = currentList.intersection(packageNameList);
|
||||
return DisabledMask(
|
||||
status: !isAccessControl,
|
||||
child: ListView.builder(
|
||||
itemCount: queryPackages.length,
|
||||
itemCount: packages.length,
|
||||
itemBuilder: (_, index) {
|
||||
final package = queryPackages[index];
|
||||
final package = packages[index];
|
||||
return PackageListItem(
|
||||
key: Key(package.packageName),
|
||||
package: package,
|
||||
@@ -485,268 +542,6 @@ class AccessControlSearchDelegate extends SearchDelegate {
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return _packageList();
|
||||
}
|
||||
}
|
||||
|
||||
class AccessControlWidget extends StatelessWidget {
|
||||
final BuildContext context;
|
||||
|
||||
const AccessControlWidget({
|
||||
super.key,
|
||||
required this.context,
|
||||
});
|
||||
|
||||
IconData _getIconWithAccessControlMode(AccessControlMode mode) {
|
||||
return switch (mode) {
|
||||
AccessControlMode.acceptSelected => Icons.adjust_outlined,
|
||||
AccessControlMode.rejectSelected => Icons.block_outlined,
|
||||
};
|
||||
}
|
||||
|
||||
String _getTextWithAccessControlMode(AccessControlMode mode) {
|
||||
return switch (mode) {
|
||||
AccessControlMode.acceptSelected => appLocalizations.whitelistMode,
|
||||
AccessControlMode.rejectSelected => appLocalizations.blacklistMode,
|
||||
};
|
||||
}
|
||||
|
||||
String _getTextWithAccessSortType(AccessSortType type) {
|
||||
return switch (type) {
|
||||
AccessSortType.none => appLocalizations.defaultText,
|
||||
AccessSortType.name => appLocalizations.name,
|
||||
AccessSortType.time => appLocalizations.time,
|
||||
};
|
||||
}
|
||||
|
||||
IconData _getIconWithProxiesSortType(AccessSortType type) {
|
||||
return switch (type) {
|
||||
AccessSortType.none => Icons.sort,
|
||||
AccessSortType.name => Icons.sort_by_alpha,
|
||||
AccessSortType.time => Icons.timeline,
|
||||
};
|
||||
}
|
||||
|
||||
String _getTextWithIsFilterSystemApp(bool isFilterSystemApp) {
|
||||
return switch (isFilterSystemApp) {
|
||||
true => appLocalizations.onlyOtherApps,
|
||||
false => appLocalizations.allApps,
|
||||
};
|
||||
}
|
||||
|
||||
List<Widget> _buildModeSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.mode,
|
||||
items: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Selector<Config, AccessControlMode>(
|
||||
selector: (_, config) => config.accessControl.mode,
|
||||
builder: (_, accessControlMode, __) {
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
children: [
|
||||
for (final item in AccessControlMode.values)
|
||||
SettingInfoCard(
|
||||
Info(
|
||||
label: _getTextWithAccessControlMode(item),
|
||||
iconData: _getIconWithAccessControlMode(item),
|
||||
),
|
||||
isSelected: accessControlMode == item,
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
mode: item,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildSortSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.sort,
|
||||
items: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Selector<Config, AccessSortType>(
|
||||
selector: (_, config) => config.accessControl.sort,
|
||||
builder: (_, accessSortType, __) {
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
children: [
|
||||
for (final item in AccessSortType.values)
|
||||
SettingInfoCard(
|
||||
Info(
|
||||
label: _getTextWithAccessSortType(item),
|
||||
iconData: _getIconWithProxiesSortType(item),
|
||||
),
|
||||
isSelected: accessSortType == item,
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
sort: item,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildSourceSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.source,
|
||||
items: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Selector<Config, bool>(
|
||||
selector: (_, config) => config.accessControl.isFilterSystemApp,
|
||||
builder: (_, isFilterSystemApp, __) {
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
children: [
|
||||
for (final item in [false, true])
|
||||
SettingTextCard(
|
||||
_getTextWithIsFilterSystemApp(item),
|
||||
isSelected: isFilterSystemApp == item,
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
config.accessControl = config.accessControl.copyWith(
|
||||
isFilterSystemApp: item,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_intelligentSelected() async {
|
||||
final appState = globalState.appController.appState;
|
||||
final config = globalState.appController.config;
|
||||
final accessControl = config.accessControl;
|
||||
final packageNames = appState.packages
|
||||
.where(
|
||||
(item) =>
|
||||
accessControl.isFilterSystemApp ? item.isSystem == false : true,
|
||||
)
|
||||
.map((item) => item.packageName);
|
||||
Navigator.of(context).pop();
|
||||
final commonScaffoldState = context.commonScaffoldState;
|
||||
if (commonScaffoldState?.mounted != true) return;
|
||||
final selectedPackageNames =
|
||||
(await commonScaffoldState?.loadingRun<List<String>>(
|
||||
() async {
|
||||
return await app?.getChinaPackageNames() ?? [];
|
||||
},
|
||||
))
|
||||
?.toSet() ??
|
||||
{};
|
||||
final acceptList = packageNames
|
||||
.where((item) => !selectedPackageNames.contains(item))
|
||||
.toList();
|
||||
final rejectList = packageNames
|
||||
.where((item) => selectedPackageNames.contains(item))
|
||||
.toList();
|
||||
config.accessControl = accessControl.copyWith(
|
||||
acceptList: acceptList,
|
||||
rejectList: rejectList,
|
||||
);
|
||||
}
|
||||
|
||||
_copyToClipboard() async {
|
||||
await globalState.safeRun(() {
|
||||
final data = globalState.appController.config.accessControl.toJson();
|
||||
Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: json.encode(data),
|
||||
),
|
||||
);
|
||||
});
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
_pasteToClipboard() async {
|
||||
await globalState.safeRun(() async {
|
||||
final config = globalState.appController.config;
|
||||
final data = await Clipboard.getData('text/plain');
|
||||
final text = data?.text;
|
||||
if (text == null) return;
|
||||
config.accessControl = AccessControl.fromJson(
|
||||
json.decode(text),
|
||||
);
|
||||
});
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
List<Widget> _buildActionSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.action,
|
||||
items: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
spacing: 16,
|
||||
children: [
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.auto_awesome),
|
||||
label: appLocalizations.intelligentSelected,
|
||||
onPressed: _intelligentSelected,
|
||||
),
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.paste),
|
||||
label: appLocalizations.clipboardImport,
|
||||
onPressed: _pasteToClipboard,
|
||||
),
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.content_copy),
|
||||
label: appLocalizations.clipboardExport,
|
||||
onPressed: _copyToClipboard,
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
..._buildModeSetting(),
|
||||
..._buildSortSetting(),
|
||||
..._buildSourceSetting(),
|
||||
..._buildActionSetting(),
|
||||
],
|
||||
),
|
||||
);
|
||||
return _packageList(_results);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/common/dav_client.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
@@ -12,9 +10,16 @@ import 'package:fl_clash/widgets/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class BackupAndRecovery extends StatelessWidget {
|
||||
class BackupAndRecovery extends StatefulWidget {
|
||||
const BackupAndRecovery({super.key});
|
||||
|
||||
@override
|
||||
State<BackupAndRecovery> createState() => _BackupAndRecoveryState();
|
||||
}
|
||||
|
||||
class _BackupAndRecoveryState extends State<BackupAndRecovery> {
|
||||
DAVClient? _client;
|
||||
|
||||
_showAddWebDAV(DAV? dav) async {
|
||||
await globalState.showCommonDialog<String>(
|
||||
child: WebDAVFormDialog(
|
||||
@@ -23,15 +28,11 @@ class BackupAndRecovery extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_backupOnWebDAV(BuildContext context, DAVClient client) async {
|
||||
_backup() async {
|
||||
final commonScaffoldState = context.commonScaffoldState;
|
||||
final res = await commonScaffoldState?.loadingRun<bool>(
|
||||
() async {
|
||||
final backupData = await globalState.appController.backupData();
|
||||
return await client.backup(Uint8List.fromList(backupData));
|
||||
},
|
||||
title: appLocalizations.backup,
|
||||
);
|
||||
final res = await commonScaffoldState?.loadingRun<bool>(() async {
|
||||
return await _client?.backup();
|
||||
});
|
||||
if (res != true) return;
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.backup,
|
||||
@@ -39,20 +40,11 @@ class BackupAndRecovery extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_recoveryOnWebDAV(
|
||||
BuildContext context,
|
||||
DAVClient client,
|
||||
RecoveryOption recoveryOption,
|
||||
) async {
|
||||
_recovery(RecoveryOption recoveryOption) async {
|
||||
final commonScaffoldState = context.commonScaffoldState;
|
||||
final res = await commonScaffoldState?.loadingRun<bool>(
|
||||
() async {
|
||||
final data = await client.recovery();
|
||||
await globalState.appController.recoveryData(data, recoveryOption);
|
||||
return true;
|
||||
},
|
||||
title: appLocalizations.recovery,
|
||||
);
|
||||
final res = await commonScaffoldState?.loadingRun<bool>(() async {
|
||||
return await _client?.recovery(recoveryOption: recoveryOption);
|
||||
});
|
||||
if (res != true) return;
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.recovery,
|
||||
@@ -60,66 +52,12 @@ class BackupAndRecovery extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_handleRecoveryOnWebDAV(BuildContext context, DAVClient client) async {
|
||||
_handleRecovery() async {
|
||||
final recoveryOption = await globalState.showCommonDialog<RecoveryOption>(
|
||||
child: const RecoveryOptionsDialog(),
|
||||
);
|
||||
if (recoveryOption == null || !context.mounted) return;
|
||||
_recoveryOnWebDAV(context, client, recoveryOption);
|
||||
}
|
||||
|
||||
_backupOnLocal(BuildContext context) async {
|
||||
final commonScaffoldState = context.commonScaffoldState;
|
||||
final res = await commonScaffoldState?.loadingRun<bool>(
|
||||
() async {
|
||||
final backupData = await globalState.appController.backupData();
|
||||
final value = await picker.saveFile(
|
||||
other.getBackupFileName(),
|
||||
Uint8List.fromList(backupData),
|
||||
);
|
||||
if(value == null) return false;
|
||||
return true;
|
||||
},
|
||||
title: appLocalizations.backup,
|
||||
);
|
||||
if (res != true) return;
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.backup,
|
||||
message: TextSpan(text: appLocalizations.backupSuccess),
|
||||
);
|
||||
}
|
||||
|
||||
_recoveryOnLocal(
|
||||
BuildContext context,
|
||||
RecoveryOption recoveryOption,
|
||||
) async {
|
||||
final file = await picker.pickerFile();
|
||||
final data = file?.bytes;
|
||||
if (data == null || !context.mounted) return;
|
||||
final commonScaffoldState = context.commonScaffoldState;
|
||||
final res = await commonScaffoldState?.loadingRun<bool>(
|
||||
() async {
|
||||
await globalState.appController.recoveryData(
|
||||
List<int>.from(data),
|
||||
recoveryOption,
|
||||
);
|
||||
return true;
|
||||
},
|
||||
title: appLocalizations.recovery,
|
||||
);
|
||||
if (res != true) return;
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.recovery,
|
||||
message: TextSpan(text: appLocalizations.recoverySuccess),
|
||||
);
|
||||
}
|
||||
|
||||
_handleRecoveryOnLocal(BuildContext context) async {
|
||||
final recoveryOption = await globalState.showCommonDialog<RecoveryOption>(
|
||||
child: const RecoveryOptionsDialog(),
|
||||
);
|
||||
if (recoveryOption == null || !context.mounted) return;
|
||||
_recoveryOnLocal(context, recoveryOption);
|
||||
if (recoveryOption == null) return;
|
||||
_recovery(recoveryOption);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -127,11 +65,12 @@ class BackupAndRecovery extends StatelessWidget {
|
||||
return Selector<Config, DAV?>(
|
||||
selector: (_, config) => config.dav,
|
||||
builder: (_, dav, __) {
|
||||
final client = dav != null ? DAVClient(dav) : null;
|
||||
return ListView(
|
||||
children: [
|
||||
ListHeader(title: appLocalizations.remote),
|
||||
if (dav == null)
|
||||
if (dav == null) {
|
||||
return ListView(
|
||||
children: [
|
||||
ListHeader(
|
||||
title: appLocalizations.account,
|
||||
),
|
||||
ListItem(
|
||||
leading: const Icon(Icons.account_box),
|
||||
title: Text(appLocalizations.noInfo),
|
||||
@@ -144,95 +83,95 @@ class BackupAndRecovery extends StatelessWidget {
|
||||
appLocalizations.bind,
|
||||
),
|
||||
),
|
||||
)
|
||||
else ...[
|
||||
ListItem(
|
||||
leading: const Icon(Icons.account_box),
|
||||
title: TooltipText(
|
||||
text: Text(
|
||||
dav.user,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(appLocalizations.connectivity),
|
||||
FutureBuilder<bool>(
|
||||
future: client!.pingCompleter.future,
|
||||
builder: (_, snapshot) {
|
||||
return Center(
|
||||
child: FadeBox(
|
||||
child: snapshot.connectionState ==
|
||||
ConnectionState.waiting
|
||||
? const SizedBox(
|
||||
width: 12,
|
||||
height: 12,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 1,
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: snapshot.data == true
|
||||
? Colors.green
|
||||
: Colors.red,
|
||||
),
|
||||
width: 12,
|
||||
height: 12,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
trailing: FilledButton.tonal(
|
||||
onPressed: () {
|
||||
_showAddWebDAV(dav);
|
||||
},
|
||||
child: Text(
|
||||
appLocalizations.edit,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_backupOnWebDAV(context, client);
|
||||
},
|
||||
title: Text(appLocalizations.backup),
|
||||
subtitle: Text(appLocalizations.remoteBackupDesc),
|
||||
),
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_handleRecoveryOnWebDAV(context, client);
|
||||
},
|
||||
title: Text(appLocalizations.recovery),
|
||||
subtitle: Text(appLocalizations.remoteRecoveryDesc),
|
||||
),
|
||||
],
|
||||
ListHeader(title: appLocalizations.local),
|
||||
);
|
||||
}
|
||||
_client = DAVClient(dav);
|
||||
final pingFuture = _client!.pingCompleter.future;
|
||||
return ListView(
|
||||
children: [
|
||||
ListHeader(title: appLocalizations.account),
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_backupOnLocal(context);
|
||||
},
|
||||
title: Text(appLocalizations.backup),
|
||||
subtitle: Text(appLocalizations.localBackupDesc),
|
||||
leading: const Icon(Icons.account_box),
|
||||
title: TooltipText(
|
||||
text: Text(
|
||||
dav.user,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(appLocalizations.connectivity),
|
||||
FutureBuilder<bool>(
|
||||
future: pingFuture,
|
||||
builder: (_, snapshot) {
|
||||
return Center(
|
||||
child: FadeBox(
|
||||
key: const Key("fade_box_1"),
|
||||
child: snapshot.connectionState == ConnectionState.waiting
|
||||
? const SizedBox(
|
||||
width: 12,
|
||||
height: 12,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 1,
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: snapshot.data == true
|
||||
? Colors.green
|
||||
: Colors.red,
|
||||
),
|
||||
width: 12,
|
||||
height: 12,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
trailing: FilledButton.tonal(
|
||||
onPressed: () {
|
||||
_showAddWebDAV(dav);
|
||||
},
|
||||
child: Text(
|
||||
appLocalizations.edit,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_handleRecoveryOnLocal(context);
|
||||
FutureBuilder<bool>(
|
||||
future: pingFuture,
|
||||
builder: (_, snapshot) {
|
||||
return FadeBox(
|
||||
key: const Key("fade_box_2"),
|
||||
child: snapshot.data == true
|
||||
? Column(
|
||||
children: [
|
||||
ListHeader(
|
||||
title: appLocalizations.backupAndRecovery),
|
||||
ListItem(
|
||||
onTap: _backup,
|
||||
title: Text(appLocalizations.backup),
|
||||
subtitle: Text(appLocalizations.backupDesc),
|
||||
),
|
||||
ListItem(
|
||||
onTap: _handleRecovery,
|
||||
title: Text(appLocalizations.recovery),
|
||||
subtitle: Text(appLocalizations.recoveryDesc),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Container(),
|
||||
);
|
||||
},
|
||||
title: Text(appLocalizations.recovery),
|
||||
subtitle: Text(appLocalizations.localRecoveryDesc),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -241,50 +180,6 @@ class BackupAndRecovery extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class RecoveryOptionsDialog extends StatefulWidget {
|
||||
const RecoveryOptionsDialog({super.key});
|
||||
|
||||
@override
|
||||
State<RecoveryOptionsDialog> createState() => _RecoveryOptionsDialogState();
|
||||
}
|
||||
|
||||
class _RecoveryOptionsDialogState extends State<RecoveryOptionsDialog> {
|
||||
_handleOnTab(RecoveryOption? value) {
|
||||
if (value == null) return;
|
||||
Navigator.of(context).pop(value);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(appLocalizations.recovery),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 16,
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 250,
|
||||
child: Wrap(
|
||||
children: [
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_handleOnTab(RecoveryOption.onlyProfiles);
|
||||
},
|
||||
title: Text(appLocalizations.recoveryProfiles),
|
||||
),
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_handleOnTab(RecoveryOption.all);
|
||||
},
|
||||
title: Text(appLocalizations.recoveryAll),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WebDAVFormDialog extends StatefulWidget {
|
||||
final DAV? dav;
|
||||
|
||||
@@ -343,7 +238,7 @@ class _WebDAVFormDialogState extends State<WebDAVFormDialog> {
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: uriController,
|
||||
maxLines: 5,
|
||||
maxLines: 2,
|
||||
minLines: 1,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.link),
|
||||
@@ -418,3 +313,47 @@ class _WebDAVFormDialogState extends State<WebDAVFormDialog> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RecoveryOptionsDialog extends StatefulWidget {
|
||||
const RecoveryOptionsDialog({super.key});
|
||||
|
||||
@override
|
||||
State<RecoveryOptionsDialog> createState() => _RecoveryOptionsDialogState();
|
||||
}
|
||||
|
||||
class _RecoveryOptionsDialogState extends State<RecoveryOptionsDialog> {
|
||||
_handleOnTab(RecoveryOption? value) {
|
||||
if (value == null) return;
|
||||
Navigator.of(context).pop(value);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(appLocalizations.recovery),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 16,
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 250,
|
||||
child: Wrap(
|
||||
children: [
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_handleOnTab(RecoveryOption.onlyProfiles);
|
||||
},
|
||||
title: Text(appLocalizations.recoveryProfiles),
|
||||
),
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_handleOnTab(RecoveryOption.all);
|
||||
},
|
||||
title: Text(appLocalizations.recoveryAll),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,40 +137,11 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
}
|
||||
}
|
||||
|
||||
_updateKeepAliveInterval(int keepAliveInterval) async {
|
||||
final newKeepAliveIntervalString =
|
||||
await globalState.showCommonDialog<String>(
|
||||
child: KeepAliveIntervalFormDialog(
|
||||
keepAliveInterval: keepAliveInterval,
|
||||
),
|
||||
);
|
||||
if (newKeepAliveIntervalString != null &&
|
||||
newKeepAliveIntervalString != "$keepAliveInterval" &&
|
||||
mounted) {
|
||||
try {
|
||||
final newKeepAliveInterval = int.parse(newKeepAliveIntervalString);
|
||||
if (newKeepAliveInterval <= 0) {
|
||||
throw "Invalid keepAliveInterval";
|
||||
}
|
||||
globalState.appController.clashConfig.keepAliveInterval =
|
||||
newKeepAliveInterval;
|
||||
globalState.appController.updateClashConfigDebounce();
|
||||
} catch (e) {
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.testUrl,
|
||||
message: TextSpan(
|
||||
text: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildAppSection() {
|
||||
return generateSection(
|
||||
title: appLocalizations.app,
|
||||
items: [
|
||||
if (Platform.isAndroid) ...[
|
||||
if (Platform.isAndroid)...[
|
||||
Selector<Config, bool>(
|
||||
selector: (_, config) => config.allowBypass,
|
||||
builder: (_, allowBypass, __) {
|
||||
@@ -292,19 +263,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<ClashConfig, int>(
|
||||
selector: (_, config) => config.keepAliveInterval,
|
||||
builder: (_, value, __) {
|
||||
return ListItem(
|
||||
leading: const Icon(Icons.timer_outlined),
|
||||
title: Text(appLocalizations.keepAliveIntervalDesc),
|
||||
subtitle: Text("$value ${appLocalizations.seconds}"),
|
||||
onTap: () {
|
||||
_updateKeepAliveInterval(value);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
Selector<Config, String>(
|
||||
selector: (_, config) => config.testUrl,
|
||||
builder: (_, value, __) {
|
||||
@@ -631,64 +589,3 @@ class _TestUrlFormDialogState extends State<TestUrlFormDialog> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class KeepAliveIntervalFormDialog extends StatefulWidget {
|
||||
final int keepAliveInterval;
|
||||
|
||||
const KeepAliveIntervalFormDialog({
|
||||
super.key,
|
||||
required this.keepAliveInterval,
|
||||
});
|
||||
|
||||
@override
|
||||
State<KeepAliveIntervalFormDialog> createState() =>
|
||||
_KeepAliveIntervalFormDialogState();
|
||||
}
|
||||
|
||||
class _KeepAliveIntervalFormDialogState
|
||||
extends State<KeepAliveIntervalFormDialog> {
|
||||
late TextEditingController keepAliveIntervalController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
keepAliveIntervalController =
|
||||
TextEditingController(text: "${widget.keepAliveInterval}");
|
||||
}
|
||||
|
||||
_handleUpdate() async {
|
||||
final keepAliveInterval = keepAliveIntervalController.value.text;
|
||||
if (keepAliveInterval.isEmpty) return;
|
||||
Navigator.of(context).pop<String>(keepAliveInterval);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(appLocalizations.keepAliveIntervalDesc),
|
||||
content: SizedBox(
|
||||
width: 300,
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
TextField(
|
||||
maxLines: 1,
|
||||
minLines: 1,
|
||||
controller: keepAliveIntervalController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
suffixText: appLocalizations.seconds,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _handleUpdate,
|
||||
child: Text(appLocalizations.submit),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:country_flags/country_flags.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
|
||||
@@ -121,7 +121,7 @@ class _EditProfileState extends State<EditProfile> {
|
||||
}
|
||||
|
||||
_uploadProfileFile() async {
|
||||
final platformFile = await globalState.safeRun(picker.pickerFile);
|
||||
final platformFile = await globalState.safeRun(picker.pickerConfigFile);
|
||||
if (platformFile?.bytes == null) return;
|
||||
fileData = platformFile?.bytes;
|
||||
fileInfoNotifier.value = fileInfoNotifier.value?.copyWith(
|
||||
@@ -260,19 +260,23 @@ class _EditProfileState extends State<EditProfile> {
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
),
|
||||
child: ListView.separated(
|
||||
padding: kMaterialListPadding.copyWith(
|
||||
bottom: 72,
|
||||
),
|
||||
itemBuilder: (_, index) {
|
||||
return items[index];
|
||||
},
|
||||
separatorBuilder: (_, __) {
|
||||
return const SizedBox(
|
||||
height: 24,
|
||||
child: ScrollOverBuilder(
|
||||
builder: (isOver) {
|
||||
return ListView.separated(
|
||||
padding: kMaterialListPadding.copyWith(
|
||||
bottom: isOver ? 72 : 36,
|
||||
),
|
||||
itemBuilder: (_, index) {
|
||||
return items[index];
|
||||
},
|
||||
separatorBuilder: (_, __) {
|
||||
return const SizedBox(
|
||||
height: 24,
|
||||
);
|
||||
},
|
||||
itemCount: items.length,
|
||||
);
|
||||
},
|
||||
itemCount: items.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -27,6 +27,8 @@ class ProfilesFragment extends StatefulWidget {
|
||||
class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
Function? applyConfigDebounce;
|
||||
|
||||
List<GlobalObjectKey<_ProfileItemState>> profileItemKeys = [];
|
||||
|
||||
_handleShowAddExtendPage() {
|
||||
showExtendPage(
|
||||
globalState.navigatorKey.currentState!.context,
|
||||
@@ -49,37 +51,17 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
}
|
||||
|
||||
_updateProfiles() async {
|
||||
final appController = globalState.appController;
|
||||
final config = appController.config;
|
||||
final profiles = appController.config.profiles;
|
||||
final updateProfiles = profiles.map<Future>(
|
||||
(profile) async {
|
||||
config.setProfile(
|
||||
profile.copyWith(isUpdating: true),
|
||||
);
|
||||
try {
|
||||
await appController.updateProfile(profile);
|
||||
if (profile.id == appController.config.currentProfile?.id) {
|
||||
appController.applyProfile(isPrue: true);
|
||||
}
|
||||
} catch (_) {
|
||||
config.setProfile(
|
||||
profile.copyWith(
|
||||
isUpdating: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
final updateProfiles = profileItemKeys.map<Future>(
|
||||
(key) async => await key.currentState?.updateProfile(false));
|
||||
await Future.wait(updateProfiles);
|
||||
}
|
||||
|
||||
_initScaffoldState() {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) {
|
||||
if (!context.mounted) return;
|
||||
final commonScaffoldState =
|
||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
||||
if (!context.mounted) return;
|
||||
commonScaffoldState?.actions = [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
@@ -92,6 +74,19 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
);
|
||||
}
|
||||
|
||||
_changeProfile(String? id) async {
|
||||
final appController = globalState.appController;
|
||||
final config = appController.config;
|
||||
if (id == config.currentProfileId) return;
|
||||
config.currentProfileId = id;
|
||||
applyConfigDebounce ??= debounce<Function()>(() async {
|
||||
await appController.applyProfile();
|
||||
appController.appState.delayMap = {};
|
||||
appController.saveConfigPreferences();
|
||||
});
|
||||
applyConfigDebounce!();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FloatLayout(
|
||||
@@ -124,33 +119,40 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
label: appLocalizations.nullProfileDesc,
|
||||
);
|
||||
}
|
||||
profileItemKeys = state.profiles
|
||||
.map(
|
||||
(profile) => GlobalObjectKey<_ProfileItemState>(profile.id))
|
||||
.toList();
|
||||
final columns = _getColumns(state.viewMode);
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 16,
|
||||
bottom: 88,
|
||||
),
|
||||
child: Grid(
|
||||
mainAxisSpacing: 16,
|
||||
crossAxisSpacing: 16,
|
||||
crossAxisCount: columns,
|
||||
children: [
|
||||
for (int i = 0; i < state.profiles.length; i++)
|
||||
GridItem(
|
||||
child: ProfileItem(
|
||||
key: Key(state.profiles[i].id),
|
||||
profile: state.profiles[i],
|
||||
groupValue: state.currentProfileId,
|
||||
onChanged:
|
||||
globalState.appController.changeProfile,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ScrollOverBuilder(
|
||||
builder: (isOver) {
|
||||
return SingleChildScrollView(
|
||||
padding: EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 16,
|
||||
bottom: 16 + (isOver ? 72 : 0),
|
||||
),
|
||||
child: Grid(
|
||||
mainAxisSpacing: 16,
|
||||
crossAxisSpacing: 16,
|
||||
crossAxisCount: columns,
|
||||
children: [
|
||||
for (int i = 0; i < state.profiles.length; i++)
|
||||
GridItem(
|
||||
child: ProfileItem(
|
||||
key: profileItemKeys[i],
|
||||
profile: state.profiles[i],
|
||||
groupValue: state.currentProfileId,
|
||||
onChanged: _changeProfile,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -160,7 +162,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileItem extends StatelessWidget {
|
||||
class ProfileItem extends StatefulWidget {
|
||||
final Profile profile;
|
||||
final String? groupValue;
|
||||
final void Function(String? value) onChanged;
|
||||
@@ -172,15 +174,22 @@ class ProfileItem extends StatelessWidget {
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
_handleDeleteProfile(BuildContext context) async {
|
||||
@override
|
||||
State<ProfileItem> createState() => _ProfileItemState();
|
||||
}
|
||||
|
||||
class _ProfileItemState extends State<ProfileItem> {
|
||||
final isUpdating = ValueNotifier<bool>(false);
|
||||
|
||||
_handleDeleteProfile() async {
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.tip,
|
||||
message: TextSpan(
|
||||
text: appLocalizations.deleteProfileTip,
|
||||
),
|
||||
onTab: () async {
|
||||
await globalState.appController.deleteProfile(profile.id);
|
||||
if (context.mounted) {
|
||||
await globalState.appController.deleteProfile(widget.profile.id);
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
@@ -191,47 +200,41 @@ class ProfileItem extends StatelessWidget {
|
||||
await globalState.safeRun<void>(updateProfile);
|
||||
}
|
||||
|
||||
Future updateProfile() async {
|
||||
final appController = globalState.appController;
|
||||
final config = appController.config;
|
||||
if (profile.type == ProfileType.file) return;
|
||||
await globalState.safeRun(() async {
|
||||
try {
|
||||
config.setProfile(
|
||||
profile.copyWith(
|
||||
isUpdating: true,
|
||||
),
|
||||
);
|
||||
await appController.updateProfile(profile);
|
||||
if (profile.id == appController.config.currentProfile?.id) {
|
||||
appController.applyProfile(isPrue: true);
|
||||
}
|
||||
} catch (e) {
|
||||
config.setProfile(
|
||||
profile.copyWith(
|
||||
isUpdating: false,
|
||||
),
|
||||
);
|
||||
Future updateProfile([isSingle = true]) async {
|
||||
isUpdating.value = true;
|
||||
try {
|
||||
final appController = globalState.appController;
|
||||
await appController.updateProfile(widget.profile);
|
||||
if (widget.profile.id == appController.config.currentProfile?.id) {
|
||||
globalState.appController.applyProfile(isPrue: true);
|
||||
}
|
||||
} catch (e) {
|
||||
isUpdating.value = false;
|
||||
if (!isSingle) {
|
||||
return e.toString();
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
});
|
||||
}
|
||||
isUpdating.value = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
_handleShowEditExtendPage(BuildContext context) {
|
||||
_handleShowEditExtendPage() {
|
||||
showExtendPage(
|
||||
context,
|
||||
body: EditProfile(
|
||||
profile: profile,
|
||||
profile: widget.profile,
|
||||
context: context,
|
||||
),
|
||||
title: "${appLocalizations.edit}${appLocalizations.profile}",
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildUserInfo(BuildContext context, UserInfo userInfo) {
|
||||
List<Widget> _buildUserInfo(UserInfo userInfo) {
|
||||
final use = userInfo.upload + userInfo.download;
|
||||
final total = userInfo.total;
|
||||
if (total == 0) {
|
||||
if(total == 0){
|
||||
return [];
|
||||
}
|
||||
final useShow = TrafficValue(value: use).show;
|
||||
@@ -258,13 +261,13 @@ class ProfileItem extends StatelessWidget {
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildUrlProfileInfo(BuildContext context) {
|
||||
List<Widget> _buildUrlProfileInfo(Profile profile) {
|
||||
final userInfo = profile.userInfo;
|
||||
return [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (userInfo != null) ..._buildUserInfo(context, userInfo),
|
||||
if (userInfo != null) ..._buildUserInfo(userInfo),
|
||||
Text(
|
||||
profile.lastUpdateDate?.lastUpdateTimeDesc ?? "",
|
||||
style: context.textTheme.labelMedium?.toLight,
|
||||
@@ -272,7 +275,7 @@ class ProfileItem extends StatelessWidget {
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildFileProfileInfo(BuildContext context) {
|
||||
List<Widget> _buildFileProfileInfo(Profile profile) {
|
||||
return [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
@@ -284,8 +287,50 @@ class ProfileItem extends StatelessWidget {
|
||||
];
|
||||
}
|
||||
|
||||
_buildTitle(Profile profile) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
profile.label ?? profile.id,
|
||||
style: context.textTheme.titleMedium,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
...switch (profile.type) {
|
||||
ProfileType.file => _buildFileProfileInfo(
|
||||
profile,
|
||||
),
|
||||
ProfileType.url => _buildUrlProfileInfo(
|
||||
profile,
|
||||
),
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
isUpdating.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final profile = widget.profile;
|
||||
final groupValue = widget.groupValue;
|
||||
final onChanged = widget.onChanged;
|
||||
return CommonCard(
|
||||
isSelected: profile.id == groupValue,
|
||||
onPressed: () {
|
||||
@@ -298,75 +343,55 @@ class ProfileItem extends StatelessWidget {
|
||||
trailing: SizedBox(
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: FadeBox(
|
||||
child: profile.isUpdating
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: CommonPopupMenu<ProfileActions>(
|
||||
items: [
|
||||
CommonPopupMenuItem(
|
||||
action: ProfileActions.edit,
|
||||
label: appLocalizations.edit,
|
||||
iconData: Icons.edit,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: isUpdating,
|
||||
builder: (_, isUpdating, ___) {
|
||||
return FadeBox(
|
||||
child: isUpdating
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: CommonPopupMenu<ProfileActions>(
|
||||
items: [
|
||||
CommonPopupMenuItem(
|
||||
action: ProfileActions.edit,
|
||||
label: appLocalizations.edit,
|
||||
iconData: Icons.edit,
|
||||
),
|
||||
if (profile.type == ProfileType.url)
|
||||
CommonPopupMenuItem(
|
||||
action: ProfileActions.update,
|
||||
label: appLocalizations.update,
|
||||
iconData: Icons.sync,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: ProfileActions.delete,
|
||||
label: appLocalizations.delete,
|
||||
iconData: Icons.delete,
|
||||
),
|
||||
],
|
||||
onSelected: (ProfileActions? action) async {
|
||||
switch (action) {
|
||||
case ProfileActions.edit:
|
||||
_handleShowEditExtendPage();
|
||||
break;
|
||||
case ProfileActions.delete:
|
||||
_handleDeleteProfile();
|
||||
break;
|
||||
case ProfileActions.update:
|
||||
_handleUpdateProfile();
|
||||
break;
|
||||
case null:
|
||||
break;
|
||||
}
|
||||
},
|
||||
),
|
||||
if (profile.type == ProfileType.url)
|
||||
CommonPopupMenuItem(
|
||||
action: ProfileActions.update,
|
||||
label: appLocalizations.update,
|
||||
iconData: Icons.sync,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: ProfileActions.delete,
|
||||
label: appLocalizations.delete,
|
||||
iconData: Icons.delete,
|
||||
),
|
||||
],
|
||||
onSelected: (ProfileActions? action) async {
|
||||
switch (action) {
|
||||
case ProfileActions.edit:
|
||||
_handleShowEditExtendPage(context);
|
||||
break;
|
||||
case ProfileActions.delete:
|
||||
_handleDeleteProfile(context);
|
||||
break;
|
||||
case ProfileActions.update:
|
||||
_handleUpdateProfile();
|
||||
break;
|
||||
case null:
|
||||
break;
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
profile.label ?? profile.id,
|
||||
style: context.textTheme.titleMedium,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
...switch (profile.type) {
|
||||
ProfileType.file => _buildFileProfileInfo(context),
|
||||
ProfileType.url => _buildUrlProfileInfo(context),
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
title: _buildTitle(profile),
|
||||
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/scaffold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:re_editor/re_editor.dart';
|
||||
import 'package:re_highlight/languages/yaml.dart';
|
||||
import 'package:re_highlight/styles/atom-one-light.dart';
|
||||
|
||||
class ViewProfile extends StatefulWidget {
|
||||
final Profile profile;
|
||||
|
||||
const ViewProfile({
|
||||
super.key,
|
||||
required this.profile,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ViewProfile> createState() => _ViewProfileState();
|
||||
}
|
||||
|
||||
class _ViewProfileState extends State<ViewProfile> {
|
||||
bool readOnly = true;
|
||||
final CodeLineEditingController _controller = CodeLineEditingController();
|
||||
final key = GlobalKey<CommonScaffoldState>();
|
||||
final _focusNode = FocusNode();
|
||||
String? rawText;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
appPath.getProfilePath(widget.profile.id).then((path) async {
|
||||
if (path == null) return;
|
||||
final file = File(path);
|
||||
rawText = await file.readAsString();
|
||||
_controller.text = rawText ?? "";
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_controller.dispose();
|
||||
_focusNode.dispose();
|
||||
}
|
||||
|
||||
Profile get profile => widget.profile;
|
||||
|
||||
_handleChangeReadOnly() async {
|
||||
if (readOnly == true) {
|
||||
setState(() {
|
||||
readOnly = false;
|
||||
});
|
||||
} else {
|
||||
if (_controller.text == rawText) return;
|
||||
final newProfile = await key.currentState?.loadingRun<Profile>(() async {
|
||||
return await profile.saveFileWithString(_controller.text);
|
||||
});
|
||||
if (newProfile == null) return;
|
||||
globalState.appController.config.setProfile(newProfile);
|
||||
setState(() {
|
||||
readOnly = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonScaffold(
|
||||
key: key,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _controller.undo,
|
||||
icon: const Icon(Icons.undo),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _controller.redo,
|
||||
icon: const Icon(Icons.redo),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _handleChangeReadOnly,
|
||||
icon: readOnly ? const Icon(Icons.edit) : const Icon(Icons.save),
|
||||
),
|
||||
],
|
||||
body: CodeEditor(
|
||||
readOnly: readOnly,
|
||||
focusNode: _focusNode,
|
||||
scrollbarBuilder: (context, child, details) {
|
||||
return Scrollbar(
|
||||
controller: details.controller,
|
||||
thickness: 8,
|
||||
radius: const Radius.circular(2),
|
||||
interactive: true,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
showCursorWhenReadOnly: false,
|
||||
controller: _controller,
|
||||
shortcutsActivatorsBuilder:
|
||||
const DefaultCodeShortcutsActivatorsBuilder(),
|
||||
indicatorBuilder: (
|
||||
context,
|
||||
editingController,
|
||||
chunkController,
|
||||
notifier,
|
||||
) {
|
||||
return Row(
|
||||
children: [
|
||||
DefaultCodeLineNumber(
|
||||
controller: editingController,
|
||||
notifier: notifier,
|
||||
),
|
||||
DefaultCodeChunkIndicator(
|
||||
width: 20,
|
||||
controller: chunkController,
|
||||
notifier: notifier,
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
toolbarController:
|
||||
!readOnly ? ContextMenuControllerImpl(_focusNode) : null,
|
||||
style: CodeEditorStyle(
|
||||
fontSize: 14,
|
||||
codeTheme: CodeHighlightTheme(
|
||||
languages: {
|
||||
'yaml': CodeHighlightThemeMode(
|
||||
mode: langYaml,
|
||||
)
|
||||
},
|
||||
theme: atomOneLightTheme,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: widget.profile.label ?? widget.profile.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ContextMenuItemWidget extends PopupMenuItem<void> {
|
||||
ContextMenuItemWidget({
|
||||
super.key,
|
||||
required String text,
|
||||
required VoidCallback super.onTap,
|
||||
}) : super(child: Text(text));
|
||||
}
|
||||
|
||||
class ContextMenuControllerImpl implements SelectionToolbarController {
|
||||
OverlayEntry? _overlayEntry;
|
||||
|
||||
final FocusNode focusNode;
|
||||
|
||||
ContextMenuControllerImpl(
|
||||
this.focusNode,
|
||||
);
|
||||
|
||||
_removeOverLayEntry() {
|
||||
_overlayEntry?.remove();
|
||||
_overlayEntry = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void hide(BuildContext context) {
|
||||
// _removeOverLayEntry();
|
||||
}
|
||||
|
||||
_handleCut(CodeLineEditingController controller) {
|
||||
controller.cut();
|
||||
_removeOverLayEntry();
|
||||
}
|
||||
|
||||
_handleCopy(CodeLineEditingController controller) async {
|
||||
await controller.copy();
|
||||
_removeOverLayEntry();
|
||||
}
|
||||
|
||||
_handlePaste(CodeLineEditingController controller) {
|
||||
controller.paste();
|
||||
_removeOverLayEntry();
|
||||
}
|
||||
|
||||
@override
|
||||
void show({
|
||||
required BuildContext context,
|
||||
required CodeLineEditingController controller,
|
||||
required TextSelectionToolbarAnchors anchors,
|
||||
Rect? renderRect,
|
||||
required LayerLink layerLink,
|
||||
required ValueNotifier<bool> visibility,
|
||||
}) {
|
||||
if (controller.selectedText.isEmpty) {
|
||||
return;
|
||||
}
|
||||
_removeOverLayEntry();
|
||||
final relativeRect = RelativeRect.fromSize(
|
||||
(anchors.primaryAnchor) &
|
||||
const Size(150, double.infinity),
|
||||
MediaQuery.of(context).size,
|
||||
);
|
||||
_overlayEntry ??= OverlayEntry(
|
||||
builder: (context) => ValueListenableBuilder<CodeLineEditingValue>(
|
||||
valueListenable: controller,
|
||||
builder: (_, __, child) {
|
||||
if (controller.selectedText.isEmpty) {
|
||||
_removeOverLayEntry();
|
||||
}
|
||||
return child!;
|
||||
},
|
||||
child: Positioned(
|
||||
left: relativeRect.left,
|
||||
top: relativeRect.top,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
FocusScope.of(context).requestFocus(focusNode);
|
||||
},
|
||||
child: Container(
|
||||
width: 200,
|
||||
height: 200,
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
Overlay.of(context).insert(_overlayEntry!);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/constant.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
@@ -41,7 +42,7 @@ double getItemHeight(ProxyCardType proxyCardType) {
|
||||
|
||||
delayTest(List<Proxy> proxies) async {
|
||||
final appController = globalState.appController;
|
||||
final delayProxies = proxies.map<Future>((proxy) async {
|
||||
for (final proxy in proxies) {
|
||||
final proxyName = appController.appState.getRealProxyName(proxy.name);
|
||||
globalState.appController.setDelay(
|
||||
Delay(
|
||||
@@ -49,9 +50,11 @@ delayTest(List<Proxy> proxies) async {
|
||||
value: 0,
|
||||
),
|
||||
);
|
||||
globalState.appController.setDelay(await clashCore.getDelay(proxyName));
|
||||
});
|
||||
await Future.wait(delayProxies);
|
||||
clashCore.getDelay(proxyName).then((delay) {
|
||||
globalState.appController.setDelay(delay);
|
||||
});
|
||||
}
|
||||
await Future.delayed(httpTimeoutDuration + moreDuration);
|
||||
appController.appState.sortNum++;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/app.dart';
|
||||
import 'package:fl_clash/models/ffi.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
typedef UpdatingMap = Map<String, bool>;
|
||||
|
||||
class Providers extends StatefulWidget {
|
||||
const Providers({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<Providers> createState() => _ProvidersState();
|
||||
}
|
||||
|
||||
class _ProvidersState extends State<Providers> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) {
|
||||
final commonScaffoldState =
|
||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
||||
commonScaffoldState?.actions = [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_updateProviders();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.sync,
|
||||
),
|
||||
)
|
||||
];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_updateProviders() async {
|
||||
final appState = globalState.appController.appState;
|
||||
final providers = globalState.appController.appState.providers;
|
||||
final updateProviders = providers.map<Future>(
|
||||
(provider) async {
|
||||
appState.setProvider(
|
||||
provider.copyWith(isUpdating: true),
|
||||
);
|
||||
await clashCore.updateExternalProvider(
|
||||
providerName: provider.name,
|
||||
);
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
},
|
||||
);
|
||||
await Future.wait(updateProviders);
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<AppState, List<ExternalProvider>>(
|
||||
selector: (_, appState) => appState.providers,
|
||||
builder: (_, providers, ___) {
|
||||
return ListView.separated(
|
||||
itemBuilder: (_, index) {
|
||||
return ProviderItem(
|
||||
provider: providers[index],
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, index) {
|
||||
return const Divider(
|
||||
height: 0,
|
||||
);
|
||||
},
|
||||
itemCount: providers.length,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProviderItem extends StatelessWidget {
|
||||
final ExternalProvider provider;
|
||||
|
||||
const ProviderItem({
|
||||
super.key,
|
||||
required this.provider,
|
||||
});
|
||||
|
||||
_handleUpdateProvider() async {
|
||||
await globalState.safeRun<void>(() async {
|
||||
final appState = globalState.appController.appState;
|
||||
if (provider.vehicleType != "HTTP") return;
|
||||
await globalState.safeRun(() async {
|
||||
appState.setProvider(
|
||||
provider.copyWith(
|
||||
isUpdating: true,
|
||||
),
|
||||
);
|
||||
final message = await clashCore.updateExternalProvider(
|
||||
providerName: provider.name,
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
});
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
});
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
}
|
||||
|
||||
_handleSideLoadProvider() async {
|
||||
await globalState.safeRun<void>(() async {
|
||||
final platformFile = await picker.pickerFile();
|
||||
final appState = globalState.appController.appState;
|
||||
final bytes = platformFile?.bytes;
|
||||
if (bytes == null) return;
|
||||
final file = await File(provider.path).create(recursive: true);
|
||||
await file.writeAsBytes(bytes);
|
||||
final providerName = provider.name;
|
||||
var message = await clashCore.sideLoadExternalProvider(
|
||||
providerName: providerName,
|
||||
data: utf8.decode(bytes),
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
});
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
}
|
||||
|
||||
String _buildProviderDesc() {
|
||||
final baseInfo =
|
||||
"${provider.type}(${provider.vehicleType}) · ${provider.updateAt.lastUpdateTimeDesc}";
|
||||
final count = provider.count;
|
||||
return switch (count == 0) {
|
||||
true => baseInfo,
|
||||
false => "$baseInfo · $count${appLocalizations.entries}",
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
title: Text(provider.name),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
_buildProviderDesc(),
|
||||
),
|
||||
Text(
|
||||
provider.path,
|
||||
style: context.textTheme.bodyMedium?.toLight,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 6,
|
||||
spacing: 12,
|
||||
children: [
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.upload),
|
||||
label: appLocalizations.upload,
|
||||
onPressed: _handleSideLoadProvider,
|
||||
),
|
||||
if (provider.vehicleType == "HTTP")
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.sync),
|
||||
label: appLocalizations.sync,
|
||||
onPressed: _handleUpdateProvider,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: SizedBox(
|
||||
height: 48,
|
||||
width: 48,
|
||||
child: FadeBox(
|
||||
child: provider.isUpdating
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'providers.dart';
|
||||
import 'setting.dart';
|
||||
import 'tab.dart';
|
||||
|
||||
@@ -20,37 +19,18 @@ class ProxiesFragment extends StatefulWidget {
|
||||
class _ProxiesFragmentState extends State<ProxiesFragment> {
|
||||
final GlobalKey<ProxiesTabFragmentState> _proxiesTabKey = GlobalKey();
|
||||
|
||||
_initActions(ProxiesType proxiesType, bool hasProvider) {
|
||||
_initActions(ProxiesType proxiesType) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
final commonScaffoldState =
|
||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
||||
commonScaffoldState?.actions = [
|
||||
if (hasProvider) ...[
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showExtendPage(
|
||||
forceNotSide: true,
|
||||
extendPageWidth: 360,
|
||||
context,
|
||||
body: const Providers(),
|
||||
title: appLocalizations.externalResources,
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.swap_vert_circle_outlined,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
],
|
||||
if (proxiesType == ProxiesType.tab) ...[
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_proxiesTabKey.currentState?.scrollToGroupSelected();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.adjust_outlined,
|
||||
Icons.gps_fixed,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
@@ -80,18 +60,18 @@ class _ProxiesFragmentState extends State<ProxiesFragment> {
|
||||
return Selector<Config, ProxiesType>(
|
||||
selector: (_, config) => config.proxiesType,
|
||||
builder: (_, proxiesType, __) {
|
||||
return ProxiesActionsBuilder(
|
||||
builder: (state, child) {
|
||||
if (state.isCurrent) {
|
||||
_initActions(proxiesType, state.hasProvider);
|
||||
return Selector<AppState, bool>(
|
||||
selector: (_, appState) => appState.currentLabel == 'proxies',
|
||||
builder: (_, isCurrent, child) {
|
||||
if (isCurrent) {
|
||||
_initActions(proxiesType);
|
||||
}
|
||||
return child!;
|
||||
},
|
||||
child: switch (proxiesType) {
|
||||
ProxiesType.tab => ProxiesTabFragment(
|
||||
key: _proxiesTabKey,
|
||||
),
|
||||
ProxiesType.list => const ProxiesListFragment(),
|
||||
return switch (proxiesType) {
|
||||
ProxiesType.tab => ProxiesTabFragment(
|
||||
key: _proxiesTabKey,
|
||||
),
|
||||
ProxiesType.list => const ProxiesListFragment(),
|
||||
};
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -189,4 +189,74 @@ class ProxiesSettingWidget extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SettingInfoCard extends StatelessWidget {
|
||||
final Info info;
|
||||
final bool? isSelected;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const SettingInfoCard(
|
||||
this.info, {
|
||||
super.key,
|
||||
this.isSelected,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
isSelected: isSelected,
|
||||
onPressed: onPressed,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Icon(info.iconData),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
info.label,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingTextCard extends StatelessWidget {
|
||||
final String text;
|
||||
final bool? isSelected;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const SettingTextCard(
|
||||
this.text, {
|
||||
super.key,
|
||||
this.isSelected,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
onPressed: onPressed,
|
||||
isSelected: isSelected,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
text,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/fragments/proxies/setting.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
|
||||
@@ -22,11 +22,80 @@ class GeoItem {
|
||||
});
|
||||
}
|
||||
|
||||
class Resources extends StatelessWidget {
|
||||
class Resources extends StatefulWidget {
|
||||
const Resources({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
State<Resources> createState() => _ResourcesState();
|
||||
}
|
||||
|
||||
class _ResourcesState extends State<Resources> {
|
||||
List<ExternalProvider> externalProviders = [];
|
||||
|
||||
List<GlobalObjectKey<_ProviderItemState>> providerItemKeys = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_syncExternalProviders();
|
||||
});
|
||||
}
|
||||
|
||||
_syncExternalProviders() async {
|
||||
externalProviders = await clashCore.getExternalProviders();
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
_updateProviders() async {
|
||||
final updateProviders = providerItemKeys.map<Future>(
|
||||
(key) async => await key.currentState?.updateProvider(false),
|
||||
);
|
||||
await Future.wait(updateProviders);
|
||||
_syncExternalProviders();
|
||||
}
|
||||
|
||||
List<Widget> _buildExternalProviderSection() {
|
||||
List<GlobalObjectKey<_ProviderItemState>> keys = [];
|
||||
final res = generateInfoSection(
|
||||
info: Info(
|
||||
iconData: Icons.source,
|
||||
label: appLocalizations.externalResources,
|
||||
),
|
||||
actions: [
|
||||
IconButton.filledTonal(
|
||||
onPressed: () {
|
||||
_updateProviders();
|
||||
},
|
||||
padding: const EdgeInsets.all(4),
|
||||
iconSize: 20,
|
||||
icon: const Icon(
|
||||
Icons.sync,
|
||||
),
|
||||
)
|
||||
],
|
||||
items: externalProviders.map(
|
||||
(externalProvider) {
|
||||
final key =
|
||||
GlobalObjectKey<_ProviderItemState>(externalProvider.name);
|
||||
keys.add(key);
|
||||
return ProviderItem(
|
||||
key: key,
|
||||
provider: externalProvider,
|
||||
onUpdated: () {
|
||||
_syncExternalProviders();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
providerItemKeys = keys;
|
||||
return res;
|
||||
}
|
||||
|
||||
List<Widget> _buildGeoDataSection() {
|
||||
const geoItems = <GeoItem>[
|
||||
GeoItem(
|
||||
label: "GeoIp",
|
||||
@@ -42,19 +111,26 @@ class Resources extends StatelessWidget {
|
||||
GeoItem(label: "ASN", fileName: asnFileName, key: "asn"),
|
||||
];
|
||||
|
||||
return ListView.separated(
|
||||
itemBuilder: (_, index) {
|
||||
final geoItem = geoItems[index];
|
||||
return GeoDataListItem(
|
||||
return generateInfoSection(
|
||||
info: Info(
|
||||
iconData: Icons.storage,
|
||||
label: appLocalizations.geoData,
|
||||
),
|
||||
items: geoItems.map(
|
||||
(geoItem) => GeoDataListItem(
|
||||
geoItem: geoItem,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return const Divider(
|
||||
height: 0,
|
||||
);
|
||||
},
|
||||
itemCount: geoItems.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return generateListView(
|
||||
[
|
||||
..._buildGeoDataSection(),
|
||||
..._buildExternalProviderSection(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -150,6 +226,9 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 6,
|
||||
spacing: 12,
|
||||
@@ -182,9 +261,9 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
||||
updateGeoDateItem() async {
|
||||
isUpdating.value = true;
|
||||
try {
|
||||
final message = await clashCore.updateGeoData(
|
||||
geoName: geoItem.fileName,
|
||||
geoType: geoItem.label,
|
||||
final message = await clashCore.updateExternalProvider(
|
||||
providerName: geoItem.fileName,
|
||||
providerType: geoItem.label,
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
} catch (e) {
|
||||
@@ -236,6 +315,117 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
||||
}
|
||||
}
|
||||
|
||||
class ProviderItem extends StatefulWidget {
|
||||
final ExternalProvider provider;
|
||||
final Function onUpdated;
|
||||
|
||||
const ProviderItem({
|
||||
super.key,
|
||||
required this.provider,
|
||||
required this.onUpdated,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ProviderItem> createState() => _ProviderItemState();
|
||||
}
|
||||
|
||||
class _ProviderItemState extends State<ProviderItem> {
|
||||
final isUpdating = ValueNotifier<bool>(false);
|
||||
|
||||
ExternalProvider get provider => widget.provider;
|
||||
|
||||
_handleUpdateProfile() async {
|
||||
await globalState.safeRun<void>(updateProvider);
|
||||
widget.onUpdated();
|
||||
}
|
||||
|
||||
updateProvider([isSingle = true]) async {
|
||||
if (provider.vehicleType != "HTTP") return;
|
||||
isUpdating.value = true;
|
||||
try {
|
||||
final message = await clashCore.updateExternalProvider(
|
||||
providerName: provider.name,
|
||||
providerType: provider.type,
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
} catch (e) {
|
||||
isUpdating.value = false;
|
||||
if (!isSingle) {
|
||||
return e.toString();
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
isUpdating.value = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
String _buildProviderDesc() {
|
||||
return "${provider.type} (${provider.vehicleType}) · ${provider.updateAt.lastUpdateTimeDesc}";
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
isUpdating.dispose();
|
||||
}
|
||||
|
||||
Widget _buildSubtitle() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
_buildProviderDesc(),
|
||||
),
|
||||
if (provider.vehicleType == "HTTP") ...[
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.sync),
|
||||
label: appLocalizations.sync,
|
||||
onPressed: () {
|
||||
_handleUpdateProfile();
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListItem(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
title: Text(provider.name),
|
||||
subtitle: _buildSubtitle(),
|
||||
trailing: SizedBox(
|
||||
height: 48,
|
||||
width: 48,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: isUpdating,
|
||||
builder: (_, isUpdating, ___) {
|
||||
return FadeBox(
|
||||
child: isUpdating
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: const SizedBox(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateGeoUrlFormDialog extends StatefulWidget {
|
||||
final String title;
|
||||
final String url;
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
"hours": "Hours",
|
||||
"days": "Days",
|
||||
"minutes": "Minutes",
|
||||
"seconds": "Seconds",
|
||||
"ago": " Ago",
|
||||
"just": "Just",
|
||||
"qrcode": "QR code",
|
||||
@@ -131,10 +130,12 @@
|
||||
"notSelectedTip": "The current proxy group cannot be selected.",
|
||||
"tip": "tip",
|
||||
"backupAndRecovery": "Backup and Recovery",
|
||||
"backupAndRecoveryDesc": "Sync data via WebDAV or file",
|
||||
"backupAndRecoveryDesc": "Sync data by WebDAV",
|
||||
"account": "Account",
|
||||
"backup": "Backup",
|
||||
"backupDesc": "Backup local data to WebDAV",
|
||||
"recovery": "Recovery",
|
||||
"recoveryDesc": "Recovery data from WebDAV",
|
||||
"recoveryProfiles": "Only recovery profiles",
|
||||
"recoveryAll": "Recovery all data",
|
||||
"recoverySuccess": "Recovery success",
|
||||
@@ -219,22 +220,5 @@
|
||||
"onlyStatisticsProxy": "Only statistics proxy",
|
||||
"onlyStatisticsProxyDesc": "When turned on, only statistics proxy traffic",
|
||||
"deleteProfileTip": "Sure you want to delete the current profile?",
|
||||
"prueBlackMode": "Prue black mode",
|
||||
"keepAliveIntervalDesc": "Tcp keep alive interval",
|
||||
"entries": " entries",
|
||||
"local": "Local",
|
||||
"remote": "Remote",
|
||||
"remoteBackupDesc": "Backup local data to WebDAV",
|
||||
"remoteRecoveryDesc": "Recovery data from WebDAV",
|
||||
"localBackupDesc": "Backup local data to local",
|
||||
"localRecoveryDesc": "Recovery data from file",
|
||||
"mode": "Mode",
|
||||
"time": "Time",
|
||||
"source": "Source",
|
||||
"allApps": "All apps",
|
||||
"onlyOtherApps": "Only third-party apps",
|
||||
"action": "Action",
|
||||
"intelligentSelected": "Intelligent selection",
|
||||
"clipboardImport": "Clipboard import",
|
||||
"clipboardExport": "Export clipboard"
|
||||
"prueBlackMode": "Prue black mode"
|
||||
}
|
||||
@@ -66,7 +66,6 @@
|
||||
"hours": "小时",
|
||||
"days": "天",
|
||||
"minutes": "分钟",
|
||||
"seconds": "秒",
|
||||
"ago": "前",
|
||||
"just": "刚刚",
|
||||
"qrcode": "二维码",
|
||||
@@ -131,10 +130,12 @@
|
||||
"notSelectedTip": "当前代理组无法选中",
|
||||
"tip": "提示",
|
||||
"backupAndRecovery": "备份与恢复",
|
||||
"backupAndRecoveryDesc": "通过WebDAV或者文件同步数据",
|
||||
"backupAndRecoveryDesc": "通过WebDAV同步数据",
|
||||
"account": "账号",
|
||||
"backup": "备份",
|
||||
"backupDesc": "备份数据到WebDAV",
|
||||
"recovery": "恢复",
|
||||
"recoveryDesc": "从WebDAV恢复数据",
|
||||
"recoveryProfiles": "仅恢复配置文件",
|
||||
"recoveryAll": "恢复所有数据",
|
||||
"recoverySuccess": "恢复成功",
|
||||
@@ -219,22 +220,5 @@
|
||||
"onlyStatisticsProxy": "仅统计代理",
|
||||
"onlyStatisticsProxyDesc": "开启后,将只统计代理流量",
|
||||
"deleteProfileTip": "确定要删除当前配置吗?",
|
||||
"prueBlackMode": "纯黑模式",
|
||||
"keepAliveIntervalDesc": "TCP保持活动间隔",
|
||||
"entries": "个条目",
|
||||
"local": "本地",
|
||||
"remote": "远程",
|
||||
"remoteBackupDesc": "备份数据到WebDAV",
|
||||
"remoteRecoveryDesc": "通过WebDAV恢复数据",
|
||||
"localBackupDesc": "备份数据到本地",
|
||||
"localRecoveryDesc": "通过文件恢复数据",
|
||||
"mode": "模式",
|
||||
"time": "时间",
|
||||
"source": "来源",
|
||||
"allApps": "所有应用",
|
||||
"onlyOtherApps": "仅第三方应用",
|
||||
"action": "操作",
|
||||
"intelligentSelected": "智能选择",
|
||||
"clipboardImport": "剪贴板导入",
|
||||
"clipboardExport": "导出剪贴板"
|
||||
"prueBlackMode": "纯黑模式"
|
||||
}
|
||||
@@ -33,7 +33,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"account": MessageLookupByLibrary.simpleMessage("Account"),
|
||||
"accountTip":
|
||||
MessageLookupByLibrary.simpleMessage("Account cannot be empty"),
|
||||
"action": MessageLookupByLibrary.simpleMessage("Action"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("Add"),
|
||||
"address": MessageLookupByLibrary.simpleMessage("Address"),
|
||||
"addressHelp":
|
||||
@@ -41,7 +40,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"addressTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Please enter a valid WebDAV address"),
|
||||
"ago": MessageLookupByLibrary.simpleMessage(" Ago"),
|
||||
"allApps": MessageLookupByLibrary.simpleMessage("All apps"),
|
||||
"allowBypass": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow applications to bypass VPN"),
|
||||
"allowBypassDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -76,8 +74,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"backup": MessageLookupByLibrary.simpleMessage("Backup"),
|
||||
"backupAndRecovery":
|
||||
MessageLookupByLibrary.simpleMessage("Backup and Recovery"),
|
||||
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Sync data via WebDAV or file"),
|
||||
"backupAndRecoveryDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Sync data by WebDAV"),
|
||||
"backupDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Backup local data to WebDAV"),
|
||||
"backupSuccess": MessageLookupByLibrary.simpleMessage("Backup success"),
|
||||
"bind": MessageLookupByLibrary.simpleMessage("Bind"),
|
||||
"blacklistMode": MessageLookupByLibrary.simpleMessage("Blacklist mode"),
|
||||
@@ -91,10 +91,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"checkUpdateError": MessageLookupByLibrary.simpleMessage(
|
||||
"The current application is already the latest version"),
|
||||
"checking": MessageLookupByLibrary.simpleMessage("Checking..."),
|
||||
"clipboardExport":
|
||||
MessageLookupByLibrary.simpleMessage("Export clipboard"),
|
||||
"clipboardImport":
|
||||
MessageLookupByLibrary.simpleMessage("Clipboard import"),
|
||||
"columns": MessageLookupByLibrary.simpleMessage("Columns"),
|
||||
"compatible":
|
||||
MessageLookupByLibrary.simpleMessage("Compatibility mode"),
|
||||
@@ -133,7 +129,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"download": MessageLookupByLibrary.simpleMessage("Download"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("Edit"),
|
||||
"en": MessageLookupByLibrary.simpleMessage("English"),
|
||||
"entries": MessageLookupByLibrary.simpleMessage(" entries"),
|
||||
"exclude":
|
||||
MessageLookupByLibrary.simpleMessage("Hidden from recent tasks"),
|
||||
"excludeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -173,22 +168,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"infiniteTime":
|
||||
MessageLookupByLibrary.simpleMessage("Long term effective"),
|
||||
"init": MessageLookupByLibrary.simpleMessage("Init"),
|
||||
"intelligentSelected":
|
||||
MessageLookupByLibrary.simpleMessage("Intelligent selection"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
|
||||
"When turned on it will be able to receive IPv6 traffic"),
|
||||
"just": MessageLookupByLibrary.simpleMessage("Just"),
|
||||
"keepAliveIntervalDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Tcp keep alive interval"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("Language"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("Light"),
|
||||
"list": MessageLookupByLibrary.simpleMessage("List"),
|
||||
"local": MessageLookupByLibrary.simpleMessage("Local"),
|
||||
"localBackupDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Backup local data to local"),
|
||||
"localRecoveryDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Recovery data from file"),
|
||||
"logLevel": MessageLookupByLibrary.simpleMessage("LogLevel"),
|
||||
"logcat": MessageLookupByLibrary.simpleMessage("Logcat"),
|
||||
"logcatDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -201,7 +187,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Modify the default system exit event"),
|
||||
"minutes": MessageLookupByLibrary.simpleMessage("Minutes"),
|
||||
"mode": MessageLookupByLibrary.simpleMessage("Mode"),
|
||||
"months": MessageLookupByLibrary.simpleMessage("Months"),
|
||||
"more": MessageLookupByLibrary.simpleMessage("More"),
|
||||
"name": MessageLookupByLibrary.simpleMessage("Name"),
|
||||
@@ -225,8 +210,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"No profile, Please add a profile"),
|
||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"),
|
||||
"oneColumn": MessageLookupByLibrary.simpleMessage("One column"),
|
||||
"onlyOtherApps":
|
||||
MessageLookupByLibrary.simpleMessage("Only third-party apps"),
|
||||
"onlyStatisticsProxy":
|
||||
MessageLookupByLibrary.simpleMessage("Only statistics proxy"),
|
||||
"onlyStatisticsProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -282,15 +265,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"recovery": MessageLookupByLibrary.simpleMessage("Recovery"),
|
||||
"recoveryAll":
|
||||
MessageLookupByLibrary.simpleMessage("Recovery all data"),
|
||||
"recoveryDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Recovery data from WebDAV"),
|
||||
"recoveryProfiles":
|
||||
MessageLookupByLibrary.simpleMessage("Only recovery profiles"),
|
||||
"recoverySuccess":
|
||||
MessageLookupByLibrary.simpleMessage("Recovery success"),
|
||||
"remote": MessageLookupByLibrary.simpleMessage("Remote"),
|
||||
"remoteBackupDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Backup local data to WebDAV"),
|
||||
"remoteRecoveryDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Recovery data from WebDAV"),
|
||||
"requests": MessageLookupByLibrary.simpleMessage("Requests"),
|
||||
"requestsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"View recently request records"),
|
||||
@@ -300,7 +280,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"rule": MessageLookupByLibrary.simpleMessage("Rule"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("Save"),
|
||||
"search": MessageLookupByLibrary.simpleMessage("Search"),
|
||||
"seconds": MessageLookupByLibrary.simpleMessage("Seconds"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("Select all"),
|
||||
"selected": MessageLookupByLibrary.simpleMessage("Selected"),
|
||||
"settings": MessageLookupByLibrary.simpleMessage("Settings"),
|
||||
@@ -311,7 +290,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Start in the background"),
|
||||
"size": MessageLookupByLibrary.simpleMessage("Size"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("Sort"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("Source"),
|
||||
"startVpn": MessageLookupByLibrary.simpleMessage("Staring VPN..."),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("Style"),
|
||||
@@ -334,7 +312,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Set dark mode,adjust the color"),
|
||||
"themeMode": MessageLookupByLibrary.simpleMessage("Theme mode"),
|
||||
"threeColumns": MessageLookupByLibrary.simpleMessage("Three columns"),
|
||||
"time": MessageLookupByLibrary.simpleMessage("Time"),
|
||||
"tip": MessageLookupByLibrary.simpleMessage("tip"),
|
||||
"tools": MessageLookupByLibrary.simpleMessage("Tools"),
|
||||
"trafficUsage": MessageLookupByLibrary.simpleMessage("Traffic usage"),
|
||||
|
||||
@@ -31,13 +31,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("选中应用将会被排除在VPN之外"),
|
||||
"account": MessageLookupByLibrary.simpleMessage("账号"),
|
||||
"accountTip": MessageLookupByLibrary.simpleMessage("账号不能为空"),
|
||||
"action": MessageLookupByLibrary.simpleMessage("操作"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("添加"),
|
||||
"address": MessageLookupByLibrary.simpleMessage("地址"),
|
||||
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
|
||||
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
|
||||
"ago": MessageLookupByLibrary.simpleMessage("前"),
|
||||
"allApps": MessageLookupByLibrary.simpleMessage("所有应用"),
|
||||
"allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过VPN"),
|
||||
"allowBypassDesc":
|
||||
MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"),
|
||||
@@ -64,7 +62,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"backup": MessageLookupByLibrary.simpleMessage("备份"),
|
||||
"backupAndRecovery": MessageLookupByLibrary.simpleMessage("备份与恢复"),
|
||||
"backupAndRecoveryDesc":
|
||||
MessageLookupByLibrary.simpleMessage("通过WebDAV或者文件同步数据"),
|
||||
MessageLookupByLibrary.simpleMessage("通过WebDAV同步数据"),
|
||||
"backupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
|
||||
"backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"),
|
||||
"bind": MessageLookupByLibrary.simpleMessage("绑定"),
|
||||
"blacklistMode": MessageLookupByLibrary.simpleMessage("黑名单模式"),
|
||||
@@ -75,8 +74,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"),
|
||||
"checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"),
|
||||
"checking": MessageLookupByLibrary.simpleMessage("检测中..."),
|
||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"),
|
||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"),
|
||||
"columns": MessageLookupByLibrary.simpleMessage("列数"),
|
||||
"compatible": MessageLookupByLibrary.simpleMessage("兼容模式"),
|
||||
"compatibleDesc":
|
||||
@@ -109,7 +106,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"download": MessageLookupByLibrary.simpleMessage("下载"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("编辑"),
|
||||
"en": MessageLookupByLibrary.simpleMessage("英语"),
|
||||
"entries": MessageLookupByLibrary.simpleMessage("个条目"),
|
||||
"exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"),
|
||||
"excludeDesc":
|
||||
MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"),
|
||||
@@ -140,18 +136,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
|
||||
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
|
||||
"init": MessageLookupByLibrary.simpleMessage("初始化"),
|
||||
"intelligentSelected": MessageLookupByLibrary.simpleMessage("智能选择"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
|
||||
"just": MessageLookupByLibrary.simpleMessage("刚刚"),
|
||||
"keepAliveIntervalDesc":
|
||||
MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("语言"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("浅色"),
|
||||
"list": MessageLookupByLibrary.simpleMessage("列表"),
|
||||
"local": MessageLookupByLibrary.simpleMessage("本地"),
|
||||
"localBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到本地"),
|
||||
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过文件恢复数据"),
|
||||
"logLevel": MessageLookupByLibrary.simpleMessage("日志等级"),
|
||||
"logcat": MessageLookupByLibrary.simpleMessage("日志捕获"),
|
||||
"logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"),
|
||||
@@ -162,7 +152,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"minimizeOnExitDesc":
|
||||
MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
|
||||
"minutes": MessageLookupByLibrary.simpleMessage("分钟"),
|
||||
"mode": MessageLookupByLibrary.simpleMessage("模式"),
|
||||
"months": MessageLookupByLibrary.simpleMessage("月"),
|
||||
"more": MessageLookupByLibrary.simpleMessage("更多"),
|
||||
"name": MessageLookupByLibrary.simpleMessage("名称"),
|
||||
@@ -182,7 +171,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
|
||||
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
|
||||
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
|
||||
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("仅第三方应用"),
|
||||
"onlyStatisticsProxy": MessageLookupByLibrary.simpleMessage("仅统计代理"),
|
||||
"onlyStatisticsProxyDesc":
|
||||
MessageLookupByLibrary.simpleMessage("开启后,将只统计代理流量"),
|
||||
@@ -225,12 +213,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
|
||||
"recovery": MessageLookupByLibrary.simpleMessage("恢复"),
|
||||
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
|
||||
"recoveryDesc": MessageLookupByLibrary.simpleMessage("从WebDAV恢复数据"),
|
||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
|
||||
"remote": MessageLookupByLibrary.simpleMessage("远程"),
|
||||
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
|
||||
"remoteRecoveryDesc":
|
||||
MessageLookupByLibrary.simpleMessage("通过WebDAV恢复数据"),
|
||||
"requests": MessageLookupByLibrary.simpleMessage("请求"),
|
||||
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求记录"),
|
||||
"resources": MessageLookupByLibrary.simpleMessage("资源"),
|
||||
@@ -238,7 +223,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"rule": MessageLookupByLibrary.simpleMessage("规则"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("保存"),
|
||||
"search": MessageLookupByLibrary.simpleMessage("搜索"),
|
||||
"seconds": MessageLookupByLibrary.simpleMessage("秒"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("全选"),
|
||||
"selected": MessageLookupByLibrary.simpleMessage("已选择"),
|
||||
"settings": MessageLookupByLibrary.simpleMessage("设置"),
|
||||
@@ -248,7 +232,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("后台启动"),
|
||||
"size": MessageLookupByLibrary.simpleMessage("尺寸"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("排序"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("来源"),
|
||||
"startVpn": MessageLookupByLibrary.simpleMessage("正在启动VPN..."),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("风格"),
|
||||
@@ -269,7 +252,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),
|
||||
"themeMode": MessageLookupByLibrary.simpleMessage("主题模式"),
|
||||
"threeColumns": MessageLookupByLibrary.simpleMessage("三列"),
|
||||
"time": MessageLookupByLibrary.simpleMessage("时间"),
|
||||
"tip": MessageLookupByLibrary.simpleMessage("提示"),
|
||||
"tools": MessageLookupByLibrary.simpleMessage("工具"),
|
||||
"trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"),
|
||||
|
||||
@@ -720,16 +720,6 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Seconds`
|
||||
String get seconds {
|
||||
return Intl.message(
|
||||
'Seconds',
|
||||
name: 'seconds',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// ` Ago`
|
||||
String get ago {
|
||||
return Intl.message(
|
||||
@@ -1370,10 +1360,10 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Sync data via WebDAV or file`
|
||||
/// `Sync data by WebDAV`
|
||||
String get backupAndRecoveryDesc {
|
||||
return Intl.message(
|
||||
'Sync data via WebDAV or file',
|
||||
'Sync data by WebDAV',
|
||||
name: 'backupAndRecoveryDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
@@ -1400,6 +1390,16 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Backup local data to WebDAV`
|
||||
String get backupDesc {
|
||||
return Intl.message(
|
||||
'Backup local data to WebDAV',
|
||||
name: 'backupDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Recovery`
|
||||
String get recovery {
|
||||
return Intl.message(
|
||||
@@ -1410,6 +1410,16 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Recovery data from WebDAV`
|
||||
String get recoveryDesc {
|
||||
return Intl.message(
|
||||
'Recovery data from WebDAV',
|
||||
name: 'recoveryDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Only recovery profiles`
|
||||
String get recoveryProfiles {
|
||||
return Intl.message(
|
||||
@@ -2259,176 +2269,6 @@ class AppLocalizations {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Tcp keep alive interval`
|
||||
String get keepAliveIntervalDesc {
|
||||
return Intl.message(
|
||||
'Tcp keep alive interval',
|
||||
name: 'keepAliveIntervalDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// ` entries`
|
||||
String get entries {
|
||||
return Intl.message(
|
||||
' entries',
|
||||
name: 'entries',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Local`
|
||||
String get local {
|
||||
return Intl.message(
|
||||
'Local',
|
||||
name: 'local',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Remote`
|
||||
String get remote {
|
||||
return Intl.message(
|
||||
'Remote',
|
||||
name: 'remote',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Backup local data to WebDAV`
|
||||
String get remoteBackupDesc {
|
||||
return Intl.message(
|
||||
'Backup local data to WebDAV',
|
||||
name: 'remoteBackupDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Recovery data from WebDAV`
|
||||
String get remoteRecoveryDesc {
|
||||
return Intl.message(
|
||||
'Recovery data from WebDAV',
|
||||
name: 'remoteRecoveryDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Backup local data to local`
|
||||
String get localBackupDesc {
|
||||
return Intl.message(
|
||||
'Backup local data to local',
|
||||
name: 'localBackupDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Recovery data from file`
|
||||
String get localRecoveryDesc {
|
||||
return Intl.message(
|
||||
'Recovery data from file',
|
||||
name: 'localRecoveryDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Mode`
|
||||
String get mode {
|
||||
return Intl.message(
|
||||
'Mode',
|
||||
name: 'mode',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Time`
|
||||
String get time {
|
||||
return Intl.message(
|
||||
'Time',
|
||||
name: 'time',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Source`
|
||||
String get source {
|
||||
return Intl.message(
|
||||
'Source',
|
||||
name: 'source',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `All apps`
|
||||
String get allApps {
|
||||
return Intl.message(
|
||||
'All apps',
|
||||
name: 'allApps',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Only third-party apps`
|
||||
String get onlyOtherApps {
|
||||
return Intl.message(
|
||||
'Only third-party apps',
|
||||
name: 'onlyOtherApps',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Action`
|
||||
String get action {
|
||||
return Intl.message(
|
||||
'Action',
|
||||
name: 'action',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Intelligent selection`
|
||||
String get intelligentSelected {
|
||||
return Intl.message(
|
||||
'Intelligent selection',
|
||||
name: 'intelligentSelected',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Clipboard import`
|
||||
String get clipboardImport {
|
||||
return Intl.message(
|
||||
'Clipboard import',
|
||||
name: 'clipboardImport',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Export clipboard`
|
||||
String get clipboardExport {
|
||||
return Intl.message(
|
||||
'Export clipboard',
|
||||
name: 'clipboardExport',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -130,13 +130,13 @@ class ServiceMessageHandler with ServiceMessageListener {
|
||||
final Function(Fd fd) _onProtect;
|
||||
final Function(Process process) _onProcess;
|
||||
final Function(String runTime) _onStarted;
|
||||
final Function(String providerName) _onLoaded;
|
||||
final Function(String groupName) _onLoaded;
|
||||
|
||||
const ServiceMessageHandler({
|
||||
required Function(Fd fd) onProtect,
|
||||
required Function(Process process) onProcess,
|
||||
required Function(String runTime) onStarted,
|
||||
required Function(String providerName) onLoaded,
|
||||
required Function(String groupName) onLoaded,
|
||||
}) : _onProtect = onProtect,
|
||||
_onProcess = onProcess,
|
||||
_onStarted = onStarted,
|
||||
@@ -158,8 +158,8 @@ class ServiceMessageHandler with ServiceMessageListener {
|
||||
}
|
||||
|
||||
@override
|
||||
onLoaded(String providerName) {
|
||||
_onLoaded(providerName);
|
||||
onLoaded(String groupName) {
|
||||
_onLoaded(groupName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'connection.dart';
|
||||
import 'ffi.dart';
|
||||
import 'log.dart';
|
||||
import 'navigation.dart';
|
||||
import 'package.dart';
|
||||
import 'profile.dart';
|
||||
import 'proxy.dart';
|
||||
import 'system_color_scheme.dart';
|
||||
@@ -36,8 +35,6 @@ class AppState with ChangeNotifier {
|
||||
double _viewWidth;
|
||||
List<Connection> _requests;
|
||||
num _checkIpNum;
|
||||
List<ExternalProvider> _providers;
|
||||
List<Package> _packages;
|
||||
|
||||
AppState({
|
||||
required Mode mode,
|
||||
@@ -57,8 +54,6 @@ class AppState with ChangeNotifier {
|
||||
_totalTraffic = Traffic(),
|
||||
_delayMap = {},
|
||||
_groups = [],
|
||||
_providers = [],
|
||||
_packages = [],
|
||||
_isCompatible = isCompatible,
|
||||
_systemColorSchemes = const SystemColorSchemes();
|
||||
|
||||
@@ -335,32 +330,6 @@ class AppState with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
List<Package> get packages => _packages;
|
||||
|
||||
set packages(List<Package> value) {
|
||||
if (!const ListEquality<Package>().equals(_packages, value)) {
|
||||
_packages = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
List<ExternalProvider> get providers => _providers;
|
||||
|
||||
set providers(List<ExternalProvider> value) {
|
||||
if (!const ListEquality<ExternalProvider>().equals(_providers, value)) {
|
||||
_providers = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
setProvider(ExternalProvider? provider) {
|
||||
if(provider == null) return;
|
||||
final index = _providers.indexWhere((item) => item.name == provider.name);
|
||||
if (index == -1) return;
|
||||
_providers = List.from(_providers)..[index] = provider;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Group? getGroupWithName(String groupName) {
|
||||
final index =
|
||||
currentGroups.indexWhere((element) => element.name == groupName);
|
||||
|
||||
@@ -119,7 +119,6 @@ class ClashConfig extends ChangeNotifier {
|
||||
String _externalController;
|
||||
Mode _mode;
|
||||
FindProcessMode _findProcessMode;
|
||||
int _keepAliveInterval;
|
||||
bool _unifiedDelay;
|
||||
bool _tcpConcurrent;
|
||||
Tun _tun;
|
||||
@@ -140,7 +139,6 @@ class ClashConfig extends ChangeNotifier {
|
||||
_unifiedDelay = false,
|
||||
_geodataLoader = geodataLoaderMemconservative,
|
||||
_externalController = '',
|
||||
_keepAliveInterval = 30,
|
||||
_dns = Dns(),
|
||||
_geoXUrl = defaultGeoXMap,
|
||||
_rules = [];
|
||||
@@ -205,16 +203,6 @@ class ClashConfig extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(name: "keep-alive-interval", defaultValue: 30)
|
||||
int get keepAliveInterval => _keepAliveInterval;
|
||||
|
||||
set keepAliveInterval(int value) {
|
||||
if (_keepAliveInterval != value) {
|
||||
_keepAliveInterval = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool get ipv6 => _ipv6;
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ class AccessControl with _$AccessControl {
|
||||
@Default(AccessControlMode.rejectSelected) AccessControlMode mode,
|
||||
@Default([]) List<String> acceptList,
|
||||
@Default([]) List<String> rejectList,
|
||||
@Default(AccessSortType.none) AccessSortType sort,
|
||||
@Default(true) bool isFilterSystemApp,
|
||||
}) = _AccessControl;
|
||||
|
||||
@@ -26,18 +25,10 @@ class AccessControl with _$AccessControl {
|
||||
_$AccessControlFromJson(json);
|
||||
}
|
||||
|
||||
extension AccessControlExt on AccessControl {
|
||||
List<String> get currentList => switch (mode) {
|
||||
AccessControlMode.acceptSelected => acceptList,
|
||||
AccessControlMode.rejectSelected => rejectList,
|
||||
};
|
||||
}
|
||||
|
||||
@freezed
|
||||
class CoreState with _$CoreState {
|
||||
const factory CoreState({
|
||||
AccessControl? accessControl,
|
||||
required String currentProfileName,
|
||||
required bool allowBypass,
|
||||
required bool systemProxy,
|
||||
required int mixedPort,
|
||||
|
||||
@@ -24,7 +24,7 @@ class ConfigExtendedParams with _$ConfigExtendedParams {
|
||||
@freezed
|
||||
class UpdateConfigParams with _$UpdateConfigParams {
|
||||
const factory UpdateConfigParams({
|
||||
@JsonKey(name: "profile-id") required String profileId,
|
||||
@JsonKey(name: "profile-path") String? profilePath,
|
||||
required ClashConfig config,
|
||||
required ConfigExtendedParams params,
|
||||
}) = _UpdateConfigParams;
|
||||
@@ -123,9 +123,6 @@ class ExternalProvider with _$ExternalProvider {
|
||||
const factory ExternalProvider({
|
||||
required String name,
|
||||
required String type,
|
||||
required String path,
|
||||
required int count,
|
||||
@Default(false) bool isUpdating,
|
||||
@JsonKey(name: "vehicle-type") required String vehicleType,
|
||||
@JsonKey(name: "update-at") required DateTime updateAt,
|
||||
}) = _ExternalProvider;
|
||||
@@ -143,7 +140,7 @@ abstract mixin class AppMessageListener {
|
||||
|
||||
void onStarted(String runTime) {}
|
||||
|
||||
void onLoaded(String providerName) {}
|
||||
void onLoaded(String groupName) {}
|
||||
}
|
||||
|
||||
abstract mixin class ServiceMessageListener {
|
||||
@@ -153,5 +150,7 @@ abstract mixin class ServiceMessageListener {
|
||||
|
||||
onStarted(String runTime) {}
|
||||
|
||||
onLoaded(String providerName) {}
|
||||
onLoaded(String groupName) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => ClashConfig()
|
||||
..logLevel =
|
||||
$enumDecodeNullable(_$LogLevelEnumMap, json['log-level']) ?? LogLevel.info
|
||||
..externalController = json['external-controller'] as String? ?? ''
|
||||
..keepAliveInterval = (json['keep-alive-interval'] as num?)?.toInt() ?? 30
|
||||
..ipv6 = json['ipv6'] as bool? ?? false
|
||||
..geodataLoader = json['geodata-loader'] as String? ?? 'memconservative'
|
||||
..unifiedDelay = json['unified-delay'] as bool? ?? false
|
||||
@@ -76,7 +75,6 @@ Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) =>
|
||||
'allow-lan': instance.allowLan,
|
||||
'log-level': _$LogLevelEnumMap[instance.logLevel]!,
|
||||
'external-controller': instance.externalController,
|
||||
'keep-alive-interval': instance.keepAliveInterval,
|
||||
'ipv6': instance.ipv6,
|
||||
'geodata-loader': instance.geodataLoader,
|
||||
'unified-delay': instance.unifiedDelay,
|
||||
|
||||
@@ -23,7 +23,6 @@ mixin _$AccessControl {
|
||||
AccessControlMode get mode => throw _privateConstructorUsedError;
|
||||
List<String> get acceptList => throw _privateConstructorUsedError;
|
||||
List<String> get rejectList => throw _privateConstructorUsedError;
|
||||
AccessSortType get sort => throw _privateConstructorUsedError;
|
||||
bool get isFilterSystemApp => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -42,7 +41,6 @@ abstract class $AccessControlCopyWith<$Res> {
|
||||
{AccessControlMode mode,
|
||||
List<String> acceptList,
|
||||
List<String> rejectList,
|
||||
AccessSortType sort,
|
||||
bool isFilterSystemApp});
|
||||
}
|
||||
|
||||
@@ -62,7 +60,6 @@ class _$AccessControlCopyWithImpl<$Res, $Val extends AccessControl>
|
||||
Object? mode = null,
|
||||
Object? acceptList = null,
|
||||
Object? rejectList = null,
|
||||
Object? sort = null,
|
||||
Object? isFilterSystemApp = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
@@ -78,10 +75,6 @@ class _$AccessControlCopyWithImpl<$Res, $Val extends AccessControl>
|
||||
? _value.rejectList
|
||||
: rejectList // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
sort: null == sort
|
||||
? _value.sort
|
||||
: sort // ignore: cast_nullable_to_non_nullable
|
||||
as AccessSortType,
|
||||
isFilterSystemApp: null == isFilterSystemApp
|
||||
? _value.isFilterSystemApp
|
||||
: isFilterSystemApp // ignore: cast_nullable_to_non_nullable
|
||||
@@ -102,7 +95,6 @@ abstract class _$$AccessControlImplCopyWith<$Res>
|
||||
{AccessControlMode mode,
|
||||
List<String> acceptList,
|
||||
List<String> rejectList,
|
||||
AccessSortType sort,
|
||||
bool isFilterSystemApp});
|
||||
}
|
||||
|
||||
@@ -120,7 +112,6 @@ class __$$AccessControlImplCopyWithImpl<$Res>
|
||||
Object? mode = null,
|
||||
Object? acceptList = null,
|
||||
Object? rejectList = null,
|
||||
Object? sort = null,
|
||||
Object? isFilterSystemApp = null,
|
||||
}) {
|
||||
return _then(_$AccessControlImpl(
|
||||
@@ -136,10 +127,6 @@ class __$$AccessControlImplCopyWithImpl<$Res>
|
||||
? _value._rejectList
|
||||
: rejectList // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
sort: null == sort
|
||||
? _value.sort
|
||||
: sort // ignore: cast_nullable_to_non_nullable
|
||||
as AccessSortType,
|
||||
isFilterSystemApp: null == isFilterSystemApp
|
||||
? _value.isFilterSystemApp
|
||||
: isFilterSystemApp // ignore: cast_nullable_to_non_nullable
|
||||
@@ -155,7 +142,6 @@ class _$AccessControlImpl implements _AccessControl {
|
||||
{this.mode = AccessControlMode.rejectSelected,
|
||||
final List<String> acceptList = const [],
|
||||
final List<String> rejectList = const [],
|
||||
this.sort = AccessSortType.none,
|
||||
this.isFilterSystemApp = true})
|
||||
: _acceptList = acceptList,
|
||||
_rejectList = rejectList;
|
||||
@@ -184,16 +170,13 @@ class _$AccessControlImpl implements _AccessControl {
|
||||
return EqualUnmodifiableListView(_rejectList);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final AccessSortType sort;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFilterSystemApp;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AccessControl(mode: $mode, acceptList: $acceptList, rejectList: $rejectList, sort: $sort, isFilterSystemApp: $isFilterSystemApp)';
|
||||
return 'AccessControl(mode: $mode, acceptList: $acceptList, rejectList: $rejectList, isFilterSystemApp: $isFilterSystemApp)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -206,7 +189,6 @@ class _$AccessControlImpl implements _AccessControl {
|
||||
.equals(other._acceptList, _acceptList) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._rejectList, _rejectList) &&
|
||||
(identical(other.sort, sort) || other.sort == sort) &&
|
||||
(identical(other.isFilterSystemApp, isFilterSystemApp) ||
|
||||
other.isFilterSystemApp == isFilterSystemApp));
|
||||
}
|
||||
@@ -218,7 +200,6 @@ class _$AccessControlImpl implements _AccessControl {
|
||||
mode,
|
||||
const DeepCollectionEquality().hash(_acceptList),
|
||||
const DeepCollectionEquality().hash(_rejectList),
|
||||
sort,
|
||||
isFilterSystemApp);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@@ -240,7 +221,6 @@ abstract class _AccessControl implements AccessControl {
|
||||
{final AccessControlMode mode,
|
||||
final List<String> acceptList,
|
||||
final List<String> rejectList,
|
||||
final AccessSortType sort,
|
||||
final bool isFilterSystemApp}) = _$AccessControlImpl;
|
||||
|
||||
factory _AccessControl.fromJson(Map<String, dynamic> json) =
|
||||
@@ -253,8 +233,6 @@ abstract class _AccessControl implements AccessControl {
|
||||
@override
|
||||
List<String> get rejectList;
|
||||
@override
|
||||
AccessSortType get sort;
|
||||
@override
|
||||
bool get isFilterSystemApp;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
@@ -269,7 +247,6 @@ CoreState _$CoreStateFromJson(Map<String, dynamic> json) {
|
||||
/// @nodoc
|
||||
mixin _$CoreState {
|
||||
AccessControl? get accessControl => throw _privateConstructorUsedError;
|
||||
String get currentProfileName => throw _privateConstructorUsedError;
|
||||
bool get allowBypass => throw _privateConstructorUsedError;
|
||||
bool get systemProxy => throw _privateConstructorUsedError;
|
||||
int get mixedPort => throw _privateConstructorUsedError;
|
||||
@@ -288,7 +265,6 @@ abstract class $CoreStateCopyWith<$Res> {
|
||||
@useResult
|
||||
$Res call(
|
||||
{AccessControl? accessControl,
|
||||
String currentProfileName,
|
||||
bool allowBypass,
|
||||
bool systemProxy,
|
||||
int mixedPort,
|
||||
@@ -311,7 +287,6 @@ class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
|
||||
@override
|
||||
$Res call({
|
||||
Object? accessControl = freezed,
|
||||
Object? currentProfileName = null,
|
||||
Object? allowBypass = null,
|
||||
Object? systemProxy = null,
|
||||
Object? mixedPort = null,
|
||||
@@ -322,10 +297,6 @@ class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
|
||||
? _value.accessControl
|
||||
: accessControl // ignore: cast_nullable_to_non_nullable
|
||||
as AccessControl?,
|
||||
currentProfileName: null == currentProfileName
|
||||
? _value.currentProfileName
|
||||
: currentProfileName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
allowBypass: null == allowBypass
|
||||
? _value.allowBypass
|
||||
: allowBypass // ignore: cast_nullable_to_non_nullable
|
||||
@@ -368,7 +339,6 @@ abstract class _$$CoreStateImplCopyWith<$Res>
|
||||
@useResult
|
||||
$Res call(
|
||||
{AccessControl? accessControl,
|
||||
String currentProfileName,
|
||||
bool allowBypass,
|
||||
bool systemProxy,
|
||||
int mixedPort,
|
||||
@@ -390,7 +360,6 @@ class __$$CoreStateImplCopyWithImpl<$Res>
|
||||
@override
|
||||
$Res call({
|
||||
Object? accessControl = freezed,
|
||||
Object? currentProfileName = null,
|
||||
Object? allowBypass = null,
|
||||
Object? systemProxy = null,
|
||||
Object? mixedPort = null,
|
||||
@@ -401,10 +370,6 @@ class __$$CoreStateImplCopyWithImpl<$Res>
|
||||
? _value.accessControl
|
||||
: accessControl // ignore: cast_nullable_to_non_nullable
|
||||
as AccessControl?,
|
||||
currentProfileName: null == currentProfileName
|
||||
? _value.currentProfileName
|
||||
: currentProfileName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
allowBypass: null == allowBypass
|
||||
? _value.allowBypass
|
||||
: allowBypass // ignore: cast_nullable_to_non_nullable
|
||||
@@ -430,7 +395,6 @@ class __$$CoreStateImplCopyWithImpl<$Res>
|
||||
class _$CoreStateImpl implements _CoreState {
|
||||
const _$CoreStateImpl(
|
||||
{this.accessControl,
|
||||
required this.currentProfileName,
|
||||
required this.allowBypass,
|
||||
required this.systemProxy,
|
||||
required this.mixedPort,
|
||||
@@ -442,8 +406,6 @@ class _$CoreStateImpl implements _CoreState {
|
||||
@override
|
||||
final AccessControl? accessControl;
|
||||
@override
|
||||
final String currentProfileName;
|
||||
@override
|
||||
final bool allowBypass;
|
||||
@override
|
||||
final bool systemProxy;
|
||||
@@ -454,7 +416,7 @@ class _$CoreStateImpl implements _CoreState {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CoreState(accessControl: $accessControl, currentProfileName: $currentProfileName, allowBypass: $allowBypass, systemProxy: $systemProxy, mixedPort: $mixedPort, onlyProxy: $onlyProxy)';
|
||||
return 'CoreState(accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, mixedPort: $mixedPort, onlyProxy: $onlyProxy)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -464,8 +426,6 @@ class _$CoreStateImpl implements _CoreState {
|
||||
other is _$CoreStateImpl &&
|
||||
(identical(other.accessControl, accessControl) ||
|
||||
other.accessControl == accessControl) &&
|
||||
(identical(other.currentProfileName, currentProfileName) ||
|
||||
other.currentProfileName == currentProfileName) &&
|
||||
(identical(other.allowBypass, allowBypass) ||
|
||||
other.allowBypass == allowBypass) &&
|
||||
(identical(other.systemProxy, systemProxy) ||
|
||||
@@ -478,8 +438,8 @@ class _$CoreStateImpl implements _CoreState {
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, accessControl,
|
||||
currentProfileName, allowBypass, systemProxy, mixedPort, onlyProxy);
|
||||
int get hashCode => Object.hash(runtimeType, accessControl, allowBypass,
|
||||
systemProxy, mixedPort, onlyProxy);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -498,7 +458,6 @@ class _$CoreStateImpl implements _CoreState {
|
||||
abstract class _CoreState implements CoreState {
|
||||
const factory _CoreState(
|
||||
{final AccessControl? accessControl,
|
||||
required final String currentProfileName,
|
||||
required final bool allowBypass,
|
||||
required final bool systemProxy,
|
||||
required final int mixedPort,
|
||||
@@ -510,8 +469,6 @@ abstract class _CoreState implements CoreState {
|
||||
@override
|
||||
AccessControl? get accessControl;
|
||||
@override
|
||||
String get currentProfileName;
|
||||
@override
|
||||
bool get allowBypass;
|
||||
@override
|
||||
bool get systemProxy;
|
||||
|
||||
@@ -117,8 +117,6 @@ _$AccessControlImpl _$$AccessControlImplFromJson(Map<String, dynamic> json) =>
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const [],
|
||||
sort: $enumDecodeNullable(_$AccessSortTypeEnumMap, json['sort']) ??
|
||||
AccessSortType.none,
|
||||
isFilterSystemApp: json['isFilterSystemApp'] as bool? ?? true,
|
||||
);
|
||||
|
||||
@@ -127,7 +125,6 @@ Map<String, dynamic> _$$AccessControlImplToJson(_$AccessControlImpl instance) =>
|
||||
'mode': _$AccessControlModeEnumMap[instance.mode]!,
|
||||
'acceptList': instance.acceptList,
|
||||
'rejectList': instance.rejectList,
|
||||
'sort': _$AccessSortTypeEnumMap[instance.sort]!,
|
||||
'isFilterSystemApp': instance.isFilterSystemApp,
|
||||
};
|
||||
|
||||
@@ -136,19 +133,12 @@ const _$AccessControlModeEnumMap = {
|
||||
AccessControlMode.rejectSelected: 'rejectSelected',
|
||||
};
|
||||
|
||||
const _$AccessSortTypeEnumMap = {
|
||||
AccessSortType.none: 'none',
|
||||
AccessSortType.name: 'name',
|
||||
AccessSortType.time: 'time',
|
||||
};
|
||||
|
||||
_$CoreStateImpl _$$CoreStateImplFromJson(Map<String, dynamic> json) =>
|
||||
_$CoreStateImpl(
|
||||
accessControl: json['accessControl'] == null
|
||||
? null
|
||||
: AccessControl.fromJson(
|
||||
json['accessControl'] as Map<String, dynamic>),
|
||||
currentProfileName: json['currentProfileName'] as String,
|
||||
allowBypass: json['allowBypass'] as bool,
|
||||
systemProxy: json['systemProxy'] as bool,
|
||||
mixedPort: (json['mixedPort'] as num).toInt(),
|
||||
@@ -158,7 +148,6 @@ _$CoreStateImpl _$$CoreStateImplFromJson(Map<String, dynamic> json) =>
|
||||
Map<String, dynamic> _$$CoreStateImplToJson(_$CoreStateImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'accessControl': instance.accessControl,
|
||||
'currentProfileName': instance.currentProfileName,
|
||||
'allowBypass': instance.allowBypass,
|
||||
'systemProxy': instance.systemProxy,
|
||||
'mixedPort': instance.mixedPort,
|
||||
|
||||
@@ -248,8 +248,8 @@ UpdateConfigParams _$UpdateConfigParamsFromJson(Map<String, dynamic> json) {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UpdateConfigParams {
|
||||
@JsonKey(name: "profile-id")
|
||||
String get profileId => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "profile-path")
|
||||
String? get profilePath => throw _privateConstructorUsedError;
|
||||
ClashConfig get config => throw _privateConstructorUsedError;
|
||||
ConfigExtendedParams get params => throw _privateConstructorUsedError;
|
||||
|
||||
@@ -266,7 +266,7 @@ abstract class $UpdateConfigParamsCopyWith<$Res> {
|
||||
_$UpdateConfigParamsCopyWithImpl<$Res, UpdateConfigParams>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{@JsonKey(name: "profile-id") String profileId,
|
||||
{@JsonKey(name: "profile-path") String? profilePath,
|
||||
ClashConfig config,
|
||||
ConfigExtendedParams params});
|
||||
|
||||
@@ -286,15 +286,15 @@ class _$UpdateConfigParamsCopyWithImpl<$Res, $Val extends UpdateConfigParams>
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? profileId = null,
|
||||
Object? profilePath = freezed,
|
||||
Object? config = null,
|
||||
Object? params = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
profileId: null == profileId
|
||||
? _value.profileId
|
||||
: profileId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
profilePath: freezed == profilePath
|
||||
? _value.profilePath
|
||||
: profilePath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
config: null == config
|
||||
? _value.config
|
||||
: config // ignore: cast_nullable_to_non_nullable
|
||||
@@ -324,7 +324,7 @@ abstract class _$$UpdateConfigParamsImplCopyWith<$Res>
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{@JsonKey(name: "profile-id") String profileId,
|
||||
{@JsonKey(name: "profile-path") String? profilePath,
|
||||
ClashConfig config,
|
||||
ConfigExtendedParams params});
|
||||
|
||||
@@ -343,15 +343,15 @@ class __$$UpdateConfigParamsImplCopyWithImpl<$Res>
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? profileId = null,
|
||||
Object? profilePath = freezed,
|
||||
Object? config = null,
|
||||
Object? params = null,
|
||||
}) {
|
||||
return _then(_$UpdateConfigParamsImpl(
|
||||
profileId: null == profileId
|
||||
? _value.profileId
|
||||
: profileId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
profilePath: freezed == profilePath
|
||||
? _value.profilePath
|
||||
: profilePath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
config: null == config
|
||||
? _value.config
|
||||
: config // ignore: cast_nullable_to_non_nullable
|
||||
@@ -368,7 +368,7 @@ class __$$UpdateConfigParamsImplCopyWithImpl<$Res>
|
||||
@JsonSerializable()
|
||||
class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
|
||||
const _$UpdateConfigParamsImpl(
|
||||
{@JsonKey(name: "profile-id") required this.profileId,
|
||||
{@JsonKey(name: "profile-path") this.profilePath,
|
||||
required this.config,
|
||||
required this.params});
|
||||
|
||||
@@ -376,8 +376,8 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
|
||||
_$$UpdateConfigParamsImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey(name: "profile-id")
|
||||
final String profileId;
|
||||
@JsonKey(name: "profile-path")
|
||||
final String? profilePath;
|
||||
@override
|
||||
final ClashConfig config;
|
||||
@override
|
||||
@@ -385,7 +385,7 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UpdateConfigParams(profileId: $profileId, config: $config, params: $params)';
|
||||
return 'UpdateConfigParams(profilePath: $profilePath, config: $config, params: $params)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -393,15 +393,15 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$UpdateConfigParamsImpl &&
|
||||
(identical(other.profileId, profileId) ||
|
||||
other.profileId == profileId) &&
|
||||
(identical(other.profilePath, profilePath) ||
|
||||
other.profilePath == profilePath) &&
|
||||
(identical(other.config, config) || other.config == config) &&
|
||||
(identical(other.params, params) || other.params == params));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, profileId, config, params);
|
||||
int get hashCode => Object.hash(runtimeType, profilePath, config, params);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -420,7 +420,7 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
|
||||
|
||||
abstract class _UpdateConfigParams implements UpdateConfigParams {
|
||||
const factory _UpdateConfigParams(
|
||||
{@JsonKey(name: "profile-id") required final String profileId,
|
||||
{@JsonKey(name: "profile-path") final String? profilePath,
|
||||
required final ClashConfig config,
|
||||
required final ConfigExtendedParams params}) = _$UpdateConfigParamsImpl;
|
||||
|
||||
@@ -428,8 +428,8 @@ abstract class _UpdateConfigParams implements UpdateConfigParams {
|
||||
_$UpdateConfigParamsImpl.fromJson;
|
||||
|
||||
@override
|
||||
@JsonKey(name: "profile-id")
|
||||
String get profileId;
|
||||
@JsonKey(name: "profile-path")
|
||||
String? get profilePath;
|
||||
@override
|
||||
ClashConfig get config;
|
||||
@override
|
||||
@@ -1687,9 +1687,6 @@ ExternalProvider _$ExternalProviderFromJson(Map<String, dynamic> json) {
|
||||
mixin _$ExternalProvider {
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
String get type => throw _privateConstructorUsedError;
|
||||
String get path => throw _privateConstructorUsedError;
|
||||
int get count => throw _privateConstructorUsedError;
|
||||
bool get isUpdating => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "vehicle-type")
|
||||
String get vehicleType => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "update-at")
|
||||
@@ -1710,9 +1707,6 @@ abstract class $ExternalProviderCopyWith<$Res> {
|
||||
$Res call(
|
||||
{String name,
|
||||
String type,
|
||||
String path,
|
||||
int count,
|
||||
bool isUpdating,
|
||||
@JsonKey(name: "vehicle-type") String vehicleType,
|
||||
@JsonKey(name: "update-at") DateTime updateAt});
|
||||
}
|
||||
@@ -1732,9 +1726,6 @@ class _$ExternalProviderCopyWithImpl<$Res, $Val extends ExternalProvider>
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? type = null,
|
||||
Object? path = null,
|
||||
Object? count = null,
|
||||
Object? isUpdating = null,
|
||||
Object? vehicleType = null,
|
||||
Object? updateAt = null,
|
||||
}) {
|
||||
@@ -1747,18 +1738,6 @@ class _$ExternalProviderCopyWithImpl<$Res, $Val extends ExternalProvider>
|
||||
? _value.type
|
||||
: type // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
path: null == path
|
||||
? _value.path
|
||||
: path // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
count: null == count
|
||||
? _value.count
|
||||
: count // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
isUpdating: null == isUpdating
|
||||
? _value.isUpdating
|
||||
: isUpdating // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
vehicleType: null == vehicleType
|
||||
? _value.vehicleType
|
||||
: vehicleType // ignore: cast_nullable_to_non_nullable
|
||||
@@ -1782,9 +1761,6 @@ abstract class _$$ExternalProviderImplCopyWith<$Res>
|
||||
$Res call(
|
||||
{String name,
|
||||
String type,
|
||||
String path,
|
||||
int count,
|
||||
bool isUpdating,
|
||||
@JsonKey(name: "vehicle-type") String vehicleType,
|
||||
@JsonKey(name: "update-at") DateTime updateAt});
|
||||
}
|
||||
@@ -1802,9 +1778,6 @@ class __$$ExternalProviderImplCopyWithImpl<$Res>
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? type = null,
|
||||
Object? path = null,
|
||||
Object? count = null,
|
||||
Object? isUpdating = null,
|
||||
Object? vehicleType = null,
|
||||
Object? updateAt = null,
|
||||
}) {
|
||||
@@ -1817,18 +1790,6 @@ class __$$ExternalProviderImplCopyWithImpl<$Res>
|
||||
? _value.type
|
||||
: type // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
path: null == path
|
||||
? _value.path
|
||||
: path // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
count: null == count
|
||||
? _value.count
|
||||
: count // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
isUpdating: null == isUpdating
|
||||
? _value.isUpdating
|
||||
: isUpdating // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
vehicleType: null == vehicleType
|
||||
? _value.vehicleType
|
||||
: vehicleType // ignore: cast_nullable_to_non_nullable
|
||||
@@ -1847,9 +1808,6 @@ class _$ExternalProviderImpl implements _ExternalProvider {
|
||||
const _$ExternalProviderImpl(
|
||||
{required this.name,
|
||||
required this.type,
|
||||
required this.path,
|
||||
required this.count,
|
||||
this.isUpdating = false,
|
||||
@JsonKey(name: "vehicle-type") required this.vehicleType,
|
||||
@JsonKey(name: "update-at") required this.updateAt});
|
||||
|
||||
@@ -1861,13 +1819,6 @@ class _$ExternalProviderImpl implements _ExternalProvider {
|
||||
@override
|
||||
final String type;
|
||||
@override
|
||||
final String path;
|
||||
@override
|
||||
final int count;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isUpdating;
|
||||
@override
|
||||
@JsonKey(name: "vehicle-type")
|
||||
final String vehicleType;
|
||||
@override
|
||||
@@ -1876,7 +1827,7 @@ class _$ExternalProviderImpl implements _ExternalProvider {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ExternalProvider(name: $name, type: $type, path: $path, count: $count, isUpdating: $isUpdating, vehicleType: $vehicleType, updateAt: $updateAt)';
|
||||
return 'ExternalProvider(name: $name, type: $type, vehicleType: $vehicleType, updateAt: $updateAt)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1886,10 +1837,6 @@ class _$ExternalProviderImpl implements _ExternalProvider {
|
||||
other is _$ExternalProviderImpl &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.type, type) || other.type == type) &&
|
||||
(identical(other.path, path) || other.path == path) &&
|
||||
(identical(other.count, count) || other.count == count) &&
|
||||
(identical(other.isUpdating, isUpdating) ||
|
||||
other.isUpdating == isUpdating) &&
|
||||
(identical(other.vehicleType, vehicleType) ||
|
||||
other.vehicleType == vehicleType) &&
|
||||
(identical(other.updateAt, updateAt) ||
|
||||
@@ -1898,8 +1845,8 @@ class _$ExternalProviderImpl implements _ExternalProvider {
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, name, type, path, count, isUpdating, vehicleType, updateAt);
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, name, type, vehicleType, updateAt);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -1920,9 +1867,6 @@ abstract class _ExternalProvider implements ExternalProvider {
|
||||
const factory _ExternalProvider(
|
||||
{required final String name,
|
||||
required final String type,
|
||||
required final String path,
|
||||
required final int count,
|
||||
final bool isUpdating,
|
||||
@JsonKey(name: "vehicle-type") required final String vehicleType,
|
||||
@JsonKey(name: "update-at") required final DateTime updateAt}) =
|
||||
_$ExternalProviderImpl;
|
||||
@@ -1935,12 +1879,6 @@ abstract class _ExternalProvider implements ExternalProvider {
|
||||
@override
|
||||
String get type;
|
||||
@override
|
||||
String get path;
|
||||
@override
|
||||
int get count;
|
||||
@override
|
||||
bool get isUpdating;
|
||||
@override
|
||||
@JsonKey(name: "vehicle-type")
|
||||
String get vehicleType;
|
||||
@override
|
||||
|
||||
@@ -27,7 +27,7 @@ Map<String, dynamic> _$$ConfigExtendedParamsImplToJson(
|
||||
_$UpdateConfigParamsImpl _$$UpdateConfigParamsImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$UpdateConfigParamsImpl(
|
||||
profileId: json['profile-id'] as String,
|
||||
profilePath: json['profile-path'] as String?,
|
||||
config: ClashConfig.fromJson(json['config'] as Map<String, dynamic>),
|
||||
params:
|
||||
ConfigExtendedParams.fromJson(json['params'] as Map<String, dynamic>),
|
||||
@@ -36,7 +36,7 @@ _$UpdateConfigParamsImpl _$$UpdateConfigParamsImplFromJson(
|
||||
Map<String, dynamic> _$$UpdateConfigParamsImplToJson(
|
||||
_$UpdateConfigParamsImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'profile-id': instance.profileId,
|
||||
'profile-path': instance.profilePath,
|
||||
'config': instance.config,
|
||||
'params': instance.params,
|
||||
};
|
||||
@@ -156,9 +156,6 @@ _$ExternalProviderImpl _$$ExternalProviderImplFromJson(
|
||||
_$ExternalProviderImpl(
|
||||
name: json['name'] as String,
|
||||
type: json['type'] as String,
|
||||
path: json['path'] as String,
|
||||
count: (json['count'] as num).toInt(),
|
||||
isUpdating: json['isUpdating'] as bool? ?? false,
|
||||
vehicleType: json['vehicle-type'] as String,
|
||||
updateAt: DateTime.parse(json['update-at'] as String),
|
||||
);
|
||||
@@ -168,9 +165,6 @@ Map<String, dynamic> _$$ExternalProviderImplToJson(
|
||||
<String, dynamic>{
|
||||
'name': instance.name,
|
||||
'type': instance.type,
|
||||
'path': instance.path,
|
||||
'count': instance.count,
|
||||
'isUpdating': instance.isUpdating,
|
||||
'vehicle-type': instance.vehicleType,
|
||||
'update-at': instance.updateAt.toIso8601String(),
|
||||
};
|
||||
|
||||
@@ -23,7 +23,6 @@ mixin _$Package {
|
||||
String get packageName => throw _privateConstructorUsedError;
|
||||
String get label => throw _privateConstructorUsedError;
|
||||
bool get isSystem => throw _privateConstructorUsedError;
|
||||
int get firstInstallTime => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
@@ -35,8 +34,7 @@ abstract class $PackageCopyWith<$Res> {
|
||||
factory $PackageCopyWith(Package value, $Res Function(Package) then) =
|
||||
_$PackageCopyWithImpl<$Res, Package>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String packageName, String label, bool isSystem, int firstInstallTime});
|
||||
$Res call({String packageName, String label, bool isSystem});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -55,7 +53,6 @@ class _$PackageCopyWithImpl<$Res, $Val extends Package>
|
||||
Object? packageName = null,
|
||||
Object? label = null,
|
||||
Object? isSystem = null,
|
||||
Object? firstInstallTime = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
packageName: null == packageName
|
||||
@@ -70,10 +67,6 @@ class _$PackageCopyWithImpl<$Res, $Val extends Package>
|
||||
? _value.isSystem
|
||||
: isSystem // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
firstInstallTime: null == firstInstallTime
|
||||
? _value.firstInstallTime
|
||||
: firstInstallTime // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -85,8 +78,7 @@ abstract class _$$PackageImplCopyWith<$Res> implements $PackageCopyWith<$Res> {
|
||||
__$$PackageImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String packageName, String label, bool isSystem, int firstInstallTime});
|
||||
$Res call({String packageName, String label, bool isSystem});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -103,7 +95,6 @@ class __$$PackageImplCopyWithImpl<$Res>
|
||||
Object? packageName = null,
|
||||
Object? label = null,
|
||||
Object? isSystem = null,
|
||||
Object? firstInstallTime = null,
|
||||
}) {
|
||||
return _then(_$PackageImpl(
|
||||
packageName: null == packageName
|
||||
@@ -118,10 +109,6 @@ class __$$PackageImplCopyWithImpl<$Res>
|
||||
? _value.isSystem
|
||||
: isSystem // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
firstInstallTime: null == firstInstallTime
|
||||
? _value.firstInstallTime
|
||||
: firstInstallTime // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -130,10 +117,7 @@ class __$$PackageImplCopyWithImpl<$Res>
|
||||
@JsonSerializable()
|
||||
class _$PackageImpl implements _Package {
|
||||
const _$PackageImpl(
|
||||
{required this.packageName,
|
||||
required this.label,
|
||||
required this.isSystem,
|
||||
required this.firstInstallTime});
|
||||
{required this.packageName, required this.label, required this.isSystem});
|
||||
|
||||
factory _$PackageImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$PackageImplFromJson(json);
|
||||
@@ -144,12 +128,10 @@ class _$PackageImpl implements _Package {
|
||||
final String label;
|
||||
@override
|
||||
final bool isSystem;
|
||||
@override
|
||||
final int firstInstallTime;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Package(packageName: $packageName, label: $label, isSystem: $isSystem, firstInstallTime: $firstInstallTime)';
|
||||
return 'Package(packageName: $packageName, label: $label, isSystem: $isSystem)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -161,15 +143,12 @@ class _$PackageImpl implements _Package {
|
||||
other.packageName == packageName) &&
|
||||
(identical(other.label, label) || other.label == label) &&
|
||||
(identical(other.isSystem, isSystem) ||
|
||||
other.isSystem == isSystem) &&
|
||||
(identical(other.firstInstallTime, firstInstallTime) ||
|
||||
other.firstInstallTime == firstInstallTime));
|
||||
other.isSystem == isSystem));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, packageName, label, isSystem, firstInstallTime);
|
||||
int get hashCode => Object.hash(runtimeType, packageName, label, isSystem);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -189,8 +168,7 @@ abstract class _Package implements Package {
|
||||
const factory _Package(
|
||||
{required final String packageName,
|
||||
required final String label,
|
||||
required final bool isSystem,
|
||||
required final int firstInstallTime}) = _$PackageImpl;
|
||||
required final bool isSystem}) = _$PackageImpl;
|
||||
|
||||
factory _Package.fromJson(Map<String, dynamic> json) = _$PackageImpl.fromJson;
|
||||
|
||||
@@ -201,8 +179,6 @@ abstract class _Package implements Package {
|
||||
@override
|
||||
bool get isSystem;
|
||||
@override
|
||||
int get firstInstallTime;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$PackageImplCopyWith<_$PackageImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
@@ -11,7 +11,6 @@ _$PackageImpl _$$PackageImplFromJson(Map<String, dynamic> json) =>
|
||||
packageName: json['packageName'] as String,
|
||||
label: json['label'] as String,
|
||||
isSystem: json['isSystem'] as bool,
|
||||
firstInstallTime: (json['firstInstallTime'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) =>
|
||||
@@ -19,5 +18,4 @@ Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) =>
|
||||
'packageName': instance.packageName,
|
||||
'label': instance.label,
|
||||
'isSystem': instance.isSystem,
|
||||
'firstInstallTime': instance.firstInstallTime,
|
||||
};
|
||||
|
||||
@@ -223,8 +223,6 @@ mixin _$Profile {
|
||||
bool get autoUpdate => throw _privateConstructorUsedError;
|
||||
Map<String, String> get selectedMap => throw _privateConstructorUsedError;
|
||||
Set<String> get unfoldSet => throw _privateConstructorUsedError;
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
bool get isUpdating => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
@@ -246,8 +244,7 @@ abstract class $ProfileCopyWith<$Res> {
|
||||
UserInfo? userInfo,
|
||||
bool autoUpdate,
|
||||
Map<String, String> selectedMap,
|
||||
Set<String> unfoldSet,
|
||||
@JsonKey(includeToJson: false, includeFromJson: false) bool isUpdating});
|
||||
Set<String> unfoldSet});
|
||||
|
||||
$UserInfoCopyWith<$Res>? get userInfo;
|
||||
}
|
||||
@@ -275,7 +272,6 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
|
||||
Object? autoUpdate = null,
|
||||
Object? selectedMap = null,
|
||||
Object? unfoldSet = null,
|
||||
Object? isUpdating = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
@@ -318,10 +314,6 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
|
||||
? _value.unfoldSet
|
||||
: unfoldSet // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,
|
||||
isUpdating: null == isUpdating
|
||||
? _value.isUpdating
|
||||
: isUpdating // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@@ -355,8 +347,7 @@ abstract class _$$ProfileImplCopyWith<$Res> implements $ProfileCopyWith<$Res> {
|
||||
UserInfo? userInfo,
|
||||
bool autoUpdate,
|
||||
Map<String, String> selectedMap,
|
||||
Set<String> unfoldSet,
|
||||
@JsonKey(includeToJson: false, includeFromJson: false) bool isUpdating});
|
||||
Set<String> unfoldSet});
|
||||
|
||||
@override
|
||||
$UserInfoCopyWith<$Res>? get userInfo;
|
||||
@@ -383,7 +374,6 @@ class __$$ProfileImplCopyWithImpl<$Res>
|
||||
Object? autoUpdate = null,
|
||||
Object? selectedMap = null,
|
||||
Object? unfoldSet = null,
|
||||
Object? isUpdating = null,
|
||||
}) {
|
||||
return _then(_$ProfileImpl(
|
||||
id: null == id
|
||||
@@ -426,10 +416,6 @@ class __$$ProfileImplCopyWithImpl<$Res>
|
||||
? _value._unfoldSet
|
||||
: unfoldSet // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,
|
||||
isUpdating: null == isUpdating
|
||||
? _value.isUpdating
|
||||
: isUpdating // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -447,9 +433,7 @@ class _$ProfileImpl implements _Profile {
|
||||
this.userInfo,
|
||||
this.autoUpdate = true,
|
||||
final Map<String, String> selectedMap = const {},
|
||||
final Set<String> unfoldSet = const {},
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
this.isUpdating = false})
|
||||
final Set<String> unfoldSet = const {}})
|
||||
: _selectedMap = selectedMap,
|
||||
_unfoldSet = unfoldSet;
|
||||
|
||||
@@ -492,13 +476,9 @@ class _$ProfileImpl implements _Profile {
|
||||
return EqualUnmodifiableSetView(_unfoldSet);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
final bool isUpdating;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Profile(id: $id, label: $label, currentGroupName: $currentGroupName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate, selectedMap: $selectedMap, unfoldSet: $unfoldSet, isUpdating: $isUpdating)';
|
||||
return 'Profile(id: $id, label: $label, currentGroupName: $currentGroupName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate, selectedMap: $selectedMap, unfoldSet: $unfoldSet)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -522,9 +502,7 @@ class _$ProfileImpl implements _Profile {
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._selectedMap, _selectedMap) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._unfoldSet, _unfoldSet) &&
|
||||
(identical(other.isUpdating, isUpdating) ||
|
||||
other.isUpdating == isUpdating));
|
||||
.equals(other._unfoldSet, _unfoldSet));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@@ -540,8 +518,7 @@ class _$ProfileImpl implements _Profile {
|
||||
userInfo,
|
||||
autoUpdate,
|
||||
const DeepCollectionEquality().hash(_selectedMap),
|
||||
const DeepCollectionEquality().hash(_unfoldSet),
|
||||
isUpdating);
|
||||
const DeepCollectionEquality().hash(_unfoldSet));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -568,9 +545,7 @@ abstract class _Profile implements Profile {
|
||||
final UserInfo? userInfo,
|
||||
final bool autoUpdate,
|
||||
final Map<String, String> selectedMap,
|
||||
final Set<String> unfoldSet,
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
final bool isUpdating}) = _$ProfileImpl;
|
||||
final Set<String> unfoldSet}) = _$ProfileImpl;
|
||||
|
||||
factory _Profile.fromJson(Map<String, dynamic> json) = _$ProfileImpl.fromJson;
|
||||
|
||||
@@ -595,9 +570,6 @@ abstract class _Profile implements Profile {
|
||||
@override
|
||||
Set<String> get unfoldSet;
|
||||
@override
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
bool get isUpdating;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ProfileImplCopyWith<_$ProfileImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
@@ -2372,7 +2372,6 @@ abstract class _MoreToolsSelectorState implements MoreToolsSelectorState {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PackageListSelectorState {
|
||||
List<Package> get packages => throw _privateConstructorUsedError;
|
||||
AccessControl get accessControl => throw _privateConstructorUsedError;
|
||||
bool get isAccessControl => throw _privateConstructorUsedError;
|
||||
|
||||
@@ -2387,10 +2386,7 @@ abstract class $PackageListSelectorStateCopyWith<$Res> {
|
||||
$Res Function(PackageListSelectorState) then) =
|
||||
_$PackageListSelectorStateCopyWithImpl<$Res, PackageListSelectorState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{List<Package> packages,
|
||||
AccessControl accessControl,
|
||||
bool isAccessControl});
|
||||
$Res call({AccessControl accessControl, bool isAccessControl});
|
||||
|
||||
$AccessControlCopyWith<$Res> get accessControl;
|
||||
}
|
||||
@@ -2409,15 +2405,10 @@ class _$PackageListSelectorStateCopyWithImpl<$Res,
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? packages = null,
|
||||
Object? accessControl = null,
|
||||
Object? isAccessControl = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
packages: null == packages
|
||||
? _value.packages
|
||||
: packages // ignore: cast_nullable_to_non_nullable
|
||||
as List<Package>,
|
||||
accessControl: null == accessControl
|
||||
? _value.accessControl
|
||||
: accessControl // ignore: cast_nullable_to_non_nullable
|
||||
@@ -2447,10 +2438,7 @@ abstract class _$$PackageListSelectorStateImplCopyWith<$Res>
|
||||
__$$PackageListSelectorStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{List<Package> packages,
|
||||
AccessControl accessControl,
|
||||
bool isAccessControl});
|
||||
$Res call({AccessControl accessControl, bool isAccessControl});
|
||||
|
||||
@override
|
||||
$AccessControlCopyWith<$Res> get accessControl;
|
||||
@@ -2469,15 +2457,10 @@ class __$$PackageListSelectorStateImplCopyWithImpl<$Res>
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? packages = null,
|
||||
Object? accessControl = null,
|
||||
Object? isAccessControl = null,
|
||||
}) {
|
||||
return _then(_$PackageListSelectorStateImpl(
|
||||
packages: null == packages
|
||||
? _value._packages
|
||||
: packages // ignore: cast_nullable_to_non_nullable
|
||||
as List<Package>,
|
||||
accessControl: null == accessControl
|
||||
? _value.accessControl
|
||||
: accessControl // ignore: cast_nullable_to_non_nullable
|
||||
@@ -2494,18 +2477,7 @@ class __$$PackageListSelectorStateImplCopyWithImpl<$Res>
|
||||
|
||||
class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
|
||||
const _$PackageListSelectorStateImpl(
|
||||
{required final List<Package> packages,
|
||||
required this.accessControl,
|
||||
required this.isAccessControl})
|
||||
: _packages = packages;
|
||||
|
||||
final List<Package> _packages;
|
||||
@override
|
||||
List<Package> get packages {
|
||||
if (_packages is EqualUnmodifiableListView) return _packages;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_packages);
|
||||
}
|
||||
{required this.accessControl, required this.isAccessControl});
|
||||
|
||||
@override
|
||||
final AccessControl accessControl;
|
||||
@@ -2514,7 +2486,7 @@ class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PackageListSelectorState(packages: $packages, accessControl: $accessControl, isAccessControl: $isAccessControl)';
|
||||
return 'PackageListSelectorState(accessControl: $accessControl, isAccessControl: $isAccessControl)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2522,7 +2494,6 @@ class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$PackageListSelectorStateImpl &&
|
||||
const DeepCollectionEquality().equals(other._packages, _packages) &&
|
||||
(identical(other.accessControl, accessControl) ||
|
||||
other.accessControl == accessControl) &&
|
||||
(identical(other.isAccessControl, isAccessControl) ||
|
||||
@@ -2530,11 +2501,7 @@ class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
const DeepCollectionEquality().hash(_packages),
|
||||
accessControl,
|
||||
isAccessControl);
|
||||
int get hashCode => Object.hash(runtimeType, accessControl, isAccessControl);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -2546,12 +2513,9 @@ class _$PackageListSelectorStateImpl implements _PackageListSelectorState {
|
||||
|
||||
abstract class _PackageListSelectorState implements PackageListSelectorState {
|
||||
const factory _PackageListSelectorState(
|
||||
{required final List<Package> packages,
|
||||
required final AccessControl accessControl,
|
||||
{required final AccessControl accessControl,
|
||||
required final bool isAccessControl}) = _$PackageListSelectorStateImpl;
|
||||
|
||||
@override
|
||||
List<Package> get packages;
|
||||
@override
|
||||
AccessControl get accessControl;
|
||||
@override
|
||||
@@ -2851,28 +2815,32 @@ abstract class _ProxiesListHeaderSelectorState
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProxiesActionsState {
|
||||
bool get isCurrent => throw _privateConstructorUsedError;
|
||||
bool get hasProvider => throw _privateConstructorUsedError;
|
||||
mixin _$CurrentGroupProxyNameSelectorState {
|
||||
String? get proxyName => throw _privateConstructorUsedError;
|
||||
String? get proxyName2 => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$ProxiesActionsStateCopyWith<ProxiesActionsState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
$CurrentGroupProxyNameSelectorStateCopyWith<
|
||||
CurrentGroupProxyNameSelectorState>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ProxiesActionsStateCopyWith<$Res> {
|
||||
factory $ProxiesActionsStateCopyWith(
|
||||
ProxiesActionsState value, $Res Function(ProxiesActionsState) then) =
|
||||
_$ProxiesActionsStateCopyWithImpl<$Res, ProxiesActionsState>;
|
||||
abstract class $CurrentGroupProxyNameSelectorStateCopyWith<$Res> {
|
||||
factory $CurrentGroupProxyNameSelectorStateCopyWith(
|
||||
CurrentGroupProxyNameSelectorState value,
|
||||
$Res Function(CurrentGroupProxyNameSelectorState) then) =
|
||||
_$CurrentGroupProxyNameSelectorStateCopyWithImpl<$Res,
|
||||
CurrentGroupProxyNameSelectorState>;
|
||||
@useResult
|
||||
$Res call({bool isCurrent, bool hasProvider});
|
||||
$Res call({String? proxyName, String? proxyName2});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ProxiesActionsStateCopyWithImpl<$Res, $Val extends ProxiesActionsState>
|
||||
implements $ProxiesActionsStateCopyWith<$Res> {
|
||||
_$ProxiesActionsStateCopyWithImpl(this._value, this._then);
|
||||
class _$CurrentGroupProxyNameSelectorStateCopyWithImpl<$Res,
|
||||
$Val extends CurrentGroupProxyNameSelectorState>
|
||||
implements $CurrentGroupProxyNameSelectorStateCopyWith<$Res> {
|
||||
_$CurrentGroupProxyNameSelectorStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
@@ -2882,109 +2850,117 @@ class _$ProxiesActionsStateCopyWithImpl<$Res, $Val extends ProxiesActionsState>
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isCurrent = null,
|
||||
Object? hasProvider = null,
|
||||
Object? proxyName = freezed,
|
||||
Object? proxyName2 = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
isCurrent: null == isCurrent
|
||||
? _value.isCurrent
|
||||
: isCurrent // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
hasProvider: null == hasProvider
|
||||
? _value.hasProvider
|
||||
: hasProvider // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
proxyName: freezed == proxyName
|
||||
? _value.proxyName
|
||||
: proxyName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
proxyName2: freezed == proxyName2
|
||||
? _value.proxyName2
|
||||
: proxyName2 // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ProxiesActionsStateImplCopyWith<$Res>
|
||||
implements $ProxiesActionsStateCopyWith<$Res> {
|
||||
factory _$$ProxiesActionsStateImplCopyWith(_$ProxiesActionsStateImpl value,
|
||||
$Res Function(_$ProxiesActionsStateImpl) then) =
|
||||
__$$ProxiesActionsStateImplCopyWithImpl<$Res>;
|
||||
abstract class _$$CurrentGroupProxyNameSelectorStateImplCopyWith<$Res>
|
||||
implements $CurrentGroupProxyNameSelectorStateCopyWith<$Res> {
|
||||
factory _$$CurrentGroupProxyNameSelectorStateImplCopyWith(
|
||||
_$CurrentGroupProxyNameSelectorStateImpl value,
|
||||
$Res Function(_$CurrentGroupProxyNameSelectorStateImpl) then) =
|
||||
__$$CurrentGroupProxyNameSelectorStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({bool isCurrent, bool hasProvider});
|
||||
$Res call({String? proxyName, String? proxyName2});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ProxiesActionsStateImplCopyWithImpl<$Res>
|
||||
extends _$ProxiesActionsStateCopyWithImpl<$Res, _$ProxiesActionsStateImpl>
|
||||
implements _$$ProxiesActionsStateImplCopyWith<$Res> {
|
||||
__$$ProxiesActionsStateImplCopyWithImpl(_$ProxiesActionsStateImpl _value,
|
||||
$Res Function(_$ProxiesActionsStateImpl) _then)
|
||||
class __$$CurrentGroupProxyNameSelectorStateImplCopyWithImpl<$Res>
|
||||
extends _$CurrentGroupProxyNameSelectorStateCopyWithImpl<$Res,
|
||||
_$CurrentGroupProxyNameSelectorStateImpl>
|
||||
implements _$$CurrentGroupProxyNameSelectorStateImplCopyWith<$Res> {
|
||||
__$$CurrentGroupProxyNameSelectorStateImplCopyWithImpl(
|
||||
_$CurrentGroupProxyNameSelectorStateImpl _value,
|
||||
$Res Function(_$CurrentGroupProxyNameSelectorStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isCurrent = null,
|
||||
Object? hasProvider = null,
|
||||
Object? proxyName = freezed,
|
||||
Object? proxyName2 = freezed,
|
||||
}) {
|
||||
return _then(_$ProxiesActionsStateImpl(
|
||||
isCurrent: null == isCurrent
|
||||
? _value.isCurrent
|
||||
: isCurrent // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
hasProvider: null == hasProvider
|
||||
? _value.hasProvider
|
||||
: hasProvider // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
return _then(_$CurrentGroupProxyNameSelectorStateImpl(
|
||||
proxyName: freezed == proxyName
|
||||
? _value.proxyName
|
||||
: proxyName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
proxyName2: freezed == proxyName2
|
||||
? _value.proxyName2
|
||||
: proxyName2 // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ProxiesActionsStateImpl implements _ProxiesActionsState {
|
||||
const _$ProxiesActionsStateImpl(
|
||||
{required this.isCurrent, required this.hasProvider});
|
||||
class _$CurrentGroupProxyNameSelectorStateImpl
|
||||
implements _CurrentGroupProxyNameSelectorState {
|
||||
const _$CurrentGroupProxyNameSelectorStateImpl(
|
||||
{required this.proxyName, required this.proxyName2});
|
||||
|
||||
@override
|
||||
final bool isCurrent;
|
||||
final String? proxyName;
|
||||
@override
|
||||
final bool hasProvider;
|
||||
final String? proxyName2;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProxiesActionsState(isCurrent: $isCurrent, hasProvider: $hasProvider)';
|
||||
return 'CurrentGroupProxyNameSelectorState(proxyName: $proxyName, proxyName2: $proxyName2)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ProxiesActionsStateImpl &&
|
||||
(identical(other.isCurrent, isCurrent) ||
|
||||
other.isCurrent == isCurrent) &&
|
||||
(identical(other.hasProvider, hasProvider) ||
|
||||
other.hasProvider == hasProvider));
|
||||
other is _$CurrentGroupProxyNameSelectorStateImpl &&
|
||||
(identical(other.proxyName, proxyName) ||
|
||||
other.proxyName == proxyName) &&
|
||||
(identical(other.proxyName2, proxyName2) ||
|
||||
other.proxyName2 == proxyName2));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, isCurrent, hasProvider);
|
||||
int get hashCode => Object.hash(runtimeType, proxyName, proxyName2);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ProxiesActionsStateImplCopyWith<_$ProxiesActionsStateImpl> get copyWith =>
|
||||
__$$ProxiesActionsStateImplCopyWithImpl<_$ProxiesActionsStateImpl>(
|
||||
this, _$identity);
|
||||
_$$CurrentGroupProxyNameSelectorStateImplCopyWith<
|
||||
_$CurrentGroupProxyNameSelectorStateImpl>
|
||||
get copyWith => __$$CurrentGroupProxyNameSelectorStateImplCopyWithImpl<
|
||||
_$CurrentGroupProxyNameSelectorStateImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ProxiesActionsState implements ProxiesActionsState {
|
||||
const factory _ProxiesActionsState(
|
||||
{required final bool isCurrent,
|
||||
required final bool hasProvider}) = _$ProxiesActionsStateImpl;
|
||||
abstract class _CurrentGroupProxyNameSelectorState
|
||||
implements CurrentGroupProxyNameSelectorState {
|
||||
const factory _CurrentGroupProxyNameSelectorState(
|
||||
{required final String? proxyName,
|
||||
required final String? proxyName2}) =
|
||||
_$CurrentGroupProxyNameSelectorStateImpl;
|
||||
|
||||
@override
|
||||
bool get isCurrent;
|
||||
String? get proxyName;
|
||||
@override
|
||||
bool get hasProvider;
|
||||
String? get proxyName2;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ProxiesActionsStateImplCopyWith<_$ProxiesActionsStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
_$$CurrentGroupProxyNameSelectorStateImplCopyWith<
|
||||
_$CurrentGroupProxyNameSelectorStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ class Package with _$Package {
|
||||
required String packageName,
|
||||
required String label,
|
||||
required bool isSystem,
|
||||
required int firstInstallTime,
|
||||
}) = _Package;
|
||||
|
||||
factory Package.fromJson(Map<String, Object?> json) =>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// ignore_for_file: invalid_annotation_target
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
@@ -56,9 +55,6 @@ class Profile with _$Profile {
|
||||
@Default(true) bool autoUpdate,
|
||||
@Default({}) SelectedMap selectedMap,
|
||||
@Default({}) Set<String> unfoldSet,
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
@Default(false)
|
||||
bool isUpdating,
|
||||
}) = _Profile;
|
||||
|
||||
factory Profile.fromJson(Map<String, Object?> json) =>
|
||||
@@ -67,7 +63,7 @@ class Profile with _$Profile {
|
||||
factory Profile.normal({
|
||||
String? label,
|
||||
String url = '',
|
||||
}) {
|
||||
}) {
|
||||
return Profile(
|
||||
label: label,
|
||||
url: url,
|
||||
@@ -81,7 +77,8 @@ extension ProfileExtension on Profile {
|
||||
ProfileType get type =>
|
||||
url.isEmpty == true ? ProfileType.file : ProfileType.url;
|
||||
|
||||
bool get realAutoUpdate => url.isEmpty == true ? false : autoUpdate;
|
||||
bool get realAutoUpdate =>
|
||||
url.isEmpty == true ? false : autoUpdate;
|
||||
|
||||
Future<void> checkAndUpdate() async {
|
||||
final isExists = await check();
|
||||
|
||||
@@ -41,4 +41,4 @@ class Proxy with _$Proxy {
|
||||
}) = _Proxy;
|
||||
|
||||
factory Proxy.fromJson(Map<String, Object?> json) => _$ProxyFromJson(json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
|
||||
part 'generated/selector.freezed.dart';
|
||||
|
||||
@@ -135,42 +132,11 @@ class MoreToolsSelectorState with _$MoreToolsSelectorState {
|
||||
@freezed
|
||||
class PackageListSelectorState with _$PackageListSelectorState {
|
||||
const factory PackageListSelectorState({
|
||||
required List<Package> packages,
|
||||
required AccessControl accessControl,
|
||||
required bool isAccessControl,
|
||||
}) = _PackageListSelectorState;
|
||||
}
|
||||
|
||||
extension PackageListSelectorStateExt on PackageListSelectorState {
|
||||
List<Package> getList(List<String> selectedList) {
|
||||
final isFilterSystemApp = accessControl.isFilterSystemApp;
|
||||
final sort = accessControl.sort;
|
||||
return packages
|
||||
.where((item) => isFilterSystemApp ? item.isSystem == false : true)
|
||||
.sorted(
|
||||
(a, b) {
|
||||
return switch (sort) {
|
||||
AccessSortType.none => 0,
|
||||
AccessSortType.name =>
|
||||
other.sortByChar(
|
||||
PinyinHelper.getPinyin(a.label),
|
||||
PinyinHelper.getPinyin(b.label),
|
||||
),
|
||||
AccessSortType.time => a.firstInstallTime.compareTo(b.firstInstallTime),
|
||||
};
|
||||
},
|
||||
).sorted(
|
||||
(a, b) {
|
||||
final isSelectA = selectedList.contains(a.packageName);
|
||||
final isSelectB = selectedList.contains(b.packageName);
|
||||
if (isSelectA && isSelectB) return 0;
|
||||
if (isSelectA) return -1;
|
||||
if (isSelectB) return 1;
|
||||
return 0;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ColumnsSelectorState with _$ColumnsSelectorState {
|
||||
@@ -189,9 +155,9 @@ class ProxiesListHeaderSelectorState with _$ProxiesListHeaderSelectorState {
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProxiesActionsState with _$ProxiesActionsState {
|
||||
const factory ProxiesActionsState({
|
||||
required bool isCurrent,
|
||||
required bool hasProvider,
|
||||
}) = _ProxiesActionsState;
|
||||
}
|
||||
class CurrentGroupProxyNameSelectorState with _$CurrentGroupProxyNameSelectorState {
|
||||
const factory CurrentGroupProxyNameSelectorState({
|
||||
required String? proxyName,
|
||||
required String? proxyName2,
|
||||
}) = _CurrentGroupProxyNameSelectorState;
|
||||
}
|
||||
@@ -48,16 +48,6 @@ class App {
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<String>> getChinaPackageNames() async {
|
||||
final packageNamesString =
|
||||
await methodChannel.invokeMethod<String>("getChinaPackageNames");
|
||||
return Isolate.run<List<String>>(() {
|
||||
final List<dynamic> packageNamesRaw =
|
||||
packageNamesString != null ? json.decode(packageNamesString) : [];
|
||||
return packageNamesRaw.map((e) => e.toString()).toList();
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> openFile(String path) async {
|
||||
return await methodChannel.invokeMethod<bool>("openFile", {
|
||||
"path": path,
|
||||
|
||||
@@ -81,6 +81,7 @@ class Proxy extends ProxyPlatform {
|
||||
bool get isStart => startTime != null && startTime!.isBeforeNow;
|
||||
|
||||
onStarted(int? fd) {
|
||||
debugPrint("onStarted ==> $fd");
|
||||
if (fd == null) return;
|
||||
if (receiver != null) {
|
||||
receiver!.close();
|
||||
|
||||
@@ -45,10 +45,11 @@ class GlobalState {
|
||||
required Config config,
|
||||
bool isPatch = true,
|
||||
}) async {
|
||||
final profilePath = await appPath.getProfilePath(config.currentProfileId);
|
||||
await config.currentProfile?.checkAndUpdate();
|
||||
final res = await clashCore.updateConfig(
|
||||
UpdateConfigParams(
|
||||
profileId: config.currentProfileId ?? "",
|
||||
profilePath: profilePath,
|
||||
config: clashConfig,
|
||||
params: ConfigExtendedParams(
|
||||
isPatch: isPatch,
|
||||
@@ -95,12 +96,8 @@ class GlobalState {
|
||||
config: config,
|
||||
isPatch: false,
|
||||
);
|
||||
clashCore.setProfileName(config.currentProfile?.label ?? '');
|
||||
await updateGroups(appState);
|
||||
await updateProviders(appState);
|
||||
}
|
||||
|
||||
updateProviders(AppState appState) async {
|
||||
appState.providers = await clashCore.getExternalProviders();
|
||||
}
|
||||
|
||||
init({
|
||||
@@ -121,7 +118,6 @@ class GlobalState {
|
||||
systemProxy: config.systemProxy,
|
||||
mixedPort: clashConfig.mixedPort,
|
||||
onlyProxy: config.onlyProxy,
|
||||
currentProfileName: config.currentProfile?.label ?? config.currentProfileId ?? "",
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -208,7 +204,7 @@ class GlobalState {
|
||||
final traffic = clashCore.getTraffic();
|
||||
if (Platform.isAndroid && isVpnService == true) {
|
||||
proxy?.startForeground(
|
||||
title: clashCore.getState().currentProfileName,
|
||||
title: clashCore.getProfileName(),
|
||||
content: "$traffic",
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ScrollOverBuilder extends StatefulWidget {
|
||||
final Widget Function(bool isOver) builder;
|
||||
@@ -17,6 +15,7 @@ class ScrollOverBuilder extends StatefulWidget {
|
||||
class _ScrollOverBuilderState extends State<ScrollOverBuilder> {
|
||||
final isOverNotifier = ValueNotifier<bool>(false);
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
@@ -39,29 +38,3 @@ class _ScrollOverBuilderState extends State<ScrollOverBuilder> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProxiesActionsBuilder extends StatelessWidget {
|
||||
final Widget? child;
|
||||
final Widget Function(
|
||||
ProxiesActionsState state,
|
||||
Widget? child,
|
||||
) builder;
|
||||
|
||||
const ProxiesActionsBuilder({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.builder,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<AppState, ProxiesActionsState>(
|
||||
selector: (_, appState) => ProxiesActionsState(
|
||||
isCurrent: appState.currentLabel == "proxies",
|
||||
hasProvider: appState.providers.isNotEmpty,
|
||||
),
|
||||
builder: (_, state, child) => builder(state, child),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,39 +32,40 @@ class InfoHeader extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (info.iconData != null) ...[
|
||||
Icon(
|
||||
info.iconData,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
],
|
||||
Flexible(
|
||||
child: TooltipText(
|
||||
text: Text(
|
||||
info.label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
...actions,
|
||||
if (info.iconData != null) ...[
|
||||
Icon(
|
||||
info.iconData,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
],
|
||||
Flexible(
|
||||
child: TooltipText(
|
||||
text: Text(
|
||||
info.label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
...actions,
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -145,11 +146,13 @@ class CommonCard extends StatelessWidget {
|
||||
childWidget = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
InfoHeader(
|
||||
info: info!,
|
||||
Flexible(
|
||||
flex: 0,
|
||||
child: InfoHeader(
|
||||
info: info!,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -27,8 +27,6 @@ class _ClashContainerState extends State<ClashContainer>
|
||||
systemProxy: config.systemProxy,
|
||||
mixedPort: clashConfig.mixedPort,
|
||||
onlyProxy: config.onlyProxy,
|
||||
currentProfileName:
|
||||
config.currentProfile?.label ?? config.currentProfileId ?? "",
|
||||
),
|
||||
builder: (__, state, child) {
|
||||
clashCore.setState(state);
|
||||
@@ -38,32 +36,9 @@ class _ClashContainerState extends State<ClashContainer>
|
||||
);
|
||||
}
|
||||
|
||||
_changeProfileHandle() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
final appController = globalState.appController;
|
||||
appController.appState.delayMap = {};
|
||||
await appController.applyProfile();
|
||||
});
|
||||
}
|
||||
|
||||
Widget _changeProfileContainer(Widget child) {
|
||||
return Selector<Config, String?>(
|
||||
selector: (_, config) => config.currentProfileId,
|
||||
builder: (__, state, child) {
|
||||
_changeProfileHandle();
|
||||
return child!;
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _changeProfileContainer(
|
||||
_updateCoreState(
|
||||
widget.child,
|
||||
),
|
||||
);
|
||||
return _updateCoreState(widget.child);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -79,11 +54,10 @@ class _ClashContainerState extends State<ClashContainer>
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onDelay(Delay delay) async {
|
||||
void onDelay(Delay delay) {
|
||||
final appController = globalState.appController;
|
||||
appController.setDelay(delay);
|
||||
super.onDelay(delay);
|
||||
await globalState.appController.updateGroupDebounce();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -99,15 +73,16 @@ class _ClashContainerState extends State<ClashContainer>
|
||||
}
|
||||
|
||||
@override
|
||||
void onLoaded(String providerName) {
|
||||
void onLoaded(String groupName) {
|
||||
final appController = globalState.appController;
|
||||
appController.appState.setProvider(
|
||||
clashCore.getExternalProvider(
|
||||
providerName,
|
||||
),
|
||||
final currentSelectedMap = appController.config.currentSelectedMap;
|
||||
final proxyName = currentSelectedMap[groupName];
|
||||
if (proxyName == null) return;
|
||||
appController.changeProxy(
|
||||
groupName: groupName,
|
||||
proxyName: proxyName,
|
||||
);
|
||||
appController.addCheckIpNumDebounce();
|
||||
super.onLoaded(providerName);
|
||||
super.onLoaded(proxyName);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'card.dart';
|
||||
|
||||
class SettingInfoCard extends StatelessWidget {
|
||||
final Info info;
|
||||
final bool? isSelected;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const SettingInfoCard(
|
||||
this.info, {
|
||||
super.key,
|
||||
this.isSelected,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
isSelected: isSelected,
|
||||
onPressed: onPressed,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Icon(info.iconData),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
info.label,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingTextCard extends StatelessWidget {
|
||||
final String text;
|
||||
final bool? isSelected;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const SettingTextCard(
|
||||
this.text, {
|
||||
super.key,
|
||||
this.isSelected,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
onPressed: onPressed,
|
||||
isSelected: isSelected,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
text,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,10 @@ import 'package:fl_clash/widgets/scaffold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'side_sheet.dart';
|
||||
|
||||
showExtendPage(
|
||||
BuildContext context, {
|
||||
showExtendPage(BuildContext context, {
|
||||
required Widget body,
|
||||
required String title,
|
||||
double? extendPageWidth,
|
||||
bool forceNotSide = false,
|
||||
Widget? action,
|
||||
}) {
|
||||
final NavigatorState navigator = Navigator.of(context);
|
||||
@@ -19,24 +17,23 @@ showExtendPage(
|
||||
key: globalKey,
|
||||
child: body,
|
||||
);
|
||||
final isMobile =
|
||||
globalState.appController.appState.viewMode == ViewMode.mobile;
|
||||
final isNotSide = isMobile || forceNotSide;
|
||||
final isMobile = globalState.appController.appState.viewMode ==
|
||||
ViewMode.mobile;
|
||||
navigator.push(
|
||||
ModalSideSheetRoute(
|
||||
modalBarrierColor: Colors.black38,
|
||||
builder: (context) {
|
||||
final commonScaffold = CommonScaffold(
|
||||
automaticallyImplyLeading: isNotSide,
|
||||
actions: isNotSide
|
||||
automaticallyImplyLeading: isMobile ? true : false,
|
||||
actions: isMobile
|
||||
? null
|
||||
: [
|
||||
const SizedBox(
|
||||
height: kToolbarHeight,
|
||||
width: kToolbarHeight,
|
||||
child: CloseButton(),
|
||||
),
|
||||
],
|
||||
const SizedBox(
|
||||
height: kToolbarHeight,
|
||||
width: kToolbarHeight,
|
||||
child: CloseButton(),
|
||||
),
|
||||
],
|
||||
title: title,
|
||||
body: uniqueBody,
|
||||
);
|
||||
|
||||
@@ -24,5 +24,4 @@ export 'fade_box.dart';
|
||||
export 'app_state_container.dart';
|
||||
export 'text.dart';
|
||||
export 'connection_item.dart';
|
||||
export 'builder.dart';
|
||||
export 'setting.dart';
|
||||
export 'builder.dart';
|
||||
@@ -220,14 +220,16 @@ class _WindowHeaderState extends State<WindowHeader> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const Positioned(
|
||||
left: 0,
|
||||
child: AppIcon(),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
child: _buildActions(),
|
||||
),
|
||||
if (!Platform.isMacOS) ...[
|
||||
const Positioned(
|
||||
left: 0,
|
||||
child: AppIcon(),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
child: _buildActions(),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
68
pubspec.lock
68
pubspec.lock
@@ -34,7 +34,7 @@ packages:
|
||||
source: hosted
|
||||
version: "3.5.1"
|
||||
archive:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||
@@ -525,22 +525,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
isolate_contactor:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: isolate_contactor
|
||||
sha256: f1be0a90f91e4309ef37cc45280b2a84e769e848aae378318dd3dd263cfc482a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
isolate_manager:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: isolate_manager
|
||||
sha256: "8fb916c4444fd408f089448f904f083ac3e169ea1789fd4d987b25809af92188"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.1"
|
||||
jovial_misc:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -593,18 +577,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
version: "10.0.4"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
version: "3.0.3"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -629,14 +613,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
lpinyin:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: lpinyin
|
||||
sha256: "0bb843363f1f65170efd09fbdfc760c7ec34fc6354f9fcb2f89e74866a0d814a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -646,13 +622,13 @@ packages:
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
version: "0.8.0"
|
||||
menu_base:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -665,10 +641,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
version: "1.12.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -852,22 +828,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
re_editor:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: re_editor
|
||||
sha256: abae2b015799c936b9f9b68888e2c55007dd159b4654a85da22ce1af84efbd17
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
re_highlight:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: re_highlight
|
||||
sha256: "6c4ac3f76f939fb7ca9df013df98526634e17d8f7460e028bd23a035870024f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.3"
|
||||
screen_retriever:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1029,10 +989,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
version: "0.7.0"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1133,10 +1093,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.4"
|
||||
version: "14.2.1"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: fl_clash
|
||||
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||
publish_to: 'none'
|
||||
version: 0.8.52+202408111
|
||||
version: 0.8.50+202408011
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
|
||||
@@ -40,10 +40,7 @@ dependencies:
|
||||
country_flags: ^2.2.0
|
||||
win32: ^5.5.1
|
||||
ffi: ^2.1.2
|
||||
re_editor: ^0.3.1
|
||||
re_highlight: ^0.0.3
|
||||
archive: ^3.6.1
|
||||
lpinyin: ^2.0.3
|
||||
material_color_utilities: ^0.8.0
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
@@ -2,18 +2,7 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/other.dart';
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
|
||||
void main() {
|
||||
print(PinyinHelper.getPinyin("ABC"));
|
||||
print(PinyinHelper.getPinyin("阿里巴巴"));
|
||||
|
||||
print('a'.compareTo('B'));
|
||||
print('A'.compareTo('B'));
|
||||
}
|
||||
|
||||
startService() async {
|
||||
void main() async {
|
||||
// 定义服务器将要监听的地址和端口
|
||||
final host = InternetAddress.anyIPv4; // 监听所有网络接口
|
||||
const port = 8080; // 使用 8080 端口
|
||||
|
||||
Reference in New Issue
Block a user