Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba91fab2b5 | ||
|
|
18add7fba3 | ||
|
|
0d3034f216 | ||
|
|
313faa8cf3 | ||
|
|
4ba2f72b1a | ||
|
|
d6c22d4cca | ||
|
|
614dfc841a | ||
|
|
5afa552b6c | ||
|
|
ee0e2a15a7 | ||
|
|
7b7fd084bf | ||
|
|
03c39bf4bf | ||
|
|
fd7c3be02f | ||
|
|
b67d221063 | ||
|
|
1a74c0a12f | ||
|
|
9fc9c53427 | ||
|
|
8954b88d7f | ||
|
|
3990f8d7fa | ||
|
|
98688bcd2a | ||
|
|
2957311682 | ||
|
|
87a903af60 |
24
.github/workflows/build.yml
vendored
24
.github/workflows/build.yml
vendored
@@ -21,25 +21,11 @@ jobs:
|
||||
os: macos-13
|
||||
|
||||
steps:
|
||||
- name: Setup Mingw64
|
||||
if: startsWith(matrix.platform,'windows')
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: mingw64
|
||||
install: mingw-w64-x86_64-gcc
|
||||
update: true
|
||||
|
||||
|
||||
- name: Set Mingw64 Env
|
||||
if: startsWith(matrix.platform,'windows')
|
||||
run: |
|
||||
echo "${{ runner.temp }}\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Check Matrix
|
||||
run: |
|
||||
echo "Running on ${{ matrix.os }}"
|
||||
echo "Arch: ${{ runner.arch }}"
|
||||
gcc --version
|
||||
echo "Running on ${{ matrix.os }}"
|
||||
echo "Arch: ${{ runner.arch }}"
|
||||
gcc --version
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -66,10 +52,10 @@ jobs:
|
||||
if: startsWith(matrix.platform,'android')
|
||||
run: |
|
||||
echo "${{ secrets.KEYSTORE }}" | base64 --decode > android/app/keystore.jks
|
||||
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/local.properties
|
||||
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/local.properties
|
||||
echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/local.properties
|
||||
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/local.properties
|
||||
|
||||
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
|
||||
@@ -22,10 +22,8 @@
|
||||
<application
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:extractNativeLibs="true"
|
||||
android:label="FlClash"
|
||||
tools:targetApi="n">
|
||||
android:label="FlClash">
|
||||
<activity
|
||||
android:name="com.follow.clash.MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
|
||||
@@ -44,7 +44,6 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
private var props: Props? = null
|
||||
private lateinit var title: String
|
||||
private lateinit var content: String
|
||||
var isBlockNotification: Boolean = false
|
||||
|
||||
private val connection = object : ServiceConnection {
|
||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||
@@ -136,6 +135,7 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
GlobalState.runTime = null;
|
||||
}
|
||||
|
||||
@SuppressLint("ForegroundServiceType")
|
||||
private fun startForeground() {
|
||||
if (GlobalState.runState.value != RunState.START) return
|
||||
flClashVpnService?.startForeground(title, content)
|
||||
@@ -152,14 +152,13 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
if (permission == PackageManager.PERMISSION_GRANTED) {
|
||||
startForeground()
|
||||
} else {
|
||||
if (isBlockNotification) return
|
||||
if (activity == null) return
|
||||
ActivityCompat.requestPermissions(
|
||||
activity!!,
|
||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||
NOTIFICATION_PERMISSION_REQUEST_CODE
|
||||
)
|
||||
|
||||
activity?.let {
|
||||
ActivityCompat.requestPermissions(
|
||||
it,
|
||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||
NOTIFICATION_PERMISSION_REQUEST_CODE
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
startForeground()
|
||||
@@ -193,14 +192,11 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
grantResults: IntArray
|
||||
): Boolean {
|
||||
if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
|
||||
isBlockNotification = true
|
||||
if (grantResults.isNotEmpty()) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
startForeground()
|
||||
}
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
startForeground()
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,8 +7,4 @@
|
||||
<certificates src="user" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">localhost</domain>
|
||||
<domain includeSubdomains="true">127.0.0.1</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
Submodule core/Clash.Meta updated: 35e0465263...48425d7cf9
@@ -340,8 +340,8 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
|
||||
targetConfig.Mode = patchConfig.Mode
|
||||
targetConfig.Tun.Enable = patchConfig.Tun.Enable
|
||||
targetConfig.Tun.Device = patchConfig.Tun.Device
|
||||
targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
|
||||
targetConfig.Tun.Stack = patchConfig.Tun.Stack
|
||||
//targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
|
||||
//targetConfig.Tun.Stack = patchConfig.Tun.Stack
|
||||
targetConfig.GeodataLoader = patchConfig.GeodataLoader
|
||||
targetConfig.Profile.StoreSelected = false
|
||||
if targetConfig.DNS.Enable == false {
|
||||
@@ -396,6 +396,7 @@ func applyConfig(isPatch bool) {
|
||||
if isPatch {
|
||||
patchConfig(cfg.General)
|
||||
} else {
|
||||
executor.Shutdown()
|
||||
runtime.GC()
|
||||
hub.UltraApplyConfig(cfg, true)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package dart_bridge
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include "stdint.h"
|
||||
#include "include/dart_api_dl.h"
|
||||
#include "include/dart_api_dl.c"
|
||||
@@ -29,7 +28,6 @@ func SendToPort(port int64, msg string) {
|
||||
var obj C.Dart_CObject
|
||||
obj._type = C.Dart_CObject_kString
|
||||
msgString := C.CString(msg)
|
||||
defer C.free(unsafe.Pointer(msgString))
|
||||
ptr := unsafe.Pointer(&obj.value[0])
|
||||
*(**C.char)(ptr) = msgString
|
||||
isSuccess := C.GoDart_PostCObject(C.Dart_Port_DL(port), &obj)
|
||||
|
||||
47
core/hub.go
47
core/hub.go
@@ -1,8 +1,5 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
bridge "core/dart-bridge"
|
||||
@@ -74,8 +71,8 @@ func forceGc() {
|
||||
//export validateConfig
|
||||
func validateConfig(s *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
bytes := []byte(C.GoString(s))
|
||||
go func() {
|
||||
bytes := []byte(C.GoString(s))
|
||||
_, err := config.UnmarshalRawConfig(bytes)
|
||||
if err != nil {
|
||||
bridge.SendToPort(i, err.Error())
|
||||
@@ -88,8 +85,8 @@ func validateConfig(s *C.char, port C.longlong) {
|
||||
//export updateConfig
|
||||
func updateConfig(s *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
paramsString := C.GoString(s)
|
||||
go func() {
|
||||
paramsString := C.GoString(s)
|
||||
var params = &GenerateConfigParams{}
|
||||
err := json.Unmarshal([]byte(paramsString), params)
|
||||
if err != nil {
|
||||
@@ -150,32 +147,30 @@ func getProxies() *C.char {
|
||||
}
|
||||
|
||||
//export changeProxy
|
||||
func changeProxy(s *C.char) {
|
||||
paramsString := C.GoString(s)
|
||||
func changeProxy(s *C.char) bool {
|
||||
go func() {
|
||||
paramsString := C.GoString(s)
|
||||
var params = &ChangeProxyParams{}
|
||||
err := json.Unmarshal([]byte(paramsString), params)
|
||||
if err != nil {
|
||||
log.Infoln("Unmarshal ChangeProxyParams %v", err)
|
||||
}
|
||||
groupName := *params.GroupName
|
||||
proxyName := *params.ProxyName
|
||||
proxies := tunnel.ProxiesWithProviders()
|
||||
group := proxies[groupName]
|
||||
if group == nil {
|
||||
proxy := proxies[*params.GroupName]
|
||||
if proxy == nil {
|
||||
return
|
||||
}
|
||||
adapterProxy := group.(*adapter.Proxy)
|
||||
log.Infoln("change proxy %s", proxy.Name())
|
||||
adapterProxy := proxy.(*adapter.Proxy)
|
||||
selector, ok := adapterProxy.ProxyAdapter.(*outboundgroup.Selector)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err = selector.Set(proxyName)
|
||||
if err == nil {
|
||||
log.Infoln("[Selector] %s selected %s", groupName, proxyName)
|
||||
if err := selector.Set(*params.ProxyName); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
return true
|
||||
}
|
||||
|
||||
//export getTraffic
|
||||
@@ -216,8 +211,8 @@ func resetTraffic() {
|
||||
//export asyncTestDelay
|
||||
func asyncTestDelay(s *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
paramsString := C.GoString(s)
|
||||
go func() {
|
||||
paramsString := C.GoString(s)
|
||||
var params = &TestDelayParams{}
|
||||
err := json.Unmarshal([]byte(paramsString), params)
|
||||
if err != nil {
|
||||
@@ -301,6 +296,7 @@ func closeConnections() bool {
|
||||
//export closeConnection
|
||||
func closeConnection(id *C.char) bool {
|
||||
connectionId := C.GoString(id)
|
||||
|
||||
err := statistic.DefaultManager.Get(connectionId).Close()
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -311,13 +307,10 @@ func closeConnection(id *C.char) bool {
|
||||
//export getProviders
|
||||
func getProviders() *C.char {
|
||||
data, err := json.Marshal(tunnel.Providers())
|
||||
var msg *C.char
|
||||
if err != nil {
|
||||
msg = C.CString("")
|
||||
return msg
|
||||
return C.CString("")
|
||||
}
|
||||
msg = C.CString(string(data))
|
||||
return msg
|
||||
return C.CString(string(data))
|
||||
}
|
||||
|
||||
//export getProvider
|
||||
@@ -367,9 +360,10 @@ func getExternalProviders() *C.char {
|
||||
//export updateExternalProvider
|
||||
func updateExternalProvider(providerName *C.char, providerType *C.char, port C.longlong) {
|
||||
i := int64(port)
|
||||
providerNameString := C.GoString(providerName)
|
||||
providerTypeString := C.GoString(providerType)
|
||||
go func() {
|
||||
providerNameString := C.GoString(providerName)
|
||||
providerTypeString := C.GoString(providerType)
|
||||
|
||||
switch providerTypeString {
|
||||
case "Proxy":
|
||||
providers := tunnel.Providers()
|
||||
@@ -415,11 +409,6 @@ func initNativeApiBridge(api unsafe.Pointer, port C.longlong) {
|
||||
bridge.Port = &i
|
||||
}
|
||||
|
||||
//export freeCString
|
||||
func freeCString(s *C.char) {
|
||||
C.free(unsafe.Pointer(s))
|
||||
}
|
||||
|
||||
func init() {
|
||||
provider.HealthcheckHook = func(name string, delay uint16) {
|
||||
delayData := &Delay{
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
//go:build android
|
||||
|
||||
package platform
|
||||
|
||||
import "syscall"
|
||||
|
||||
var nullFd int
|
||||
var maxFdCount int
|
||||
|
||||
func init() {
|
||||
fd, err := syscall.Open("/dev/null", syscall.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
nullFd = fd
|
||||
|
||||
var limit syscall.Rlimit
|
||||
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
maxFdCount = 1024
|
||||
} else {
|
||||
maxFdCount = int(limit.Cur)
|
||||
}
|
||||
|
||||
maxFdCount = maxFdCount / 4 * 3
|
||||
}
|
||||
|
||||
func ShouldBlockConnection() bool {
|
||||
fd, err := syscall.Dup(nullFd)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
_ = syscall.Close(fd)
|
||||
|
||||
if fd > maxFdCount {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -71,8 +71,8 @@ func setProcessMap(s *C.char) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
paramsString := C.GoString(s)
|
||||
go func() {
|
||||
paramsString := C.GoString(s)
|
||||
var processMapItem = &ProcessMapItem{}
|
||||
err := json.Unmarshal([]byte(paramsString), processMapItem)
|
||||
if err == nil {
|
||||
|
||||
48
core/tun.go
48
core/tun.go
@@ -4,9 +4,7 @@ package main
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"core/platform"
|
||||
t "core/tun"
|
||||
"errors"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"golang.org/x/sync/semaphore"
|
||||
@@ -18,21 +16,6 @@ import (
|
||||
var tunLock sync.Mutex
|
||||
var tun *t.Tun
|
||||
|
||||
type FdMap struct {
|
||||
m sync.Map
|
||||
}
|
||||
|
||||
func (cm *FdMap) Store(key int) {
|
||||
cm.m.Store(key, struct{}{})
|
||||
}
|
||||
|
||||
func (cm *FdMap) Load(key int) bool {
|
||||
_, ok := cm.m.Load(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
var fdMap FdMap
|
||||
|
||||
//export startTUN
|
||||
func startTUN(fd C.int) {
|
||||
go func() {
|
||||
@@ -76,40 +59,13 @@ func stopTun() {
|
||||
}()
|
||||
}
|
||||
|
||||
var errBlocked = errors.New("blocked")
|
||||
|
||||
//export setFdMap
|
||||
func setFdMap(fd C.long) {
|
||||
fdInt := int(fd)
|
||||
go func() {
|
||||
fdMap.Store(fdInt)
|
||||
}()
|
||||
}
|
||||
|
||||
func init() {
|
||||
dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error {
|
||||
if platform.ShouldBlockConnection() {
|
||||
return errBlocked
|
||||
}
|
||||
return conn.Control(func(fd uintptr) {
|
||||
fdInt := int(fd)
|
||||
//timeout := time.After(100 * time.Millisecond)
|
||||
if tun != nil {
|
||||
tun.MarkSocket(fdInt)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
tun.MarkSocket(int(fd))
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
//for {
|
||||
// select {
|
||||
// case <-timeout:
|
||||
// return
|
||||
// default:
|
||||
// exists := fdMap.Load(fdInt)
|
||||
// if exists {
|
||||
// return
|
||||
// }
|
||||
// time.Sleep(20 * time.Millisecond)
|
||||
// }
|
||||
//}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +130,7 @@ class ApplicationState extends State<Application> {
|
||||
httpTimeoutDuration,
|
||||
(timer) async {
|
||||
await globalState.appController.updateGroups();
|
||||
globalState.appController.appState.sortNum++;
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -164,6 +165,7 @@ class ApplicationState extends State<Application> {
|
||||
themeMode: state.themeMode,
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
fontFamily: '',
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
colorScheme: _getAppColorScheme(
|
||||
brightness: Brightness.light,
|
||||
@@ -173,6 +175,7 @@ class ApplicationState extends State<Application> {
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
fontFamily: '',
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
colorScheme: _getAppColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
|
||||
@@ -45,10 +45,10 @@ class ClashCore {
|
||||
}
|
||||
|
||||
bool init(String homeDir) {
|
||||
final homeDirChar = homeDir.toNativeUtf8().cast<Char>();
|
||||
final isInit = clashFFI.initClash(homeDirChar) == 1;
|
||||
malloc.free(homeDirChar);
|
||||
return isInit;
|
||||
return clashFFI.initClash(
|
||||
homeDir.toNativeUtf8().cast(),
|
||||
) ==
|
||||
1;
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
@@ -67,12 +67,10 @@ class ClashCore {
|
||||
receiver.close();
|
||||
}
|
||||
});
|
||||
final dataChar = data.toNativeUtf8().cast<Char>();
|
||||
clashFFI.validateConfig(
|
||||
dataChar,
|
||||
data.toNativeUtf8().cast(),
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(dataChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@@ -86,23 +84,20 @@ class ClashCore {
|
||||
}
|
||||
});
|
||||
final params = json.encode(updateConfigParams);
|
||||
final paramsChar = params.toNativeUtf8().cast<Char>();
|
||||
clashFFI.updateConfig(
|
||||
paramsChar,
|
||||
params.toNativeUtf8().cast(),
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(paramsChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<List<Group>> getProxiesGroups() {
|
||||
final proxiesRaw = clashFFI.getProxies();
|
||||
final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString();
|
||||
clashFFI.freeCString(proxiesRaw);
|
||||
return Isolate.run<List<Group>>(() {
|
||||
if (proxiesRawString.isEmpty) return [];
|
||||
final proxies = (json.decode(proxiesRawString) ?? {}) as Map;
|
||||
if (proxies.isEmpty) return [];
|
||||
if(proxiesRawString.isEmpty) return [];
|
||||
final proxies = json.decode(proxiesRawString) as Map;
|
||||
if(proxies.isEmpty) return [];
|
||||
final groupNames = [
|
||||
UsedProxy.GLOBAL.name,
|
||||
...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) {
|
||||
@@ -116,8 +111,7 @@ class ClashCore {
|
||||
group["all"] = ((group["all"] ?? []) as List)
|
||||
.map(
|
||||
(name) => proxies[name],
|
||||
)
|
||||
.where((proxy) => proxy != null)
|
||||
)
|
||||
.toList();
|
||||
return group;
|
||||
}).toList();
|
||||
@@ -128,15 +122,14 @@ class ClashCore {
|
||||
Future<List<ExternalProvider>> getExternalProviders() {
|
||||
final externalProvidersRaw = clashFFI.getExternalProviders();
|
||||
final externalProvidersRawString =
|
||||
externalProvidersRaw.cast<Utf8>().toDartString();
|
||||
clashFFI.freeCString(externalProvidersRaw);
|
||||
externalProvidersRaw.cast<Utf8>().toDartString();
|
||||
return Isolate.run<List<ExternalProvider>>(() {
|
||||
final externalProviders =
|
||||
(json.decode(externalProvidersRawString) as List<dynamic>)
|
||||
.map(
|
||||
(item) => ExternalProvider.fromJson(item),
|
||||
)
|
||||
.toList();
|
||||
(json.decode(externalProvidersRawString) as List<dynamic>)
|
||||
.map(
|
||||
(item) => ExternalProvider.fromJson(item),
|
||||
)
|
||||
.toList();
|
||||
return externalProviders;
|
||||
});
|
||||
}
|
||||
@@ -153,24 +146,17 @@ class ClashCore {
|
||||
receiver.close();
|
||||
}
|
||||
});
|
||||
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
|
||||
final providerTypeChar = providerType.toNativeUtf8().cast<Char>();
|
||||
clashFFI.updateExternalProvider(
|
||||
providerNameChar,
|
||||
providerTypeChar,
|
||||
providerName.toNativeUtf8().cast(),
|
||||
providerType.toNativeUtf8().cast(),
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(providerNameChar);
|
||||
malloc.free(providerTypeChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
bool changeProxy(ChangeProxyParams changeProxyParams) {
|
||||
final params = json.encode(changeProxyParams);
|
||||
final paramsChar = params.toNativeUtf8().cast<Char>();
|
||||
final isInit = clashFFI.changeProxy(paramsChar) == 1;
|
||||
malloc.free(paramsChar);
|
||||
return isInit;
|
||||
return clashFFI.changeProxy(params.toNativeUtf8().cast()) == 1;
|
||||
}
|
||||
|
||||
Future<Delay> getDelay(String proxyName) {
|
||||
@@ -186,16 +172,13 @@ class ClashCore {
|
||||
receiver.close();
|
||||
}
|
||||
});
|
||||
final delayParamsChar =
|
||||
json.encode(delayParams).toNativeUtf8().cast<Char>();
|
||||
clashFFI.asyncTestDelay(
|
||||
delayParamsChar,
|
||||
json.encode(delayParams).toNativeUtf8().cast(),
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(delayParamsChar);
|
||||
Future.delayed(httpTimeoutDuration + moreDuration, () {
|
||||
receiver.close();
|
||||
if (!completer.isCompleted) {
|
||||
if(!completer.isCompleted){
|
||||
completer.complete(
|
||||
Delay(name: proxyName, value: -1),
|
||||
);
|
||||
@@ -205,33 +188,28 @@ class ClashCore {
|
||||
}
|
||||
|
||||
clearEffect(String path) {
|
||||
final pathChar = path.toNativeUtf8().cast<Char>();
|
||||
clashFFI.clearEffect(pathChar);
|
||||
malloc.free(pathChar);
|
||||
clashFFI.clearEffect(path.toNativeUtf8().cast());
|
||||
}
|
||||
|
||||
VersionInfo getVersionInfo() {
|
||||
final versionInfoRaw = clashFFI.getVersionInfo();
|
||||
final versionInfo = json.decode(versionInfoRaw.cast<Utf8>().toDartString());
|
||||
clashFFI.freeCString(versionInfoRaw);
|
||||
return VersionInfo.fromJson(versionInfo);
|
||||
}
|
||||
|
||||
Traffic getTraffic() {
|
||||
final trafficRaw = clashFFI.getTraffic();
|
||||
final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString());
|
||||
clashFFI.freeCString(trafficRaw);
|
||||
return Traffic.fromMap(trafficMap);
|
||||
}
|
||||
|
||||
Traffic getTotalTraffic() {
|
||||
final trafficRaw = clashFFI.getTotalTraffic();
|
||||
final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString());
|
||||
clashFFI.freeCString(trafficRaw);
|
||||
return Traffic.fromMap(trafficMap);
|
||||
}
|
||||
|
||||
void resetTraffic() {
|
||||
void resetTraffic(){
|
||||
clashFFI.resetTraffic();
|
||||
}
|
||||
|
||||
@@ -256,14 +234,7 @@ class ClashCore {
|
||||
}
|
||||
|
||||
void setProcessMap(ProcessMapItem processMapItem) {
|
||||
final processMapItemChar =
|
||||
json.encode(processMapItem).toNativeUtf8().cast<Char>();
|
||||
clashFFI.setProcessMap(processMapItemChar);
|
||||
malloc.free(processMapItemChar);
|
||||
}
|
||||
|
||||
void setFdMap(int fd) {
|
||||
clashFFI.setFdMap(fd);
|
||||
clashFFI.setProcessMap(json.encode(processMapItem).toNativeUtf8().cast());
|
||||
}
|
||||
|
||||
// DateTime? getRunTime() {
|
||||
@@ -275,16 +246,13 @@ class ClashCore {
|
||||
List<Connection> getConnections() {
|
||||
final connectionsDataRaw = clashFFI.getConnections();
|
||||
final connectionsData =
|
||||
json.decode(connectionsDataRaw.cast<Utf8>().toDartString()) as Map;
|
||||
clashFFI.freeCString(connectionsDataRaw);
|
||||
json.decode(connectionsDataRaw.cast<Utf8>().toDartString()) as Map;
|
||||
final connectionsRaw = connectionsData['connections'] as List? ?? [];
|
||||
return connectionsRaw.map((e) => Connection.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
closeConnections(String id) {
|
||||
final idChar = id.toNativeUtf8().cast<Char>();
|
||||
clashFFI.closeConnection(idChar);
|
||||
malloc.free(idChar);
|
||||
clashFFI.closeConnection(id.toNativeUtf8().cast());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ class Android {
|
||||
init() async {
|
||||
app?.onExit = () {
|
||||
clashCore.shutdown();
|
||||
print("adsadda==>");
|
||||
exit(0);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ const coreName = "clash.meta";
|
||||
const packageName = "FlClash";
|
||||
const httpTimeoutDuration = Duration(milliseconds: 5000);
|
||||
const moreDuration = Duration(milliseconds: 100);
|
||||
const animateDuration = Duration(milliseconds: 100);
|
||||
const defaultUpdateDuration = Duration(days: 1);
|
||||
const mmdbFileName = "geoip.metadb";
|
||||
const geoSiteFileName = "GeoSite.dat";
|
||||
|
||||
@@ -7,12 +7,8 @@ extension BuildContextExtension on BuildContext {
|
||||
return findAncestorStateOfType<CommonScaffoldState>();
|
||||
}
|
||||
|
||||
Size get appSize{
|
||||
return MediaQuery.of(this).size;
|
||||
}
|
||||
|
||||
double get width {
|
||||
return appSize.width;
|
||||
return MediaQuery.of(this).size.width;
|
||||
}
|
||||
|
||||
ColorScheme get colorScheme => Theme.of(this).colorScheme;
|
||||
|
||||
@@ -1,35 +1,22 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
import 'common.dart';
|
||||
class AppPackage{
|
||||
|
||||
class AppPackage {
|
||||
static AppPackage? _instance;
|
||||
|
||||
Completer<PackageInfo> packageInfoCompleter = Completer();
|
||||
|
||||
AppPackage._internal() {
|
||||
PackageInfo.fromPlatform().then(
|
||||
(value) => packageInfoCompleter.complete(value),
|
||||
PackageInfo.fromPlatform().then(
|
||||
(value) => packageInfoCompleter.complete(value),
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> getUa() async {
|
||||
final packageInfo = await packageInfoCompleter.future;
|
||||
final uas = [
|
||||
"$appName/v${packageInfo.version}",
|
||||
"clash-verge/v1.6.6",
|
||||
"Platform/${Platform.operatingSystem}",
|
||||
];
|
||||
return uas.join(" ");
|
||||
}
|
||||
|
||||
factory AppPackage() {
|
||||
_instance ??= AppPackage._internal();
|
||||
return _instance!;
|
||||
}
|
||||
}
|
||||
|
||||
final appPackage = AppPackage();
|
||||
final appPackage = AppPackage();
|
||||
@@ -2,20 +2,21 @@ import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/ip.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
|
||||
import 'constant.dart';
|
||||
import 'other.dart';
|
||||
import 'package.dart';
|
||||
|
||||
class Request {
|
||||
late final Dio _dio;
|
||||
int? _port;
|
||||
bool _isStart = false;
|
||||
|
||||
Request() {
|
||||
_dio = Dio();
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
headers: {"User-Agent": coreName},
|
||||
),
|
||||
);
|
||||
_dio.interceptors.add(InterceptorsWrapper(
|
||||
onRequest: (options, handler) {
|
||||
_syncProxy();
|
||||
@@ -44,14 +45,10 @@ class Request {
|
||||
}
|
||||
|
||||
Future<Response> getFileResponseForUrl(String url) async {
|
||||
final ua = await appPackage.getUa();
|
||||
final response = await _dio
|
||||
.get(
|
||||
url,
|
||||
options: Options(
|
||||
headers: {
|
||||
"User-Agent": ua,
|
||||
},
|
||||
responseType: ResponseType.bytes,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -10,5 +10,5 @@ extension TextStyleExtension on TextStyle {
|
||||
|
||||
TextStyle get toBold => copyWith(fontWeight: FontWeight.bold);
|
||||
|
||||
TextStyle get toMinus => copyWith(fontSize: fontSize! - 2);
|
||||
TextStyle get toMinus => copyWith(fontSize: fontSize! - 1);
|
||||
}
|
||||
|
||||
@@ -359,9 +359,7 @@ class AppController {
|
||||
}
|
||||
|
||||
addProfileFormURL(String url) async {
|
||||
if (globalState.navigatorKey.currentState?.canPop() ?? false) {
|
||||
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
|
||||
}
|
||||
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
|
||||
toProfiles();
|
||||
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
|
||||
if (commonScaffoldState?.mounted != true) return;
|
||||
@@ -407,54 +405,9 @@ class AppController {
|
||||
addProfileFormURL(url);
|
||||
}
|
||||
|
||||
int get columns =>
|
||||
globalState.getColumns(appState.viewMode, config.proxiesColumns);
|
||||
|
||||
changeColumns() {
|
||||
config.proxiesColumns = globalState.getColumns(
|
||||
appState.viewMode,
|
||||
columns - 1,
|
||||
);
|
||||
}
|
||||
|
||||
updateViewWidth(double width) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
appState.viewWidth = width;
|
||||
});
|
||||
}
|
||||
|
||||
List<Proxy> _sortOfName(List<Proxy> proxies) {
|
||||
return List.of(proxies)
|
||||
..sort(
|
||||
(a, b) => other.sortByChar(a.name, b.name),
|
||||
);
|
||||
}
|
||||
|
||||
List<Proxy> _sortOfDelay(List<Proxy> proxies) {
|
||||
return proxies = List.of(proxies)
|
||||
..sort(
|
||||
(a, b) {
|
||||
final aDelay = appState.getDelay(a.name);
|
||||
final bDelay = appState.getDelay(b.name);
|
||||
if (aDelay == null && bDelay == null) {
|
||||
return 0;
|
||||
}
|
||||
if (aDelay == null || aDelay == -1) {
|
||||
return 1;
|
||||
}
|
||||
if (bDelay == null || bDelay == -1) {
|
||||
return -1;
|
||||
}
|
||||
return aDelay.compareTo(bDelay);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<Proxy> getSortProxies(List<Proxy> proxies) {
|
||||
return switch (config.proxiesSortType) {
|
||||
ProxiesSortType.none => proxies,
|
||||
ProxiesSortType.delay => _sortOfDelay(proxies),
|
||||
ProxiesSortType.name => _sortOfName(proxies),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,10 +65,7 @@ enum RecoveryOption {
|
||||
onlyProfiles,
|
||||
}
|
||||
|
||||
enum ChipType { action, delete }
|
||||
|
||||
enum CommonCardType { plain, filled }
|
||||
|
||||
enum ProxiesType { tab, expansion }
|
||||
|
||||
enum ProxyCardType { expand, shrink }
|
||||
enum ChipType {
|
||||
action,
|
||||
delete,
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
builder: (_, ipv6, __) {
|
||||
return ListItem.switchItem(
|
||||
leading: const Icon(Icons.water_outlined),
|
||||
title: const Text("IPv6"),
|
||||
title: const Text("Ipv6"),
|
||||
subtitle: Text(appLocalizations.ipv6Desc),
|
||||
delegate: SwitchDelegate(
|
||||
value: ipv6,
|
||||
@@ -337,7 +337,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
|
||||
Widget _buildMoreSection() {
|
||||
final items = [
|
||||
if (system.isDesktop)
|
||||
if (false)
|
||||
Selector<ClashConfig, bool>(
|
||||
selector: (_, clashConfig) => clashConfig.tun.enable,
|
||||
builder: (_, tunEnable, __) {
|
||||
@@ -361,7 +361,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
];
|
||||
if(items.isEmpty) return Container();
|
||||
return Section(
|
||||
title: appLocalizations.more,
|
||||
title: appLocalizations.general,
|
||||
child: Column(
|
||||
children: [
|
||||
for (final item in items) ...[
|
||||
|
||||
@@ -198,7 +198,7 @@ class ConnectionItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
String _getRequestText(Metadata metadata) {
|
||||
var text = "${metadata.network}://";
|
||||
var text = "${metadata.network}:://";
|
||||
final ips = [
|
||||
metadata.host,
|
||||
metadata.destinationIP,
|
||||
|
||||
@@ -13,7 +13,6 @@ class CoreInfo extends StatelessWidget {
|
||||
selector: (_, appState) => appState.versionInfo,
|
||||
builder: (_, versionInfo, __) {
|
||||
return CommonCard(
|
||||
onPressed: () {},
|
||||
info: Info(
|
||||
label: appLocalizations.coreInfo,
|
||||
iconData: Icons.memory,
|
||||
|
||||
@@ -56,7 +56,7 @@ class _DashboardFragmentState extends State<DashboardFragment> {
|
||||
),
|
||||
GridItem(
|
||||
crossAxisCellCount: isDesktop ? 4 : 6,
|
||||
child: const IntranetIP(),
|
||||
child: const IntranetIp(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -5,14 +5,14 @@ import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class IntranetIP extends StatefulWidget {
|
||||
const IntranetIP({super.key});
|
||||
class IntranetIp extends StatefulWidget {
|
||||
const IntranetIp({super.key});
|
||||
|
||||
@override
|
||||
State<IntranetIP> createState() => _IntranetIPState();
|
||||
State<IntranetIp> createState() => _IntranetIpState();
|
||||
}
|
||||
|
||||
class _IntranetIPState extends State<IntranetIP> {
|
||||
class _IntranetIpState extends State<IntranetIp> {
|
||||
final ipNotifier = ValueNotifier<String>("");
|
||||
|
||||
Future<String?> getLocalIpAddress() async {
|
||||
@@ -45,15 +45,12 @@ class _IntranetIPState extends State<IntranetIP> {
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
info: Info(
|
||||
label: appLocalizations.intranetIP,
|
||||
label: appLocalizations.intranetIp,
|
||||
iconData: Icons.devices,
|
||||
),
|
||||
onPressed: (){
|
||||
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16).copyWith(top: 0),
|
||||
height: globalState.appController.measure.titleLargeHeight + 24 - 2,
|
||||
height: globalState.appController.measure.titleLargeHeight + 24 - 1,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: ipNotifier,
|
||||
builder: (_, value, __) {
|
||||
|
||||
@@ -78,7 +78,6 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
valueListenable: ipInfoNotifier,
|
||||
builder: (_, ipInfo, __) {
|
||||
return CommonCard(
|
||||
onPressed: () {},
|
||||
child: Column(
|
||||
children: [
|
||||
Flexible(
|
||||
@@ -135,9 +134,8 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: globalState.appController.measure.titleLargeHeight +
|
||||
24 -
|
||||
2,
|
||||
height:
|
||||
globalState.appController.measure.titleLargeHeight + 24 - 1,
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.all(16).copyWith(top: 0),
|
||||
child: FadeBox(
|
||||
@@ -168,8 +166,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
"timeout",
|
||||
style: context.textTheme.titleLarge
|
||||
?.copyWith(color: Colors.red)
|
||||
.toSoftBold
|
||||
.toMinus,
|
||||
.toSoftBold.toMinus,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
|
||||
@@ -111,7 +111,6 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
onPressed: () {},
|
||||
info: Info(
|
||||
label: appLocalizations.networkSpeed,
|
||||
iconData: Icons.speed,
|
||||
|
||||
@@ -33,7 +33,6 @@ class OutboundMode extends StatelessWidget {
|
||||
selector: (_, clashConfig) => clashConfig.mode,
|
||||
builder: (_, mode, __) {
|
||||
return CommonCard(
|
||||
onPressed: () {},
|
||||
info: Info(
|
||||
label: appLocalizations.outboundMode,
|
||||
iconData: Icons.call_split,
|
||||
|
||||
@@ -51,7 +51,6 @@ class TrafficUsage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonCard(
|
||||
onPressed: () {},
|
||||
info: Info(
|
||||
label: appLocalizations.trafficUsage,
|
||||
iconData: Icons.data_saver_off,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export 'proxies.dart';
|
||||
export 'proxies/proxies.dart';
|
||||
export 'dashboard/dashboard.dart';
|
||||
export 'tools.dart';
|
||||
export 'profiles/profiles.dart';
|
||||
|
||||
@@ -25,9 +25,7 @@ class AddProfile extends StatelessWidget {
|
||||
final url = await Navigator.of(context)
|
||||
.push<String>(MaterialPageRoute(builder: (_) => const ScanPage()));
|
||||
if (url != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_){
|
||||
_handleAddProfileFormURL(url);
|
||||
});
|
||||
_handleAddProfileFormURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,8 +91,7 @@ class _URLFormDialogState extends State<URLFormDialog> {
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
TextField(
|
||||
maxLines: 5,
|
||||
minLines: 1,
|
||||
maxLines: null,
|
||||
controller: urlController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
|
||||
@@ -94,8 +94,7 @@ class _EditProfileState extends State<EditProfile> {
|
||||
ListItem(
|
||||
title: TextFormField(
|
||||
controller: urlController,
|
||||
maxLines: 5,
|
||||
minLines: 1,
|
||||
maxLines: null,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: appLocalizations.url,
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:fl_clash/fragments/profiles/edit_profile.dart';
|
||||
import 'package:fl_clash/fragments/profiles/view_profile.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -29,7 +28,6 @@ class ProfilesFragment extends StatefulWidget {
|
||||
|
||||
class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
final hasPadding = ValueNotifier<bool>(false);
|
||||
Function? applyConfigDebounce;
|
||||
|
||||
List<GlobalObjectKey<_ProfileItemState>> profileItemKeys = [];
|
||||
|
||||
@@ -57,7 +55,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
_updateProfiles() async {
|
||||
final updateProfiles = profileItemKeys.map<Future>(
|
||||
(key) async => await key.currentState?.updateProfile(false));
|
||||
await Future.wait(updateProfiles);
|
||||
final result = await Future.wait(updateProfiles);
|
||||
}
|
||||
|
||||
_initScaffoldState() {
|
||||
@@ -88,30 +86,12 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
hasPadding.dispose();
|
||||
}
|
||||
|
||||
_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 Selector<AppState, bool>(
|
||||
@@ -174,7 +154,8 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
key: profileItemKeys[i],
|
||||
profile: state.profiles[i],
|
||||
groupValue: state.currentProfileId,
|
||||
onChanged: _changeProfile,
|
||||
onChanged:
|
||||
globalState.appController.changeProfile,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -456,16 +437,16 @@ class _ProfileItemState extends State<ProfileItem> {
|
||||
label: appLocalizations.update,
|
||||
iconData: Icons.sync,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: ProfileActions.view,
|
||||
label: appLocalizations.view,
|
||||
iconData: Icons.visibility,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: ProfileActions.delete,
|
||||
label: appLocalizations.delete,
|
||||
iconData: Icons.delete,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: ProfileActions.view,
|
||||
label: "查看",
|
||||
iconData: Icons.visibility,
|
||||
),
|
||||
],
|
||||
onSelected: (ProfileActions? action) async {
|
||||
switch (action) {
|
||||
|
||||
@@ -1,918 +0,0 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_clash/clash/core.dart';
|
||||
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';
|
||||
|
||||
class ProxiesFragment extends StatefulWidget {
|
||||
const ProxiesFragment({super.key});
|
||||
|
||||
@override
|
||||
State<ProxiesFragment> createState() => _ProxiesFragmentState();
|
||||
}
|
||||
|
||||
class _ProxiesFragmentState extends State<ProxiesFragment> {
|
||||
_initActions() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
final commonScaffoldState =
|
||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
||||
final items = [
|
||||
CommonPopupMenuItem(
|
||||
action: ProxiesSortType.none,
|
||||
label: appLocalizations.defaultSort,
|
||||
iconData: Icons.reorder,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: ProxiesSortType.delay,
|
||||
label: appLocalizations.delaySort,
|
||||
iconData: Icons.network_ping,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: ProxiesSortType.name,
|
||||
label: appLocalizations.nameSort,
|
||||
iconData: Icons.sort_by_alpha,
|
||||
),
|
||||
];
|
||||
commonScaffoldState?.actions = [
|
||||
Selector<Config, ProxiesType>(
|
||||
selector: (_, config) => config.proxiesType,
|
||||
builder: (_, proxiesType, __) {
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
switch (proxiesType) {
|
||||
ProxiesType.tab => Icons.view_list,
|
||||
ProxiesType.expansion => Icons.view_carousel,
|
||||
},
|
||||
),
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
config.proxiesType = config.proxiesType == ProxiesType.tab
|
||||
? ProxiesType.expansion
|
||||
: ProxiesType.tab;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.view_column,
|
||||
),
|
||||
onPressed: () {
|
||||
globalState.appController.changeColumns();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.transform_sharp),
|
||||
onPressed: () {
|
||||
final config = globalState.appController.config;
|
||||
config.proxyCardType = config.proxyCardType == ProxyCardType.expand
|
||||
? ProxyCardType.shrink
|
||||
: ProxyCardType.expand;
|
||||
},
|
||||
),
|
||||
Selector<Config, ProxiesSortType>(
|
||||
selector: (_, config) => config.proxiesSortType,
|
||||
builder: (_, proxiesSortType, __) {
|
||||
return CommonPopupMenu<ProxiesSortType>.radio(
|
||||
items: items,
|
||||
icon: const Icon(Icons.sort_sharp),
|
||||
onSelected: (value) {
|
||||
final config = context.read<Config>();
|
||||
config.proxiesSortType = value;
|
||||
},
|
||||
selectedValue: proxiesSortType,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
)
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<AppState, bool>(
|
||||
selector: (_, appState) => appState.currentLabel == 'proxies',
|
||||
builder: (_, isCurrent, child) {
|
||||
if (isCurrent) {
|
||||
_initActions();
|
||||
}
|
||||
return child!;
|
||||
},
|
||||
child: Selector<Config, ProxiesType>(
|
||||
selector: (_, config) => config.proxiesType,
|
||||
builder: (_, proxiesType, __) {
|
||||
return switch (proxiesType) {
|
||||
ProxiesType.tab => const ProxiesTabFragment(),
|
||||
ProxiesType.expansion => const ProxiesExpansionPanelFragment(),
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProxiesTabFragment extends StatefulWidget {
|
||||
const ProxiesTabFragment({super.key});
|
||||
|
||||
@override
|
||||
State<ProxiesTabFragment> createState() => _ProxiesTabFragmentState();
|
||||
}
|
||||
|
||||
class _ProxiesTabFragmentState extends State<ProxiesTabFragment>
|
||||
with TickerProviderStateMixin {
|
||||
TabController? _tabController;
|
||||
|
||||
_handleTabControllerChange() {
|
||||
final indexIsChanging = _tabController?.indexIsChanging ?? false;
|
||||
if (indexIsChanging) return;
|
||||
final index = _tabController?.index;
|
||||
if (index == null) return;
|
||||
final appController = globalState.appController;
|
||||
final currentGroups = appController.appState.currentGroups;
|
||||
if (currentGroups.length > index) {
|
||||
appController.config.updateCurrentGroupName(currentGroups[index].name);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_tabController?.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector2<AppState, Config, ProxiesSelectorState>(
|
||||
selector: (_, appState, config) {
|
||||
final currentGroups = appState.currentGroups;
|
||||
final groupNames = currentGroups.map((e) => e.name).toList();
|
||||
return ProxiesSelectorState(
|
||||
groupNames: groupNames,
|
||||
currentGroupName: config.currentGroupName,
|
||||
);
|
||||
},
|
||||
shouldRebuild: (prev, next) {
|
||||
if (!const ListEquality<String>()
|
||||
.equals(prev.groupNames, next.groupNames)) {
|
||||
_tabController?.removeListener(_handleTabControllerChange);
|
||||
_tabController?.dispose();
|
||||
_tabController = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
builder: (_, state, __) {
|
||||
final index = state.groupNames.indexWhere(
|
||||
(item) => item == state.currentGroupName,
|
||||
);
|
||||
_tabController ??= TabController(
|
||||
length: state.groupNames.length,
|
||||
initialIndex: index == -1 ? 0 : index,
|
||||
vsync: this,
|
||||
)..addListener(_handleTabControllerChange);
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TabBar(
|
||||
controller: _tabController,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
dividerColor: Colors.transparent,
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.start,
|
||||
overlayColor: const WidgetStatePropertyAll(Colors.transparent),
|
||||
tabs: [
|
||||
for (final groupName in state.groupNames)
|
||||
Tab(
|
||||
text: groupName,
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
for (final groupName in state.groupNames)
|
||||
KeepContainer(
|
||||
key: ObjectKey(groupName),
|
||||
child: ProxyGroupView(
|
||||
groupName: groupName,
|
||||
type: ProxiesType.tab,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProxiesExpansionPanelFragment extends StatefulWidget {
|
||||
const ProxiesExpansionPanelFragment({super.key});
|
||||
|
||||
@override
|
||||
State<ProxiesExpansionPanelFragment> createState() =>
|
||||
_ProxiesExpansionPanelFragmentState();
|
||||
}
|
||||
|
||||
class _ProxiesExpansionPanelFragmentState
|
||||
extends State<ProxiesExpansionPanelFragment> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector2<AppState, Config, ProxiesSelectorState>(
|
||||
selector: (_, appState, config) {
|
||||
final currentGroups = appState.currentGroups;
|
||||
final groupNames = currentGroups.map((e) => e.name).toList();
|
||||
return ProxiesSelectorState(
|
||||
groupNames: groupNames,
|
||||
currentGroupName: config.currentGroupName,
|
||||
);
|
||||
},
|
||||
builder: (_, state, __) {
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: state.groupNames.length,
|
||||
itemBuilder: (_, index) {
|
||||
final groupName = state.groupNames[index];
|
||||
return ProxyGroupView(
|
||||
key: PageStorageKey(groupName),
|
||||
groupName: groupName,
|
||||
type: ProxiesType.expansion,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return const SizedBox(
|
||||
height: 16,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProxyGroupView extends StatefulWidget {
|
||||
final String groupName;
|
||||
final ProxiesType type;
|
||||
|
||||
const ProxyGroupView({
|
||||
super.key,
|
||||
required this.groupName,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ProxyGroupView> createState() => _ProxyGroupViewState();
|
||||
}
|
||||
|
||||
class _ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
var isLock = false;
|
||||
final isBoundaryNotifier = ValueNotifier<bool>(false);
|
||||
final scrollController = ScrollController();
|
||||
var isEnd = false;
|
||||
|
||||
String get groupName => widget.groupName;
|
||||
|
||||
ProxiesType get type => widget.type;
|
||||
|
||||
double _getItemHeight(ProxyCardType proxyCardType) {
|
||||
final isExpand = proxyCardType == ProxyCardType.expand;
|
||||
final measure = globalState.appController.measure;
|
||||
final baseHeight =
|
||||
12 * 2 + measure.bodyMediumHeight * 2 + measure.bodySmallHeight + 8;
|
||||
return isExpand ? baseHeight + measure.labelSmallHeight + 8 : baseHeight;
|
||||
}
|
||||
|
||||
_delayTest(List<Proxy> proxies) async {
|
||||
if (isLock) return;
|
||||
isLock = true;
|
||||
final appController = globalState.appController;
|
||||
for (final proxy in proxies) {
|
||||
final proxyName =
|
||||
appController.appState.getRealProxyName(proxy.name) ?? proxy.name;
|
||||
globalState.appController.setDelay(
|
||||
Delay(
|
||||
name: proxyName,
|
||||
value: 0,
|
||||
),
|
||||
);
|
||||
clashCore.getDelay(proxyName).then((delay) {
|
||||
globalState.appController.setDelay(delay);
|
||||
});
|
||||
}
|
||||
await Future.delayed(httpTimeoutDuration + moreDuration);
|
||||
appController.appState.sortNum++;
|
||||
isLock = false;
|
||||
}
|
||||
|
||||
Widget _currentProxyNameBuilder({
|
||||
required Widget Function(String) builder,
|
||||
}) {
|
||||
return Selector2<AppState, Config, String>(
|
||||
selector: (_, appState, config) {
|
||||
final group = appState.getGroupWithName(groupName)!;
|
||||
return config.currentSelectedMap[groupName] ?? group.now ?? '';
|
||||
},
|
||||
builder: (_, value, ___) {
|
||||
return builder(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTabGroupView({
|
||||
required List<Proxy> proxies,
|
||||
required int columns,
|
||||
required ProxyCardType proxyCardType,
|
||||
}) {
|
||||
final sortedProxies = globalState.appController.getSortProxies(
|
||||
proxies,
|
||||
);
|
||||
return DelayTestButtonContainer(
|
||||
onClick: () async {
|
||||
await _delayTest(
|
||||
proxies,
|
||||
);
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: GridView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: columns,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisExtent: _getItemHeight(proxyCardType),
|
||||
),
|
||||
itemCount: sortedProxies.length,
|
||||
itemBuilder: (_, index) {
|
||||
final proxy = sortedProxies[index];
|
||||
return _currentProxyNameBuilder(builder: (value) {
|
||||
return ProxyCard(
|
||||
type: proxyCardType,
|
||||
key: ValueKey('$groupName.${proxy.name}'),
|
||||
isSelected: value == proxy.name,
|
||||
proxy: proxy,
|
||||
groupName: groupName,
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _androidExpansionHandle(Widget child) {
|
||||
// return NotificationListener<ScrollNotification>(
|
||||
// onNotification: (ScrollNotification notification) {
|
||||
// if (notification is ScrollEndNotification) {
|
||||
// if (notification.metrics.atEdge) {
|
||||
// isEnd = notification.metrics.pixels ==
|
||||
// notification.metrics.maxScrollExtent;
|
||||
// isBoundaryNotifier.value = true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// },
|
||||
// child: Listener(
|
||||
// onPointerMove: (details) {
|
||||
// double yOffset = details.delta.dy;
|
||||
// final isEnd = scrollController.position.maxScrollExtent == scrollController.position.pixels;
|
||||
// final isTop = scrollController.position.minScrollExtent == scrollController.position.pixels;
|
||||
// if(isEnd || isTop){
|
||||
// isBoundaryNotifier.value = true;
|
||||
// } else if (yOffset > 0 && scrollController.position.maxScrollExtent == scrollController.position.pixels) {
|
||||
// isBoundaryNotifier.value = false;
|
||||
// } else if (yOffset < 0 && !isEnd) {
|
||||
// isBoundaryNotifier.value = false;
|
||||
// }
|
||||
// },
|
||||
// child: child,
|
||||
// ),
|
||||
// );
|
||||
return Listener(
|
||||
onPointerMove: (details) {
|
||||
double yOffset = details.delta.dy;
|
||||
final isEnd = scrollController.position.maxScrollExtent ==
|
||||
scrollController.position.pixels;
|
||||
final isTop = scrollController.position.minScrollExtent ==
|
||||
scrollController.position.pixels;
|
||||
if (isEnd && yOffset < 0) {
|
||||
isBoundaryNotifier.value = true;
|
||||
} else if (isTop && yOffset > 0) {
|
||||
isBoundaryNotifier.value = true;
|
||||
} else {
|
||||
isBoundaryNotifier.value = false;
|
||||
}
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildExpansionGroupView({
|
||||
required List<Proxy> proxies,
|
||||
required int columns,
|
||||
required ProxyCardType proxyCardType,
|
||||
}) {
|
||||
final sortedProxies = globalState.appController.getSortProxies(
|
||||
proxies,
|
||||
);
|
||||
final group =
|
||||
globalState.appController.appState.getGroupWithName(groupName)!;
|
||||
final itemHeight = _getItemHeight(proxyCardType);
|
||||
final innerHeight = context.appSize.height - 200;
|
||||
final lines = (sortedProxies.length / columns).ceil();
|
||||
final minLines =
|
||||
innerHeight >= 200 ? (innerHeight / itemHeight).floor() : 3;
|
||||
final hasScrollable = lines > minLines;
|
||||
final height = (itemHeight + 8) * min(lines, minLines) - 8;
|
||||
return Selector<Config, Set<String>>(
|
||||
selector: (_, config) => config.currentUnfoldSet,
|
||||
builder: (_, currentUnfoldSet, __) {
|
||||
return CommonCard(
|
||||
child: ExpansionTile(
|
||||
childrenPadding: const EdgeInsets.all(8),
|
||||
initiallyExpanded: currentUnfoldSet.contains(groupName),
|
||||
iconColor: context.colorScheme.onSurfaceVariant,
|
||||
onExpansionChanged: (value) {
|
||||
final tempUnfoldSet = Set<String>.from(currentUnfoldSet);
|
||||
if (value) {
|
||||
tempUnfoldSet.add(groupName);
|
||||
} else {
|
||||
tempUnfoldSet.remove(groupName);
|
||||
}
|
||||
globalState.appController.config.updateCurrentUnfoldSet(
|
||||
tempUnfoldSet,
|
||||
);
|
||||
},
|
||||
controlAffinity: ListTileControlAffinity.trailing,
|
||||
title: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(groupName),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
group.type.name,
|
||||
style: context.textTheme.labelMedium?.toLight,
|
||||
),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: _currentProxyNameBuilder(
|
||||
builder: (value) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (value.isNotEmpty) ...[
|
||||
Icon(
|
||||
Icons.arrow_right,
|
||||
color: context
|
||||
.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
value,
|
||||
style: context
|
||||
.textTheme.labelMedium?.toLight,
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.network_ping,
|
||||
size: 20,
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
onPressed: () {
|
||||
_delayTest(sortedProxies);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide.none,
|
||||
),
|
||||
collapsedShape: const RoundedRectangleBorder(
|
||||
side: BorderSide.none,
|
||||
),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: height,
|
||||
child: Platform.isAndroid
|
||||
? _androidExpansionHandle(
|
||||
ValueListenableBuilder(
|
||||
valueListenable: isBoundaryNotifier,
|
||||
builder: (_, isBoundary, child) {
|
||||
return Scrollbar(
|
||||
thickness: 6,
|
||||
interactive: true,
|
||||
radius: const Radius.circular(6),
|
||||
child: GridView.builder(
|
||||
key: widget.key,
|
||||
controller: scrollController,
|
||||
physics: isBoundary || !hasScrollable
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: const AlwaysScrollableScrollPhysics(),
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: columns,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisExtent: _getItemHeight(proxyCardType),
|
||||
),
|
||||
itemCount: sortedProxies.length,
|
||||
itemBuilder: (_, index) {
|
||||
final proxy = sortedProxies[index];
|
||||
return _currentProxyNameBuilder(
|
||||
builder: (value) {
|
||||
return ProxyCard(
|
||||
style: CommonCardType.filled,
|
||||
type: proxyCardType,
|
||||
isSelected: value == proxy.name,
|
||||
key: ValueKey('$groupName.${proxy.name}'),
|
||||
proxy: proxy,
|
||||
groupName: groupName,
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: GridView.builder(
|
||||
key: widget.key,
|
||||
controller: scrollController,
|
||||
physics: !hasScrollable
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: const AlwaysScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: columns,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisExtent: _getItemHeight(proxyCardType),
|
||||
),
|
||||
itemCount: sortedProxies.length,
|
||||
itemBuilder: (_, index) {
|
||||
final proxy = sortedProxies[index];
|
||||
return _currentProxyNameBuilder(builder: (value) {
|
||||
return ProxyCard(
|
||||
style: CommonCardType.filled,
|
||||
type: proxyCardType,
|
||||
isSelected: value == proxy.name,
|
||||
key: ValueKey('$groupName.${proxy.name}'),
|
||||
proxy: proxy,
|
||||
groupName: groupName,
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
isBoundaryNotifier.dispose();
|
||||
scrollController.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector2<AppState, Config, ProxyGroupSelectorState>(
|
||||
selector: (_, appState, config) {
|
||||
final group = appState.getGroupWithName(groupName)!;
|
||||
return ProxyGroupSelectorState(
|
||||
proxyCardType: config.proxyCardType,
|
||||
proxiesSortType: config.proxiesSortType,
|
||||
columns: globalState.appController.columns,
|
||||
sortNum: appState.sortNum,
|
||||
proxies: group.all,
|
||||
);
|
||||
},
|
||||
builder: (_, state, __) {
|
||||
final proxies = state.proxies;
|
||||
final columns = state.columns;
|
||||
final proxyCardType = state.proxyCardType;
|
||||
return switch (type) {
|
||||
ProxiesType.tab => _buildTabGroupView(
|
||||
proxies: proxies,
|
||||
columns: columns,
|
||||
proxyCardType: proxyCardType,
|
||||
),
|
||||
ProxiesType.expansion => _buildExpansionGroupView(
|
||||
proxies: proxies,
|
||||
columns: columns,
|
||||
proxyCardType: proxyCardType,
|
||||
),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DelayTestButtonContainer extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Future Function() onClick;
|
||||
|
||||
const DelayTestButtonContainer({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.onClick,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DelayTestButtonContainer> createState() =>
|
||||
_DelayTestButtonContainerState();
|
||||
}
|
||||
|
||||
class _DelayTestButtonContainerState extends State<DelayTestButtonContainer>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _scale;
|
||||
|
||||
_healthcheck() async {
|
||||
_controller.forward();
|
||||
await widget.onClick();
|
||||
_controller.reverse();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(
|
||||
milliseconds: 200,
|
||||
),
|
||||
);
|
||||
_scale = Tween<double>(
|
||||
begin: 1.0,
|
||||
end: 0.0,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: const Interval(
|
||||
0,
|
||||
1,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_controller.reverse();
|
||||
return FloatLayout(
|
||||
floatingWidget: FloatWrapper(
|
||||
child: AnimatedBuilder(
|
||||
animation: _controller.view,
|
||||
builder: (_, child) {
|
||||
return SizedBox(
|
||||
width: 56,
|
||||
height: 56,
|
||||
child: Transform.scale(
|
||||
scale: _scale.value,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: FloatingActionButton(
|
||||
heroTag: null,
|
||||
onPressed: _healthcheck,
|
||||
child: const Icon(Icons.network_ping),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProxyCard extends StatelessWidget {
|
||||
final String groupName;
|
||||
final Proxy proxy;
|
||||
final bool isSelected;
|
||||
final CommonCardType style;
|
||||
final ProxyCardType type;
|
||||
|
||||
const ProxyCard({
|
||||
super.key,
|
||||
required this.groupName,
|
||||
required this.proxy,
|
||||
required this.isSelected,
|
||||
this.style = CommonCardType.plain,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
Measure get measure => globalState.appController.measure;
|
||||
|
||||
Widget _buildDelayText() {
|
||||
return SizedBox(
|
||||
height: measure.labelSmallHeight,
|
||||
child: Selector<AppState, int?>(
|
||||
selector: (context, appState) => appState.getDelay(
|
||||
proxy.name,
|
||||
),
|
||||
builder: (context, delay, __) {
|
||||
return FadeBox(
|
||||
child: Builder(
|
||||
builder: (_) {
|
||||
if (delay == null) {
|
||||
return Container();
|
||||
}
|
||||
if (delay == 0) {
|
||||
return SizedBox(
|
||||
height: measure.labelSmallHeight,
|
||||
width: measure.labelSmallHeight,
|
||||
child: const CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
return Text(
|
||||
delay > 0 ? '$delay ms' : "Timeout",
|
||||
style: context.textTheme.labelSmall?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: other.getDelayColor(
|
||||
delay,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProxyNameText(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: measure.bodyMediumHeight * 2,
|
||||
child: Text(
|
||||
proxy.name,
|
||||
maxLines: 2,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_changeProxy(BuildContext context) {
|
||||
final appController = globalState.appController;
|
||||
final group = appController.appState.getGroupWithName(groupName)!;
|
||||
if (group.type != GroupType.Selector) {
|
||||
globalState.showSnackBar(
|
||||
context,
|
||||
message: appLocalizations.notSelectedTip,
|
||||
);
|
||||
return;
|
||||
}
|
||||
globalState.appController.config.updateCurrentSelectedMap(
|
||||
groupName,
|
||||
proxy.name,
|
||||
);
|
||||
clashCore.changeProxy(
|
||||
ChangeProxyParams(
|
||||
groupName: groupName,
|
||||
proxyName: proxy.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final measure = globalState.appController.measure;
|
||||
final delayText = _buildDelayText();
|
||||
final proxyNameText = _buildProxyNameText(context);
|
||||
return CommonCard(
|
||||
type: style,
|
||||
key: key,
|
||||
onPressed: () {
|
||||
_changeProxy(context);
|
||||
},
|
||||
isSelected: isSelected,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
proxyNameText,
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (type == ProxyCardType.expand) ...[
|
||||
SizedBox(
|
||||
height: measure.bodySmallHeight,
|
||||
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(
|
||||
height: 8,
|
||||
),
|
||||
delayText,
|
||||
] else
|
||||
SizedBox(
|
||||
height: measure.bodySmallHeight,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: TooltipText(
|
||||
text: Text(
|
||||
proxy.type,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color:
|
||||
context.textTheme.bodySmall?.color?.toLight(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
delayText,
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
59
lib/fragments/proxies/expansion_panel.dart
Normal file
59
lib/fragments/proxies/expansion_panel.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/widgets/card.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ProxiesExpansionPanelFragment extends StatefulWidget {
|
||||
const ProxiesExpansionPanelFragment({super.key});
|
||||
|
||||
@override
|
||||
State<ProxiesExpansionPanelFragment> createState() =>
|
||||
_ProxiesExpansionPanelFragmentState();
|
||||
}
|
||||
|
||||
class _ProxiesExpansionPanelFragmentState
|
||||
extends State<ProxiesExpansionPanelFragment> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector2<AppState, Config, ProxiesSelectorState>(
|
||||
selector: (_, appState, config) {
|
||||
final currentGroups = appState.currentGroups;
|
||||
final groupNames = currentGroups.map((e) => e.name).toList();
|
||||
return ProxiesSelectorState(
|
||||
groupNames: groupNames,
|
||||
currentGroupName: config.currentGroupName,
|
||||
);
|
||||
},
|
||||
builder: (_, state, __) {
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: state.groupNames.length,
|
||||
itemBuilder: (_, index) {
|
||||
final groupName = state.groupNames[index];
|
||||
return CommonCard(
|
||||
child: ExpansionTile(
|
||||
title: Text(groupName),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(0.0),
|
||||
side: const BorderSide(color: Colors.transparent),
|
||||
),
|
||||
collapsedShape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(0.0),
|
||||
side: const BorderSide(color: Colors.transparent),
|
||||
),
|
||||
children: [
|
||||
Text("1212313"),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return const SizedBox(
|
||||
height: 16,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
72
lib/fragments/proxies/proxies.dart
Normal file
72
lib/fragments/proxies/proxies.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/fragments/proxies/tabview.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ProxiesFragment extends StatefulWidget {
|
||||
const ProxiesFragment({super.key});
|
||||
|
||||
@override
|
||||
State<ProxiesFragment> createState() => _ProxiesFragmentState();
|
||||
}
|
||||
|
||||
class _ProxiesFragmentState extends State<ProxiesFragment> {
|
||||
|
||||
_initActions() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
final commonScaffoldState =
|
||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
||||
final items = [
|
||||
CommonPopupMenuItem(
|
||||
action: ProxiesSortType.none,
|
||||
label: appLocalizations.defaultSort,
|
||||
iconData: Icons.sort,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: ProxiesSortType.delay,
|
||||
label: appLocalizations.delaySort,
|
||||
iconData: Icons.network_ping),
|
||||
CommonPopupMenuItem(
|
||||
action: ProxiesSortType.name,
|
||||
label: appLocalizations.nameSort,
|
||||
iconData: Icons.sort_by_alpha),
|
||||
];
|
||||
commonScaffoldState?.actions = [
|
||||
Selector<Config, ProxiesSortType>(
|
||||
selector: (_, config) => config.proxiesSortType,
|
||||
builder: (_, proxiesSortType, __) {
|
||||
return CommonPopupMenu<ProxiesSortType>.radio(
|
||||
items: items,
|
||||
onSelected: (value) {
|
||||
final config = context.read<Config>();
|
||||
config.proxiesSortType = value;
|
||||
},
|
||||
selectedValue: proxiesSortType,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
)
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<AppState, bool>(
|
||||
selector: (_, appState) => appState.currentLabel == 'proxies',
|
||||
builder: (_, isCurrent, child) {
|
||||
if (isCurrent) {
|
||||
_initActions();
|
||||
}
|
||||
return child!;
|
||||
},
|
||||
child: const ProxiesTabFragment(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
470
lib/fragments/proxies/tabview.dart
Normal file
470
lib/fragments/proxies/tabview.dart
Normal file
@@ -0,0 +1,470 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
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';
|
||||
|
||||
class ProxiesTabFragment extends StatefulWidget {
|
||||
const ProxiesTabFragment({super.key});
|
||||
|
||||
@override
|
||||
State<ProxiesTabFragment> createState() => _ProxiesTabFragmentState();
|
||||
}
|
||||
|
||||
class _ProxiesTabFragmentState extends State<ProxiesTabFragment> with TickerProviderStateMixin {
|
||||
TabController? _tabController;
|
||||
|
||||
_handleTabControllerChange() {
|
||||
final indexIsChanging = _tabController?.indexIsChanging ?? false;
|
||||
if (indexIsChanging) return;
|
||||
final index = _tabController?.index;
|
||||
if (index == null) return;
|
||||
final appController = globalState.appController;
|
||||
final currentGroups = appController.appState.currentGroups;
|
||||
if (currentGroups.length > index) {
|
||||
appController.config.updateCurrentGroupName(currentGroups[index].name);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_tabController?.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector2<AppState, Config, ProxiesSelectorState>(
|
||||
selector: (_, appState, config) {
|
||||
final currentGroups = appState.currentGroups;
|
||||
final groupNames = currentGroups.map((e) => e.name).toList();
|
||||
return ProxiesSelectorState(
|
||||
groupNames: groupNames,
|
||||
currentGroupName: config.currentGroupName,
|
||||
);
|
||||
},
|
||||
shouldRebuild: (prev, next) {
|
||||
if (!const ListEquality<String>()
|
||||
.equals(prev.groupNames, next.groupNames)) {
|
||||
_tabController?.removeListener(_handleTabControllerChange);
|
||||
_tabController?.dispose();
|
||||
_tabController = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
builder: (_, state, __) {
|
||||
final index = state.groupNames.indexWhere(
|
||||
(item) => item == state.currentGroupName,
|
||||
);
|
||||
_tabController ??= TabController(
|
||||
length: state.groupNames.length,
|
||||
initialIndex: index == -1 ? 0 : index,
|
||||
vsync: this,
|
||||
)..addListener(_handleTabControllerChange);
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TabBar(
|
||||
controller: _tabController,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
dividerColor: Colors.transparent,
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.start,
|
||||
overlayColor:
|
||||
const WidgetStatePropertyAll(Colors.transparent),
|
||||
tabs: [
|
||||
for (final groupName in state.groupNames)
|
||||
Tab(
|
||||
text: groupName,
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
for (final groupName in state.groupNames)
|
||||
KeepContainer(
|
||||
key: ObjectKey(groupName),
|
||||
child: ProxiesTabView(
|
||||
groupName: groupName,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProxiesTabView extends StatelessWidget {
|
||||
final String groupName;
|
||||
|
||||
const ProxiesTabView({
|
||||
super.key,
|
||||
required this.groupName,
|
||||
});
|
||||
|
||||
List<Proxy> _sortOfName(List<Proxy> proxies) {
|
||||
return List.of(proxies)
|
||||
..sort(
|
||||
(a, b) => other.sortByChar(a.name, b.name),
|
||||
);
|
||||
}
|
||||
|
||||
List<Proxy> _sortOfDelay(BuildContext context, List<Proxy> proxies) {
|
||||
final appState = context.read<AppState>();
|
||||
return proxies = List.of(proxies)
|
||||
..sort(
|
||||
(a, b) {
|
||||
final aDelay = appState.getDelay(a.name);
|
||||
final bDelay = appState.getDelay(b.name);
|
||||
if (aDelay == null && bDelay == null) {
|
||||
return 0;
|
||||
}
|
||||
if (aDelay == null || aDelay == -1) {
|
||||
return 1;
|
||||
}
|
||||
if (bDelay == null || bDelay == -1) {
|
||||
return -1;
|
||||
}
|
||||
return aDelay.compareTo(bDelay);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_getProxies(
|
||||
BuildContext context,
|
||||
List<Proxy> proxies,
|
||||
ProxiesSortType proxiesSortType,
|
||||
) {
|
||||
if (proxiesSortType == ProxiesSortType.delay) {
|
||||
return _sortOfDelay(context, proxies);
|
||||
}
|
||||
if (proxiesSortType == ProxiesSortType.name) return _sortOfName(proxies);
|
||||
return proxies;
|
||||
}
|
||||
|
||||
double _getItemHeight(BuildContext context) {
|
||||
final measure = globalState.appController.measure;
|
||||
return 12 * 2 +
|
||||
measure.bodyMediumHeight * 2 +
|
||||
measure.bodySmallHeight +
|
||||
measure.labelSmallHeight +
|
||||
8 * 2;
|
||||
}
|
||||
|
||||
int _getColumns(ViewMode viewMode) {
|
||||
switch (viewMode) {
|
||||
case ViewMode.mobile:
|
||||
return 2;
|
||||
case ViewMode.laptop:
|
||||
return 3;
|
||||
case ViewMode.desktop:
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
_delayTest(List<Proxy> proxies) async {
|
||||
for (final proxy in proxies) {
|
||||
final appController = globalState.appController;
|
||||
final proxyName =
|
||||
appController.appState.getRealProxyName(proxy.name) ?? proxy.name;
|
||||
globalState.appController.setDelay(
|
||||
Delay(
|
||||
name: proxyName,
|
||||
value: 0,
|
||||
),
|
||||
);
|
||||
clashCore.getDelay(proxyName).then((delay) {
|
||||
globalState.appController.setDelay(delay);
|
||||
});
|
||||
}
|
||||
await Future.delayed(httpTimeoutDuration + moreDuration);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector2<AppState, Config, ProxiesTabViewSelectorState>(
|
||||
selector: (_, appState, config) {
|
||||
return ProxiesTabViewSelectorState(
|
||||
proxiesSortType: config.proxiesSortType,
|
||||
sortNum: appState.sortNum,
|
||||
group: appState.getGroupWithName(groupName)!,
|
||||
viewMode: appState.viewMode,
|
||||
);
|
||||
},
|
||||
builder: (_, state, __) {
|
||||
final proxies = _getProxies(
|
||||
context,
|
||||
state.group.all,
|
||||
state.proxiesSortType,
|
||||
);
|
||||
return DelayTestButtonContainer(
|
||||
onClick: () async {
|
||||
await _delayTest(
|
||||
state.group.all,
|
||||
);
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: GridView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: _getColumns(state.viewMode),
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisExtent: _getItemHeight(context),
|
||||
),
|
||||
itemCount: proxies.length,
|
||||
itemBuilder: (_, index) {
|
||||
final proxy = proxies[index];
|
||||
return ProxyCard(
|
||||
key: ValueKey('$groupName.${proxy.name}'),
|
||||
proxy: proxy,
|
||||
groupName: groupName,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProxyCard extends StatelessWidget {
|
||||
final String groupName;
|
||||
final Proxy proxy;
|
||||
|
||||
const ProxyCard({
|
||||
super.key,
|
||||
required this.groupName,
|
||||
required this.proxy,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final measure = globalState.appController.measure;
|
||||
return Selector3<AppState, Config, ClashConfig, ProxiesCardSelectorState>(
|
||||
selector: (_, appState, config, clashConfig) {
|
||||
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,
|
||||
);
|
||||
},
|
||||
builder: (_, state, __) {
|
||||
return CommonCard(
|
||||
isSelected: state.isSelected,
|
||||
onPressed: () {
|
||||
final appController = globalState.appController;
|
||||
final group = appController.appState.getGroupWithName(groupName)!;
|
||||
if (group.type != GroupType.Selector) {
|
||||
globalState.showSnackBar(
|
||||
context,
|
||||
message: appLocalizations.notSelectedTip,
|
||||
);
|
||||
return;
|
||||
}
|
||||
globalState.appController.config.updateCurrentSelectedMap(
|
||||
groupName,
|
||||
proxy.name,
|
||||
);
|
||||
globalState.appController.changeProxy();
|
||||
},
|
||||
selectWidget: Container(
|
||||
alignment: Alignment.topRight,
|
||||
margin: const EdgeInsets.all(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
),
|
||||
child: const SelectIcon(),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: measure.bodyMediumHeight * 2,
|
||||
child: Text(
|
||||
proxy.name,
|
||||
maxLines: 2,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
SizedBox(
|
||||
height: measure.bodySmallHeight,
|
||||
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(
|
||||
height: 8,
|
||||
),
|
||||
SizedBox(
|
||||
height: measure.labelSmallHeight,
|
||||
child: Selector<AppState, int?>(
|
||||
selector: (context, appState) => appState.getDelay(
|
||||
proxy.name,
|
||||
),
|
||||
builder: (_, delay, __) {
|
||||
return FadeBox(
|
||||
child: Builder(
|
||||
builder: (_) {
|
||||
if (delay == null) {
|
||||
return Container();
|
||||
}
|
||||
if (delay == 0) {
|
||||
return SizedBox(
|
||||
height: measure.labelSmallHeight,
|
||||
width: measure.labelSmallHeight,
|
||||
child: const CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
return Text(
|
||||
delay > 0 ? '$delay ms' : "Timeout",
|
||||
style: context.textTheme.labelSmall?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: other.getDelayColor(
|
||||
delay,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DelayTestButtonContainer extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Future Function() onClick;
|
||||
|
||||
const DelayTestButtonContainer({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.onClick,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DelayTestButtonContainer> createState() =>
|
||||
_DelayTestButtonContainerState();
|
||||
}
|
||||
|
||||
class _DelayTestButtonContainerState extends State<DelayTestButtonContainer>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _scale;
|
||||
|
||||
_healthcheck() async {
|
||||
_controller.forward();
|
||||
await widget.onClick();
|
||||
_controller.reverse();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(
|
||||
milliseconds: 200,
|
||||
),
|
||||
);
|
||||
_scale = Tween<double>(
|
||||
begin: 1.0,
|
||||
end: 0.0,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: const Interval(
|
||||
0,
|
||||
1,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_controller.reverse();
|
||||
return FloatLayout(
|
||||
floatingWidget: FloatWrapper(
|
||||
child: AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (_, child) {
|
||||
return SizedBox(
|
||||
width: 56,
|
||||
height: 56,
|
||||
child: Transform.scale(
|
||||
scale: _scale.value,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: FloatingActionButton(
|
||||
heroTag: null,
|
||||
onPressed: _healthcheck,
|
||||
child: const Icon(Icons.network_ping),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -193,7 +193,7 @@ class RequestItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
String _getRequestText(Metadata metadata) {
|
||||
var text = "${metadata.network}://";
|
||||
var text = "${metadata.network}:://";
|
||||
final ips = [
|
||||
metadata.host,
|
||||
metadata.destinationIP,
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
"noMoreInfoDesc": "No more info",
|
||||
"profileParseErrorDesc": "profile parse error",
|
||||
"proxyPort": "ProxyPort",
|
||||
"proxyPortDesc": "Set the Clash listening port",
|
||||
"proxyPortDesc": "Set the clash listening port",
|
||||
"port": "Port",
|
||||
"logLevel": "LogLevel",
|
||||
"show": "Show",
|
||||
@@ -161,19 +161,20 @@
|
||||
"checking": "Checking...",
|
||||
"country": "Country",
|
||||
"checkError": "Check error",
|
||||
"ipCheckTimeout": "Ip check timeout",
|
||||
"search": "Search",
|
||||
"allowBypass": "Allow applications to bypass VPN",
|
||||
"allowBypassDesc": "Some apps can bypass VPN when turned on",
|
||||
"externalController": "ExternalController",
|
||||
"externalControllerDesc": "Once enabled, the Clash kernel can be controlled on port 9090",
|
||||
"ipv6Desc": "When turned on it will be able to receive IPv6 traffic",
|
||||
"externalControllerDesc": "Once enabled, the clash kernel can be controlled on port 9090",
|
||||
"ipv6Desc": "When turned on it will be able to receive ipv6 traffic",
|
||||
"app": "App",
|
||||
"general": "General",
|
||||
"systemProxyDesc": "Attach HTTP proxy to VpnService",
|
||||
"unifiedDelay": "Unified delay",
|
||||
"unifiedDelayDesc": "Remove extra delays such as handshaking",
|
||||
"tcpConcurrent": "TCP concurrent",
|
||||
"tcpConcurrentDesc": "Enabling it will allow TCP concurrency",
|
||||
"tcpConcurrent": "Tcp concurrent",
|
||||
"tcpConcurrentDesc": "Enabling it will allow tcp concurrency",
|
||||
"geodataLoader": "Geo Low Memory Mode",
|
||||
"geodataLoaderDesc": "Enabling will use the Geo low memory loader",
|
||||
"requests": "Requests",
|
||||
@@ -187,7 +188,7 @@
|
||||
"connectionsDesc": "View current connection",
|
||||
"nullRequestsDesc": "No requests",
|
||||
"nullConnectionsDesc": "No connections",
|
||||
"intranetIP": "Intranet IP",
|
||||
"intranetIp": "Intranet IP",
|
||||
"view": "View",
|
||||
"cut": "Cut",
|
||||
"copy": "Copy",
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
"noMoreInfoDesc": "暂无更多信息",
|
||||
"profileParseErrorDesc": "配置文件解析错误",
|
||||
"proxyPort": "代理端口",
|
||||
"proxyPortDesc": "设置Clash监听端口",
|
||||
"proxyPortDesc": "设置clash监听端口",
|
||||
"port": "端口",
|
||||
"logLevel": "日志等级",
|
||||
"show": "显示",
|
||||
@@ -161,19 +161,20 @@
|
||||
"checking": "检测中...",
|
||||
"country": "区域",
|
||||
"checkError": "检测失败",
|
||||
"ipCheckTimeout": "Ip检测超时",
|
||||
"search": "搜索",
|
||||
"allowBypass": "允许应用绕过VPN",
|
||||
"allowBypass": "允许应用绕过vpn",
|
||||
"allowBypassDesc": "开启后部分应用可绕过VPN",
|
||||
"externalController": "外部控制器",
|
||||
"externalControllerDesc": "开启后将可以通过9090端口控制Clash内核",
|
||||
"ipv6Desc": "开启后将可以接收IPv6流量",
|
||||
"externalControllerDesc": "开启后将可以通过9090端口控制clash内核",
|
||||
"ipv6Desc": "开启后将可以接收ipv6流量",
|
||||
"app": "应用",
|
||||
"general": "基础",
|
||||
"systemProxyDesc": "为VpnService附加HTTP代理",
|
||||
"unifiedDelay": "统一延迟",
|
||||
"unifiedDelayDesc": "去除握手等额外延迟",
|
||||
"tcpConcurrent": "TCP并发",
|
||||
"tcpConcurrentDesc": "开启后允许TCP并发",
|
||||
"tcpConcurrentDesc": "开启后允许tcp并发",
|
||||
"geodataLoader": "Geo低内存模式",
|
||||
"geodataLoaderDesc": "开启将使用Geo低内存加载器",
|
||||
"requests": "请求",
|
||||
@@ -187,7 +188,7 @@
|
||||
"connectionsDesc": "查看当前连接",
|
||||
"nullRequestsDesc": "暂无请求",
|
||||
"nullConnectionsDesc": "暂无连接",
|
||||
"intranetIP": "内网 IP",
|
||||
"intranetIp": "内网 IP",
|
||||
"view": "查看",
|
||||
"cut": "剪切",
|
||||
"copy": "复制",
|
||||
|
||||
@@ -127,7 +127,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"externalController":
|
||||
MessageLookupByLibrary.simpleMessage("ExternalController"),
|
||||
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Once enabled, the Clash kernel can be controlled on port 9090"),
|
||||
"Once enabled, the clash kernel can be controlled on port 9090"),
|
||||
"externalResources":
|
||||
MessageLookupByLibrary.simpleMessage("External resources"),
|
||||
"file": MessageLookupByLibrary.simpleMessage("File"),
|
||||
@@ -152,9 +152,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"infiniteTime":
|
||||
MessageLookupByLibrary.simpleMessage("Long term effective"),
|
||||
"init": MessageLookupByLibrary.simpleMessage("Init"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
|
||||
"intranetIp": MessageLookupByLibrary.simpleMessage("Intranet IP"),
|
||||
"ipCheckTimeout":
|
||||
MessageLookupByLibrary.simpleMessage("Ip check timeout"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
|
||||
"When turned on it will be able to receive IPv6 traffic"),
|
||||
"When turned on it will be able to receive ipv6 traffic"),
|
||||
"just": MessageLookupByLibrary.simpleMessage("Just"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("Language"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("Light"),
|
||||
@@ -228,7 +230,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("Proxies"),
|
||||
"proxyPort": MessageLookupByLibrary.simpleMessage("ProxyPort"),
|
||||
"proxyPortDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Set the Clash listening port"),
|
||||
"Set the clash listening port"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("QR code"),
|
||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Scan QR code to obtain profile"),
|
||||
@@ -266,9 +268,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"tabAnimation": MessageLookupByLibrary.simpleMessage("Tab animation"),
|
||||
"tabAnimationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"When enabled, the home tab will add a toggle animation"),
|
||||
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP concurrent"),
|
||||
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("Tcp concurrent"),
|
||||
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Enabling it will allow TCP concurrency"),
|
||||
"Enabling it will allow tcp concurrency"),
|
||||
"theme": MessageLookupByLibrary.simpleMessage("Theme"),
|
||||
"themeColor": MessageLookupByLibrary.simpleMessage("Theme color"),
|
||||
"themeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
|
||||
@@ -36,7 +36,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
|
||||
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
|
||||
"ago": MessageLookupByLibrary.simpleMessage("前"),
|
||||
"allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过VPN"),
|
||||
"allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过vpn"),
|
||||
"allowBypassDesc":
|
||||
MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"),
|
||||
"allowLan": MessageLookupByLibrary.simpleMessage("局域网代理"),
|
||||
@@ -104,7 +104,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"expirationTime": MessageLookupByLibrary.simpleMessage("到期时间"),
|
||||
"externalController": MessageLookupByLibrary.simpleMessage("外部控制器"),
|
||||
"externalControllerDesc":
|
||||
MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制Clash内核"),
|
||||
MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制clash内核"),
|
||||
"externalResources": MessageLookupByLibrary.simpleMessage("外部资源"),
|
||||
"file": MessageLookupByLibrary.simpleMessage("文件"),
|
||||
"fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"),
|
||||
@@ -123,8 +123,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
|
||||
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
|
||||
"init": MessageLookupByLibrary.simpleMessage("初始化"),
|
||||
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
|
||||
"intranetIp": MessageLookupByLibrary.simpleMessage("内网 IP"),
|
||||
"ipCheckTimeout": MessageLookupByLibrary.simpleMessage("Ip检测超时"),
|
||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收ipv6流量"),
|
||||
"just": MessageLookupByLibrary.simpleMessage("刚刚"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("语言"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("浅色"),
|
||||
@@ -185,7 +186,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"project": MessageLookupByLibrary.simpleMessage("项目"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("代理"),
|
||||
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
|
||||
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
|
||||
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置clash监听端口"),
|
||||
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
|
||||
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
|
||||
"recovery": MessageLookupByLibrary.simpleMessage("恢复"),
|
||||
@@ -216,7 +217,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"tabAnimationDesc":
|
||||
MessageLookupByLibrary.simpleMessage("开启后,主页选项卡将添加切换动画"),
|
||||
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"),
|
||||
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许TCP并发"),
|
||||
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许tcp并发"),
|
||||
"theme": MessageLookupByLibrary.simpleMessage("主题"),
|
||||
"themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"),
|
||||
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),
|
||||
|
||||
@@ -1170,10 +1170,10 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Set the Clash listening port`
|
||||
/// `Set the clash listening port`
|
||||
String get proxyPortDesc {
|
||||
return Intl.message(
|
||||
'Set the Clash listening port',
|
||||
'Set the clash listening port',
|
||||
name: 'proxyPortDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
@@ -1670,6 +1670,16 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Ip check timeout`
|
||||
String get ipCheckTimeout {
|
||||
return Intl.message(
|
||||
'Ip check timeout',
|
||||
name: 'ipCheckTimeout',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Search`
|
||||
String get search {
|
||||
return Intl.message(
|
||||
@@ -1710,20 +1720,20 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Once enabled, the Clash kernel can be controlled on port 9090`
|
||||
/// `Once enabled, the clash kernel can be controlled on port 9090`
|
||||
String get externalControllerDesc {
|
||||
return Intl.message(
|
||||
'Once enabled, the Clash kernel can be controlled on port 9090',
|
||||
'Once enabled, the clash kernel can be controlled on port 9090',
|
||||
name: 'externalControllerDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `When turned on it will be able to receive IPv6 traffic`
|
||||
/// `When turned on it will be able to receive ipv6 traffic`
|
||||
String get ipv6Desc {
|
||||
return Intl.message(
|
||||
'When turned on it will be able to receive IPv6 traffic',
|
||||
'When turned on it will be able to receive ipv6 traffic',
|
||||
name: 'ipv6Desc',
|
||||
desc: '',
|
||||
args: [],
|
||||
@@ -1780,20 +1790,20 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `TCP concurrent`
|
||||
/// `Tcp concurrent`
|
||||
String get tcpConcurrent {
|
||||
return Intl.message(
|
||||
'TCP concurrent',
|
||||
'Tcp concurrent',
|
||||
name: 'tcpConcurrent',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Enabling it will allow TCP concurrency`
|
||||
/// `Enabling it will allow tcp concurrency`
|
||||
String get tcpConcurrentDesc {
|
||||
return Intl.message(
|
||||
'Enabling it will allow TCP concurrency',
|
||||
'Enabling it will allow tcp concurrency',
|
||||
name: 'tcpConcurrentDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
@@ -1931,10 +1941,10 @@ class AppLocalizations {
|
||||
}
|
||||
|
||||
/// `Intranet IP`
|
||||
String get intranetIP {
|
||||
String get intranetIp {
|
||||
return Intl.message(
|
||||
'Intranet IP',
|
||||
name: 'intranetIP',
|
||||
name: 'intranetIp',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
|
||||
@@ -49,15 +49,11 @@ Future<void> vpnService() async {
|
||||
isCompatible: config.isCompatible,
|
||||
selectedMap: config.currentSelectedMap,
|
||||
);
|
||||
clashMessage.addListener(
|
||||
ClashMessageListenerWithVpn(
|
||||
onTun: (String fd) async {
|
||||
final fdInt = int.parse(fd);
|
||||
await proxyManager.setProtect(fdInt);
|
||||
clashCore.setFdMap(fdInt);
|
||||
},
|
||||
),
|
||||
);
|
||||
clashMessage.addListener(ClashMessageListenerWithVpn(onTun: (String fd) {
|
||||
proxyManager.setProtect(
|
||||
int.parse(fd),
|
||||
);
|
||||
}));
|
||||
|
||||
await globalState.init(
|
||||
appState: appState,
|
||||
|
||||
@@ -123,10 +123,9 @@ class AppState with ChangeNotifier {
|
||||
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)) ??
|
||||
proxyName;
|
||||
return getRealProxyName(selectedMap.containsKey(proxyName)
|
||||
? selectedMap[proxyName]
|
||||
: group.now);
|
||||
}
|
||||
|
||||
String? get showProxyName {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// ignore_for_file: invalid_annotation_target
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/common/constant.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -237,12 +235,7 @@ class ClashConfig extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Tun get tun {
|
||||
if (Platform.isAndroid) {
|
||||
return _tun.copyWith(enable: false);
|
||||
}
|
||||
return _tun;
|
||||
}
|
||||
Tun get tun => _tun;
|
||||
|
||||
set tun(Tun value) {
|
||||
if (_tun != value) {
|
||||
@@ -289,4 +282,9 @@ class ClashConfig extends ChangeNotifier {
|
||||
factory ClashConfig.fromJson(Map<String, dynamic> json) {
|
||||
return _$ClashConfigFromJson(json);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ClashConfig{_mixedPort: $_mixedPort, _allowLan: $_allowLan, _mode: $_mode, _logLevel: $_logLevel, _tun: $_tun, _dns: $_dns, _rules: $_rules}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
@@ -57,9 +56,6 @@ class Config extends ChangeNotifier {
|
||||
bool _allowBypass;
|
||||
bool _systemProxy;
|
||||
DAV? _dav;
|
||||
ProxiesType _proxiesType;
|
||||
ProxyCardType _proxyCardType;
|
||||
int _proxiesColumns;
|
||||
|
||||
Config()
|
||||
: _profiles = [],
|
||||
@@ -77,10 +73,7 @@ class Config extends ChangeNotifier {
|
||||
_systemProxy = true,
|
||||
_accessControl = const AccessControl(),
|
||||
_isAnimateToPage = true,
|
||||
_allowBypass = true,
|
||||
_proxyCardType = ProxyCardType.expand,
|
||||
_proxiesType = ProxiesType.tab,
|
||||
_proxiesColumns = 2;
|
||||
_allowBypass = true;
|
||||
|
||||
deleteProfileById(String id) {
|
||||
_profiles = profiles.where((element) => element.id != id).toList();
|
||||
@@ -157,19 +150,6 @@ class Config extends ChangeNotifier {
|
||||
|
||||
String? get currentGroupName => currentProfile?.currentGroupName;
|
||||
|
||||
Set<String> get currentUnfoldSet => currentProfile?.unfoldSet ?? {};
|
||||
|
||||
updateCurrentUnfoldSet(Set<String> value) {
|
||||
if (!const SetEquality<String>().equals(currentUnfoldSet, value)) {
|
||||
_setProfile(
|
||||
currentProfile!.copyWith(
|
||||
unfoldSet: value,
|
||||
),
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
updateCurrentGroupName(String groupName) {
|
||||
if (currentProfile != null &&
|
||||
currentProfile!.currentGroupName != groupName) {
|
||||
@@ -384,36 +364,6 @@ class Config extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: ProxiesType.tab)
|
||||
ProxiesType get proxiesType => _proxiesType;
|
||||
|
||||
set proxiesType(ProxiesType value) {
|
||||
if (_proxiesType != value) {
|
||||
_proxiesType = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: ProxyCardType.expand)
|
||||
ProxyCardType get proxyCardType => _proxyCardType;
|
||||
|
||||
set proxyCardType(ProxyCardType value) {
|
||||
if (_proxyCardType != value) {
|
||||
_proxyCardType = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: 2)
|
||||
int get proxiesColumns => _proxiesColumns;
|
||||
|
||||
set proxiesColumns(int value) {
|
||||
if (_proxiesColumns != value) {
|
||||
_proxiesColumns = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
update([
|
||||
Config? config,
|
||||
RecoveryOption recoveryOptions = RecoveryOption.all,
|
||||
@@ -433,7 +383,6 @@ class Config extends ChangeNotifier {
|
||||
_autoLaunch = config._autoLaunch;
|
||||
_silentLaunch = config._silentLaunch;
|
||||
_autoRun = config._autoRun;
|
||||
_proxiesType = config._proxiesType;
|
||||
_openLog = config._openLog;
|
||||
_themeMode = config._themeMode;
|
||||
_locale = config._locale;
|
||||
|
||||
@@ -34,14 +34,7 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
|
||||
..isCompatible = json['isCompatible'] as bool? ?? true
|
||||
..autoCheckUpdate = json['autoCheckUpdate'] as bool? ?? true
|
||||
..allowBypass = json['allowBypass'] as bool? ?? true
|
||||
..systemProxy = json['systemProxy'] as bool? ?? true
|
||||
..proxiesType =
|
||||
$enumDecodeNullable(_$ProxiesTypeEnumMap, json['proxiesType']) ??
|
||||
ProxiesType.tab
|
||||
..proxyCardType =
|
||||
$enumDecodeNullable(_$ProxyCardTypeEnumMap, json['proxyCardType']) ??
|
||||
ProxyCardType.expand
|
||||
..proxiesColumns = (json['proxiesColumns'] as num?)?.toInt() ?? 2;
|
||||
..systemProxy = json['systemProxy'] as bool? ?? true;
|
||||
|
||||
Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
|
||||
'profiles': instance.profiles,
|
||||
@@ -63,9 +56,6 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
|
||||
'autoCheckUpdate': instance.autoCheckUpdate,
|
||||
'allowBypass': instance.allowBypass,
|
||||
'systemProxy': instance.systemProxy,
|
||||
'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!,
|
||||
'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!,
|
||||
'proxiesColumns': instance.proxiesColumns,
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
@@ -80,16 +70,6 @@ const _$ProxiesSortTypeEnumMap = {
|
||||
ProxiesSortType.name: 'name',
|
||||
};
|
||||
|
||||
const _$ProxiesTypeEnumMap = {
|
||||
ProxiesType.tab: 'tab',
|
||||
ProxiesType.expansion: 'expansion',
|
||||
};
|
||||
|
||||
const _$ProxyCardTypeEnumMap = {
|
||||
ProxyCardType.expand: 'expand',
|
||||
ProxyCardType.shrink: 'shrink',
|
||||
};
|
||||
|
||||
_$AccessControlImpl _$$AccessControlImplFromJson(Map<String, dynamic> json) =>
|
||||
_$AccessControlImpl(
|
||||
mode: $enumDecodeNullable(_$AccessControlModeEnumMap, json['mode']) ??
|
||||
|
||||
@@ -222,7 +222,6 @@ mixin _$Profile {
|
||||
UserInfo? get userInfo => throw _privateConstructorUsedError;
|
||||
bool get autoUpdate => throw _privateConstructorUsedError;
|
||||
Map<String, String> get selectedMap => throw _privateConstructorUsedError;
|
||||
Set<String> get unfoldSet => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
@@ -243,8 +242,7 @@ abstract class $ProfileCopyWith<$Res> {
|
||||
Duration autoUpdateDuration,
|
||||
UserInfo? userInfo,
|
||||
bool autoUpdate,
|
||||
Map<String, String> selectedMap,
|
||||
Set<String> unfoldSet});
|
||||
Map<String, String> selectedMap});
|
||||
|
||||
$UserInfoCopyWith<$Res>? get userInfo;
|
||||
}
|
||||
@@ -271,7 +269,6 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
|
||||
Object? userInfo = freezed,
|
||||
Object? autoUpdate = null,
|
||||
Object? selectedMap = null,
|
||||
Object? unfoldSet = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
@@ -310,10 +307,6 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
|
||||
? _value.selectedMap
|
||||
: selectedMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>,
|
||||
unfoldSet: null == unfoldSet
|
||||
? _value.unfoldSet
|
||||
: unfoldSet // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@@ -346,8 +339,7 @@ abstract class _$$ProfileImplCopyWith<$Res> implements $ProfileCopyWith<$Res> {
|
||||
Duration autoUpdateDuration,
|
||||
UserInfo? userInfo,
|
||||
bool autoUpdate,
|
||||
Map<String, String> selectedMap,
|
||||
Set<String> unfoldSet});
|
||||
Map<String, String> selectedMap});
|
||||
|
||||
@override
|
||||
$UserInfoCopyWith<$Res>? get userInfo;
|
||||
@@ -373,7 +365,6 @@ class __$$ProfileImplCopyWithImpl<$Res>
|
||||
Object? userInfo = freezed,
|
||||
Object? autoUpdate = null,
|
||||
Object? selectedMap = null,
|
||||
Object? unfoldSet = null,
|
||||
}) {
|
||||
return _then(_$ProfileImpl(
|
||||
id: null == id
|
||||
@@ -412,10 +403,6 @@ class __$$ProfileImplCopyWithImpl<$Res>
|
||||
? _value._selectedMap
|
||||
: selectedMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>,
|
||||
unfoldSet: null == unfoldSet
|
||||
? _value._unfoldSet
|
||||
: unfoldSet // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -432,10 +419,8 @@ class _$ProfileImpl implements _Profile {
|
||||
required this.autoUpdateDuration,
|
||||
this.userInfo,
|
||||
this.autoUpdate = true,
|
||||
final Map<String, String> selectedMap = const {},
|
||||
final Set<String> unfoldSet = const {}})
|
||||
: _selectedMap = selectedMap,
|
||||
_unfoldSet = unfoldSet;
|
||||
final Map<String, String> selectedMap = const {}})
|
||||
: _selectedMap = selectedMap;
|
||||
|
||||
factory _$ProfileImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ProfileImplFromJson(json);
|
||||
@@ -467,18 +452,9 @@ class _$ProfileImpl implements _Profile {
|
||||
return EqualUnmodifiableMapView(_selectedMap);
|
||||
}
|
||||
|
||||
final Set<String> _unfoldSet;
|
||||
@override
|
||||
@JsonKey()
|
||||
Set<String> get unfoldSet {
|
||||
if (_unfoldSet is EqualUnmodifiableSetView) return _unfoldSet;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableSetView(_unfoldSet);
|
||||
}
|
||||
|
||||
@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)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -500,9 +476,7 @@ class _$ProfileImpl implements _Profile {
|
||||
(identical(other.autoUpdate, autoUpdate) ||
|
||||
other.autoUpdate == autoUpdate) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._selectedMap, _selectedMap) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._unfoldSet, _unfoldSet));
|
||||
.equals(other._selectedMap, _selectedMap));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@@ -517,8 +491,7 @@ class _$ProfileImpl implements _Profile {
|
||||
autoUpdateDuration,
|
||||
userInfo,
|
||||
autoUpdate,
|
||||
const DeepCollectionEquality().hash(_selectedMap),
|
||||
const DeepCollectionEquality().hash(_unfoldSet));
|
||||
const DeepCollectionEquality().hash(_selectedMap));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -544,8 +517,7 @@ abstract class _Profile implements Profile {
|
||||
required final Duration autoUpdateDuration,
|
||||
final UserInfo? userInfo,
|
||||
final bool autoUpdate,
|
||||
final Map<String, String> selectedMap,
|
||||
final Set<String> unfoldSet}) = _$ProfileImpl;
|
||||
final Map<String, String> selectedMap}) = _$ProfileImpl;
|
||||
|
||||
factory _Profile.fromJson(Map<String, dynamic> json) = _$ProfileImpl.fromJson;
|
||||
|
||||
@@ -568,8 +540,6 @@ abstract class _Profile implements Profile {
|
||||
@override
|
||||
Map<String, String> get selectedMap;
|
||||
@override
|
||||
Set<String> get unfoldSet;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ProfileImplCopyWith<_$ProfileImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
@@ -41,10 +41,6 @@ _$ProfileImpl _$$ProfileImplFromJson(Map<String, dynamic> json) =>
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
) ??
|
||||
const {},
|
||||
unfoldSet: (json['unfoldSet'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toSet() ??
|
||||
const {},
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfileImplToJson(_$ProfileImpl instance) =>
|
||||
@@ -58,5 +54,4 @@ Map<String, dynamic> _$$ProfileImplToJson(_$ProfileImpl instance) =>
|
||||
'userInfo': instance.userInfo,
|
||||
'autoUpdate': instance.autoUpdate,
|
||||
'selectedMap': instance.selectedMap,
|
||||
'unfoldSet': instance.unfoldSet.toList(),
|
||||
};
|
||||
|
||||
@@ -1730,37 +1730,39 @@ abstract class _ProxiesSelectorState implements ProxiesSelectorState {
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProxyGroupSelectorState {
|
||||
mixin _$ProxiesTabViewSelectorState {
|
||||
ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError;
|
||||
ProxyCardType get proxyCardType => throw _privateConstructorUsedError;
|
||||
num get sortNum => throw _privateConstructorUsedError;
|
||||
List<Proxy> get proxies => throw _privateConstructorUsedError;
|
||||
int get columns => throw _privateConstructorUsedError;
|
||||
Group get group => throw _privateConstructorUsedError;
|
||||
ViewMode get viewMode => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$ProxyGroupSelectorStateCopyWith<ProxyGroupSelectorState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
$ProxiesTabViewSelectorStateCopyWith<ProxiesTabViewSelectorState>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ProxyGroupSelectorStateCopyWith<$Res> {
|
||||
factory $ProxyGroupSelectorStateCopyWith(ProxyGroupSelectorState value,
|
||||
$Res Function(ProxyGroupSelectorState) then) =
|
||||
_$ProxyGroupSelectorStateCopyWithImpl<$Res, ProxyGroupSelectorState>;
|
||||
abstract class $ProxiesTabViewSelectorStateCopyWith<$Res> {
|
||||
factory $ProxiesTabViewSelectorStateCopyWith(
|
||||
ProxiesTabViewSelectorState value,
|
||||
$Res Function(ProxiesTabViewSelectorState) then) =
|
||||
_$ProxiesTabViewSelectorStateCopyWithImpl<$Res,
|
||||
ProxiesTabViewSelectorState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{ProxiesSortType proxiesSortType,
|
||||
ProxyCardType proxyCardType,
|
||||
num sortNum,
|
||||
List<Proxy> proxies,
|
||||
int columns});
|
||||
Group group,
|
||||
ViewMode viewMode});
|
||||
|
||||
$GroupCopyWith<$Res> get group;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ProxyGroupSelectorStateCopyWithImpl<$Res,
|
||||
$Val extends ProxyGroupSelectorState>
|
||||
implements $ProxyGroupSelectorStateCopyWith<$Res> {
|
||||
_$ProxyGroupSelectorStateCopyWithImpl(this._value, this._then);
|
||||
class _$ProxiesTabViewSelectorStateCopyWithImpl<$Res,
|
||||
$Val extends ProxiesTabViewSelectorState>
|
||||
implements $ProxiesTabViewSelectorStateCopyWith<$Res> {
|
||||
_$ProxiesTabViewSelectorStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
@@ -1771,177 +1773,165 @@ class _$ProxyGroupSelectorStateCopyWithImpl<$Res,
|
||||
@override
|
||||
$Res call({
|
||||
Object? proxiesSortType = null,
|
||||
Object? proxyCardType = null,
|
||||
Object? sortNum = null,
|
||||
Object? proxies = null,
|
||||
Object? columns = null,
|
||||
Object? group = null,
|
||||
Object? viewMode = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
proxiesSortType: null == proxiesSortType
|
||||
? _value.proxiesSortType
|
||||
: proxiesSortType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxiesSortType,
|
||||
proxyCardType: null == proxyCardType
|
||||
? _value.proxyCardType
|
||||
: proxyCardType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxyCardType,
|
||||
sortNum: null == sortNum
|
||||
? _value.sortNum
|
||||
: sortNum // ignore: cast_nullable_to_non_nullable
|
||||
as num,
|
||||
proxies: null == proxies
|
||||
? _value.proxies
|
||||
: proxies // ignore: cast_nullable_to_non_nullable
|
||||
as List<Proxy>,
|
||||
columns: null == columns
|
||||
? _value.columns
|
||||
: columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
group: null == group
|
||||
? _value.group
|
||||
: group // ignore: cast_nullable_to_non_nullable
|
||||
as Group,
|
||||
viewMode: null == viewMode
|
||||
? _value.viewMode
|
||||
: viewMode // ignore: cast_nullable_to_non_nullable
|
||||
as ViewMode,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$GroupCopyWith<$Res> get group {
|
||||
return $GroupCopyWith<$Res>(_value.group, (value) {
|
||||
return _then(_value.copyWith(group: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ProxyGroupSelectorStateImplCopyWith<$Res>
|
||||
implements $ProxyGroupSelectorStateCopyWith<$Res> {
|
||||
factory _$$ProxyGroupSelectorStateImplCopyWith(
|
||||
_$ProxyGroupSelectorStateImpl value,
|
||||
$Res Function(_$ProxyGroupSelectorStateImpl) then) =
|
||||
__$$ProxyGroupSelectorStateImplCopyWithImpl<$Res>;
|
||||
abstract class _$$ProxiesTabViewSelectorStateImplCopyWith<$Res>
|
||||
implements $ProxiesTabViewSelectorStateCopyWith<$Res> {
|
||||
factory _$$ProxiesTabViewSelectorStateImplCopyWith(
|
||||
_$ProxiesTabViewSelectorStateImpl value,
|
||||
$Res Function(_$ProxiesTabViewSelectorStateImpl) then) =
|
||||
__$$ProxiesTabViewSelectorStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{ProxiesSortType proxiesSortType,
|
||||
ProxyCardType proxyCardType,
|
||||
num sortNum,
|
||||
List<Proxy> proxies,
|
||||
int columns});
|
||||
Group group,
|
||||
ViewMode viewMode});
|
||||
|
||||
@override
|
||||
$GroupCopyWith<$Res> get group;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ProxyGroupSelectorStateImplCopyWithImpl<$Res>
|
||||
extends _$ProxyGroupSelectorStateCopyWithImpl<$Res,
|
||||
_$ProxyGroupSelectorStateImpl>
|
||||
implements _$$ProxyGroupSelectorStateImplCopyWith<$Res> {
|
||||
__$$ProxyGroupSelectorStateImplCopyWithImpl(
|
||||
_$ProxyGroupSelectorStateImpl _value,
|
||||
$Res Function(_$ProxyGroupSelectorStateImpl) _then)
|
||||
class __$$ProxiesTabViewSelectorStateImplCopyWithImpl<$Res>
|
||||
extends _$ProxiesTabViewSelectorStateCopyWithImpl<$Res,
|
||||
_$ProxiesTabViewSelectorStateImpl>
|
||||
implements _$$ProxiesTabViewSelectorStateImplCopyWith<$Res> {
|
||||
__$$ProxiesTabViewSelectorStateImplCopyWithImpl(
|
||||
_$ProxiesTabViewSelectorStateImpl _value,
|
||||
$Res Function(_$ProxiesTabViewSelectorStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? proxiesSortType = null,
|
||||
Object? proxyCardType = null,
|
||||
Object? sortNum = null,
|
||||
Object? proxies = null,
|
||||
Object? columns = null,
|
||||
Object? group = null,
|
||||
Object? viewMode = null,
|
||||
}) {
|
||||
return _then(_$ProxyGroupSelectorStateImpl(
|
||||
return _then(_$ProxiesTabViewSelectorStateImpl(
|
||||
proxiesSortType: null == proxiesSortType
|
||||
? _value.proxiesSortType
|
||||
: proxiesSortType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxiesSortType,
|
||||
proxyCardType: null == proxyCardType
|
||||
? _value.proxyCardType
|
||||
: proxyCardType // ignore: cast_nullable_to_non_nullable
|
||||
as ProxyCardType,
|
||||
sortNum: null == sortNum
|
||||
? _value.sortNum
|
||||
: sortNum // ignore: cast_nullable_to_non_nullable
|
||||
as num,
|
||||
proxies: null == proxies
|
||||
? _value._proxies
|
||||
: proxies // ignore: cast_nullable_to_non_nullable
|
||||
as List<Proxy>,
|
||||
columns: null == columns
|
||||
? _value.columns
|
||||
: columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
group: null == group
|
||||
? _value.group
|
||||
: group // ignore: cast_nullable_to_non_nullable
|
||||
as Group,
|
||||
viewMode: null == viewMode
|
||||
? _value.viewMode
|
||||
: viewMode // ignore: cast_nullable_to_non_nullable
|
||||
as ViewMode,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ProxyGroupSelectorStateImpl implements _ProxyGroupSelectorState {
|
||||
const _$ProxyGroupSelectorStateImpl(
|
||||
class _$ProxiesTabViewSelectorStateImpl
|
||||
implements _ProxiesTabViewSelectorState {
|
||||
const _$ProxiesTabViewSelectorStateImpl(
|
||||
{required this.proxiesSortType,
|
||||
required this.proxyCardType,
|
||||
required this.sortNum,
|
||||
required final List<Proxy> proxies,
|
||||
required this.columns})
|
||||
: _proxies = proxies;
|
||||
required this.group,
|
||||
required this.viewMode});
|
||||
|
||||
@override
|
||||
final ProxiesSortType proxiesSortType;
|
||||
@override
|
||||
final ProxyCardType proxyCardType;
|
||||
@override
|
||||
final num sortNum;
|
||||
final List<Proxy> _proxies;
|
||||
@override
|
||||
List<Proxy> get proxies {
|
||||
if (_proxies is EqualUnmodifiableListView) return _proxies;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_proxies);
|
||||
}
|
||||
|
||||
final Group group;
|
||||
@override
|
||||
final int columns;
|
||||
final ViewMode viewMode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProxyGroupSelectorState(proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, proxies: $proxies, columns: $columns)';
|
||||
return 'ProxiesTabViewSelectorState(proxiesSortType: $proxiesSortType, sortNum: $sortNum, group: $group, viewMode: $viewMode)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ProxyGroupSelectorStateImpl &&
|
||||
other is _$ProxiesTabViewSelectorStateImpl &&
|
||||
(identical(other.proxiesSortType, proxiesSortType) ||
|
||||
other.proxiesSortType == proxiesSortType) &&
|
||||
(identical(other.proxyCardType, proxyCardType) ||
|
||||
other.proxyCardType == proxyCardType) &&
|
||||
(identical(other.sortNum, sortNum) || other.sortNum == sortNum) &&
|
||||
const DeepCollectionEquality().equals(other._proxies, _proxies) &&
|
||||
(identical(other.columns, columns) || other.columns == columns));
|
||||
(identical(other.group, group) || other.group == group) &&
|
||||
(identical(other.viewMode, viewMode) ||
|
||||
other.viewMode == viewMode));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, proxiesSortType, proxyCardType,
|
||||
sortNum, const DeepCollectionEquality().hash(_proxies), columns);
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, proxiesSortType, sortNum, group, viewMode);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ProxyGroupSelectorStateImplCopyWith<_$ProxyGroupSelectorStateImpl>
|
||||
get copyWith => __$$ProxyGroupSelectorStateImplCopyWithImpl<
|
||||
_$ProxyGroupSelectorStateImpl>(this, _$identity);
|
||||
_$$ProxiesTabViewSelectorStateImplCopyWith<_$ProxiesTabViewSelectorStateImpl>
|
||||
get copyWith => __$$ProxiesTabViewSelectorStateImplCopyWithImpl<
|
||||
_$ProxiesTabViewSelectorStateImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ProxyGroupSelectorState implements ProxyGroupSelectorState {
|
||||
const factory _ProxyGroupSelectorState(
|
||||
abstract class _ProxiesTabViewSelectorState
|
||||
implements ProxiesTabViewSelectorState {
|
||||
const factory _ProxiesTabViewSelectorState(
|
||||
{required final ProxiesSortType proxiesSortType,
|
||||
required final ProxyCardType proxyCardType,
|
||||
required final num sortNum,
|
||||
required final List<Proxy> proxies,
|
||||
required final int columns}) = _$ProxyGroupSelectorStateImpl;
|
||||
required final Group group,
|
||||
required final ViewMode viewMode}) = _$ProxiesTabViewSelectorStateImpl;
|
||||
|
||||
@override
|
||||
ProxiesSortType get proxiesSortType;
|
||||
@override
|
||||
ProxyCardType get proxyCardType;
|
||||
@override
|
||||
num get sortNum;
|
||||
@override
|
||||
List<Proxy> get proxies;
|
||||
Group get group;
|
||||
@override
|
||||
int get columns;
|
||||
ViewMode get viewMode;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ProxyGroupSelectorStateImplCopyWith<_$ProxyGroupSelectorStateImpl>
|
||||
_$$ProxiesTabViewSelectorStateImplCopyWith<_$ProxiesTabViewSelectorStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ class Profile with _$Profile {
|
||||
UserInfo? userInfo,
|
||||
@Default(true) bool autoUpdate,
|
||||
@Default({}) SelectedMap selectedMap,
|
||||
@Default({}) Set<String> unfoldSet,
|
||||
}) = _Profile;
|
||||
|
||||
factory Profile.fromJson(Map<String, Object?> json) =>
|
||||
|
||||
@@ -99,14 +99,13 @@ class ProxiesSelectorState with _$ProxiesSelectorState {
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProxyGroupSelectorState with _$ProxyGroupSelectorState {
|
||||
const factory ProxyGroupSelectorState({
|
||||
class ProxiesTabViewSelectorState with _$ProxiesTabViewSelectorState {
|
||||
const factory ProxiesTabViewSelectorState({
|
||||
required ProxiesSortType proxiesSortType,
|
||||
required ProxyCardType proxyCardType,
|
||||
required num sortNum,
|
||||
required List<Proxy> proxies,
|
||||
required int columns,
|
||||
}) = _ProxyGroupSelectorState;
|
||||
required Group group,
|
||||
required ViewMode viewMode,
|
||||
}) = _ProxiesTabViewSelectorState;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'dart:io';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/widgets/scaffold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -43,14 +42,13 @@ class GlobalState {
|
||||
}) async {
|
||||
final profilePath = await appPath.getProfilePath(config.currentProfileId);
|
||||
await config.currentProfile?.checkAndUpdate();
|
||||
final res = await clashCore.updateConfig(
|
||||
UpdateConfigParams(
|
||||
profilePath: profilePath,
|
||||
config: clashConfig,
|
||||
isPatch: isPatch,
|
||||
isCompatible: config.isCompatible,
|
||||
),
|
||||
);
|
||||
debugPrint("update config");
|
||||
final res = await clashCore.updateConfig(UpdateConfigParams(
|
||||
profilePath: profilePath,
|
||||
config: clashConfig,
|
||||
isPatch: isPatch,
|
||||
isCompatible: config.isCompatible,
|
||||
));
|
||||
if (res.isNotEmpty) throw res;
|
||||
}
|
||||
|
||||
@@ -263,18 +261,6 @@ class GlobalState {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
int getColumns(ViewMode viewMode, int currentColumns) {
|
||||
final targetColumnsArray = switch (viewMode) {
|
||||
ViewMode.mobile => [2, 1],
|
||||
ViewMode.laptop => [3, 2],
|
||||
ViewMode.desktop => [4, 3],
|
||||
};
|
||||
if (targetColumnsArray.contains(currentColumns)) {
|
||||
return currentColumns;
|
||||
}
|
||||
return targetColumnsArray.first;
|
||||
}
|
||||
}
|
||||
|
||||
final globalState = GlobalState();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'text.dart';
|
||||
@@ -55,7 +54,6 @@ class CommonCard extends StatelessWidget {
|
||||
const CommonCard({
|
||||
super.key,
|
||||
bool? isSelected,
|
||||
this.type = CommonCardType.plain,
|
||||
this.onPressed,
|
||||
this.info,
|
||||
this.selectWidget,
|
||||
@@ -67,14 +65,10 @@ class CommonCard extends StatelessWidget {
|
||||
final Widget? selectWidget;
|
||||
final Widget child;
|
||||
final Info? info;
|
||||
final CommonCardType type;
|
||||
|
||||
BorderSide getBorderSide(BuildContext context, Set<WidgetState> states) {
|
||||
if(type == CommonCardType.filled){
|
||||
return BorderSide.none;
|
||||
}
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final hoverColor = isSelected
|
||||
var hoverColor = isSelected
|
||||
? colorScheme.primary.toLight()
|
||||
: colorScheme.primary.toLighter();
|
||||
if (states.contains(WidgetState.hovered) ||
|
||||
@@ -92,28 +86,17 @@ class CommonCard extends StatelessWidget {
|
||||
|
||||
Color? getBackgroundColor(BuildContext context, Set<WidgetState> states) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
switch(type){
|
||||
case CommonCardType.plain:
|
||||
if (isSelected) {
|
||||
return colorScheme.secondaryContainer;
|
||||
}
|
||||
if (states.isEmpty) {
|
||||
return colorScheme.secondaryContainer.toLittle();
|
||||
}
|
||||
return Theme.of(context)
|
||||
.outlinedButtonTheme
|
||||
.style
|
||||
?.backgroundColor
|
||||
?.resolve(states);
|
||||
case CommonCardType.filled:
|
||||
if (isSelected) {
|
||||
return colorScheme.secondaryContainer;
|
||||
}
|
||||
if (states.isEmpty) {
|
||||
return colorScheme.surfaceContainerLow;
|
||||
}
|
||||
return colorScheme.surfaceContainer;
|
||||
if (isSelected) {
|
||||
return colorScheme.secondaryContainer;
|
||||
}
|
||||
if (states.isEmpty) {
|
||||
return colorScheme.secondaryContainer.toLittle();
|
||||
}
|
||||
return Theme.of(context)
|
||||
.outlinedButtonTheme
|
||||
.style
|
||||
?.backgroundColor
|
||||
?.resolve(states);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -153,7 +136,11 @@ class CommonCard extends StatelessWidget {
|
||||
(states) => getBorderSide(context, states),
|
||||
),
|
||||
),
|
||||
onPressed: onPressed,
|
||||
onPressed: () {
|
||||
if (onPressed != null) {
|
||||
onPressed!();
|
||||
}
|
||||
},
|
||||
child: Builder(
|
||||
builder: (_) {
|
||||
List<Widget> children = [];
|
||||
|
||||
@@ -17,17 +17,12 @@ class CommonChip extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (type == ChipType.delete) {
|
||||
if(type == ChipType.delete){
|
||||
return Chip(
|
||||
avatar: avatar,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 0,
|
||||
horizontal: 4,
|
||||
),
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
onDeleted: onPressed ?? () {},
|
||||
side:
|
||||
BorderSide(color: Theme.of(context).dividerColor.withOpacity(0.2)),
|
||||
side: BorderSide(color: Theme.of(context).dividerColor.withOpacity(0.2)),
|
||||
labelStyle: Theme.of(context).textTheme.bodyMedium,
|
||||
label: Text(label),
|
||||
);
|
||||
@@ -35,10 +30,6 @@ class CommonChip extends StatelessWidget {
|
||||
return ActionChip(
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
avatar: avatar,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 0,
|
||||
horizontal: 4,
|
||||
),
|
||||
onPressed: onPressed ?? () {},
|
||||
side: BorderSide(color: Theme.of(context).dividerColor.withOpacity(0.2)),
|
||||
labelStyle: Theme.of(context).textTheme.bodyMedium,
|
||||
|
||||
@@ -51,10 +51,8 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onTun(String fd) async {
|
||||
final fdInt = int.parse(fd);
|
||||
await proxyManager.setProtect(fdInt);
|
||||
clashCore.setFdMap(fdInt);
|
||||
void onTun(String fd) {
|
||||
proxyManager.setProtect(int.parse(fd));
|
||||
super.onTun(fd);
|
||||
}
|
||||
|
||||
@@ -75,4 +73,10 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
|
||||
globalState.appController.appState.addRequest(connection);
|
||||
super.onRequest(connection);
|
||||
}
|
||||
|
||||
@override
|
||||
void onRun(String runTime) async {
|
||||
// proxy?.updateStartTime();
|
||||
super.onRun(runTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.33+202407012
|
||||
version: 0.8.27
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user