Compare commits

...

4 Commits

Author SHA1 Message Date
chen08209
472cea9037 Add compatibility mode and adapt clash scheme. 2024-05-11 14:10:06 +08:00
chen08209
08d07498b9 update Version 2024-05-07 18:32:21 +08:00
chen08209
d5aa09949a Reconstruction application proxy logic 2024-05-07 18:31:14 +08:00
chen08209
fd1dfe5c60 Fix Tab destroy error 2024-05-06 19:05:27 +08:00
33 changed files with 1008 additions and 592 deletions

View File

@@ -3,6 +3,7 @@ package main
import "C"
import (
"github.com/metacubex/mihomo/adapter/inbound"
ap "github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/component/resolver"
@@ -16,15 +17,53 @@ import (
"math"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
)
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 GenerateConfigParams struct {
ProfilePath *string `json:"profile-path"`
Config *config.RawConfig `json:"config" `
IsPatch *bool `json:"is-patch"`
ProfilePath *string `json:"profile-path"`
Config *config.RawConfig `json:"config" `
IsPatch *bool `json:"is-patch"`
IsCompatible *bool `json:"is-compatible"`
}
type ChangeProxyParams struct {
@@ -90,6 +129,19 @@ func readFile(path string) ([]byte, error) {
return data, err
}
func removeFile(path string) error {
absPath, err := filepath.Abs(path)
if err != nil {
return err
}
err = os.Remove(absPath)
if err != nil {
return err
}
return nil
}
func getRawConfigWithPath(path *string) *config.RawConfig {
if path == nil {
return config.DefaultRawConfig()
@@ -108,18 +160,164 @@ func getRawConfigWithPath(path *string) *config.RawConfig {
}
}
func decorationConfig(profilePath *string, cfg config.RawConfig) *config.RawConfig {
func decorationConfig(profilePath *string, cfg config.RawConfig, compatible bool) *config.RawConfig {
prof := getRawConfigWithPath(profilePath)
overwriteConfig(prof, cfg)
overwriteConfig(prof, cfg, compatible)
return prof
}
func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig) {
func Reduce[T any, U any](s []T, initVal U, f func(U, T) U) U {
for _, v := range s {
initVal = f(initVal, v)
}
return initVal
}
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
func replaceFromMap(s string, m map[string]string) string {
for k, v := range m {
s = strings.ReplaceAll(s, k, v)
}
return s
}
func removeDuplicateFromSlice[T any](slice []T) []T {
result := make([]T, 0)
seen := make(map[any]struct{})
for _, value := range slice {
if _, ok := seen[value]; !ok {
result = append(result, value)
seen[value] = struct{}{}
}
}
return result
}
func generateProxyGroupAndRule(proxyGroup *[]map[string]any, rule *[]string) {
var replacements = map[string]string{}
var selectArr []map[string]any
var urlTestArr []map[string]any
var fallbackArr []map[string]any
for _, group := range *proxyGroup {
switch group["type"] {
case "select":
selectArr = append(selectArr, group)
replacements[group["name"].(string)] = "Proxy"
break
case "url-test":
urlTestArr = append(urlTestArr, group)
replacements[group["name"].(string)] = "Auto"
break
case "fallback":
fallbackArr = append(fallbackArr, group)
replacements[group["name"].(string)] = "Fallback"
break
default:
break
}
}
ProxyProxies := Reduce(selectArr, []string{}, func(res []string, cur map[string]any) []string {
if cur["proxies"] == nil {
return res
}
for _, proxyName := range cur["proxies"].([]interface{}) {
if str, ok := proxyName.(string); ok {
str = replaceFromMap(str, replacements)
if str != "Proxy" {
res = append(res, str)
}
}
}
return res
})
ProxyProxies = removeDuplicateFromSlice(ProxyProxies)
AutoProxies := Reduce(urlTestArr, []string{}, func(res []string, cur map[string]any) []string {
if cur["proxies"] == nil {
return res
}
for _, proxyName := range cur["proxies"].([]interface{}) {
if str, ok := proxyName.(string); ok {
str = replaceFromMap(str, replacements)
if str != "Auto" {
res = append(res, str)
}
}
}
return res
})
AutoProxies = removeDuplicateFromSlice(AutoProxies)
FallbackProxies := Reduce(fallbackArr, []string{}, func(res []string, cur map[string]any) []string {
if cur["proxies"] == nil {
return res
}
for _, proxyName := range cur["proxies"].([]interface{}) {
if str, ok := proxyName.(string); ok {
str = replaceFromMap(str, replacements)
if str != "Fallback" {
res = append(res, str)
}
}
}
return res
})
FallbackProxies = removeDuplicateFromSlice(FallbackProxies)
var computedProxyGroup []map[string]any
if len(ProxyProxies) > 0 {
computedProxyGroup = append(computedProxyGroup,
map[string]any{
"name": "Proxy",
"type": "select",
"proxies": ProxyProxies,
})
}
if len(AutoProxies) > 0 {
computedProxyGroup = append(computedProxyGroup,
map[string]any{
"name": "Auto",
"type": "url-test",
"proxies": AutoProxies,
})
}
if len(FallbackProxies) > 0 {
computedProxyGroup = append(computedProxyGroup,
map[string]any{
"name": "Fallback",
"type": "fallback",
"proxies": FallbackProxies,
})
}
computedRule := Map(*rule, func(value string) string {
return replaceFromMap(value, replacements)
})
*proxyGroup = computedProxyGroup
*rule = computedRule
}
func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig, compatible bool) {
targetConfig.ExternalController = ""
targetConfig.ExternalUI = ""
targetConfig.Interface = ""
targetConfig.ExternalUIURL = ""
targetConfig.IPv6 = patchConfig.IPv6
//targetConfig.IPv6 = patchConfig.IPv6
targetConfig.LogLevel = patchConfig.LogLevel
targetConfig.FindProcessMode = process.FindProcessAlways
targetConfig.AllowLan = patchConfig.AllowLan
@@ -129,23 +327,22 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
targetConfig.Tun.Device = patchConfig.Tun.Device
targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
targetConfig.Tun.Stack = patchConfig.Tun.Stack
targetConfig.GeodataLoader = "standard"
targetConfig.Profile.StoreSelected = false
if targetConfig.DNS.Enable == false {
targetConfig.DNS = patchConfig.DNS
} else {
targetConfig.DNS.UseHosts = patchConfig.DNS.UseHosts
targetConfig.DNS.EnhancedMode = patchConfig.DNS.EnhancedMode
targetConfig.DNS.IPv6 = patchConfig.DNS.IPv6
targetConfig.DNS.DefaultNameserver = append(patchConfig.DNS.DefaultNameserver, targetConfig.DNS.DefaultNameserver...)
targetConfig.DNS.NameServer = append(patchConfig.DNS.NameServer, targetConfig.DNS.NameServer...)
targetConfig.DNS.FakeIPFilter = append(patchConfig.DNS.FakeIPFilter, targetConfig.DNS.FakeIPFilter...)
targetConfig.DNS.Fallback = append(patchConfig.DNS.Fallback, targetConfig.DNS.Fallback...)
if runtime.GOOS == "android" {
targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, "dhcp://"+dns.SystemDNSPlaceholder)
} else if runtime.GOOS == "windows" {
targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder)
}
}
if runtime.GOOS == "android" {
targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, "dhcp://"+dns.SystemDNSPlaceholder)
} else if runtime.GOOS == "windows" {
targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder)
}
if compatible == false {
targetConfig.ProxyProvider = make(map[string]map[string]any)
targetConfig.RuleProvider = make(map[string]map[string]any)
generateProxyGroupAndRule(&targetConfig.ProxyGroup, &targetConfig.Rule)
}
}
func patchConfig(general *config.General) {
@@ -175,10 +372,8 @@ func patchConfig(general *config.General) {
const concurrentCount = math.MaxInt
var wg = sync.WaitGroup{}
var exit = make(chan struct{})
func hcCompatibleProvider(proxyProviders map[string]provider.ProxyProvider) {
wg := sync.WaitGroup{}
ch := make(chan struct{}, concurrentCount)
for _, proxyProvider := range proxyProviders {
proxyProvider := proxyProvider
@@ -186,11 +381,6 @@ func hcCompatibleProvider(proxyProviders map[string]provider.ProxyProvider) {
log.Infoln("Start initial Compatible provider %s", proxyProvider.Name())
wg.Add(1)
ch <- struct{}{}
select {
case <-exit:
return // 收到退出信号,退出协程
default:
}
go func() {
defer func() { <-ch; wg.Done() }()
if err := proxyProvider.Initial(); err != nil {
@@ -202,13 +392,6 @@ func hcCompatibleProvider(proxyProviders map[string]provider.ProxyProvider) {
}
}
func cancelHc() {
close(exit)
wg.Wait()
exit = make(chan struct{})
wg = sync.WaitGroup{}
}
func applyConfig(isPatch bool) {
cfg, err := config.ParseRawConfig(currentConfig)
if err != nil {

View File

@@ -19,11 +19,11 @@ type Message struct {
Data interface{} `json:"data"`
}
func (message *Message) toJson() string {
func (message *Message) Json() string {
data, _ := json.Marshal(message)
return string(data)
}
func SendMessage(message Message) {
SendToPort(*Port, message.toJson())
SendToPort(*Port, message.Json())
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant"
@@ -74,7 +75,7 @@ func updateConfig(s *C.char, port C.longlong) {
bridge.SendToPort(i, err.Error())
return
}
prof := decorationConfig(params.ProfilePath, *params.Config)
prof := decorationConfig(params.ProfilePath, *params.Config, *params.IsCompatible)
currentConfig = prof
if *params.IsPatch {
applyConfig(true)
@@ -85,6 +86,39 @@ func updateConfig(s *C.char, port C.longlong) {
}()
}
//export clearEffect
func clearEffect(s *C.char) {
path := 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)
}()
}
//export getProxies
func getProxies() *C.char {
data, err := json.Marshal(tunnel.ProxiesWithProviders())
@@ -96,34 +130,31 @@ func getProxies() *C.char {
//export changeProxy
func changeProxy(s *C.char) bool {
paramsString := C.GoString(s)
var params = &ChangeProxyParams{}
err := json.Unmarshal([]byte(paramsString), params)
if err != nil {
log.Infoln("Unmarshal ChangeProxyParams %v", err)
return false
}
proxies := tunnel.ProxiesWithProviders()
proxy := proxies[*params.GroupName]
if proxy == nil {
return false
}
log.Infoln("change proxy %s", proxy.Name())
adapterProxy := proxy.(*adapter.Proxy)
selector, ok := adapterProxy.ProxyAdapter.(*outboundgroup.Selector)
if !ok {
return false
}
if err := selector.Set(*params.ProxyName); err != nil {
return false
}
go func() {
paramsString := C.GoString(s)
var params = &ChangeProxyParams{}
err := json.Unmarshal([]byte(paramsString), params)
if err != nil {
log.Infoln("Unmarshal ChangeProxyParams %v", err)
}
proxies := tunnel.ProxiesWithProviders()
proxy := proxies[*params.GroupName]
if proxy == nil {
return
}
log.Infoln("change proxy %s", proxy.Name())
adapterProxy := proxy.(*adapter.Proxy)
selector, ok := adapterProxy.ProxyAdapter.(*outboundgroup.Selector)
if !ok {
return
}
if err := selector.Set(*params.ProxyName); err != nil {
return
}
}()
return true
}
// clearEffect
func clearConfigEffect() {
}
//export getTraffic
func getTraffic() *C.char {
up, down := statistic.DefaultManager.Now()

View File

@@ -27,8 +27,14 @@ runAppWithPreferences(
ChangeNotifierProvider<Config>(
create: (_) => config,
),
ChangeNotifierProvider<AppState>(
ChangeNotifierProxyProvider2<Config, ClashConfig, AppState>(
create: (_) => appState,
update: (_, config, clashConfig, appState) {
appState?.mode = clashConfig.mode;
appState?.isCompatible = config.isCompatible;
appState?.selectedMap = config.currentSelectedMap;
return appState!;
},
)
],
child: child,
@@ -70,6 +76,7 @@ class ApplicationState extends State<Application> {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
appController.afterInit();
appController.initLink();
_updateGroups();
});
}
@@ -102,6 +109,18 @@ class ApplicationState extends State<Application> {
});
}
_updateGroups() {
if (globalState.groupsUpdateTimer != null) {
globalState.groupsUpdateTimer?.cancel();
globalState.groupsUpdateTimer = null;
}
globalState.groupsUpdateTimer ??=
Timer.periodic(appConstant.httpTimeoutDuration, (timer) async {
await appController.updateGroups();
appController.appState.sortNum++;
});
}
@override
Widget build(context) {
return AppStateContainer(

View File

@@ -100,9 +100,6 @@ class ClashCore {
.map(
(name) => proxies[name],
)
.where(
(proxy) => proxy["type"] != GroupType.Selector.value,
)
.toList();
return group;
}).toList();
@@ -110,6 +107,31 @@ class ClashCore {
});
}
Future<DelayMap> getDelayMap() {
final proxiesRaw = clashFFI.getProxies();
final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString();
return Isolate.run<DelayMap>(() {
final proxies = json.decode(proxiesRawString) as Map<String, dynamic>;
return proxies.map<String, int?>(
(k, v) {
final history = v["history"] as List<dynamic>;
if (history.isEmpty) {
return MapEntry(
k,
null,
);
} else {
final delay = history.last["delay"];
return MapEntry(
k,
delay != 0 ? delay : -1,
);
}
},
);
});
}
bool changeProxy(ChangeProxyParams changeProxyParams) {
final params = json.encode(changeProxyParams);
return clashFFI.changeProxy(params.toNativeUtf8().cast()) == 1;
@@ -124,6 +146,10 @@ class ClashCore {
return true;
}
clearEffect(String path) {
clashFFI.clearEffect(path.toNativeUtf8().cast());
}
healthcheck() {
clashFFI.healthcheck();
}

View File

@@ -924,6 +924,20 @@ class ClashFFI {
late final _updateConfig =
_updateConfigPtr.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
void clearEffect(
ffi.Pointer<ffi.Char> s,
) {
return _clearEffect(
s,
);
}
late final _clearEffectPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
'clearEffect');
late final _clearEffect =
_clearEffectPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
ffi.Pointer<ffi.Char> getProxies() {
return _getProxies();
}

View File

@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:flutter/services.dart';
import 'core.dart';
class ClashService {

View File

@@ -1,6 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
@@ -87,13 +85,7 @@ class AppController {
config.deleteProfileById(id);
final profilePath = await appPath.getProfilePath(id);
if (profilePath == null) return;
final file = File(profilePath);
Isolate.run(() async {
final isExists = await file.exists();
if (isExists) {
file.delete();
}
});
clashCore.clearEffect(profilePath);
if (config.currentProfileId == id) {
if (config.profiles.isNotEmpty) {
final updateId = config.profiles.first.id;
@@ -172,13 +164,6 @@ class AppController {
appState.systemColorSchemes = systemColorSchemes;
}
clearCurrentDelay() {
final currentProxyName =
appState.getCurrentProxyName(config.currentProxyName, clashConfig.mode);
if (currentProxyName == null) return;
appState.setDelay(Delay(name: currentProxyName, value: null));
}
savePreferences() async {
await saveConfigPreferences();
await saveClashConfigPreferences();
@@ -251,6 +236,10 @@ class AppController {
appState.setDelay(delay);
}
updateDelayMap() async {
appState.delayMap = await clashCore.getDelayMap();
}
toPage(int index, {bool hasAnimate = false}) {
final nextLabel = globalState.currentNavigationItems[index].label;
appState.currentLabel = nextLabel;
@@ -361,4 +350,13 @@ class AppController {
},
);
}
clearShowProxyDelay() {
final showProxyDelay = appState.getRealProxyName(appState.showProxyName);
if (showProxyDelay != null) {
appState.setDelay(
Delay(name: showProxyDelay, value: null),
);
}
}
}

View File

@@ -2,6 +2,8 @@
enum GroupType { Selector, URLTest, Fallback }
enum GroupName { GLOBAL, Proxy, Auto, Fallback }
extension GroupTypeExtension on GroupType {
static List<String> get valueList => GroupType.values
.map(

View File

@@ -35,6 +35,26 @@ class ApplicationSettingFragment extends StatelessWidget {
);
},
),
Selector<Config, bool>(
selector: (_, config) => config.isCompatible,
builder: (_, isCompatible, __) {
return ListItem.switchItem(
leading: const Icon(Icons.device_hub),
title: const Text("兼容模式"),
subtitle: const Text("开启将失去部分应用能力获得全量的Clash的支持"),
delegate: SwitchDelegate(
value: isCompatible,
onChanged: (bool value) async {
final appController = context.appController;
appController.config.isCompatible = value;
await appController.updateClashConfig(isPatch: false);
await appController.updateGroups();
appController.changeProxy();
},
),
);
},
),
if (system.isDesktop)
Selector<Config, bool>(
selector: (_, config) => config.autoLaunch,

View File

@@ -120,7 +120,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
return ListView.separated(
itemBuilder: (_, index) {
return Container(
height: 84,
padding: kMaterialListPadding,
alignment: Alignment.center,
child: items[index],
);

View File

@@ -1,6 +1,6 @@
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/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@@ -25,11 +25,6 @@ class _NetworkDetectionState extends State<NetworkDetection> {
),
);
}
if (currentProxyName == UsedProxy.DIRECT.name) {
return const Icon(
Icons.offline_bolt_outlined,
);
}
if (delay == 0 || delay == null) {
return const AspectRatio(
aspectRatio: 1,
@@ -78,6 +73,52 @@ class _NetworkDetectionState extends State<NetworkDetection> {
);
}
_updateCurrentDelay(
String? currentProxyName,
int? delay,
bool isCurrent,
bool isInit,
) {
if (!isCurrent || currentProxyName == null || !isInit) return;
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (delay == null) {
context.appController.setDelay(
Delay(
name: currentProxyName,
value: 0,
),
);
globalState.updateCurrentDelay(
currentProxyName,
);
}
});
}
_updateCurrentDelayContainer(Widget child) {
return Selector2<AppState, Config, UpdateCurrentDelaySelectorState>(
selector: (_, appState, config) {
return UpdateCurrentDelaySelectorState(
isInit: appState.isInit,
currentProxyName: appState.getRealProxyName(appState.showProxyName),
delay: appState
.delayMap[appState.getRealProxyName(appState.showProxyName)],
isCurrent: appState.currentLabel == 'dashboard',
);
},
builder: (_, state, __) {
_updateCurrentDelay(
state.currentProxyName,
state.delay,
state.isCurrent,
state.isInit,
);
return child;
},
child: child,
);
}
@override
Widget build(BuildContext context) {
return CommonCard(
@@ -85,59 +126,57 @@ class _NetworkDetectionState extends State<NetworkDetection> {
iconData: Icons.network_check,
label: appLocalizations.networkDetection,
),
child: Selector3<AppState, Config, ClashConfig,
NetworkDetectionSelectorState>(
selector: (_, appState, config, clashConfig) {
final proxyName = appState.getCurrentProxyName(
config.currentProxyName,
clashConfig.mode,
);
return NetworkDetectionSelectorState(
isInit: appState.isInit,
currentProxyName: proxyName,
delay: appState.getDelay(
proxyName,
),
);
},
builder: (_, state, __) {
return Container(
padding: const EdgeInsets.all(16).copyWith(top: 0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 0,
child: TooltipText(
text: Text(
state.currentProxyName ?? appLocalizations.noProxy,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style:
Theme.of(context).textTheme.titleMedium?.toSoftBold(),
),
),
),
const SizedBox(
height: 8,
),
Flexible(
child: Container(
height: context.appController.measure.titleLargeHeight,
alignment: Alignment.centerLeft,
child: FadeBox(
child: _buildDescription(
state.currentProxyName,
state.delay,
child: _updateCurrentDelayContainer(
Selector<AppState, NetworkDetectionSelectorState>(
selector: (_, appState) {
return NetworkDetectionSelectorState(
currentProxyName: appState.showProxyName,
delay: appState.getDelay(
appState.showProxyName,
),
);
},
builder: (_, state, __) {
return Container(
padding: const EdgeInsets.all(16).copyWith(top: 0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 0,
child: TooltipText(
text: Text(
state.currentProxyName ?? appLocalizations.noProxy,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: Theme.of(context)
.textTheme
.titleMedium
?.toSoftBold(),
),
),
),
),
],
),
);
},
const SizedBox(
height: 8,
),
Flexible(
child: Container(
height: context.appController.measure.titleLargeHeight,
alignment: Alignment.centerLeft,
child: FadeBox(
child: _buildDescription(
state.currentProxyName,
state.delay,
),
),
),
),
],
),
);
},
),
),
);
}

View File

@@ -11,10 +11,18 @@ class OutboundMode extends StatelessWidget {
_changeMode(BuildContext context, Mode? value) async {
final appController = context.appController;
final clashConfig = context.read<ClashConfig>();
final clashConfig = appController.clashConfig;
final config = appController.config;
if (value == null || clashConfig.mode == value) return;
clashConfig.mode = value;
await appController.updateClashConfig();
if (!config.isCompatible) {
final proxySelected = config.currentSelectedMap[GroupName.Proxy.name];
final globalSelected = config.currentSelectedMap[GroupName.GLOBAL.name];
if (proxySelected != null && globalSelected == null) {
config.updateCurrentSelectedMap(GroupName.GLOBAL.name, proxySelected);
}
}
appController.changeProxy();
}
@@ -54,7 +62,8 @@ class OutboundMode extends StatelessWidget {
),
title: Text(
Intl.message(item.name),
style: Theme.of(context)
style: Theme
.of(context)
.textTheme
.titleMedium
?.toSoftBold(),

View File

@@ -51,7 +51,7 @@ class _StartButtonState extends State<StartButton>
final appController = context.appController;
await appController.updateSystemProxy(isStart);
if (isStart && mounted) {
appController.healthcheck();
appController.clearShowProxyDelay();
}
}
@@ -66,14 +66,17 @@ class _StartButtonState extends State<StartButton>
if (!state.isInit || !state.hasProfile) {
return Container();
}
final textWidth = context.appController.measure.computeTextSize(
Text(
Other.getTimeDifference(
DateTime.now(),
),
style: Theme.of(context).textTheme.titleMedium?.toSoftBold(),
),
).width +
final textWidth = context.appController.measure
.computeTextSize(
Text(
Other.getTimeDifference(
DateTime.now(),
),
style:
Theme.of(context).textTheme.titleMedium?.toSoftBold(),
),
)
.width +
16;
return AnimatedBuilder(
animation: _controller.view,

View File

@@ -1,4 +1,4 @@
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
import 'package:provider/provider.dart';
@@ -69,29 +69,23 @@ class _ProxiesFragmentState extends State<ProxiesFragment>
},
child: Selector3<AppState, Config, ClashConfig, ProxiesSelectorState>(
selector: (_, appState, config, clashConfig) {
final currentGroups = appState.getCurrentGroups(clashConfig.mode);
final currentGroups = appState.currentGroups;
final groupNames = currentGroups.map((e) => e.name).toList();
final currentProxyName = appState.getCurrentGroupNameWithGroups(
currentGroups,
config.currentGroupName,
clashConfig.mode,
);
final currentIndex = currentGroups
.indexWhere((element) => element.name == currentProxyName);
return ProxiesSelectorState(
currentIndex: currentIndex,
groupNames: groupNames,
);
},
builder: (_, state, __) {
if (_tabController != null) {
_tabController!.dispose();
shouldRebuild: (prev, next) {
if (prev.groupNames.length != next.groupNames.length) {
_tabController?.dispose();
_tabController = null;
}
_tabController = TabController(
return prev != next;
},
builder: (_, state, __) {
_tabController ??= TabController(
length: state.groupNames.length,
vsync: this,
initialIndex: state.currentIndex,
);
return Column(
mainAxisAlignment: MainAxisAlignment.start,
@@ -152,12 +146,11 @@ class ProxiesTabView extends StatelessWidget {
List<Proxy> _sortOfDelay(BuildContext context, List<Proxy> proxies) {
final appState = context.read<AppState>();
final delayMap = appState.delayMap;
return proxies = List.of(proxies)
..sort(
(a, b) {
final aDelay = delayMap[a.name];
final bDelay = delayMap[b.name];
final aDelay = appState.delayMap[a.name];
final bDelay = appState.delayMap[b.name];
if (aDelay == null && bDelay == null) {
return 0;
}
@@ -236,12 +229,22 @@ class ProxiesTabView extends StatelessWidget {
),
SizedBox(
height: measure.bodySmallHeight,
child: Text(
proxy.type,
style: context.textTheme.bodySmall?.copyWith(
overflow: TextOverflow.ellipsis,
color: context.textTheme.bodySmall?.color?.toLight(),
child: Selector<AppState, String>(
selector: (context, appState) => appState.getDesc(
proxy.type,
proxy.name,
),
builder: (_, desc, __) {
return TooltipText(
text: Text(
desc,
style: context.textTheme.bodySmall?.copyWith(
overflow: TextOverflow.ellipsis,
color: context.textTheme.bodySmall?.color?.toLight(),
),
),
);
},
),
),
const SizedBox(
@@ -250,7 +253,9 @@ class ProxiesTabView extends StatelessWidget {
SizedBox(
height: measure.labelSmallHeight,
child: Selector<AppState, int?>(
selector: (context, appState) => appState.getDelay(proxy.name),
selector: (context, appState) => appState.getDelay(
proxy.name,
),
builder: (_, delay, __) {
return FadeBox(
child: Builder(
@@ -307,23 +312,12 @@ class ProxiesTabView extends StatelessWidget {
return Selector3<AppState, Config, ClashConfig,
ProxiesCardSelectorState>(
selector: (_, appState, config, clashConfig) {
final currentGroupName = appState.getCurrentGroupName(
config.currentGroupName,
clashConfig.mode,
);
final currentProxyName = appState.getCurrentProxyName(
config.currentProxyName,
clashConfig.mode,
);
final group = appState.getGroupWithName(groupName);
final isSelected = group.type == GroupType.Selector
? group.name == currentGroupName &&
proxy.name == currentProxyName
: group.now == proxy.name;
final group = appState.getGroupWithName(groupName)!;
bool isSelected = config.currentSelectedMap[group.name] == proxy.name ||
(config.currentSelectedMap[group.name] == null &&
group.now == proxy.name);
return ProxiesCardSelectorState(
isSelected: isSelected,
currentGroupName: currentGroupName,
currentProxyName: currentProxyName,
);
},
builder: (_, state, __) {
@@ -331,15 +325,21 @@ class ProxiesTabView extends StatelessWidget {
context,
isSelected: state.isSelected,
onPressed: () {
final appController = context.appController;
final group =
context.appController.appState.getGroupWithName(groupName);
if (group.type == GroupType.Selector) {
final config = context.read<Config>();
config.currentProfile?.groupName = group.name;
config.currentProfile?.proxyName = proxy.name;
config.update();
context.appController.changeProxy();
appController.appState.getGroupWithName(groupName)!;
if (group.type != GroupType.Selector) {
globalState.showSnackBar(
context,
message: "当前代理组无法选择",
);
return;
}
context.appController.config.updateCurrentSelectedMap(
groupName,
proxy.name,
);
context.appController.changeProxy();
},
proxy: proxy,
);
@@ -356,7 +356,7 @@ class ProxiesTabView extends StatelessWidget {
return ProxiesTabViewSelectorState(
proxiesSortType: config.proxiesSortType,
sortNum: appState.sortNum,
group: appState.getGroupWithName(groupName),
group: appState.getGroupWithName(groupName)!,
);
},
builder: (_, state, __) {

View File

@@ -17,7 +17,11 @@ Future<void> main() async {
await window?.init();
final config = await preferences.getConfig() ?? Config();
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
final appState = AppState();
final appState = AppState(
mode: clashConfig.mode,
isCompatible: config.isCompatible,
selectedMap: config.currentSelectedMap,
);
await globalState.init(
appState: appState,
config: config,
@@ -41,7 +45,11 @@ Future<void> vpnService() async {
WidgetsFlutterBinding.ensureInitialized();
final config = await preferences.getConfig() ?? Config();
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
final appState = AppState();
final appState = AppState(
mode: clashConfig.mode,
isCompatible: config.isCompatible,
selectedMap: config.currentSelectedMap,
);
clashMessage.addListener(ClashMessageListenerWithVpn(onTun: (String fd) {
proxyManager.setProtect(
int.parse(fd),

View File

@@ -5,35 +5,47 @@ import 'ffi.dart';
import 'log.dart';
import 'navigation.dart';
import 'package.dart';
import 'profile.dart';
import 'proxy.dart';
import 'system_color_scheme.dart';
import 'traffic.dart';
import 'version.dart';
typedef DelayMap = Map<String, int?>;
class AppState with ChangeNotifier {
List<NavigationItem> _navigationItems;
int? _runTime;
bool _isInit;
DelayMap _delayMap;
VersionInfo? _versionInfo;
List<Traffic> _traffics;
List<Log> _logs;
List<Package> _packages;
String _currentLabel;
SystemColorSchemes _systemColorSchemes;
List<Group> _groups;
num _sortNum;
Mode _mode;
DelayMap _delayMap;
SelectedMap _selectedMap;
bool _isCompatible;
List<Group> _groups;
AppState()
: _navigationItems = [],
_delayMap = {},
AppState({
required Mode mode,
required bool isCompatible,
required SelectedMap selectedMap,
}) : _navigationItems = [],
_isInit = false,
_currentLabel = "dashboard",
_traffics = [],
_logs = [],
_groups = [],
_selectedMap = selectedMap,
_packages = [],
_sortNum = 0,
_mode = mode,
_delayMap = {},
_groups = [],
_isCompatible = isCompatible,
_systemColorSchemes = SystemColorSchemes();
String get currentLabel => _currentLabel;
@@ -72,29 +84,38 @@ class AppState with ChangeNotifier {
}
}
DelayMap get delayMap => _delayMap;
set delayMap(DelayMap value) {
if (_delayMap != value) {
_delayMap = value;
notifyListeners();
String getDesc(String type, String? proxyName) {
final groupTypeNamesList = GroupType.values.map((e) => e.name).toList();
if (!groupTypeNamesList.contains(type)) {
return type;
} else {
final index = groups.indexWhere((element) => element.name == proxyName);
if (index == -1) return type;
return "$type(${groups[index].now})";
}
}
String? getRealProxyName(String? proxyName) {
if (proxyName == null) return null;
final index = groups.indexWhere((element) => element.name == proxyName);
if (index == -1) return proxyName;
final group = groups[index];
return getRealProxyName(selectedMap.containsKey(proxyName)
? selectedMap[proxyName]
: group.now);
}
String? get showProxyName {
if (currentGroups.isEmpty) {
return UsedProxy.DIRECT.name;
}
final firstGroup = currentGroups.first;
final firstGroupName = firstGroup.name;
return selectedMap[firstGroupName] ?? firstGroup.now;
}
int? getDelay(String? proxyName) {
if (proxyName == null) return null;
final index = groups.indexWhere((element) => element.name == proxyName);
if (index == -1) return _delayMap[proxyName];
final group = groups[index];
if (group.now == null) return null;
return _delayMap[group.now];
}
setDelay(Delay delay) {
if (_delayMap[delay.name] != delay.value) {
_delayMap = Map.from(_delayMap)..[delay.name] = delay.value;
notifyListeners();
}
return _delayMap[getRealProxyName(proxyName)];
}
VersionInfo? get versionInfo => _versionInfo;
@@ -170,53 +191,86 @@ class AppState with ChangeNotifier {
}
}
List<Group> getCurrentGroups(Mode mode) {
Mode get mode => _mode;
set mode(Mode value) {
if (_mode != value) {
_mode = value;
notifyListeners();
}
}
// String? get currentProxyName {
// if (mode == Mode.direct) return UsedProxy.DIRECT.name;
// if (_currentProxyName != null) return _currentProxyName!;
// return currentGroup?.now;
// }
//
// set currentProxyName(String? value) {
// if (_currentProxyName != value) {
// _currentProxyName = value;
// notifyListeners();
// }
// }
bool get isCompatible {
return _isCompatible;
}
set isCompatible(bool value) {
if (_isCompatible != value) {
_isCompatible = value;
notifyListeners();
}
}
SelectedMap get selectedMap {
return _selectedMap;
}
set selectedMap(SelectedMap value) {
if (!const MapEquality<String, String>().equals(_selectedMap, value)) {
_selectedMap = value;
notifyListeners();
}
}
List<Group> get currentGroups {
switch (mode) {
case Mode.direct:
return [];
case Mode.global:
return groups
.where((element) => element.name == UsedProxy.GLOBAL.name)
.where((element) => element.name == GroupName.GLOBAL.name)
.toList();
case Mode.rule:
return groups
.where((element) => element.name != UsedProxy.GLOBAL.name)
.where((element) => element.name != GroupName.GLOBAL.name)
.toList();
}
}
String? getCurrentGroupNameWithGroups(
List<Group> groups,
String? groupName,
Mode mode,
) {
switch (mode) {
case Mode.direct:
return null;
case Mode.global:
return UsedProxy.GLOBAL.name;
case Mode.rule:
return groupName ?? (groups.isNotEmpty ? groups.first.name : null);
DelayMap get delayMap {
return _delayMap;
}
set delayMap(DelayMap value) {
if (!const MapEquality<String, int?>().equals(_delayMap, value)) {
_delayMap = value;
notifyListeners();
}
}
String? getCurrentGroupName(String? groupName, Mode mode) {
final currentGroups = getCurrentGroups(mode);
return getCurrentGroupNameWithGroups(currentGroups, groupName, mode);
}
Group getGroupWithName(String groupName) {
return groups.firstWhere((e) => e.name == groupName);
}
String? getCurrentProxyName(String? proxyName, Mode mode) {
final currentGroups = getCurrentGroups(mode);
switch (mode) {
case Mode.direct:
return UsedProxy.DIRECT.name;
case Mode.global || Mode.rule:
return proxyName ??
(currentGroups.isNotEmpty ? currentGroups.first.now : null);
setDelay(Delay delay) {
if (_delayMap[delay.name] != delay.value) {
_delayMap = Map.from(_delayMap)..[delay.name] = delay.value;
notifyListeners();
}
}
Group? getGroupWithName(String groupName) {
final index =
currentGroups.indexWhere((element) => element.name == groupName);
return index != -1 ? currentGroups[index] : null;
}
}

View File

@@ -61,6 +61,7 @@ class AccessControl {
@JsonSerializable()
class Config extends ChangeNotifier {
List<Profile> _profiles;
bool _isCompatible;
String? _currentProfileId;
bool _autoLaunch;
bool _silentLaunch;
@@ -82,6 +83,7 @@ class Config extends ChangeNotifier {
_autoRun = false,
_themeMode = ThemeMode.system,
_openLog = false,
_isCompatible = false,
_primaryColor = appConstant.defaultPrimaryColor.value,
_proxiesSortType = ProxiesSortType.none,
_isMinimizeOnExit = true,
@@ -159,9 +161,18 @@ class Config extends ChangeNotifier {
}
}
String? get currentProxyName => currentProfile?.proxyName;
String? get currentGroupName => currentProfile?.groupName;
SelectedMap get currentSelectedMap {
return currentProfile?.selectedMap ?? {};
}
updateCurrentSelectedMap(String groupName, String proxyName) {
if (currentProfile?.selectedMap[groupName] != proxyName) {
currentProfile?.selectedMap = Map.from(currentProfile?.selectedMap ?? {})
..[groupName] = proxyName;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get autoLaunch {
@@ -289,6 +300,18 @@ class Config extends ChangeNotifier {
}
}
@JsonKey(defaultValue: false)
bool get isCompatible {
return _isCompatible;
}
set isCompatible(bool value) {
if (_isCompatible != value) {
_isCompatible = value;
notifyListeners();
}
}
update() {
notifyListeners();
}

View File

@@ -13,7 +13,8 @@ class UpdateConfigParams with _$UpdateConfigParams {
const factory UpdateConfigParams({
@JsonKey(name: "profile-path") String? profilePath,
required ClashConfig config,
@JsonKey(name: "is-patch") bool? isPatch,
@JsonKey(name: "is-patch") required bool isPatch,
@JsonKey(name: "is-compatible") required bool isCompatible,
}) = _UpdateConfigParams;
factory UpdateConfigParams.fromJson(Map<String, Object?> json) =>

View File

@@ -55,7 +55,8 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
..isAccessControl = json['isAccessControl'] as bool? ?? false
..accessControl =
AccessControl.fromJson(json['accessControl'] as Map<String, dynamic>)
..isAnimateToPage = json['isAnimateToPage'] as bool? ?? true;
..isAnimateToPage = json['isAnimateToPage'] as bool? ?? true
..isCompatible = json['isCompatible'] as bool? ?? false;
Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'profiles': instance.profiles,
@@ -72,6 +73,7 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'isAccessControl': instance.isAccessControl,
'accessControl': instance.accessControl,
'isAnimateToPage': instance.isAnimateToPage,
'isCompatible': instance.isCompatible,
};
const _$ThemeModeEnumMap = {

View File

@@ -24,7 +24,9 @@ mixin _$UpdateConfigParams {
String? get profilePath => throw _privateConstructorUsedError;
ClashConfig get config => throw _privateConstructorUsedError;
@JsonKey(name: "is-patch")
bool? get isPatch => throw _privateConstructorUsedError;
bool get isPatch => throw _privateConstructorUsedError;
@JsonKey(name: "is-compatible")
bool get isCompatible => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -41,7 +43,8 @@ abstract class $UpdateConfigParamsCopyWith<$Res> {
$Res call(
{@JsonKey(name: "profile-path") String? profilePath,
ClashConfig config,
@JsonKey(name: "is-patch") bool? isPatch});
@JsonKey(name: "is-patch") bool isPatch,
@JsonKey(name: "is-compatible") bool isCompatible});
}
/// @nodoc
@@ -59,7 +62,8 @@ class _$UpdateConfigParamsCopyWithImpl<$Res, $Val extends UpdateConfigParams>
$Res call({
Object? profilePath = freezed,
Object? config = null,
Object? isPatch = freezed,
Object? isPatch = null,
Object? isCompatible = null,
}) {
return _then(_value.copyWith(
profilePath: freezed == profilePath
@@ -70,10 +74,14 @@ class _$UpdateConfigParamsCopyWithImpl<$Res, $Val extends UpdateConfigParams>
? _value.config
: config // ignore: cast_nullable_to_non_nullable
as ClashConfig,
isPatch: freezed == isPatch
isPatch: null == isPatch
? _value.isPatch
: isPatch // ignore: cast_nullable_to_non_nullable
as bool?,
as bool,
isCompatible: null == isCompatible
? _value.isCompatible
: isCompatible // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
@@ -89,7 +97,8 @@ abstract class _$$UpdateConfigParamsImplCopyWith<$Res>
$Res call(
{@JsonKey(name: "profile-path") String? profilePath,
ClashConfig config,
@JsonKey(name: "is-patch") bool? isPatch});
@JsonKey(name: "is-patch") bool isPatch,
@JsonKey(name: "is-compatible") bool isCompatible});
}
/// @nodoc
@@ -105,7 +114,8 @@ class __$$UpdateConfigParamsImplCopyWithImpl<$Res>
$Res call({
Object? profilePath = freezed,
Object? config = null,
Object? isPatch = freezed,
Object? isPatch = null,
Object? isCompatible = null,
}) {
return _then(_$UpdateConfigParamsImpl(
profilePath: freezed == profilePath
@@ -116,10 +126,14 @@ class __$$UpdateConfigParamsImplCopyWithImpl<$Res>
? _value.config
: config // ignore: cast_nullable_to_non_nullable
as ClashConfig,
isPatch: freezed == isPatch
isPatch: null == isPatch
? _value.isPatch
: isPatch // ignore: cast_nullable_to_non_nullable
as bool?,
as bool,
isCompatible: null == isCompatible
? _value.isCompatible
: isCompatible // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@@ -130,7 +144,8 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
const _$UpdateConfigParamsImpl(
{@JsonKey(name: "profile-path") this.profilePath,
required this.config,
@JsonKey(name: "is-patch") this.isPatch});
@JsonKey(name: "is-patch") required this.isPatch,
@JsonKey(name: "is-compatible") required this.isCompatible});
factory _$UpdateConfigParamsImpl.fromJson(Map<String, dynamic> json) =>
_$$UpdateConfigParamsImplFromJson(json);
@@ -142,11 +157,14 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
final ClashConfig config;
@override
@JsonKey(name: "is-patch")
final bool? isPatch;
final bool isPatch;
@override
@JsonKey(name: "is-compatible")
final bool isCompatible;
@override
String toString() {
return 'UpdateConfigParams(profilePath: $profilePath, config: $config, isPatch: $isPatch)';
return 'UpdateConfigParams(profilePath: $profilePath, config: $config, isPatch: $isPatch, isCompatible: $isCompatible)';
}
@override
@@ -157,12 +175,15 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
(identical(other.profilePath, profilePath) ||
other.profilePath == profilePath) &&
(identical(other.config, config) || other.config == config) &&
(identical(other.isPatch, isPatch) || other.isPatch == isPatch));
(identical(other.isPatch, isPatch) || other.isPatch == isPatch) &&
(identical(other.isCompatible, isCompatible) ||
other.isCompatible == isCompatible));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, profilePath, config, isPatch);
int get hashCode =>
Object.hash(runtimeType, profilePath, config, isPatch, isCompatible);
@JsonKey(ignore: true)
@override
@@ -183,7 +204,8 @@ abstract class _UpdateConfigParams implements UpdateConfigParams {
const factory _UpdateConfigParams(
{@JsonKey(name: "profile-path") final String? profilePath,
required final ClashConfig config,
@JsonKey(name: "is-patch") final bool? isPatch}) =
@JsonKey(name: "is-patch") required final bool isPatch,
@JsonKey(name: "is-compatible") required final bool isCompatible}) =
_$UpdateConfigParamsImpl;
factory _UpdateConfigParams.fromJson(Map<String, dynamic> json) =
@@ -196,7 +218,10 @@ abstract class _UpdateConfigParams implements UpdateConfigParams {
ClashConfig get config;
@override
@JsonKey(name: "is-patch")
bool? get isPatch;
bool get isPatch;
@override
@JsonKey(name: "is-compatible")
bool get isCompatible;
@override
@JsonKey(ignore: true)
_$$UpdateConfigParamsImplCopyWith<_$UpdateConfigParamsImpl> get copyWith =>

View File

@@ -11,7 +11,8 @@ _$UpdateConfigParamsImpl _$$UpdateConfigParamsImplFromJson(
_$UpdateConfigParamsImpl(
profilePath: json['profile-path'] as String?,
config: ClashConfig.fromJson(json['config'] as Map<String, dynamic>),
isPatch: json['is-patch'] as bool?,
isPatch: json['is-patch'] as bool,
isCompatible: json['is-compatible'] as bool,
);
Map<String, dynamic> _$$UpdateConfigParamsImplToJson(
@@ -20,6 +21,7 @@ Map<String, dynamic> _$$UpdateConfigParamsImplToJson(
'profile-path': instance.profilePath,
'config': instance.config,
'is-patch': instance.isPatch,
'is-compatible': instance.isCompatible,
};
_$ChangeProxyParamsImpl _$$ChangeProxyParamsImplFromJson(

View File

@@ -27,11 +27,13 @@ Profile _$ProfileFromJson(Map<String, dynamic> json) => Profile(
userInfo: json['userInfo'] == null
? null
: UserInfo.fromJson(json['userInfo'] as Map<String, dynamic>),
groupName: json['groupName'] as String?,
proxyName: json['proxyName'] as String?,
lastUpdateDate: json['lastUpdateDate'] == null
? null
: DateTime.parse(json['lastUpdateDate'] as String),
selectedMap: (json['selectedMap'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
),
autoUpdateDuration: json['autoUpdateDuration'] == null
? null
: Duration(microseconds: (json['autoUpdateDuration'] as num).toInt()),
@@ -41,11 +43,11 @@ Profile _$ProfileFromJson(Map<String, dynamic> json) => Profile(
Map<String, dynamic> _$ProfileToJson(Profile instance) => <String, dynamic>{
'id': instance.id,
'label': instance.label,
'groupName': instance.groupName,
'proxyName': instance.proxyName,
'url': instance.url,
'lastUpdateDate': instance.lastUpdateDate?.toIso8601String(),
'autoUpdateDuration': instance.autoUpdateDuration.inMicroseconds,
'userInfo': instance.userInfo,
'autoUpdate': instance.autoUpdate,
'selectedMap': instance.selectedMap,
};

View File

@@ -219,6 +219,7 @@ Proxy _$ProxyFromJson(Map<String, dynamic> json) {
mixin _$Proxy {
String get name => throw _privateConstructorUsedError;
String get type => throw _privateConstructorUsedError;
String? get now => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -230,7 +231,7 @@ abstract class $ProxyCopyWith<$Res> {
factory $ProxyCopyWith(Proxy value, $Res Function(Proxy) then) =
_$ProxyCopyWithImpl<$Res, Proxy>;
@useResult
$Res call({String name, String type});
$Res call({String name, String type, String? now});
}
/// @nodoc
@@ -248,6 +249,7 @@ class _$ProxyCopyWithImpl<$Res, $Val extends Proxy>
$Res call({
Object? name = null,
Object? type = null,
Object? now = freezed,
}) {
return _then(_value.copyWith(
name: null == name
@@ -258,6 +260,10 @@ class _$ProxyCopyWithImpl<$Res, $Val extends Proxy>
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as String,
now: freezed == now
? _value.now
: now // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
@@ -269,7 +275,7 @@ abstract class _$$ProxyImplCopyWith<$Res> implements $ProxyCopyWith<$Res> {
__$$ProxyImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String name, String type});
$Res call({String name, String type, String? now});
}
/// @nodoc
@@ -285,6 +291,7 @@ class __$$ProxyImplCopyWithImpl<$Res>
$Res call({
Object? name = null,
Object? type = null,
Object? now = freezed,
}) {
return _then(_$ProxyImpl(
name: null == name
@@ -295,6 +302,10 @@ class __$$ProxyImplCopyWithImpl<$Res>
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as String,
now: freezed == now
? _value.now
: now // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
@@ -302,21 +313,21 @@ class __$$ProxyImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$ProxyImpl implements _Proxy {
const _$ProxyImpl({this.name = "", this.type = ""});
const _$ProxyImpl({required this.name, required this.type, this.now});
factory _$ProxyImpl.fromJson(Map<String, dynamic> json) =>
_$$ProxyImplFromJson(json);
@override
@JsonKey()
final String name;
@override
@JsonKey()
final String type;
@override
final String? now;
@override
String toString() {
return 'Proxy(name: $name, type: $type)';
return 'Proxy(name: $name, type: $type, now: $now)';
}
@override
@@ -325,12 +336,13 @@ class _$ProxyImpl implements _Proxy {
(other.runtimeType == runtimeType &&
other is _$ProxyImpl &&
(identical(other.name, name) || other.name == name) &&
(identical(other.type, type) || other.type == type));
(identical(other.type, type) || other.type == type) &&
(identical(other.now, now) || other.now == now));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, name, type);
int get hashCode => Object.hash(runtimeType, name, type, now);
@JsonKey(ignore: true)
@override
@@ -347,7 +359,10 @@ class _$ProxyImpl implements _Proxy {
}
abstract class _Proxy implements Proxy {
const factory _Proxy({final String name, final String type}) = _$ProxyImpl;
const factory _Proxy(
{required final String name,
required final String type,
final String? now}) = _$ProxyImpl;
factory _Proxy.fromJson(Map<String, dynamic> json) = _$ProxyImpl.fromJson;
@@ -356,6 +371,8 @@ abstract class _Proxy implements Proxy {
@override
String get type;
@override
String? get now;
@override
@JsonKey(ignore: true)
_$$ProxyImplCopyWith<_$ProxyImpl> get copyWith =>
throw _privateConstructorUsedError;

View File

@@ -31,12 +31,14 @@ const _$GroupTypeEnumMap = {
};
_$ProxyImpl _$$ProxyImplFromJson(Map<String, dynamic> json) => _$ProxyImpl(
name: json['name'] as String? ?? "",
type: json['type'] as String? ?? "",
name: json['name'] as String,
type: json['type'] as String,
now: json['now'] as String?,
);
Map<String, dynamic> _$$ProxyImplToJson(_$ProxyImpl instance) =>
<String, dynamic>{
'name': instance.name,
'type': instance.type,
'now': instance.now,
};

View File

@@ -349,7 +349,6 @@ abstract class _UpdateCurrentDelaySelectorState
mixin _$NetworkDetectionSelectorState {
String? get currentProxyName => throw _privateConstructorUsedError;
int? get delay => throw _privateConstructorUsedError;
bool get isInit => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$NetworkDetectionSelectorStateCopyWith<NetworkDetectionSelectorState>
@@ -364,7 +363,7 @@ abstract class $NetworkDetectionSelectorStateCopyWith<$Res> {
_$NetworkDetectionSelectorStateCopyWithImpl<$Res,
NetworkDetectionSelectorState>;
@useResult
$Res call({String? currentProxyName, int? delay, bool isInit});
$Res call({String? currentProxyName, int? delay});
}
/// @nodoc
@@ -383,7 +382,6 @@ class _$NetworkDetectionSelectorStateCopyWithImpl<$Res,
$Res call({
Object? currentProxyName = freezed,
Object? delay = freezed,
Object? isInit = null,
}) {
return _then(_value.copyWith(
currentProxyName: freezed == currentProxyName
@@ -394,10 +392,6 @@ class _$NetworkDetectionSelectorStateCopyWithImpl<$Res,
? _value.delay
: delay // ignore: cast_nullable_to_non_nullable
as int?,
isInit: null == isInit
? _value.isInit
: isInit // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
@@ -411,7 +405,7 @@ abstract class _$$NetworkDetectionSelectorStateImplCopyWith<$Res>
__$$NetworkDetectionSelectorStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String? currentProxyName, int? delay, bool isInit});
$Res call({String? currentProxyName, int? delay});
}
/// @nodoc
@@ -429,7 +423,6 @@ class __$$NetworkDetectionSelectorStateImplCopyWithImpl<$Res>
$Res call({
Object? currentProxyName = freezed,
Object? delay = freezed,
Object? isInit = null,
}) {
return _then(_$NetworkDetectionSelectorStateImpl(
currentProxyName: freezed == currentProxyName
@@ -440,10 +433,6 @@ class __$$NetworkDetectionSelectorStateImplCopyWithImpl<$Res>
? _value.delay
: delay // ignore: cast_nullable_to_non_nullable
as int?,
isInit: null == isInit
? _value.isInit
: isInit // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@@ -453,20 +442,16 @@ class __$$NetworkDetectionSelectorStateImplCopyWithImpl<$Res>
class _$NetworkDetectionSelectorStateImpl
implements _NetworkDetectionSelectorState {
const _$NetworkDetectionSelectorStateImpl(
{required this.currentProxyName,
required this.delay,
required this.isInit});
{required this.currentProxyName, required this.delay});
@override
final String? currentProxyName;
@override
final int? delay;
@override
final bool isInit;
@override
String toString() {
return 'NetworkDetectionSelectorState(currentProxyName: $currentProxyName, delay: $delay, isInit: $isInit)';
return 'NetworkDetectionSelectorState(currentProxyName: $currentProxyName, delay: $delay)';
}
@override
@@ -476,12 +461,11 @@ class _$NetworkDetectionSelectorStateImpl
other is _$NetworkDetectionSelectorStateImpl &&
(identical(other.currentProxyName, currentProxyName) ||
other.currentProxyName == currentProxyName) &&
(identical(other.delay, delay) || other.delay == delay) &&
(identical(other.isInit, isInit) || other.isInit == isInit));
(identical(other.delay, delay) || other.delay == delay));
}
@override
int get hashCode => Object.hash(runtimeType, currentProxyName, delay, isInit);
int get hashCode => Object.hash(runtimeType, currentProxyName, delay);
@JsonKey(ignore: true)
@override
@@ -496,16 +480,13 @@ abstract class _NetworkDetectionSelectorState
implements NetworkDetectionSelectorState {
const factory _NetworkDetectionSelectorState(
{required final String? currentProxyName,
required final int? delay,
required final bool isInit}) = _$NetworkDetectionSelectorStateImpl;
required final int? delay}) = _$NetworkDetectionSelectorStateImpl;
@override
String? get currentProxyName;
@override
int? get delay;
@override
bool get isInit;
@override
@JsonKey(ignore: true)
_$$NetworkDetectionSelectorStateImplCopyWith<
_$NetworkDetectionSelectorStateImpl>
@@ -1741,158 +1722,8 @@ abstract class _HomeNavigationSelectorState
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ProxiesSelectorState {
int get currentIndex => throw _privateConstructorUsedError;
List<String> get groupNames => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ProxiesSelectorStateCopyWith<ProxiesSelectorState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProxiesSelectorStateCopyWith<$Res> {
factory $ProxiesSelectorStateCopyWith(ProxiesSelectorState value,
$Res Function(ProxiesSelectorState) then) =
_$ProxiesSelectorStateCopyWithImpl<$Res, ProxiesSelectorState>;
@useResult
$Res call({int currentIndex, List<String> groupNames});
}
/// @nodoc
class _$ProxiesSelectorStateCopyWithImpl<$Res,
$Val extends ProxiesSelectorState>
implements $ProxiesSelectorStateCopyWith<$Res> {
_$ProxiesSelectorStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? currentIndex = null,
Object? groupNames = null,
}) {
return _then(_value.copyWith(
currentIndex: null == currentIndex
? _value.currentIndex
: currentIndex // ignore: cast_nullable_to_non_nullable
as int,
groupNames: null == groupNames
? _value.groupNames
: groupNames // ignore: cast_nullable_to_non_nullable
as List<String>,
) as $Val);
}
}
/// @nodoc
abstract class _$$ProxiesSelectorStateImplCopyWith<$Res>
implements $ProxiesSelectorStateCopyWith<$Res> {
factory _$$ProxiesSelectorStateImplCopyWith(_$ProxiesSelectorStateImpl value,
$Res Function(_$ProxiesSelectorStateImpl) then) =
__$$ProxiesSelectorStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({int currentIndex, List<String> groupNames});
}
/// @nodoc
class __$$ProxiesSelectorStateImplCopyWithImpl<$Res>
extends _$ProxiesSelectorStateCopyWithImpl<$Res, _$ProxiesSelectorStateImpl>
implements _$$ProxiesSelectorStateImplCopyWith<$Res> {
__$$ProxiesSelectorStateImplCopyWithImpl(_$ProxiesSelectorStateImpl _value,
$Res Function(_$ProxiesSelectorStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? currentIndex = null,
Object? groupNames = null,
}) {
return _then(_$ProxiesSelectorStateImpl(
currentIndex: null == currentIndex
? _value.currentIndex
: currentIndex // ignore: cast_nullable_to_non_nullable
as int,
groupNames: null == groupNames
? _value._groupNames
: groupNames // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
}
/// @nodoc
class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState {
const _$ProxiesSelectorStateImpl(
{required this.currentIndex, required final List<String> groupNames})
: _groupNames = groupNames;
@override
final int currentIndex;
final List<String> _groupNames;
@override
List<String> get groupNames {
if (_groupNames is EqualUnmodifiableListView) return _groupNames;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_groupNames);
}
@override
String toString() {
return 'ProxiesSelectorState(currentIndex: $currentIndex, groupNames: $groupNames)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProxiesSelectorStateImpl &&
(identical(other.currentIndex, currentIndex) ||
other.currentIndex == currentIndex) &&
const DeepCollectionEquality()
.equals(other._groupNames, _groupNames));
}
@override
int get hashCode => Object.hash(runtimeType, currentIndex,
const DeepCollectionEquality().hash(_groupNames));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ProxiesSelectorStateImplCopyWith<_$ProxiesSelectorStateImpl>
get copyWith =>
__$$ProxiesSelectorStateImplCopyWithImpl<_$ProxiesSelectorStateImpl>(
this, _$identity);
}
abstract class _ProxiesSelectorState implements ProxiesSelectorState {
const factory _ProxiesSelectorState(
{required final int currentIndex,
required final List<String> groupNames}) = _$ProxiesSelectorStateImpl;
@override
int get currentIndex;
@override
List<String> get groupNames;
@override
@JsonKey(ignore: true)
_$$ProxiesSelectorStateImplCopyWith<_$ProxiesSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ProxiesCardSelectorState {
String? get currentGroupName => throw _privateConstructorUsedError;
String? get currentProxyName => throw _privateConstructorUsedError;
bool get isSelected => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -1906,8 +1737,7 @@ abstract class $ProxiesCardSelectorStateCopyWith<$Res> {
$Res Function(ProxiesCardSelectorState) then) =
_$ProxiesCardSelectorStateCopyWithImpl<$Res, ProxiesCardSelectorState>;
@useResult
$Res call(
{String? currentGroupName, String? currentProxyName, bool isSelected});
$Res call({bool isSelected});
}
/// @nodoc
@@ -1924,19 +1754,9 @@ class _$ProxiesCardSelectorStateCopyWithImpl<$Res,
@pragma('vm:prefer-inline')
@override
$Res call({
Object? currentGroupName = freezed,
Object? currentProxyName = freezed,
Object? isSelected = null,
}) {
return _then(_value.copyWith(
currentGroupName: freezed == currentGroupName
? _value.currentGroupName
: currentGroupName // ignore: cast_nullable_to_non_nullable
as String?,
currentProxyName: freezed == currentProxyName
? _value.currentProxyName
: currentProxyName // ignore: cast_nullable_to_non_nullable
as String?,
isSelected: null == isSelected
? _value.isSelected
: isSelected // ignore: cast_nullable_to_non_nullable
@@ -1954,8 +1774,7 @@ abstract class _$$ProxiesCardSelectorStateImplCopyWith<$Res>
__$$ProxiesCardSelectorStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String? currentGroupName, String? currentProxyName, bool isSelected});
$Res call({bool isSelected});
}
/// @nodoc
@@ -1971,19 +1790,9 @@ class __$$ProxiesCardSelectorStateImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? currentGroupName = freezed,
Object? currentProxyName = freezed,
Object? isSelected = null,
}) {
return _then(_$ProxiesCardSelectorStateImpl(
currentGroupName: freezed == currentGroupName
? _value.currentGroupName
: currentGroupName // ignore: cast_nullable_to_non_nullable
as String?,
currentProxyName: freezed == currentProxyName
? _value.currentProxyName
: currentProxyName // ignore: cast_nullable_to_non_nullable
as String?,
isSelected: null == isSelected
? _value.isSelected
: isSelected // ignore: cast_nullable_to_non_nullable
@@ -1995,21 +1804,14 @@ class __$$ProxiesCardSelectorStateImplCopyWithImpl<$Res>
/// @nodoc
class _$ProxiesCardSelectorStateImpl implements _ProxiesCardSelectorState {
const _$ProxiesCardSelectorStateImpl(
{required this.currentGroupName,
required this.currentProxyName,
required this.isSelected});
const _$ProxiesCardSelectorStateImpl({required this.isSelected});
@override
final String? currentGroupName;
@override
final String? currentProxyName;
@override
final bool isSelected;
@override
String toString() {
return 'ProxiesCardSelectorState(currentGroupName: $currentGroupName, currentProxyName: $currentProxyName, isSelected: $isSelected)';
return 'ProxiesCardSelectorState(isSelected: $isSelected)';
}
@override
@@ -2017,17 +1819,12 @@ class _$ProxiesCardSelectorStateImpl implements _ProxiesCardSelectorState {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProxiesCardSelectorStateImpl &&
(identical(other.currentGroupName, currentGroupName) ||
other.currentGroupName == currentGroupName) &&
(identical(other.currentProxyName, currentProxyName) ||
other.currentProxyName == currentProxyName) &&
(identical(other.isSelected, isSelected) ||
other.isSelected == isSelected));
}
@override
int get hashCode =>
Object.hash(runtimeType, currentGroupName, currentProxyName, isSelected);
int get hashCode => Object.hash(runtimeType, isSelected);
@JsonKey(ignore: true)
@override
@@ -2038,15 +1835,9 @@ class _$ProxiesCardSelectorStateImpl implements _ProxiesCardSelectorState {
}
abstract class _ProxiesCardSelectorState implements ProxiesCardSelectorState {
const factory _ProxiesCardSelectorState(
{required final String? currentGroupName,
required final String? currentProxyName,
required final bool isSelected}) = _$ProxiesCardSelectorStateImpl;
const factory _ProxiesCardSelectorState({required final bool isSelected}) =
_$ProxiesCardSelectorStateImpl;
@override
String? get currentGroupName;
@override
String? get currentProxyName;
@override
bool get isSelected;
@override
@@ -2055,6 +1846,135 @@ abstract class _ProxiesCardSelectorState implements ProxiesCardSelectorState {
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ProxiesSelectorState {
List<String> get groupNames => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ProxiesSelectorStateCopyWith<ProxiesSelectorState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProxiesSelectorStateCopyWith<$Res> {
factory $ProxiesSelectorStateCopyWith(ProxiesSelectorState value,
$Res Function(ProxiesSelectorState) then) =
_$ProxiesSelectorStateCopyWithImpl<$Res, ProxiesSelectorState>;
@useResult
$Res call({List<String> groupNames});
}
/// @nodoc
class _$ProxiesSelectorStateCopyWithImpl<$Res,
$Val extends ProxiesSelectorState>
implements $ProxiesSelectorStateCopyWith<$Res> {
_$ProxiesSelectorStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? groupNames = null,
}) {
return _then(_value.copyWith(
groupNames: null == groupNames
? _value.groupNames
: groupNames // ignore: cast_nullable_to_non_nullable
as List<String>,
) as $Val);
}
}
/// @nodoc
abstract class _$$ProxiesSelectorStateImplCopyWith<$Res>
implements $ProxiesSelectorStateCopyWith<$Res> {
factory _$$ProxiesSelectorStateImplCopyWith(_$ProxiesSelectorStateImpl value,
$Res Function(_$ProxiesSelectorStateImpl) then) =
__$$ProxiesSelectorStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({List<String> groupNames});
}
/// @nodoc
class __$$ProxiesSelectorStateImplCopyWithImpl<$Res>
extends _$ProxiesSelectorStateCopyWithImpl<$Res, _$ProxiesSelectorStateImpl>
implements _$$ProxiesSelectorStateImplCopyWith<$Res> {
__$$ProxiesSelectorStateImplCopyWithImpl(_$ProxiesSelectorStateImpl _value,
$Res Function(_$ProxiesSelectorStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? groupNames = null,
}) {
return _then(_$ProxiesSelectorStateImpl(
groupNames: null == groupNames
? _value._groupNames
: groupNames // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
}
/// @nodoc
class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState {
const _$ProxiesSelectorStateImpl({required final List<String> groupNames})
: _groupNames = groupNames;
final List<String> _groupNames;
@override
List<String> get groupNames {
if (_groupNames is EqualUnmodifiableListView) return _groupNames;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_groupNames);
}
@override
String toString() {
return 'ProxiesSelectorState(groupNames: $groupNames)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProxiesSelectorStateImpl &&
const DeepCollectionEquality()
.equals(other._groupNames, _groupNames));
}
@override
int get hashCode => Object.hash(
runtimeType, const DeepCollectionEquality().hash(_groupNames));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ProxiesSelectorStateImplCopyWith<_$ProxiesSelectorStateImpl>
get copyWith =>
__$$ProxiesSelectorStateImplCopyWithImpl<_$ProxiesSelectorStateImpl>(
this, _$identity);
}
abstract class _ProxiesSelectorState implements ProxiesSelectorState {
const factory _ProxiesSelectorState(
{required final List<String> groupNames}) = _$ProxiesSelectorStateImpl;
@override
List<String> get groupNames;
@override
@JsonKey(ignore: true)
_$$ProxiesSelectorStateImplCopyWith<_$ProxiesSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ProxiesTabViewSelectorState {
ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError;

View File

@@ -12,6 +12,8 @@ import 'common.dart';
part 'generated/profile.g.dart';
typedef SelectedMap = Map<String, String>;
@JsonSerializable()
class UserInfo {
int upload;
@@ -62,27 +64,28 @@ class UserInfo {
class Profile {
String id;
String? label;
String? groupName;
String? proxyName;
String? url;
DateTime? lastUpdateDate;
Duration autoUpdateDuration;
UserInfo? userInfo;
bool autoUpdate;
SelectedMap selectedMap;
Profile({
String? id,
this.label,
this.url,
this.userInfo,
this.groupName,
this.proxyName,
this.lastUpdateDate,
SelectedMap? selectedMap,
Duration? autoUpdateDuration,
this.autoUpdate = true,
}) : id = id ?? DateTime.now().millisecondsSinceEpoch.toString(),
autoUpdateDuration =
autoUpdateDuration ?? appConstant.defaultUpdateDuration;
autoUpdateDuration ?? appConstant.defaultUpdateDuration,
selectedMap = selectedMap ?? {};
ProfileType get type => url == null ? ProfileType.file : ProfileType.url;
@@ -158,7 +161,6 @@ class Profile {
runtimeType == other.runtimeType &&
id == other.id &&
label == other.label &&
groupName == other.groupName &&
proxyName == other.proxyName &&
url == other.url &&
lastUpdateDate == other.lastUpdateDate &&
@@ -170,7 +172,6 @@ class Profile {
int get hashCode =>
id.hashCode ^
label.hashCode ^
groupName.hashCode ^
proxyName.hashCode ^
url.hashCode ^
lastUpdateDate.hashCode ^
@@ -180,7 +181,7 @@ class Profile {
@override
String toString() {
return 'Profile{id: $id, label: $label, groupName: $groupName, proxyName: $proxyName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate}';
return 'Profile{id: $id, label: $label, proxyName: $proxyName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate}';
}
Profile copyWith({
@@ -192,14 +193,15 @@ class Profile {
DateTime? lastUpdateDate,
Duration? autoUpdateDuration,
bool? autoUpdate,
SelectedMap? selectedMap,
}) {
return Profile(
id: id,
label: label ?? this.label,
url: url ?? this.url,
groupName: groupName ?? this.groupName,
proxyName: proxyName ?? this.proxyName,
userInfo: userInfo ?? this.userInfo,
selectedMap: selectedMap ?? this.selectedMap,
lastUpdateDate: lastUpdateDate ?? this.lastUpdateDate,
autoUpdateDuration: autoUpdateDuration ?? this.autoUpdateDuration,
autoUpdate: autoUpdate ?? this.autoUpdate,

View File

@@ -6,7 +6,7 @@ part 'generated/proxy.g.dart';
part 'generated/proxy.freezed.dart';
typedef DelayMap = Map<String, int?>;
typedef ProxyMap = Map<String, Proxy>;
@freezed
class Group with _$Group {
@@ -23,8 +23,9 @@ class Group with _$Group {
@freezed
class Proxy with _$Proxy {
const factory Proxy({
@Default("") String name,
@Default("") String type,
required String name,
required String type,
String? now,
}) = _Proxy;
factory Proxy.fromJson(Map<String, Object?> json) => _$ProxyFromJson(json);

View File

@@ -32,7 +32,6 @@ class NetworkDetectionSelectorState with _$NetworkDetectionSelectorState {
const factory NetworkDetectionSelectorState({
required String? currentProxyName,
required int? delay,
required bool isInit,
}) = _NetworkDetectionSelectorState;
}
@@ -104,20 +103,17 @@ class HomeNavigationSelectorState with _$HomeNavigationSelectorState{
}
@freezed
class ProxiesSelectorState with _$ProxiesSelectorState{
const factory ProxiesSelectorState({
required int currentIndex,
required List<String> groupNames,
}) = _ProxiesSelectorState;
class ProxiesCardSelectorState with _$ProxiesCardSelectorState{
const factory ProxiesCardSelectorState({
required bool isSelected,
}) = _ProxiesCardSelectorState;
}
@freezed
class ProxiesCardSelectorState with _$ProxiesCardSelectorState{
const factory ProxiesCardSelectorState({
required String? currentGroupName,
required String? currentProxyName,
required bool isSelected,
}) = _ProxiesCardSelectorState;
class ProxiesSelectorState with _$ProxiesSelectorState{
const factory ProxiesSelectorState({
required List<String> groupNames,
}) = _ProxiesSelectorState;
}
@freezed

View File

@@ -14,8 +14,9 @@ import 'common/common.dart';
class GlobalState {
Timer? timer;
Function? updateCurrentDelayDebounce;
Function? updateSortNumDebounce;
Timer? groupsUpdateTimer;
Function? updateCurrentDelayDebounce;
PageController? pageController;
final navigatorKey = GlobalKey<NavigatorState>();
final Map<int, String?> packageNameMap = {};
@@ -49,6 +50,7 @@ class GlobalState {
profilePath: profilePath,
config: clashConfig,
isPatch: isPatch,
isCompatible: config.isCompatible,
));
}
@@ -74,19 +76,6 @@ class GlobalState {
stopListenUpdate();
}
void updateCurrentDelay(
String? proxyName,
) {
updateCurrentDelayDebounce ??= debounce<Function(String?)>((proxyName) {
if (proxyName != null) {
clashCore.delay(
proxyName,
);
}
});
updateCurrentDelayDebounce!([proxyName]);
}
applyProfile({
required AppState appState,
required Config config,
@@ -132,29 +121,20 @@ class GlobalState {
required Config config,
required ClashConfig clashConfig,
}) {
final currentGroupName =
appState.getCurrentGroupName(config.currentGroupName, clashConfig.mode);
final currentProxyName =
appState.getCurrentProxyName(config.currentProxyName, clashConfig.mode);
if (config.profiles.isEmpty || currentProxyName == null) {
stopSystemProxy();
return;
}
if (currentGroupName == null) return;
final groupIndex = appState.groups.indexWhere(
(element) => element.name == currentGroupName,
);
if (groupIndex == -1) return;
final proxyIndex = appState.groups[groupIndex].all.indexWhere(
(element) => element.name == currentProxyName,
);
if (proxyIndex == -1) return;
clashCore.changeProxy(
ChangeProxyParams(
groupName: currentGroupName,
proxyName: currentProxyName,
),
);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (config.profiles.isEmpty) {
stopSystemProxy();
return;
}
config.currentSelectedMap.forEach((key, value) {
clashCore.changeProxy(
ChangeProxyParams(
groupName: key,
proxyName: value,
),
);
});
});
}
updatePackages(AppState appState) async {
@@ -170,11 +150,11 @@ class GlobalState {
required Config config,
required ClashConfig clashConfig,
}) {
final hasGroups = appState.getCurrentGroups(clashConfig.mode).isNotEmpty;
final group = appState.currentGroups;
final hasProfile = config.profiles.isNotEmpty;
appState.navigationItems = navigation.getItems(
openLogs: config.openLogs,
hasProxies: hasGroups && hasProfile,
hasProxies: group.isNotEmpty && hasProfile,
);
}
@@ -258,6 +238,52 @@ class GlobalState {
);
}
}
showSnackBar(
BuildContext context, {
required String message,
SnackBarAction? action,
}) {
final width = context.width;
EdgeInsets margin;
if (width < 600) {
margin = const EdgeInsets.only(
bottom: 96,
right: 16,
left: 16,
);
} else {
margin = EdgeInsets.only(
bottom: 16,
left: 16,
right: width - 316,
);
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
action: action,
content: Text(message),
behavior: SnackBarBehavior.floating,
duration: const Duration(milliseconds: 1500),
margin: margin,
),
);
}
void updateCurrentDelay(
String? proxyName,
) {
updateCurrentDelayDebounce ??= debounce<Function(String?)>((proxyName) {
if (proxyName != null) {
debugPrint("[delay]=====> $proxyName");
clashCore.delay(
proxyName,
);
}
});
updateCurrentDelayDebounce!([proxyName]);
}
}
final globalState = GlobalState();

View File

@@ -23,14 +23,13 @@ class AppStateContainer extends StatelessWidget {
}
_updateNavigationsContainer(Widget child) {
return Selector3<AppState, Config, ClashConfig, UpdateNavigationsSelector>(
selector: (_, appState, config, clashConfig) {
final hasGroups =
appState.getCurrentGroups(clashConfig.mode).isNotEmpty;
return Selector2<AppState, Config, UpdateNavigationsSelector>(
selector: (_, appState, config) {
final group = appState.currentGroups;
final hasProfile = config.profiles.isNotEmpty;
return UpdateNavigationsSelector(
openLogs: config.openLogs,
hasProxies: hasGroups && hasProfile,
hasProxies: group.isNotEmpty && hasProfile,
);
},
builder: (context, state, child) {

View File

@@ -38,17 +38,8 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
@override
void onDelay(Delay delay) {
context.appController.setDelay(delay);
WidgetsBinding.instance.addPostFrameCallback((_) {
globalState.updateSortNumDebounce ??= debounce<Function()>(
() {
context.appController.updateGroups();
context.appController.appState.sortNum++;
},
milliseconds: 5000,
);
globalState.updateSortNumDebounce!();
});
final appController = context.appController;
appController.setDelay(delay);
super.onDelay(delay);
}

View File

@@ -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.7.12
version: 0.8.0
environment:
sdk: '>=3.1.0 <4.0.0'