Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fc54c5295 | ||
|
|
00a78b5fb4 |
Submodule core/Clash.Meta updated: 3d773d7fa5...fffdf84493
169
core/common.go
169
core/common.go
@@ -2,19 +2,23 @@ package main
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
ap "github.com/metacubex/mihomo/adapter/provider"
|
||||
"github.com/metacubex/mihomo/adapter/provider"
|
||||
"github.com/metacubex/mihomo/common/batch"
|
||||
"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"
|
||||
@@ -26,40 +30,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"`
|
||||
@@ -69,9 +73,9 @@ type ConfigExtendedParams struct {
|
||||
}
|
||||
|
||||
type GenerateConfigParams struct {
|
||||
ProfilePath *string `json:"profile-path"`
|
||||
Config config.RawConfig `json:"config" `
|
||||
Params ConfigExtendedParams `json:"params"`
|
||||
ProfileId string `json:"profile-id"`
|
||||
Config config.RawConfig `json:"config" `
|
||||
Params ConfigExtendedParams `json:"params"`
|
||||
}
|
||||
|
||||
type ChangeProxyParams struct {
|
||||
@@ -93,9 +97,13 @@ 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"`
|
||||
}
|
||||
|
||||
var b, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
|
||||
|
||||
func restartExecutable(execPath string) {
|
||||
var err error
|
||||
executor.Shutdown()
|
||||
@@ -145,26 +153,76 @@ func removeFile(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRawConfigWithPath(path *string) *config.RawConfig {
|
||||
if path == nil {
|
||||
return config.DefaultRawConfig()
|
||||
} else {
|
||||
bytes, err := readFile(*path)
|
||||
if err != nil {
|
||||
log.Errorln("getProfile readFile error %v", err)
|
||||
return config.DefaultRawConfig()
|
||||
}
|
||||
prof, err := config.UnmarshalRawConfig(bytes)
|
||||
if err != nil {
|
||||
log.Errorln("getProfile UnmarshalRawConfig error %v", err)
|
||||
return config.DefaultRawConfig()
|
||||
}
|
||||
return prof
|
||||
}
|
||||
func getProfilePath(id string) string {
|
||||
return filepath.Join(constant.Path.HomeDir(), "profiles", id+".yaml")
|
||||
}
|
||||
|
||||
func decorationConfig(profilePath *string, cfg config.RawConfig) *config.RawConfig {
|
||||
prof := getRawConfigWithPath(profilePath)
|
||||
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")
|
||||
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
|
||||
}
|
||||
mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
|
||||
}
|
||||
for _, mapping := range prof.RuleProvider {
|
||||
value, exist := mapping["path"].(string)
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
|
||||
}
|
||||
return prof
|
||||
}
|
||||
|
||||
func getExternalProvidersRaw() map[string]ExternalProvider {
|
||||
externalProviders := make(map[string]ExternalProvider)
|
||||
for n, p := range tunnel.Providers() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*provider.ProxySetProvider)
|
||||
externalProviders[n] = ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
Count: p.Count(),
|
||||
Path: p.Vehicle().Path(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
for n, p := range tunnel.RuleProviders() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*rp.RuleSetProvider)
|
||||
externalProviders[n] = ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
Count: p.Count(),
|
||||
Path: p.Vehicle().Path(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
return externalProviders
|
||||
}
|
||||
|
||||
func decorationConfig(profileId string, cfg config.RawConfig) *config.RawConfig {
|
||||
prof := getRawConfigWithId(profileId)
|
||||
overwriteConfig(prof, cfg)
|
||||
return prof
|
||||
}
|
||||
@@ -327,6 +385,7 @@ 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
|
||||
|
||||
176
core/hub.go
176
core/hub.go
@@ -11,7 +11,6 @@ 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"
|
||||
@@ -35,8 +34,6 @@ var configParams = ConfigExtendedParams{}
|
||||
|
||||
var isInit = false
|
||||
|
||||
var currentProfileName = ""
|
||||
|
||||
//export initClash
|
||||
func initClash(homeDirStr *C.char) bool {
|
||||
if !isInit {
|
||||
@@ -75,16 +72,6 @@ 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)
|
||||
@@ -111,7 +98,7 @@ func updateConfig(s *C.char, port C.longlong) {
|
||||
return
|
||||
}
|
||||
configParams = params.Params
|
||||
prof := decorationConfig(params.ProfilePath, params.Config)
|
||||
prof := decorationConfig(params.ProfileId, params.Config)
|
||||
currentConfig = prof
|
||||
err = applyConfig()
|
||||
if err != nil {
|
||||
@@ -124,34 +111,10 @@ func updateConfig(s *C.char, port C.longlong) {
|
||||
|
||||
//export clearEffect
|
||||
func clearEffect(s *C.char) {
|
||||
path := C.GoString(s)
|
||||
id := C.GoString(s)
|
||||
go func() {
|
||||
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)
|
||||
_ = removeFile(getProfilePath(id))
|
||||
_ = removeFile(getProfileProvidersPath(id))
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -184,10 +147,13 @@ func changeProxy(s *C.char) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err = selector.Set(proxyName)
|
||||
if proxyName == "" {
|
||||
selector.ForceSet(proxyName)
|
||||
} else {
|
||||
err = selector.Set(proxyName)
|
||||
}
|
||||
if err == nil {
|
||||
log.Infoln("[Selector] %s selected %s", groupName, proxyName)
|
||||
log.Infoln("[SelectAble] %s selected %s", groupName, proxyName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,16 +196,16 @@ func resetTraffic() {
|
||||
func asyncTestDelay(s *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
paramsString := C.GoString(s)
|
||||
go func() {
|
||||
b.Go(paramsString, func() (bool, error) {
|
||||
var params = &TestDelayParams{}
|
||||
err := json.Unmarshal([]byte(paramsString), params)
|
||||
if err != nil {
|
||||
return
|
||||
return false, nil
|
||||
}
|
||||
|
||||
expectedStatus, err := utils.NewUnsignedRanges[uint16]("")
|
||||
if err != nil {
|
||||
return
|
||||
return false, nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(params.Timeout))
|
||||
@@ -256,7 +222,7 @@ func asyncTestDelay(s *C.char, port C.longlong) {
|
||||
delayData.Value = -1
|
||||
data, _ := json.Marshal(delayData)
|
||||
bridge.SendToPort(i, string(data))
|
||||
return
|
||||
return false, nil
|
||||
}
|
||||
|
||||
delay, err := proxy.URLTest(ctx, constant.DefaultTestURL, expectedStatus)
|
||||
@@ -264,14 +230,14 @@ func asyncTestDelay(s *C.char, port C.longlong) {
|
||||
delayData.Value = -1
|
||||
data, _ := json.Marshal(delayData)
|
||||
bridge.SendToPort(i, string(data))
|
||||
return
|
||||
return false, nil
|
||||
}
|
||||
|
||||
delayData.Value = int32(delay)
|
||||
data, _ := json.Marshal(delayData)
|
||||
bridge.SendToPort(i, string(data))
|
||||
return
|
||||
}()
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
//export getVersionInfo
|
||||
@@ -345,28 +311,31 @@ func getProvider(name *C.char) *C.char {
|
||||
|
||||
//export getExternalProviders
|
||||
func getExternalProviders() *C.char {
|
||||
externalProviders := make([]ExternalProvider, 0)
|
||||
providers := tunnel.Providers()
|
||||
for n, p := range providers {
|
||||
externalProviders := make(map[string]ExternalProvider)
|
||||
for n, p := range tunnel.Providers() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*provider.ProxySetProvider)
|
||||
externalProviders = append(externalProviders, ExternalProvider{
|
||||
externalProviders[n] = ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
Count: p.Count(),
|
||||
Path: p.Vehicle().Path(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for n, p := range tunnel.RuleProviders() {
|
||||
if p.VehicleType() != cp.Compatible {
|
||||
p := p.(*rp.RuleSetProvider)
|
||||
externalProviders = append(externalProviders, ExternalProvider{
|
||||
externalProviders[n] = ExternalProvider{
|
||||
Name: n,
|
||||
Type: p.Type().String(),
|
||||
VehicleType: p.VehicleType().String(),
|
||||
Count: p.Count(),
|
||||
Path: p.Vehicle().Path(),
|
||||
UpdateAt: p.UpdatedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
data, err := json.Marshal(externalProviders)
|
||||
@@ -376,6 +345,21 @@ func getExternalProviders() *C.char {
|
||||
return C.CString(string(data))
|
||||
}
|
||||
|
||||
//export getExternalProvider
|
||||
func getExternalProvider(name *C.char) *C.char {
|
||||
externalProviderName := C.GoString(name)
|
||||
externalProviders := getExternalProvidersRaw()
|
||||
externalProvider, exist := externalProviders[externalProviderName]
|
||||
if !exist {
|
||||
return C.CString("")
|
||||
}
|
||||
data, err := json.Marshal(externalProvider)
|
||||
if err != nil {
|
||||
return C.CString("")
|
||||
}
|
||||
return C.CString(string(data))
|
||||
}
|
||||
|
||||
//export updateExternalProvider
|
||||
func updateExternalProvider(providerName *C.char, providerType *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
@@ -385,14 +369,24 @@ func updateExternalProvider(providerName *C.char, providerType *C.char, port C.l
|
||||
switch providerTypeString {
|
||||
case "Proxy":
|
||||
providers := tunnel.Providers()
|
||||
err := providers[providerNameString].Update()
|
||||
proxyProvider, exist := providers[providerNameString].(*provider.ProxySetProvider)
|
||||
if !exist {
|
||||
bridge.SendToPort(i, "proxy provider is not exist")
|
||||
return
|
||||
}
|
||||
err := proxyProvider.Update()
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
}
|
||||
case "Rule":
|
||||
providers := tunnel.RuleProviders()
|
||||
err := providers[providerNameString].Update()
|
||||
ruleProvider, exist := providers[providerNameString].(*rp.RuleSetProvider)
|
||||
if !exist {
|
||||
bridge.SendToPort(i, "rule provider is not exist")
|
||||
return
|
||||
}
|
||||
err := ruleProvider.Update()
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
return
|
||||
@@ -426,6 +420,66 @@ func updateExternalProvider(providerName *C.char, providerType *C.char, port C.l
|
||||
}()
|
||||
}
|
||||
|
||||
//func sideLoadExternalProvider(providerName *C.char, providerType *C.char, data *C.char, port C.longlong) {
|
||||
// i := int64(port)
|
||||
// bytes := []byte(C.GoString(data))
|
||||
// providerNameString := C.GoString(providerName)
|
||||
// providerTypeString := C.GoString(providerType)
|
||||
// go func() {
|
||||
// switch providerTypeString {
|
||||
// case "Proxy":
|
||||
// providers := tunnel.Providers()
|
||||
// proxyProvider, exist := providers[providerNameString].(*provider.ProxySetProvider)
|
||||
// if exist {
|
||||
// bridge.SendToPort(i, "proxy provider is not exist")
|
||||
// return
|
||||
// }
|
||||
// err := proxyProvider.Update()
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "Rule":
|
||||
// providers := tunnel.RuleProviders()
|
||||
// ruleProvider, exist := providers[providerNameString].(*rp.RuleSetProvider)
|
||||
// if exist {
|
||||
// bridge.SendToPort(i, "proxy provider is not exist")
|
||||
// return
|
||||
// }
|
||||
// err := ruleProvider.Update()
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "MMDB":
|
||||
// err := updater.UpdateMMDB(constant.Path.Resolve(providerNameString))
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "ASN":
|
||||
// err := updater.UpdateASN(constant.Path.Resolve(providerNameString))
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "GeoIp":
|
||||
// err := updater.UpdateGeoIp(constant.Path.Resolve(providerNameString))
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// case "GeoSite":
|
||||
// err := updater.UpdateGeoSite(constant.Path.Resolve(providerNameString))
|
||||
// if err != nil {
|
||||
// bridge.SendToPort(i, err.Error())
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// bridge.SendToPort(i, "")
|
||||
// }()
|
||||
//}
|
||||
|
||||
//export initNativeApiBridge
|
||||
func initNativeApiBridge(api unsafe.Pointer) {
|
||||
bridge.InitDartApi(api)
|
||||
@@ -463,7 +517,7 @@ func init() {
|
||||
Data: c,
|
||||
})
|
||||
}
|
||||
executor.DefaultProxyProviderLoadedHook = func(providerName string) {
|
||||
executor.DefaultProviderLoadedHook = func(providerName string) {
|
||||
SendMessage(Message{
|
||||
Type: LoadedMessage,
|
||||
Data: providerName,
|
||||
|
||||
@@ -21,8 +21,9 @@ type AndroidProps struct {
|
||||
|
||||
type State struct {
|
||||
AndroidProps
|
||||
MixedPort int `json:"mixedPort"`
|
||||
OnlyProxy bool `json:"onlyProxy"`
|
||||
CurrentProfileName string `json:"currentProfileName"`
|
||||
MixedPort int `json:"mixedPort"`
|
||||
OnlyProxy bool `json:"onlyProxy"`
|
||||
}
|
||||
|
||||
var state State
|
||||
|
||||
@@ -142,6 +142,7 @@ class ApplicationState extends State<Application> {
|
||||
locale: config.locale,
|
||||
themeMode: config.themeMode,
|
||||
primaryColor: config.primaryColor,
|
||||
prueBlack: config.prueBlack,
|
||||
),
|
||||
builder: (_, state, child) {
|
||||
return DynamicColorBuilder(
|
||||
@@ -180,7 +181,7 @@ class ApplicationState extends State<Application> {
|
||||
brightness: Brightness.dark,
|
||||
systemColorSchemes: systemColorSchemes,
|
||||
primaryColor: state.primaryColor,
|
||||
),
|
||||
).toPrueBlack(state.prueBlack),
|
||||
),
|
||||
home: child,
|
||||
);
|
||||
|
||||
@@ -100,22 +100,6 @@ 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();
|
||||
@@ -156,7 +140,8 @@ class ClashCore {
|
||||
clashFFI.freeCString(externalProvidersRaw);
|
||||
return Isolate.run<List<ExternalProvider>>(() {
|
||||
final externalProviders =
|
||||
(json.decode(externalProvidersRawString) as List<dynamic>)
|
||||
(json.decode(externalProvidersRawString) as Map<String, dynamic>)
|
||||
.values
|
||||
.map(
|
||||
(item) => ExternalProvider.fromJson(item),
|
||||
)
|
||||
@@ -165,6 +150,18 @@ 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);
|
||||
return ExternalProvider.fromJson(json.decode(externalProviderRawString));
|
||||
}
|
||||
|
||||
Future<String> updateExternalProvider({
|
||||
required String providerName,
|
||||
required String providerType,
|
||||
@@ -216,21 +213,13 @@ 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 path) {
|
||||
final pathChar = path.toNativeUtf8().cast<Char>();
|
||||
clashFFI.clearEffect(pathChar);
|
||||
malloc.free(pathChar);
|
||||
clearEffect(String profileId) {
|
||||
final profileIdChar = profileId.toNativeUtf8().cast<Char>();
|
||||
clashFFI.clearEffect(profileIdChar);
|
||||
malloc.free(profileIdChar);
|
||||
}
|
||||
|
||||
VersionInfo getVersionInfo() {
|
||||
|
||||
@@ -5190,30 +5190,6 @@ 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,
|
||||
@@ -5409,6 +5385,21 @@ 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 updateExternalProvider(
|
||||
ffi.Pointer<ffi.Char> providerName,
|
||||
ffi.Pointer<ffi.Char> providerType,
|
||||
|
||||
28
lib/common/archive.dart
Normal file
28
lib/common/archive.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
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,4 +16,21 @@ 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,
|
||||
background: Colors.black,
|
||||
surfaceContainer: surfaceContainer.darken(0.05),
|
||||
)
|
||||
: this;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const mmdbFileName = "geoip.metadb";
|
||||
const asnFileName = "ASN.mmdb";
|
||||
const geoIpFileName = "GeoIP.dat";
|
||||
const geoSiteFileName = "GeoSite.dat";
|
||||
final double kHeaderHeight = system.isDesktop ? (Platform.isMacOS ? 28 : 40) : 0;
|
||||
final double kHeaderHeight = system.isDesktop ? 40 : 0;
|
||||
const GeoXMap defaultGeoXMap = {
|
||||
"mmdb":
|
||||
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
@@ -43,65 +44,16 @@ class DAVClient {
|
||||
|
||||
get root => "/$appName";
|
||||
|
||||
get remoteConfig => "$root/$configKey.json";
|
||||
get backupFile => "$root/backup.zip";
|
||||
|
||||
get remoteClashConfig => "$root/$clashConfigKey.json";
|
||||
|
||||
get remoteProfiles => "$root/$profilesDirectoryName";
|
||||
|
||||
backup() async {
|
||||
final appController = globalState.appController;
|
||||
final config = appController.config;
|
||||
final clashConfig = appController.clashConfig;
|
||||
backup(Uint8List data) async {
|
||||
await client.mkdir("$root");
|
||||
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)}",
|
||||
);
|
||||
}
|
||||
await client.write("$backupFile", data);
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
Future<List<int>> recovery() async {
|
||||
final data = await client.read(backupFile);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class Navigation {
|
||||
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
|
||||
),
|
||||
const NavigationItem(
|
||||
icon: Icon(Icons.swap_vert_circle),
|
||||
icon: Icon(Icons.storage),
|
||||
label: "resources",
|
||||
description: "resourcesDesc",
|
||||
keep: false,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:isolate';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fl_clash/common/app_localizations.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/common/constant.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -208,6 +209,10 @@ class Other {
|
||||
int() => throw UnimplementedError(),
|
||||
};
|
||||
}
|
||||
|
||||
String getBackupFileName(){
|
||||
return "${appName}_backup_${DateTime.now().show}.zip";
|
||||
}
|
||||
}
|
||||
|
||||
final other = Other();
|
||||
|
||||
@@ -9,6 +9,7 @@ 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');
|
||||
@@ -23,6 +24,9 @@ class AppPath {
|
||||
getApplicationSupportDirectory().then((value) {
|
||||
cacheDir.complete(value);
|
||||
});
|
||||
getDownloadsDirectory().then((value) {
|
||||
downloadDir.complete(value);
|
||||
});
|
||||
// if (Platform.isAndroid) {
|
||||
// getApplicationSupportDirectory().then((value) {
|
||||
// cacheDir.complete(value);
|
||||
@@ -39,6 +43,11 @@ 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,22 +1,26 @@
|
||||
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?> pickerConfigFile() async {
|
||||
Future<PlatformFile?> pickerFile() async {
|
||||
final filePickerResult = await FilePicker.platform.pickFiles(
|
||||
withData: true,
|
||||
allowMultiple: false,
|
||||
initialDirectory: await appPath.getDownloadDirPath(),
|
||||
);
|
||||
return filePickerResult?.files.first;
|
||||
}
|
||||
|
||||
Future<PlatformFile?> pickerGeoDataFile() async {
|
||||
final filePickerResult = await FilePicker.platform.pickFiles(
|
||||
withData: true,
|
||||
allowMultiple: false,
|
||||
Future<String?> saveFile(String fileName,Uint8List bytes) async {
|
||||
final path = await FilePicker.platform.saveFile(
|
||||
fileName: fileName,
|
||||
initialDirectory: await appPath.getDownloadDirPath(),
|
||||
bytes: bytes,
|
||||
);
|
||||
return filePickerResult?.files.first;
|
||||
return path;
|
||||
}
|
||||
|
||||
Future<String?> pickerConfigQRCode() async {
|
||||
|
||||
@@ -20,6 +20,7 @@ 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,9 +1,14 @@
|
||||
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:path/path.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
@@ -90,9 +95,7 @@ class AppController {
|
||||
|
||||
deleteProfile(String id) async {
|
||||
config.deleteProfileById(id);
|
||||
final profilePath = await appPath.getProfilePath(id);
|
||||
if (profilePath == null) return;
|
||||
clashCore.clearEffect(profilePath);
|
||||
clashCore.clearEffect(id);
|
||||
if (config.currentProfileId == id) {
|
||||
if (config.profiles.isNotEmpty) {
|
||||
final updateId = config.profiles.first.id;
|
||||
@@ -104,8 +107,10 @@ class AppController {
|
||||
}
|
||||
|
||||
Future<void> updateProfile(Profile profile) async {
|
||||
await profile.update();
|
||||
config.setProfile(await profile.update());
|
||||
final newProfile = await profile.update();
|
||||
config.setProfile(
|
||||
newProfile.copyWith(isUpdating: false),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateClashConfig({bool isPatch = true}) async {
|
||||
@@ -140,9 +145,6 @@ class AppController {
|
||||
changeProfile(String? value) async {
|
||||
if (value == config.currentProfileId) return;
|
||||
config.currentProfileId = value;
|
||||
await applyProfile();
|
||||
appState.delayMap = {};
|
||||
saveConfigPreferences();
|
||||
}
|
||||
|
||||
autoUpdateProfiles() async {
|
||||
@@ -294,26 +296,6 @@ 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);
|
||||
@@ -403,7 +385,7 @@ class AppController {
|
||||
}
|
||||
|
||||
addProfileFormFile() async {
|
||||
final platformFile = await globalState.safeRun(picker.pickerConfigFile);
|
||||
final platformFile = await globalState.safeRun(picker.pickerFile);
|
||||
final bytes = platformFile?.bytes;
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
@@ -480,4 +462,63 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,24 +23,19 @@ class AccessFragment extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AccessFragmentState extends State<AccessFragment> {
|
||||
final packagesListenable = ValueNotifier<List<Package>>([]);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Future.delayed(const Duration(milliseconds: 300), () async {
|
||||
packagesListenable.value = await app?.getPackages() ?? [];
|
||||
});
|
||||
final appState = globalState.appController.appState;
|
||||
if (appState.packages.isEmpty) {
|
||||
Future.delayed(const Duration(milliseconds: 300), () async {
|
||||
appState.packages = await app?.getPackages() ?? [];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
packagesListenable.dispose();
|
||||
}
|
||||
|
||||
Widget _buildAppProxyModePopup() {
|
||||
final items = [
|
||||
CommonPopupMenuItem(
|
||||
@@ -156,8 +151,8 @@ class _AccessFragmentState extends State<AccessFragment> {
|
||||
// }
|
||||
|
||||
Widget _buildPackageList() {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: packagesListenable,
|
||||
return Selector<AppState, List<Package>>(
|
||||
selector: (_, appState) => appState.packages,
|
||||
builder: (_, packages, ___) {
|
||||
final accessControl = globalState.appController.config.accessControl;
|
||||
final acceptList = accessControl.acceptList;
|
||||
@@ -238,10 +233,10 @@ class _AccessFragmentState extends State<AccessFragment> {
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Flexible(
|
||||
@@ -256,10 +251,10 @@ class _AccessFragmentState extends State<AccessFragment> {
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -288,47 +283,43 @@ class _AccessFragmentState extends State<AccessFragment> {
|
||||
),
|
||||
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(
|
||||
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(
|
||||
} else {
|
||||
config.accessControl =
|
||||
config.accessControl.copyWith(
|
||||
rejectList: valueList,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
import 'dart:io';
|
||||
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';
|
||||
import 'package:fl_clash/models/config.dart';
|
||||
import 'package:fl_clash/models/dav.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/card.dart';
|
||||
import 'package:fl_clash/widgets/fade_box.dart';
|
||||
import 'package:fl_clash/widgets/list.dart';
|
||||
import 'package:fl_clash/widgets/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class BackupAndRecovery extends StatefulWidget {
|
||||
class BackupAndRecovery extends StatelessWidget {
|
||||
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(
|
||||
@@ -28,11 +25,15 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
|
||||
);
|
||||
}
|
||||
|
||||
_backup() async {
|
||||
_backupOnWebDAV(BuildContext context, DAVClient client) async {
|
||||
final commonScaffoldState = context.commonScaffoldState;
|
||||
final res = await commonScaffoldState?.loadingRun<bool>(() async {
|
||||
return await _client?.backup();
|
||||
});
|
||||
final res = await commonScaffoldState?.loadingRun<bool>(
|
||||
() async {
|
||||
final backupData = await globalState.appController.backupData();
|
||||
return await client.backup(Uint8List.fromList(backupData));
|
||||
},
|
||||
title: appLocalizations.backup,
|
||||
);
|
||||
if (res != true) return;
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.backup,
|
||||
@@ -40,11 +41,20 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
|
||||
);
|
||||
}
|
||||
|
||||
_recovery(RecoveryOption recoveryOption) async {
|
||||
_recoveryOnWebDAV(
|
||||
BuildContext context,
|
||||
DAVClient client,
|
||||
RecoveryOption recoveryOption,
|
||||
) async {
|
||||
final commonScaffoldState = context.commonScaffoldState;
|
||||
final res = await commonScaffoldState?.loadingRun<bool>(() async {
|
||||
return await _client?.recovery(recoveryOption: recoveryOption);
|
||||
});
|
||||
final res = await commonScaffoldState?.loadingRun<bool>(
|
||||
() async {
|
||||
final data = await client.recovery();
|
||||
await globalState.appController.recoveryData(data, recoveryOption);
|
||||
return true;
|
||||
},
|
||||
title: appLocalizations.recovery,
|
||||
);
|
||||
if (res != true) return;
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.recovery,
|
||||
@@ -52,12 +62,65 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
|
||||
);
|
||||
}
|
||||
|
||||
_handleRecovery() async {
|
||||
_handleRecoveryOnWebDAV(BuildContext context, DAVClient client) async {
|
||||
final recoveryOption = await globalState.showCommonDialog<RecoveryOption>(
|
||||
child: const RecoveryOptionsDialog(),
|
||||
);
|
||||
if (recoveryOption == null) return;
|
||||
_recovery(recoveryOption);
|
||||
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();
|
||||
await picker.saveFile(
|
||||
other.getBackupFileName(),
|
||||
Uint8List.fromList(backupData),
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -65,12 +128,11 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
|
||||
return Selector<Config, DAV?>(
|
||||
selector: (_, config) => config.dav,
|
||||
builder: (_, dav, __) {
|
||||
if (dav == null) {
|
||||
return ListView(
|
||||
children: [
|
||||
ListHeader(
|
||||
title: appLocalizations.account,
|
||||
),
|
||||
final client = dav != null ? DAVClient(dav) : null;
|
||||
return ListView(
|
||||
children: [
|
||||
ListHeader(title: appLocalizations.remote),
|
||||
if (dav == null)
|
||||
ListItem(
|
||||
leading: const Icon(Icons.account_box),
|
||||
title: Text(appLocalizations.noInfo),
|
||||
@@ -83,95 +145,95 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
|
||||
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),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
_client = DAVClient(dav);
|
||||
final pingFuture = _client!.pingCompleter.future;
|
||||
return ListView(
|
||||
children: [
|
||||
ListHeader(title: appLocalizations.account),
|
||||
ListHeader(title: appLocalizations.local),
|
||||
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: 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
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(),
|
||||
);
|
||||
onTap: () {
|
||||
_backupOnLocal(context);
|
||||
},
|
||||
title: Text(appLocalizations.backup),
|
||||
subtitle: Text(appLocalizations.localBackupDesc),
|
||||
),
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_handleRecoveryOnLocal(context);
|
||||
},
|
||||
title: Text(appLocalizations.recovery),
|
||||
subtitle: Text(appLocalizations.localRecoveryDesc),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -180,6 +242,50 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -238,7 +344,7 @@ class _WebDAVFormDialogState extends State<WebDAVFormDialog> {
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: uriController,
|
||||
maxLines: 2,
|
||||
maxLines: 5,
|
||||
minLines: 1,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.link),
|
||||
@@ -313,47 +419,3 @@ 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,11 +137,40 @@ 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, __) {
|
||||
@@ -263,6 +292,19 @@ 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, __) {
|
||||
@@ -589,3 +631,64 @@ 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),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'view_profile.dart';
|
||||
|
||||
class EditProfile extends StatefulWidget {
|
||||
final Profile profile;
|
||||
final BuildContext context;
|
||||
@@ -89,8 +91,11 @@ class _EditProfileState extends State<EditProfile> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<FileInfo> _getFileInfo(path) async {
|
||||
Future<FileInfo?> _getFileInfo(path) async {
|
||||
final file = File(path);
|
||||
if (!await file.exists()) {
|
||||
return null;
|
||||
}
|
||||
final lastModified = await file.lastModified();
|
||||
final size = await file.length();
|
||||
return FileInfo(
|
||||
@@ -118,7 +123,7 @@ class _EditProfileState extends State<EditProfile> {
|
||||
}
|
||||
|
||||
_uploadProfileFile() async {
|
||||
final platformFile = await globalState.safeRun(picker.pickerConfigFile);
|
||||
final platformFile = await globalState.safeRun(picker.pickerFile);
|
||||
if (platformFile?.bytes == null) return;
|
||||
fileData = platformFile?.bytes;
|
||||
fileInfoNotifier.value = fileInfoNotifier.value?.copyWith(
|
||||
@@ -127,59 +132,6 @@ class _EditProfileState extends State<EditProfile> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSubtitle() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
ValueListenableBuilder<FileInfo?>(
|
||||
valueListenable: fileInfoNotifier,
|
||||
builder: (_, fileInfo, __) {
|
||||
final height =
|
||||
globalState.appController.measure.bodyMediumHeight + 4;
|
||||
return SizedBox(
|
||||
height: height,
|
||||
child: FadeBox(
|
||||
child: fileInfo == null
|
||||
? SizedBox(
|
||||
width: height,
|
||||
height: height,
|
||||
child: const CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
fileInfo.desc,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 6,
|
||||
spacing: 12,
|
||||
children: [
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.edit),
|
||||
label: appLocalizations.edit,
|
||||
onPressed: _editProfileFile,
|
||||
),
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.upload),
|
||||
label: appLocalizations.upload,
|
||||
onPressed: _uploadProfileFile,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final items = [
|
||||
@@ -250,9 +202,49 @@ class _EditProfileState extends State<EditProfile> {
|
||||
),
|
||||
),
|
||||
],
|
||||
ListItem(
|
||||
title: Text(appLocalizations.profile),
|
||||
subtitle: _buildSubtitle(),
|
||||
ValueListenableBuilder<FileInfo?>(
|
||||
valueListenable: fileInfoNotifier,
|
||||
builder: (_, fileInfo, __) {
|
||||
return FadeBox(
|
||||
child: fileInfo == null
|
||||
? Container()
|
||||
: ListItem(
|
||||
title: Text(
|
||||
appLocalizations.profile,
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
fileInfo.desc,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 6,
|
||||
spacing: 12,
|
||||
children: [
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.edit),
|
||||
label: appLocalizations.edit,
|
||||
onPressed: _editProfileFile,
|
||||
),
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.upload),
|
||||
label: appLocalizations.upload,
|
||||
onPressed: _uploadProfileFile,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
return FloatLayout(
|
||||
@@ -270,23 +262,19 @@ class _EditProfileState extends State<EditProfile> {
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
),
|
||||
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,
|
||||
child: ListView.separated(
|
||||
padding: kMaterialListPadding.copyWith(
|
||||
bottom: 72,
|
||||
),
|
||||
itemBuilder: (_, index) {
|
||||
return items[index];
|
||||
},
|
||||
separatorBuilder: (_, __) {
|
||||
return const SizedBox(
|
||||
height: 24,
|
||||
);
|
||||
},
|
||||
itemCount: items.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -27,8 +27,6 @@ class ProfilesFragment extends StatefulWidget {
|
||||
class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
Function? applyConfigDebounce;
|
||||
|
||||
List<GlobalObjectKey<_ProfileItemState>> profileItemKeys = [];
|
||||
|
||||
_handleShowAddExtendPage() {
|
||||
showExtendPage(
|
||||
globalState.navigatorKey.currentState!.context,
|
||||
@@ -51,8 +49,28 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
}
|
||||
|
||||
_updateProfiles() async {
|
||||
final updateProfiles = profileItemKeys.map<Future>(
|
||||
(key) async => await key.currentState?.updateProfile(false));
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
await Future.wait(updateProfiles);
|
||||
}
|
||||
|
||||
@@ -74,19 +92,6 @@ 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(
|
||||
@@ -119,40 +124,33 @@ 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: 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -162,7 +160,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileItem extends StatefulWidget {
|
||||
class ProfileItem extends StatelessWidget {
|
||||
final Profile profile;
|
||||
final String? groupValue;
|
||||
final void Function(String? value) onChanged;
|
||||
@@ -174,22 +172,15 @@ class ProfileItem extends StatefulWidget {
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ProfileItem> createState() => _ProfileItemState();
|
||||
}
|
||||
|
||||
class _ProfileItemState extends State<ProfileItem> {
|
||||
final isUpdating = ValueNotifier<bool>(false);
|
||||
|
||||
_handleDeleteProfile() async {
|
||||
_handleDeleteProfile(BuildContext context) async {
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.tip,
|
||||
message: TextSpan(
|
||||
text: appLocalizations.deleteProfileTip,
|
||||
),
|
||||
onTab: () async {
|
||||
await globalState.appController.deleteProfile(widget.profile.id);
|
||||
if(mounted){
|
||||
await globalState.appController.deleteProfile(profile.id);
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
@@ -200,122 +191,101 @@ class _ProfileItemState extends State<ProfileItem> {
|
||||
await globalState.safeRun<void>(updateProfile);
|
||||
}
|
||||
|
||||
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 {
|
||||
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,
|
||||
),
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
isUpdating.value = false;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
_handleShowEditExtendPage() {
|
||||
_handleShowEditExtendPage(BuildContext context) {
|
||||
showExtendPage(
|
||||
context,
|
||||
body: EditProfile(
|
||||
profile: widget.profile,
|
||||
profile: profile,
|
||||
context: context,
|
||||
),
|
||||
title: "${appLocalizations.edit}${appLocalizations.profile}",
|
||||
);
|
||||
}
|
||||
|
||||
_buildTitle(Profile profile) {
|
||||
final textTheme = context.textTheme;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
profile.label ?? profile.id,
|
||||
style: textTheme.titleMedium,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
profile.lastUpdateDate?.lastUpdateTimeDesc ?? '',
|
||||
style: textTheme.labelMedium?.toLight,
|
||||
),
|
||||
],
|
||||
),
|
||||
Builder(builder: (context) {
|
||||
final userInfo = profile.userInfo ?? const UserInfo();
|
||||
final use = userInfo.upload + userInfo.download;
|
||||
final total = userInfo.total;
|
||||
final useShow = TrafficValue(value: use).show;
|
||||
final totalShow = TrafficValue(value: total).show;
|
||||
final progress = total == 0 ? 0.0 : use / total;
|
||||
final expireShow = userInfo.expire == 0
|
||||
? appLocalizations.infiniteTime
|
||||
: DateTime.fromMillisecondsSinceEpoch(userInfo.expire * 1000)
|
||||
.show;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
),
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 6,
|
||||
value: progress,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"$useShow / $totalShow",
|
||||
style: textTheme.labelMedium?.toLight,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
expireShow,
|
||||
style: textTheme.labelMedium?.toLight,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
List<Widget> _buildUserInfo(BuildContext context, UserInfo userInfo) {
|
||||
final use = userInfo.upload + userInfo.download;
|
||||
final total = userInfo.total;
|
||||
if (total == 0) {
|
||||
return [];
|
||||
}
|
||||
final useShow = TrafficValue(value: use).show;
|
||||
final totalShow = TrafficValue(value: total).show;
|
||||
final progress = total == 0 ? 0.0 : use / total;
|
||||
final expireShow = userInfo.expire == 0
|
||||
? appLocalizations.infiniteTime
|
||||
: DateTime.fromMillisecondsSinceEpoch(userInfo.expire * 1000).show;
|
||||
return [
|
||||
LinearProgressIndicator(
|
||||
minHeight: 6,
|
||||
value: progress,
|
||||
),
|
||||
);
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Text(
|
||||
"$useShow / $totalShow · $expireShow",
|
||||
style: context.textTheme.labelMedium?.toLight,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
isUpdating.dispose();
|
||||
super.dispose();
|
||||
List<Widget> _buildUrlProfileInfo(BuildContext context) {
|
||||
final userInfo = profile.userInfo;
|
||||
return [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (userInfo != null) ..._buildUserInfo(context, userInfo),
|
||||
Text(
|
||||
profile.lastUpdateDate?.lastUpdateTimeDesc ?? "",
|
||||
style: context.textTheme.labelMedium?.toLight,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildFileProfileInfo(BuildContext context) {
|
||||
return [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Text(
|
||||
profile.lastUpdateDate?.lastUpdateTimeDesc ?? "",
|
||||
style: context.textTheme.labelMedium?.toLight,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final profile = widget.profile;
|
||||
final groupValue = widget.groupValue;
|
||||
final onChanged = widget.onChanged;
|
||||
return CommonCard(
|
||||
isSelected: profile.id == groupValue,
|
||||
onPressed: () {
|
||||
@@ -328,55 +298,75 @@ class _ProfileItemState extends State<ProfileItem> {
|
||||
trailing: SizedBox(
|
||||
height: 40,
|
||||
width: 40,
|
||||
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;
|
||||
}
|
||||
},
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
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,
|
||||
),
|
||||
);
|
||||
|
||||
232
lib/fragments/profiles/view_profile.dart
Normal file
232
lib/fragments/profiles/view_profile.dart
Normal file
@@ -0,0 +1,232 @@
|
||||
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!);
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ double getItemHeight(ProxyCardType proxyCardType) {
|
||||
|
||||
delayTest(List<Proxy> proxies) async {
|
||||
final appController = globalState.appController;
|
||||
for (final proxy in proxies) {
|
||||
final delayProxies = proxies.map<Future>((proxy) async {
|
||||
final proxyName = appController.appState.getRealProxyName(proxy.name);
|
||||
globalState.appController.setDelay(
|
||||
Delay(
|
||||
@@ -50,11 +50,9 @@ delayTest(List<Proxy> proxies) async {
|
||||
value: 0,
|
||||
),
|
||||
);
|
||||
clashCore.getDelay(proxyName).then((delay) {
|
||||
globalState.appController.setDelay(delay);
|
||||
});
|
||||
}
|
||||
await Future.delayed(httpTimeoutDuration + moreDuration);
|
||||
globalState.appController.setDelay(await clashCore.getDelay(proxyName));
|
||||
});
|
||||
await Future.wait(delayProxies);
|
||||
appController.appState.sortNum++;
|
||||
}
|
||||
|
||||
|
||||
187
lib/fragments/proxies/providers.dart
Normal file
187
lib/fragments/proxies/providers.dart
Normal file
@@ -0,0 +1,187 @@
|
||||
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,
|
||||
providerType: provider.type,
|
||||
);
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
},
|
||||
);
|
||||
await Future.wait(updateProviders);
|
||||
}
|
||||
|
||||
@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,
|
||||
});
|
||||
|
||||
_handleUpdateProfile() async {
|
||||
await globalState.safeRun<void>(updateProvider);
|
||||
}
|
||||
|
||||
updateProvider() 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,
|
||||
providerType: provider.type,
|
||||
);
|
||||
if (message.isNotEmpty) throw message;
|
||||
});
|
||||
appState.setProvider(
|
||||
clashCore.getExternalProvider(provider.name),
|
||||
);
|
||||
}
|
||||
|
||||
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: () {},
|
||||
// ),
|
||||
if (provider.vehicleType == "HTTP")
|
||||
CommonChip(
|
||||
avatar: const Icon(Icons.sync),
|
||||
label: appLocalizations.sync,
|
||||
onPressed: () {
|
||||
_handleUpdateProfile();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: SizedBox(
|
||||
height: 48,
|
||||
width: 48,
|
||||
child: FadeBox(
|
||||
child: provider.isUpdating
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ 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';
|
||||
|
||||
@@ -19,18 +20,37 @@ class ProxiesFragment extends StatefulWidget {
|
||||
class _ProxiesFragmentState extends State<ProxiesFragment> {
|
||||
final GlobalKey<ProxiesTabFragmentState> _proxiesTabKey = GlobalKey();
|
||||
|
||||
_initActions(ProxiesType proxiesType) {
|
||||
_initActions(ProxiesType proxiesType, bool hasProvider) {
|
||||
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.gps_fixed,
|
||||
Icons.adjust_outlined,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
@@ -60,18 +80,18 @@ class _ProxiesFragmentState extends State<ProxiesFragment> {
|
||||
return Selector<Config, ProxiesType>(
|
||||
selector: (_, config) => config.proxiesType,
|
||||
builder: (_, proxiesType, __) {
|
||||
return Selector<AppState, bool>(
|
||||
selector: (_, appState) => appState.currentLabel == 'proxies',
|
||||
builder: (_, isCurrent, child) {
|
||||
if (isCurrent) {
|
||||
_initActions(proxiesType);
|
||||
return ProxiesActionsBuilder(
|
||||
builder: (state, child) {
|
||||
if (state.isCurrent) {
|
||||
_initActions(proxiesType, state.hasProvider);
|
||||
}
|
||||
return switch (proxiesType) {
|
||||
ProxiesType.tab => ProxiesTabFragment(
|
||||
key: _proxiesTabKey,
|
||||
),
|
||||
ProxiesType.list => const ProxiesListFragment(),
|
||||
};
|
||||
return child!;
|
||||
},
|
||||
child: switch (proxiesType) {
|
||||
ProxiesType.tab => ProxiesTabFragment(
|
||||
key: _proxiesTabKey,
|
||||
),
|
||||
ProxiesType.list => const ProxiesListFragment(),
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -22,80 +22,11 @@ class GeoItem {
|
||||
});
|
||||
}
|
||||
|
||||
class Resources extends StatefulWidget {
|
||||
class Resources extends StatelessWidget {
|
||||
const Resources({super.key});
|
||||
|
||||
@override
|
||||
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() {
|
||||
Widget build(BuildContext context) {
|
||||
const geoItems = <GeoItem>[
|
||||
GeoItem(
|
||||
label: "GeoIp",
|
||||
@@ -111,26 +42,19 @@ class _ResourcesState extends State<Resources> {
|
||||
GeoItem(label: "ASN", fileName: asnFileName, key: "asn"),
|
||||
];
|
||||
|
||||
return generateInfoSection(
|
||||
info: Info(
|
||||
iconData: Icons.storage,
|
||||
label: appLocalizations.geoData,
|
||||
),
|
||||
items: geoItems.map(
|
||||
(geoItem) => GeoDataListItem(
|
||||
return ListView.separated(
|
||||
itemBuilder: (_, index) {
|
||||
final geoItem = geoItems[index];
|
||||
return GeoDataListItem(
|
||||
geoItem: geoItem,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return generateListView(
|
||||
[
|
||||
..._buildGeoDataSection(),
|
||||
..._buildExternalProviderSection(),
|
||||
],
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return const Divider(
|
||||
height: 0,
|
||||
);
|
||||
},
|
||||
itemCount: geoItems.length,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -226,9 +150,6 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 6,
|
||||
spacing: 12,
|
||||
@@ -315,117 +236,6 @@ 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;
|
||||
|
||||
@@ -26,9 +26,7 @@ class ThemeFragment extends StatelessWidget {
|
||||
final previewCard = Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: CommonCard(
|
||||
onPressed: (){
|
||||
|
||||
},
|
||||
onPressed: () {},
|
||||
info: Info(
|
||||
label: appLocalizations.preview,
|
||||
iconData: Icons.looks,
|
||||
@@ -87,7 +85,6 @@ class ThemeColorsBox extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ThemeColorsBoxState extends State<ThemeColorsBox> {
|
||||
|
||||
Widget _themeModeCheckBox({
|
||||
bool? isSelected,
|
||||
required ThemeModeItem themeModeItem,
|
||||
@@ -229,6 +226,27 @@ class _ThemeColorsBoxState extends State<ThemeColorsBox> {
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Selector<Config, bool>(
|
||||
selector: (_, config) => config.prueBlack,
|
||||
builder: (_, value, ___) {
|
||||
return ListItem.switchItem(
|
||||
leading: Icon(
|
||||
Icons.contrast,
|
||||
color: context.colorScheme.primary,
|
||||
),
|
||||
title: Text(appLocalizations.prueBlackMode),
|
||||
delegate: SwitchDelegate(
|
||||
value: value,
|
||||
onChanged: (value){
|
||||
globalState.appController.config.prueBlack = value;
|
||||
}
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"hours": "Hours",
|
||||
"days": "Days",
|
||||
"minutes": "Minutes",
|
||||
"seconds": "Seconds",
|
||||
"ago": " Ago",
|
||||
"just": "Just",
|
||||
"qrcode": "QR code",
|
||||
@@ -130,12 +131,10 @@
|
||||
"notSelectedTip": "The current proxy group cannot be selected.",
|
||||
"tip": "tip",
|
||||
"backupAndRecovery": "Backup and Recovery",
|
||||
"backupAndRecoveryDesc": "Sync data by WebDAV",
|
||||
"backupAndRecoveryDesc": "Sync data via WebDAV or file",
|
||||
"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,5 +218,14 @@
|
||||
"autoCloseConnectionsDesc": "Auto close connections after change node",
|
||||
"onlyStatisticsProxy": "Only statistics proxy",
|
||||
"onlyStatisticsProxyDesc": "When turned on, only statistics proxy traffic",
|
||||
"deleteProfileTip": "Sure you want to delete the current profile?"
|
||||
"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"
|
||||
}
|
||||
@@ -66,6 +66,7 @@
|
||||
"hours": "小时",
|
||||
"days": "天",
|
||||
"minutes": "分钟",
|
||||
"seconds": "秒",
|
||||
"ago": "前",
|
||||
"just": "刚刚",
|
||||
"qrcode": "二维码",
|
||||
@@ -130,12 +131,10 @@
|
||||
"notSelectedTip": "当前代理组无法选中",
|
||||
"tip": "提示",
|
||||
"backupAndRecovery": "备份与恢复",
|
||||
"backupAndRecoveryDesc": "通过WebDAV同步数据",
|
||||
"backupAndRecoveryDesc": "通过WebDAV或者文件同步数据",
|
||||
"account": "账号",
|
||||
"backup": "备份",
|
||||
"backupDesc": "备份数据到WebDAV",
|
||||
"recovery": "恢复",
|
||||
"recoveryDesc": "从WebDAV恢复数据",
|
||||
"recoveryProfiles": "仅恢复配置文件",
|
||||
"recoveryAll": "恢复所有数据",
|
||||
"recoverySuccess": "恢复成功",
|
||||
@@ -219,5 +218,14 @@
|
||||
"autoCloseConnectionsDesc": "切换节点后自动关闭连接",
|
||||
"onlyStatisticsProxy": "仅统计代理",
|
||||
"onlyStatisticsProxyDesc": "开启后,将只统计代理流量",
|
||||
"deleteProfileTip": "确定要删除当前配置吗?"
|
||||
"deleteProfileTip": "确定要删除当前配置吗?",
|
||||
"prueBlackMode": "纯黑模式",
|
||||
"keepAliveIntervalDesc": "TCP保持活动间隔",
|
||||
"entries": "个条目",
|
||||
"local": "本地",
|
||||
"remote": "远程",
|
||||
"remoteBackupDesc": "备份数据到WebDAV",
|
||||
"remoteRecoveryDesc": "通过WebDAV恢复数据",
|
||||
"localBackupDesc": "备份数据到本地",
|
||||
"localRecoveryDesc": "通过文件恢复数据"
|
||||
}
|
||||
@@ -74,10 +74,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"backup": MessageLookupByLibrary.simpleMessage("Backup"),
|
||||
"backupAndRecovery":
|
||||
MessageLookupByLibrary.simpleMessage("Backup and Recovery"),
|
||||
"backupAndRecoveryDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Sync data by WebDAV"),
|
||||
"backupDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Backup local data to WebDAV"),
|
||||
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Sync data via WebDAV or file"),
|
||||
"backupSuccess": MessageLookupByLibrary.simpleMessage("Backup success"),
|
||||
"bind": MessageLookupByLibrary.simpleMessage("Bind"),
|
||||
"blacklistMode": MessageLookupByLibrary.simpleMessage("Blacklist mode"),
|
||||
@@ -129,6 +127,7 @@ 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(
|
||||
@@ -172,9 +171,16 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"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(
|
||||
@@ -257,18 +263,23 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"proxyPort": MessageLookupByLibrary.simpleMessage("ProxyPort"),
|
||||
"proxyPortDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Set the Clash listening port"),
|
||||
"prueBlackMode":
|
||||
MessageLookupByLibrary.simpleMessage("Prue black mode"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("QR code"),
|
||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Scan QR code to obtain profile"),
|
||||
"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"),
|
||||
@@ -278,6 +289,7 @@ 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"),
|
||||
|
||||
@@ -62,8 +62,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"backup": MessageLookupByLibrary.simpleMessage("备份"),
|
||||
"backupAndRecovery": MessageLookupByLibrary.simpleMessage("备份与恢复"),
|
||||
"backupAndRecoveryDesc":
|
||||
MessageLookupByLibrary.simpleMessage("通过WebDAV同步数据"),
|
||||
"backupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
|
||||
MessageLookupByLibrary.simpleMessage("通过WebDAV或者文件同步数据"),
|
||||
"backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"),
|
||||
"bind": MessageLookupByLibrary.simpleMessage("绑定"),
|
||||
"blacklistMode": MessageLookupByLibrary.simpleMessage("黑名单模式"),
|
||||
@@ -106,6 +105,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"download": MessageLookupByLibrary.simpleMessage("下载"),
|
||||
"edit": MessageLookupByLibrary.simpleMessage("编辑"),
|
||||
"en": MessageLookupByLibrary.simpleMessage("英语"),
|
||||
"entries": MessageLookupByLibrary.simpleMessage("个条目"),
|
||||
"exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"),
|
||||
"excludeDesc":
|
||||
MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"),
|
||||
@@ -139,9 +139,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"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("禁用将会隐藏日志入口"),
|
||||
@@ -208,13 +213,17 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"proxyGroup": MessageLookupByLibrary.simpleMessage("代理组"),
|
||||
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
|
||||
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
|
||||
"prueBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
|
||||
"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("资源"),
|
||||
@@ -222,6 +231,7 @@ 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("设置"),
|
||||
|
||||
@@ -720,6 +720,16 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Seconds`
|
||||
String get seconds {
|
||||
return Intl.message(
|
||||
'Seconds',
|
||||
name: 'seconds',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// ` Ago`
|
||||
String get ago {
|
||||
return Intl.message(
|
||||
@@ -1360,10 +1370,10 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Sync data by WebDAV`
|
||||
/// `Sync data via WebDAV or file`
|
||||
String get backupAndRecoveryDesc {
|
||||
return Intl.message(
|
||||
'Sync data by WebDAV',
|
||||
'Sync data via WebDAV or file',
|
||||
name: 'backupAndRecoveryDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
@@ -1390,16 +1400,6 @@ 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,16 +1410,6 @@ 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,6 +2249,96 @@ class AppLocalizations {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Prue black mode`
|
||||
String get prueBlackMode {
|
||||
return Intl.message(
|
||||
'Prue black mode',
|
||||
name: 'prueBlackMode',
|
||||
desc: '',
|
||||
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: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -8,6 +8,7 @@ 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';
|
||||
@@ -35,6 +36,8 @@ class AppState with ChangeNotifier {
|
||||
double _viewWidth;
|
||||
List<Connection> _requests;
|
||||
num _checkIpNum;
|
||||
List<ExternalProvider> _providers;
|
||||
List<Package> _packages;
|
||||
|
||||
AppState({
|
||||
required Mode mode,
|
||||
@@ -54,6 +57,8 @@ class AppState with ChangeNotifier {
|
||||
_totalTraffic = Traffic(),
|
||||
_delayMap = {},
|
||||
_groups = [],
|
||||
_providers = [],
|
||||
_packages = [],
|
||||
_isCompatible = isCompatible,
|
||||
_systemColorSchemes = const SystemColorSchemes();
|
||||
|
||||
@@ -330,6 +335,31 @@ 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) {
|
||||
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,6 +119,7 @@ class ClashConfig extends ChangeNotifier {
|
||||
String _externalController;
|
||||
Mode _mode;
|
||||
FindProcessMode _findProcessMode;
|
||||
int _keepAliveInterval;
|
||||
bool _unifiedDelay;
|
||||
bool _tcpConcurrent;
|
||||
Tun _tun;
|
||||
@@ -139,6 +140,7 @@ class ClashConfig extends ChangeNotifier {
|
||||
_unifiedDelay = false,
|
||||
_geodataLoader = geodataLoaderMemconservative,
|
||||
_externalController = '',
|
||||
_keepAliveInterval = 30,
|
||||
_dns = Dns(),
|
||||
_geoXUrl = defaultGeoXMap,
|
||||
_rules = [];
|
||||
@@ -203,6 +205,16 @@ 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;
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ class AccessControl with _$AccessControl {
|
||||
class CoreState with _$CoreState {
|
||||
const factory CoreState({
|
||||
AccessControl? accessControl,
|
||||
required String currentProfileName,
|
||||
required bool allowBypass,
|
||||
required bool systemProxy,
|
||||
required int mixedPort,
|
||||
@@ -82,6 +83,7 @@ class Config extends ChangeNotifier {
|
||||
String _testUrl;
|
||||
WindowProps _windowProps;
|
||||
bool _onlyProxy;
|
||||
bool _prueBlack;
|
||||
|
||||
Config()
|
||||
: _profiles = [],
|
||||
@@ -107,6 +109,7 @@ class Config extends ChangeNotifier {
|
||||
_windowProps = defaultWindowProps,
|
||||
_proxiesType = ProxiesType.tab,
|
||||
_proxiesColumns = 2,
|
||||
_prueBlack = false,
|
||||
_onlyProxy = false;
|
||||
|
||||
deleteProfileById(String id) {
|
||||
@@ -423,6 +426,17 @@ class Config extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool get prueBlack {
|
||||
return _prueBlack;
|
||||
}
|
||||
|
||||
set prueBlack(bool value) {
|
||||
if (_prueBlack != value) {
|
||||
_prueBlack = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool get isCloseConnections {
|
||||
@@ -530,6 +544,7 @@ class Config extends ChangeNotifier {
|
||||
_accessControl = config._accessControl;
|
||||
_isAnimateToPage = config._isAnimateToPage;
|
||||
_autoCheckUpdate = config._autoCheckUpdate;
|
||||
_prueBlack = config._prueBlack;
|
||||
_testUrl = config._testUrl;
|
||||
_isExclude = config._isExclude;
|
||||
_windowProps = config._windowProps;
|
||||
|
||||
@@ -24,7 +24,7 @@ class ConfigExtendedParams with _$ConfigExtendedParams {
|
||||
@freezed
|
||||
class UpdateConfigParams with _$UpdateConfigParams {
|
||||
const factory UpdateConfigParams({
|
||||
@JsonKey(name: "profile-path") String? profilePath,
|
||||
@JsonKey(name: "profile-id") required String profileId,
|
||||
required ClashConfig config,
|
||||
required ConfigExtendedParams params,
|
||||
}) = _UpdateConfigParams;
|
||||
@@ -123,6 +123,9 @@ 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;
|
||||
@@ -140,7 +143,7 @@ abstract mixin class AppMessageListener {
|
||||
|
||||
void onStarted(String runTime) {}
|
||||
|
||||
void onLoaded(String groupName) {}
|
||||
void onLoaded(String providerName) {}
|
||||
}
|
||||
|
||||
abstract mixin class ServiceMessageListener {
|
||||
@@ -150,7 +153,5 @@ abstract mixin class ServiceMessageListener {
|
||||
|
||||
onStarted(String runTime) {}
|
||||
|
||||
onLoaded(String groupName) {}
|
||||
onLoaded(String providerName) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ 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
|
||||
@@ -75,6 +76,7 @@ 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,
|
||||
|
||||
@@ -247,6 +247,7 @@ 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;
|
||||
@@ -265,6 +266,7 @@ abstract class $CoreStateCopyWith<$Res> {
|
||||
@useResult
|
||||
$Res call(
|
||||
{AccessControl? accessControl,
|
||||
String currentProfileName,
|
||||
bool allowBypass,
|
||||
bool systemProxy,
|
||||
int mixedPort,
|
||||
@@ -287,6 +289,7 @@ class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
|
||||
@override
|
||||
$Res call({
|
||||
Object? accessControl = freezed,
|
||||
Object? currentProfileName = null,
|
||||
Object? allowBypass = null,
|
||||
Object? systemProxy = null,
|
||||
Object? mixedPort = null,
|
||||
@@ -297,6 +300,10 @@ 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
|
||||
@@ -339,6 +346,7 @@ abstract class _$$CoreStateImplCopyWith<$Res>
|
||||
@useResult
|
||||
$Res call(
|
||||
{AccessControl? accessControl,
|
||||
String currentProfileName,
|
||||
bool allowBypass,
|
||||
bool systemProxy,
|
||||
int mixedPort,
|
||||
@@ -360,6 +368,7 @@ class __$$CoreStateImplCopyWithImpl<$Res>
|
||||
@override
|
||||
$Res call({
|
||||
Object? accessControl = freezed,
|
||||
Object? currentProfileName = null,
|
||||
Object? allowBypass = null,
|
||||
Object? systemProxy = null,
|
||||
Object? mixedPort = null,
|
||||
@@ -370,6 +379,10 @@ 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
|
||||
@@ -395,6 +408,7 @@ class __$$CoreStateImplCopyWithImpl<$Res>
|
||||
class _$CoreStateImpl implements _CoreState {
|
||||
const _$CoreStateImpl(
|
||||
{this.accessControl,
|
||||
required this.currentProfileName,
|
||||
required this.allowBypass,
|
||||
required this.systemProxy,
|
||||
required this.mixedPort,
|
||||
@@ -406,6 +420,8 @@ class _$CoreStateImpl implements _CoreState {
|
||||
@override
|
||||
final AccessControl? accessControl;
|
||||
@override
|
||||
final String currentProfileName;
|
||||
@override
|
||||
final bool allowBypass;
|
||||
@override
|
||||
final bool systemProxy;
|
||||
@@ -416,7 +432,7 @@ class _$CoreStateImpl implements _CoreState {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CoreState(accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, mixedPort: $mixedPort, onlyProxy: $onlyProxy)';
|
||||
return 'CoreState(accessControl: $accessControl, currentProfileName: $currentProfileName, allowBypass: $allowBypass, systemProxy: $systemProxy, mixedPort: $mixedPort, onlyProxy: $onlyProxy)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -426,6 +442,8 @@ 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) ||
|
||||
@@ -438,8 +456,8 @@ class _$CoreStateImpl implements _CoreState {
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, accessControl, allowBypass,
|
||||
systemProxy, mixedPort, onlyProxy);
|
||||
int get hashCode => Object.hash(runtimeType, accessControl,
|
||||
currentProfileName, allowBypass, systemProxy, mixedPort, onlyProxy);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -458,6 +476,7 @@ 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,
|
||||
@@ -469,6 +488,8 @@ abstract class _CoreState implements CoreState {
|
||||
@override
|
||||
AccessControl? get accessControl;
|
||||
@override
|
||||
String get currentProfileName;
|
||||
@override
|
||||
bool get allowBypass;
|
||||
@override
|
||||
bool get systemProxy;
|
||||
|
||||
@@ -36,6 +36,7 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
|
||||
..allowBypass = json['allowBypass'] as bool? ?? true
|
||||
..systemProxy = json['systemProxy'] as bool? ?? false
|
||||
..onlyProxy = json['onlyProxy'] as bool? ?? false
|
||||
..prueBlack = json['prueBlack'] as bool? ?? false
|
||||
..isCloseConnections = json['isCloseConnections'] as bool? ?? false
|
||||
..proxiesType = $enumDecodeNullable(_$ProxiesTypeEnumMap, json['proxiesType'],
|
||||
unknownValue: ProxiesType.tab) ??
|
||||
@@ -71,6 +72,7 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
|
||||
'allowBypass': instance.allowBypass,
|
||||
'systemProxy': instance.systemProxy,
|
||||
'onlyProxy': instance.onlyProxy,
|
||||
'prueBlack': instance.prueBlack,
|
||||
'isCloseConnections': instance.isCloseConnections,
|
||||
'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!,
|
||||
'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!,
|
||||
@@ -137,6 +139,7 @@ _$CoreStateImpl _$$CoreStateImplFromJson(Map<String, dynamic> json) =>
|
||||
? 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(),
|
||||
@@ -146,6 +149,7 @@ _$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-path")
|
||||
String? get profilePath => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "profile-id")
|
||||
String get profileId => 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-path") String? profilePath,
|
||||
{@JsonKey(name: "profile-id") String profileId,
|
||||
ClashConfig config,
|
||||
ConfigExtendedParams params});
|
||||
|
||||
@@ -286,15 +286,15 @@ class _$UpdateConfigParamsCopyWithImpl<$Res, $Val extends UpdateConfigParams>
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? profilePath = freezed,
|
||||
Object? profileId = null,
|
||||
Object? config = null,
|
||||
Object? params = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
profilePath: freezed == profilePath
|
||||
? _value.profilePath
|
||||
: profilePath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
profileId: null == profileId
|
||||
? _value.profileId
|
||||
: profileId // 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-path") String? profilePath,
|
||||
{@JsonKey(name: "profile-id") String profileId,
|
||||
ClashConfig config,
|
||||
ConfigExtendedParams params});
|
||||
|
||||
@@ -343,15 +343,15 @@ class __$$UpdateConfigParamsImplCopyWithImpl<$Res>
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? profilePath = freezed,
|
||||
Object? profileId = null,
|
||||
Object? config = null,
|
||||
Object? params = null,
|
||||
}) {
|
||||
return _then(_$UpdateConfigParamsImpl(
|
||||
profilePath: freezed == profilePath
|
||||
? _value.profilePath
|
||||
: profilePath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
profileId: null == profileId
|
||||
? _value.profileId
|
||||
: profileId // 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-path") this.profilePath,
|
||||
{@JsonKey(name: "profile-id") required this.profileId,
|
||||
required this.config,
|
||||
required this.params});
|
||||
|
||||
@@ -376,8 +376,8 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
|
||||
_$$UpdateConfigParamsImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey(name: "profile-path")
|
||||
final String? profilePath;
|
||||
@JsonKey(name: "profile-id")
|
||||
final String profileId;
|
||||
@override
|
||||
final ClashConfig config;
|
||||
@override
|
||||
@@ -385,7 +385,7 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UpdateConfigParams(profilePath: $profilePath, config: $config, params: $params)';
|
||||
return 'UpdateConfigParams(profileId: $profileId, 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.profilePath, profilePath) ||
|
||||
other.profilePath == profilePath) &&
|
||||
(identical(other.profileId, profileId) ||
|
||||
other.profileId == profileId) &&
|
||||
(identical(other.config, config) || other.config == config) &&
|
||||
(identical(other.params, params) || other.params == params));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, profilePath, config, params);
|
||||
int get hashCode => Object.hash(runtimeType, profileId, 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-path") final String? profilePath,
|
||||
{@JsonKey(name: "profile-id") required final String profileId,
|
||||
required final ClashConfig config,
|
||||
required final ConfigExtendedParams params}) = _$UpdateConfigParamsImpl;
|
||||
|
||||
@@ -428,8 +428,8 @@ abstract class _UpdateConfigParams implements UpdateConfigParams {
|
||||
_$UpdateConfigParamsImpl.fromJson;
|
||||
|
||||
@override
|
||||
@JsonKey(name: "profile-path")
|
||||
String? get profilePath;
|
||||
@JsonKey(name: "profile-id")
|
||||
String get profileId;
|
||||
@override
|
||||
ClashConfig get config;
|
||||
@override
|
||||
@@ -1687,6 +1687,9 @@ 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")
|
||||
@@ -1707,6 +1710,9 @@ 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});
|
||||
}
|
||||
@@ -1726,6 +1732,9 @@ 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,
|
||||
}) {
|
||||
@@ -1738,6 +1747,18 @@ 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
|
||||
@@ -1761,6 +1782,9 @@ 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});
|
||||
}
|
||||
@@ -1778,6 +1802,9 @@ 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,
|
||||
}) {
|
||||
@@ -1790,6 +1817,18 @@ 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
|
||||
@@ -1808,6 +1847,9 @@ 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});
|
||||
|
||||
@@ -1819,6 +1861,13 @@ 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
|
||||
@@ -1827,7 +1876,7 @@ class _$ExternalProviderImpl implements _ExternalProvider {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ExternalProvider(name: $name, type: $type, vehicleType: $vehicleType, updateAt: $updateAt)';
|
||||
return 'ExternalProvider(name: $name, type: $type, path: $path, count: $count, isUpdating: $isUpdating, vehicleType: $vehicleType, updateAt: $updateAt)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1837,6 +1886,10 @@ 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) ||
|
||||
@@ -1845,8 +1898,8 @@ class _$ExternalProviderImpl implements _ExternalProvider {
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, name, type, vehicleType, updateAt);
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, name, type, path, count, isUpdating, vehicleType, updateAt);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -1867,6 +1920,9 @@ 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;
|
||||
@@ -1879,6 +1935,12 @@ 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(
|
||||
profilePath: json['profile-path'] as String?,
|
||||
profileId: json['profile-id'] 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-path': instance.profilePath,
|
||||
'profile-id': instance.profileId,
|
||||
'config': instance.config,
|
||||
'params': instance.params,
|
||||
};
|
||||
@@ -156,6 +156,9 @@ _$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),
|
||||
);
|
||||
@@ -165,6 +168,9 @@ 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(),
|
||||
};
|
||||
|
||||
@@ -223,6 +223,8 @@ 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)
|
||||
@@ -244,7 +246,8 @@ abstract class $ProfileCopyWith<$Res> {
|
||||
UserInfo? userInfo,
|
||||
bool autoUpdate,
|
||||
Map<String, String> selectedMap,
|
||||
Set<String> unfoldSet});
|
||||
Set<String> unfoldSet,
|
||||
@JsonKey(includeToJson: false, includeFromJson: false) bool isUpdating});
|
||||
|
||||
$UserInfoCopyWith<$Res>? get userInfo;
|
||||
}
|
||||
@@ -272,6 +275,7 @@ 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
|
||||
@@ -314,6 +318,10 @@ 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);
|
||||
}
|
||||
|
||||
@@ -347,7 +355,8 @@ abstract class _$$ProfileImplCopyWith<$Res> implements $ProfileCopyWith<$Res> {
|
||||
UserInfo? userInfo,
|
||||
bool autoUpdate,
|
||||
Map<String, String> selectedMap,
|
||||
Set<String> unfoldSet});
|
||||
Set<String> unfoldSet,
|
||||
@JsonKey(includeToJson: false, includeFromJson: false) bool isUpdating});
|
||||
|
||||
@override
|
||||
$UserInfoCopyWith<$Res>? get userInfo;
|
||||
@@ -374,6 +383,7 @@ class __$$ProfileImplCopyWithImpl<$Res>
|
||||
Object? autoUpdate = null,
|
||||
Object? selectedMap = null,
|
||||
Object? unfoldSet = null,
|
||||
Object? isUpdating = null,
|
||||
}) {
|
||||
return _then(_$ProfileImpl(
|
||||
id: null == id
|
||||
@@ -416,6 +426,10 @@ 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,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -433,7 +447,9 @@ class _$ProfileImpl implements _Profile {
|
||||
this.userInfo,
|
||||
this.autoUpdate = true,
|
||||
final Map<String, String> selectedMap = const {},
|
||||
final Set<String> unfoldSet = const {}})
|
||||
final Set<String> unfoldSet = const {},
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
this.isUpdating = false})
|
||||
: _selectedMap = selectedMap,
|
||||
_unfoldSet = unfoldSet;
|
||||
|
||||
@@ -476,9 +492,13 @@ 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)';
|
||||
return 'Profile(id: $id, label: $label, currentGroupName: $currentGroupName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate, selectedMap: $selectedMap, unfoldSet: $unfoldSet, isUpdating: $isUpdating)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -502,7 +522,9 @@ class _$ProfileImpl implements _Profile {
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._selectedMap, _selectedMap) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._unfoldSet, _unfoldSet));
|
||||
.equals(other._unfoldSet, _unfoldSet) &&
|
||||
(identical(other.isUpdating, isUpdating) ||
|
||||
other.isUpdating == isUpdating));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@@ -518,7 +540,8 @@ class _$ProfileImpl implements _Profile {
|
||||
userInfo,
|
||||
autoUpdate,
|
||||
const DeepCollectionEquality().hash(_selectedMap),
|
||||
const DeepCollectionEquality().hash(_unfoldSet));
|
||||
const DeepCollectionEquality().hash(_unfoldSet),
|
||||
isUpdating);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -545,7 +568,9 @@ abstract class _Profile implements Profile {
|
||||
final UserInfo? userInfo,
|
||||
final bool autoUpdate,
|
||||
final Map<String, String> selectedMap,
|
||||
final Set<String> unfoldSet}) = _$ProfileImpl;
|
||||
final Set<String> unfoldSet,
|
||||
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||
final bool isUpdating}) = _$ProfileImpl;
|
||||
|
||||
factory _Profile.fromJson(Map<String, dynamic> json) = _$ProfileImpl.fromJson;
|
||||
|
||||
@@ -570,6 +595,9 @@ 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;
|
||||
|
||||
@@ -633,6 +633,7 @@ mixin _$ApplicationSelectorState {
|
||||
String? get locale => throw _privateConstructorUsedError;
|
||||
ThemeMode? get themeMode => throw _privateConstructorUsedError;
|
||||
int? get primaryColor => throw _privateConstructorUsedError;
|
||||
bool get prueBlack => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$ApplicationSelectorStateCopyWith<ApplicationSelectorState> get copyWith =>
|
||||
@@ -645,7 +646,11 @@ abstract class $ApplicationSelectorStateCopyWith<$Res> {
|
||||
$Res Function(ApplicationSelectorState) then) =
|
||||
_$ApplicationSelectorStateCopyWithImpl<$Res, ApplicationSelectorState>;
|
||||
@useResult
|
||||
$Res call({String? locale, ThemeMode? themeMode, int? primaryColor});
|
||||
$Res call(
|
||||
{String? locale,
|
||||
ThemeMode? themeMode,
|
||||
int? primaryColor,
|
||||
bool prueBlack});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -665,6 +670,7 @@ class _$ApplicationSelectorStateCopyWithImpl<$Res,
|
||||
Object? locale = freezed,
|
||||
Object? themeMode = freezed,
|
||||
Object? primaryColor = freezed,
|
||||
Object? prueBlack = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
locale: freezed == locale
|
||||
@@ -679,6 +685,10 @@ class _$ApplicationSelectorStateCopyWithImpl<$Res,
|
||||
? _value.primaryColor
|
||||
: primaryColor // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
prueBlack: null == prueBlack
|
||||
? _value.prueBlack
|
||||
: prueBlack // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -692,7 +702,11 @@ abstract class _$$ApplicationSelectorStateImplCopyWith<$Res>
|
||||
__$$ApplicationSelectorStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String? locale, ThemeMode? themeMode, int? primaryColor});
|
||||
$Res call(
|
||||
{String? locale,
|
||||
ThemeMode? themeMode,
|
||||
int? primaryColor,
|
||||
bool prueBlack});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -711,6 +725,7 @@ class __$$ApplicationSelectorStateImplCopyWithImpl<$Res>
|
||||
Object? locale = freezed,
|
||||
Object? themeMode = freezed,
|
||||
Object? primaryColor = freezed,
|
||||
Object? prueBlack = null,
|
||||
}) {
|
||||
return _then(_$ApplicationSelectorStateImpl(
|
||||
locale: freezed == locale
|
||||
@@ -725,6 +740,10 @@ class __$$ApplicationSelectorStateImplCopyWithImpl<$Res>
|
||||
? _value.primaryColor
|
||||
: primaryColor // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
prueBlack: null == prueBlack
|
||||
? _value.prueBlack
|
||||
: prueBlack // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -733,7 +752,10 @@ class __$$ApplicationSelectorStateImplCopyWithImpl<$Res>
|
||||
|
||||
class _$ApplicationSelectorStateImpl implements _ApplicationSelectorState {
|
||||
const _$ApplicationSelectorStateImpl(
|
||||
{this.locale, this.themeMode, this.primaryColor});
|
||||
{required this.locale,
|
||||
required this.themeMode,
|
||||
required this.primaryColor,
|
||||
required this.prueBlack});
|
||||
|
||||
@override
|
||||
final String? locale;
|
||||
@@ -741,10 +763,12 @@ class _$ApplicationSelectorStateImpl implements _ApplicationSelectorState {
|
||||
final ThemeMode? themeMode;
|
||||
@override
|
||||
final int? primaryColor;
|
||||
@override
|
||||
final bool prueBlack;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ApplicationSelectorState(locale: $locale, themeMode: $themeMode, primaryColor: $primaryColor)';
|
||||
return 'ApplicationSelectorState(locale: $locale, themeMode: $themeMode, primaryColor: $primaryColor, prueBlack: $prueBlack)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -756,11 +780,14 @@ class _$ApplicationSelectorStateImpl implements _ApplicationSelectorState {
|
||||
(identical(other.themeMode, themeMode) ||
|
||||
other.themeMode == themeMode) &&
|
||||
(identical(other.primaryColor, primaryColor) ||
|
||||
other.primaryColor == primaryColor));
|
||||
other.primaryColor == primaryColor) &&
|
||||
(identical(other.prueBlack, prueBlack) ||
|
||||
other.prueBlack == prueBlack));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, locale, themeMode, primaryColor);
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, locale, themeMode, primaryColor, prueBlack);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -772,9 +799,10 @@ class _$ApplicationSelectorStateImpl implements _ApplicationSelectorState {
|
||||
|
||||
abstract class _ApplicationSelectorState implements ApplicationSelectorState {
|
||||
const factory _ApplicationSelectorState(
|
||||
{final String? locale,
|
||||
final ThemeMode? themeMode,
|
||||
final int? primaryColor}) = _$ApplicationSelectorStateImpl;
|
||||
{required final String? locale,
|
||||
required final ThemeMode? themeMode,
|
||||
required final int? primaryColor,
|
||||
required final bool prueBlack}) = _$ApplicationSelectorStateImpl;
|
||||
|
||||
@override
|
||||
String? get locale;
|
||||
@@ -783,6 +811,8 @@ abstract class _ApplicationSelectorState implements ApplicationSelectorState {
|
||||
@override
|
||||
int? get primaryColor;
|
||||
@override
|
||||
bool get prueBlack;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ApplicationSelectorStateImplCopyWith<_$ApplicationSelectorStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
@@ -2785,32 +2815,28 @@ abstract class _ProxiesListHeaderSelectorState
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$CurrentGroupProxyNameSelectorState {
|
||||
String? get proxyName => throw _privateConstructorUsedError;
|
||||
String? get proxyName2 => throw _privateConstructorUsedError;
|
||||
mixin _$ProxiesActionsState {
|
||||
bool get isCurrent => throw _privateConstructorUsedError;
|
||||
bool get hasProvider => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$CurrentGroupProxyNameSelectorStateCopyWith<
|
||||
CurrentGroupProxyNameSelectorState>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
$ProxiesActionsStateCopyWith<ProxiesActionsState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $CurrentGroupProxyNameSelectorStateCopyWith<$Res> {
|
||||
factory $CurrentGroupProxyNameSelectorStateCopyWith(
|
||||
CurrentGroupProxyNameSelectorState value,
|
||||
$Res Function(CurrentGroupProxyNameSelectorState) then) =
|
||||
_$CurrentGroupProxyNameSelectorStateCopyWithImpl<$Res,
|
||||
CurrentGroupProxyNameSelectorState>;
|
||||
abstract class $ProxiesActionsStateCopyWith<$Res> {
|
||||
factory $ProxiesActionsStateCopyWith(
|
||||
ProxiesActionsState value, $Res Function(ProxiesActionsState) then) =
|
||||
_$ProxiesActionsStateCopyWithImpl<$Res, ProxiesActionsState>;
|
||||
@useResult
|
||||
$Res call({String? proxyName, String? proxyName2});
|
||||
$Res call({bool isCurrent, bool hasProvider});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$CurrentGroupProxyNameSelectorStateCopyWithImpl<$Res,
|
||||
$Val extends CurrentGroupProxyNameSelectorState>
|
||||
implements $CurrentGroupProxyNameSelectorStateCopyWith<$Res> {
|
||||
_$CurrentGroupProxyNameSelectorStateCopyWithImpl(this._value, this._then);
|
||||
class _$ProxiesActionsStateCopyWithImpl<$Res, $Val extends ProxiesActionsState>
|
||||
implements $ProxiesActionsStateCopyWith<$Res> {
|
||||
_$ProxiesActionsStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
@@ -2820,117 +2846,109 @@ class _$CurrentGroupProxyNameSelectorStateCopyWithImpl<$Res,
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? proxyName = freezed,
|
||||
Object? proxyName2 = freezed,
|
||||
Object? isCurrent = null,
|
||||
Object? hasProvider = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
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?,
|
||||
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,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$CurrentGroupProxyNameSelectorStateImplCopyWith<$Res>
|
||||
implements $CurrentGroupProxyNameSelectorStateCopyWith<$Res> {
|
||||
factory _$$CurrentGroupProxyNameSelectorStateImplCopyWith(
|
||||
_$CurrentGroupProxyNameSelectorStateImpl value,
|
||||
$Res Function(_$CurrentGroupProxyNameSelectorStateImpl) then) =
|
||||
__$$CurrentGroupProxyNameSelectorStateImplCopyWithImpl<$Res>;
|
||||
abstract class _$$ProxiesActionsStateImplCopyWith<$Res>
|
||||
implements $ProxiesActionsStateCopyWith<$Res> {
|
||||
factory _$$ProxiesActionsStateImplCopyWith(_$ProxiesActionsStateImpl value,
|
||||
$Res Function(_$ProxiesActionsStateImpl) then) =
|
||||
__$$ProxiesActionsStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String? proxyName, String? proxyName2});
|
||||
$Res call({bool isCurrent, bool hasProvider});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$CurrentGroupProxyNameSelectorStateImplCopyWithImpl<$Res>
|
||||
extends _$CurrentGroupProxyNameSelectorStateCopyWithImpl<$Res,
|
||||
_$CurrentGroupProxyNameSelectorStateImpl>
|
||||
implements _$$CurrentGroupProxyNameSelectorStateImplCopyWith<$Res> {
|
||||
__$$CurrentGroupProxyNameSelectorStateImplCopyWithImpl(
|
||||
_$CurrentGroupProxyNameSelectorStateImpl _value,
|
||||
$Res Function(_$CurrentGroupProxyNameSelectorStateImpl) _then)
|
||||
class __$$ProxiesActionsStateImplCopyWithImpl<$Res>
|
||||
extends _$ProxiesActionsStateCopyWithImpl<$Res, _$ProxiesActionsStateImpl>
|
||||
implements _$$ProxiesActionsStateImplCopyWith<$Res> {
|
||||
__$$ProxiesActionsStateImplCopyWithImpl(_$ProxiesActionsStateImpl _value,
|
||||
$Res Function(_$ProxiesActionsStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? proxyName = freezed,
|
||||
Object? proxyName2 = freezed,
|
||||
Object? isCurrent = null,
|
||||
Object? hasProvider = null,
|
||||
}) {
|
||||
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?,
|
||||
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,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$CurrentGroupProxyNameSelectorStateImpl
|
||||
implements _CurrentGroupProxyNameSelectorState {
|
||||
const _$CurrentGroupProxyNameSelectorStateImpl(
|
||||
{required this.proxyName, required this.proxyName2});
|
||||
class _$ProxiesActionsStateImpl implements _ProxiesActionsState {
|
||||
const _$ProxiesActionsStateImpl(
|
||||
{required this.isCurrent, required this.hasProvider});
|
||||
|
||||
@override
|
||||
final String? proxyName;
|
||||
final bool isCurrent;
|
||||
@override
|
||||
final String? proxyName2;
|
||||
final bool hasProvider;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CurrentGroupProxyNameSelectorState(proxyName: $proxyName, proxyName2: $proxyName2)';
|
||||
return 'ProxiesActionsState(isCurrent: $isCurrent, hasProvider: $hasProvider)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$CurrentGroupProxyNameSelectorStateImpl &&
|
||||
(identical(other.proxyName, proxyName) ||
|
||||
other.proxyName == proxyName) &&
|
||||
(identical(other.proxyName2, proxyName2) ||
|
||||
other.proxyName2 == proxyName2));
|
||||
other is _$ProxiesActionsStateImpl &&
|
||||
(identical(other.isCurrent, isCurrent) ||
|
||||
other.isCurrent == isCurrent) &&
|
||||
(identical(other.hasProvider, hasProvider) ||
|
||||
other.hasProvider == hasProvider));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, proxyName, proxyName2);
|
||||
int get hashCode => Object.hash(runtimeType, isCurrent, hasProvider);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$CurrentGroupProxyNameSelectorStateImplCopyWith<
|
||||
_$CurrentGroupProxyNameSelectorStateImpl>
|
||||
get copyWith => __$$CurrentGroupProxyNameSelectorStateImplCopyWithImpl<
|
||||
_$CurrentGroupProxyNameSelectorStateImpl>(this, _$identity);
|
||||
_$$ProxiesActionsStateImplCopyWith<_$ProxiesActionsStateImpl> get copyWith =>
|
||||
__$$ProxiesActionsStateImplCopyWithImpl<_$ProxiesActionsStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _CurrentGroupProxyNameSelectorState
|
||||
implements CurrentGroupProxyNameSelectorState {
|
||||
const factory _CurrentGroupProxyNameSelectorState(
|
||||
{required final String? proxyName,
|
||||
required final String? proxyName2}) =
|
||||
_$CurrentGroupProxyNameSelectorStateImpl;
|
||||
abstract class _ProxiesActionsState implements ProxiesActionsState {
|
||||
const factory _ProxiesActionsState(
|
||||
{required final bool isCurrent,
|
||||
required final bool hasProvider}) = _$ProxiesActionsStateImpl;
|
||||
|
||||
@override
|
||||
String? get proxyName;
|
||||
bool get isCurrent;
|
||||
@override
|
||||
String? get proxyName2;
|
||||
bool get hasProvider;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$CurrentGroupProxyNameSelectorStateImplCopyWith<
|
||||
_$CurrentGroupProxyNameSelectorStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
_$$ProxiesActionsStateImplCopyWith<_$ProxiesActionsStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: invalid_annotation_target
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
@@ -55,6 +56,9 @@ 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) =>
|
||||
@@ -63,7 +67,7 @@ class Profile with _$Profile {
|
||||
factory Profile.normal({
|
||||
String? label,
|
||||
String url = '',
|
||||
}) {
|
||||
}) {
|
||||
return Profile(
|
||||
label: label,
|
||||
url: url,
|
||||
@@ -77,8 +81,7 @@ 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);
|
||||
}
|
||||
}
|
||||
@@ -41,9 +41,10 @@ class ProfilesSelectorState with _$ProfilesSelectorState {
|
||||
@freezed
|
||||
class ApplicationSelectorState with _$ApplicationSelectorState {
|
||||
const factory ApplicationSelectorState({
|
||||
String? locale,
|
||||
ThemeMode? themeMode,
|
||||
int? primaryColor,
|
||||
required String? locale,
|
||||
required ThemeMode? themeMode,
|
||||
required int? primaryColor,
|
||||
required bool prueBlack,
|
||||
}) = _ApplicationSelectorState;
|
||||
}
|
||||
|
||||
@@ -136,7 +137,6 @@ class PackageListSelectorState with _$PackageListSelectorState {
|
||||
}) = _PackageListSelectorState;
|
||||
}
|
||||
|
||||
|
||||
@freezed
|
||||
class ColumnsSelectorState with _$ColumnsSelectorState {
|
||||
const factory ColumnsSelectorState({
|
||||
@@ -153,10 +153,11 @@ class ProxiesListHeaderSelectorState with _$ProxiesListHeaderSelectorState {
|
||||
}) = _ProxiesListHeaderSelectorState;
|
||||
}
|
||||
|
||||
|
||||
@freezed
|
||||
class CurrentGroupProxyNameSelectorState with _$CurrentGroupProxyNameSelectorState {
|
||||
const factory CurrentGroupProxyNameSelectorState({
|
||||
required String? proxyName,
|
||||
required String? proxyName2,
|
||||
}) = _CurrentGroupProxyNameSelectorState;
|
||||
class ProxiesActionsState with _$ProxiesActionsState {
|
||||
const factory ProxiesActionsState({
|
||||
required bool isCurrent,
|
||||
required bool hasProvider,
|
||||
}) = _ProxiesActionsState;
|
||||
}
|
||||
@@ -12,15 +12,15 @@ class SystemColorSchemes {
|
||||
});
|
||||
|
||||
getSystemColorSchemeForBrightness(Brightness? brightness) {
|
||||
if (brightness != null && brightness == Brightness.dark) {
|
||||
if (brightness == Brightness.dark) {
|
||||
return darkColorScheme != null
|
||||
? ColorScheme.fromSeed(
|
||||
seedColor: darkColorScheme!.primary,
|
||||
brightness: brightness,
|
||||
brightness: Brightness.dark,
|
||||
)
|
||||
: ColorScheme.fromSeed(
|
||||
seedColor: defaultPrimaryColor,
|
||||
brightness: brightness,
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
}
|
||||
return lightColorScheme != null
|
||||
|
||||
@@ -45,11 +45,10 @@ 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(
|
||||
profilePath: profilePath,
|
||||
profileId: config.currentProfileId ?? "",
|
||||
config: clashConfig,
|
||||
params: ConfigExtendedParams(
|
||||
isPatch: isPatch,
|
||||
@@ -96,8 +95,12 @@ 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({
|
||||
@@ -111,6 +114,16 @@ class GlobalState {
|
||||
config: config,
|
||||
clashConfig: clashConfig,
|
||||
);
|
||||
clashCore.setState(
|
||||
CoreState(
|
||||
accessControl: config.isAccessControl ? config.accessControl : null,
|
||||
allowBypass: config.allowBypass,
|
||||
systemProxy: config.systemProxy,
|
||||
mixedPort: clashConfig.mixedPort,
|
||||
onlyProxy: config.onlyProxy,
|
||||
currentProfileName: config.currentProfile?.label ?? config.currentProfileId ?? "",
|
||||
),
|
||||
);
|
||||
}
|
||||
updateCoreVersionInfo(appState);
|
||||
}
|
||||
@@ -195,7 +208,7 @@ class GlobalState {
|
||||
final traffic = clashCore.getTraffic();
|
||||
if (Platform.isAndroid && isVpnService == true) {
|
||||
proxy?.startForeground(
|
||||
title: clashCore.getProfileName(),
|
||||
title: clashCore.getState().currentProfileName,
|
||||
content: "$traffic",
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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;
|
||||
@@ -15,7 +17,6 @@ class ScrollOverBuilder extends StatefulWidget {
|
||||
class _ScrollOverBuilderState extends State<ScrollOverBuilder> {
|
||||
final isOverNotifier = ValueNotifier<bool>(false);
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
@@ -38,3 +39,29 @@ 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,40 +32,39 @@ class InfoHeader extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
...actions,
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -146,13 +145,11 @@ class CommonCard extends StatelessWidget {
|
||||
childWidget = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 0,
|
||||
child: InfoHeader(
|
||||
info: info!,
|
||||
),
|
||||
InfoHeader(
|
||||
info: info!,
|
||||
),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -27,6 +27,8 @@ 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);
|
||||
@@ -36,9 +38,32 @@ 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 _updateCoreState(widget.child);
|
||||
return _changeProfileContainer(
|
||||
_updateCoreState(
|
||||
widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -73,16 +98,15 @@ class _ClashContainerState extends State<ClashContainer>
|
||||
}
|
||||
|
||||
@override
|
||||
void onLoaded(String groupName) {
|
||||
void onLoaded(String providerName) {
|
||||
final appController = globalState.appController;
|
||||
final currentSelectedMap = appController.config.currentSelectedMap;
|
||||
final proxyName = currentSelectedMap[groupName];
|
||||
if (proxyName == null) return;
|
||||
appController.changeProxy(
|
||||
groupName: groupName,
|
||||
proxyName: proxyName,
|
||||
appController.appState.setProvider(
|
||||
clashCore.getExternalProvider(
|
||||
providerName,
|
||||
),
|
||||
);
|
||||
super.onLoaded(proxyName);
|
||||
appController.addCheckIpNumDebounce();
|
||||
super.onLoaded(providerName);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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:fl_clash/widgets/scaffold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'side_sheet.dart';
|
||||
|
||||
showExtendPage(
|
||||
@@ -12,6 +10,7 @@ showExtendPage(
|
||||
required Widget body,
|
||||
required String title,
|
||||
double? extendPageWidth,
|
||||
bool forceNotSide = false,
|
||||
Widget? action,
|
||||
}) {
|
||||
final NavigatorState navigator = Navigator.of(context);
|
||||
@@ -20,35 +19,32 @@ showExtendPage(
|
||||
key: globalKey,
|
||||
child: body,
|
||||
);
|
||||
final isMobile =
|
||||
globalState.appController.appState.viewMode == ViewMode.mobile;
|
||||
final isNotSide = isMobile || forceNotSide;
|
||||
navigator.push(
|
||||
ModalSideSheetRoute(
|
||||
modalBarrierColor: Colors.black38,
|
||||
builder: (context) => Selector<AppState, double>(
|
||||
selector: (_, appState) => appState.viewWidth,
|
||||
builder: (_, viewWidth, __) {
|
||||
final isMobile =
|
||||
globalState.appController.appState.viewMode == ViewMode.mobile;
|
||||
final commonScaffold = CommonScaffold(
|
||||
automaticallyImplyLeading: isMobile ? true : false,
|
||||
actions: isMobile
|
||||
? null
|
||||
: [
|
||||
const SizedBox(
|
||||
height: kToolbarHeight,
|
||||
width: kToolbarHeight,
|
||||
child: CloseButton(),
|
||||
),
|
||||
],
|
||||
title: title,
|
||||
body: uniqueBody,
|
||||
);
|
||||
return AnimatedContainer(
|
||||
duration: kThemeAnimationDuration,
|
||||
width: isMobile ? viewWidth : extendPageWidth ?? 300,
|
||||
child: commonScaffold,
|
||||
);
|
||||
},
|
||||
),
|
||||
builder: (context) {
|
||||
final commonScaffold = CommonScaffold(
|
||||
automaticallyImplyLeading: isNotSide,
|
||||
actions: isNotSide
|
||||
? null
|
||||
: [
|
||||
const SizedBox(
|
||||
height: kToolbarHeight,
|
||||
width: kToolbarHeight,
|
||||
child: CloseButton(),
|
||||
),
|
||||
],
|
||||
title: title,
|
||||
body: uniqueBody,
|
||||
);
|
||||
return SizedBox(
|
||||
width: isMobile ? context.width : extendPageWidth ?? 300,
|
||||
child: commonScaffold,
|
||||
);
|
||||
},
|
||||
constraints: const BoxConstraints(),
|
||||
filter: filter,
|
||||
),
|
||||
|
||||
@@ -220,16 +220,14 @@ class _WindowHeaderState extends State<WindowHeader> {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!Platform.isMacOS) ...[
|
||||
const Positioned(
|
||||
left: 0,
|
||||
child: AppIcon(),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
child: _buildActions(),
|
||||
),
|
||||
]
|
||||
const Positioned(
|
||||
left: 0,
|
||||
child: AppIcon(),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
child: _buildActions(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
34
pubspec.lock
34
pubspec.lock
@@ -34,7 +34,7 @@ packages:
|
||||
source: hosted
|
||||
version: "3.5.1"
|
||||
archive:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: archive
|
||||
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||
@@ -525,6 +525,22 @@ 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:
|
||||
@@ -828,6 +844,22 @@ 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:
|
||||
|
||||
@@ -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.49+202407311
|
||||
version: 0.8.51+202408051
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
|
||||
@@ -38,11 +38,12 @@ dependencies:
|
||||
webdav_client: ^1.2.2
|
||||
dio: ^5.4.3+1
|
||||
country_flags: ^2.2.0
|
||||
re_editor: ^0.3.0
|
||||
re_highlight: ^0.0.3
|
||||
win32: ^5.5.1
|
||||
ffi: ^2.1.2
|
||||
material_color_utilities: ^0.8.0
|
||||
re_editor: ^0.3.1
|
||||
re_highlight: ^0.0.3
|
||||
archive: ^3.6.1
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
Reference in New Issue
Block a user