Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dcb466fd3 | ||
|
|
acbcec358b | ||
|
|
a923549ddf | ||
|
|
07bd21580b | ||
|
|
57ceb64a5e | ||
|
|
713e83d9d8 |
24
.github/workflows/build.yml
vendored
24
.github/workflows/build.yml
vendored
@@ -21,11 +21,25 @@ 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
|
||||
@@ -52,10 +66,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,8 +22,10 @@
|
||||
<application
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:extractNativeLibs="true"
|
||||
android:label="FlClash">
|
||||
android:label="FlClash"
|
||||
tools:targetApi="n">
|
||||
<activity
|
||||
android:name="com.follow.clash.MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
|
||||
@@ -44,6 +44,7 @@ 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) {
|
||||
@@ -135,7 +136,6 @@ 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,13 +152,14 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
if (permission == PackageManager.PERMISSION_GRANTED) {
|
||||
startForeground()
|
||||
} else {
|
||||
activity?.let {
|
||||
ActivityCompat.requestPermissions(
|
||||
it,
|
||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||
NOTIFICATION_PERMISSION_REQUEST_CODE
|
||||
)
|
||||
}
|
||||
if (isBlockNotification) return
|
||||
if (activity == null) return
|
||||
ActivityCompat.requestPermissions(
|
||||
activity!!,
|
||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||
NOTIFICATION_PERMISSION_REQUEST_CODE
|
||||
)
|
||||
|
||||
}
|
||||
} else {
|
||||
startForeground()
|
||||
@@ -192,11 +193,14 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
grantResults: IntArray
|
||||
): Boolean {
|
||||
if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
startForeground()
|
||||
isBlockNotification = true
|
||||
if (grantResults.isNotEmpty()) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
startForeground()
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,4 +7,8 @@
|
||||
<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: 48425d7cf9...35e0465263
@@ -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,7 +396,6 @@ func applyConfig(isPatch bool) {
|
||||
if isPatch {
|
||||
patchConfig(cfg.General)
|
||||
} else {
|
||||
executor.Shutdown()
|
||||
runtime.GC()
|
||||
hub.UltraApplyConfig(cfg, true)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dart_bridge
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include "stdint.h"
|
||||
#include "include/dart_api_dl.h"
|
||||
#include "include/dart_api_dl.c"
|
||||
@@ -28,6 +29,7 @@ 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,5 +1,8 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
bridge "core/dart-bridge"
|
||||
@@ -71,8 +74,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())
|
||||
@@ -85,8 +88,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 {
|
||||
@@ -147,30 +150,32 @@ func getProxies() *C.char {
|
||||
}
|
||||
|
||||
//export changeProxy
|
||||
func changeProxy(s *C.char) bool {
|
||||
func changeProxy(s *C.char) {
|
||||
paramsString := C.GoString(s)
|
||||
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()
|
||||
proxy := proxies[*params.GroupName]
|
||||
if proxy == nil {
|
||||
group := proxies[groupName]
|
||||
if group == nil {
|
||||
return
|
||||
}
|
||||
log.Infoln("change proxy %s", proxy.Name())
|
||||
adapterProxy := proxy.(*adapter.Proxy)
|
||||
adapterProxy := group.(*adapter.Proxy)
|
||||
selector, ok := adapterProxy.ProxyAdapter.(*outboundgroup.Selector)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := selector.Set(*params.ProxyName); err != nil {
|
||||
return
|
||||
|
||||
err = selector.Set(proxyName)
|
||||
if err == nil {
|
||||
log.Infoln("[Selector] %s selected %s", groupName, proxyName)
|
||||
}
|
||||
}()
|
||||
return true
|
||||
}
|
||||
|
||||
//export getTraffic
|
||||
@@ -211,8 +216,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 {
|
||||
@@ -296,7 +301,6 @@ 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
|
||||
@@ -307,10 +311,13 @@ 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 {
|
||||
return C.CString("")
|
||||
msg = C.CString("")
|
||||
return msg
|
||||
}
|
||||
return C.CString(string(data))
|
||||
msg = C.CString(string(data))
|
||||
return msg
|
||||
}
|
||||
|
||||
//export getProvider
|
||||
@@ -360,10 +367,9 @@ 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()
|
||||
@@ -409,6 +415,11 @@ 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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
41
core/tun.go
41
core/tun.go
@@ -18,6 +18,21 @@ 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() {
|
||||
@@ -63,16 +78,38 @@ 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(int(fd))
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
tun.MarkSocket(fdInt)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
//for {
|
||||
// select {
|
||||
// case <-timeout:
|
||||
// return
|
||||
// default:
|
||||
// exists := fdMap.Load(fdInt)
|
||||
// if exists {
|
||||
// return
|
||||
// }
|
||||
// time.Sleep(20 * time.Millisecond)
|
||||
// }
|
||||
//}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,6 @@ class ApplicationState extends State<Application> {
|
||||
builder: (lightDynamic, darkDynamic) {
|
||||
_updateSystemColorSchemes(lightDynamic, darkDynamic);
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
navigatorKey: globalState.navigatorKey,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
|
||||
@@ -45,10 +45,10 @@ class ClashCore {
|
||||
}
|
||||
|
||||
bool init(String homeDir) {
|
||||
return clashFFI.initClash(
|
||||
homeDir.toNativeUtf8().cast(),
|
||||
) ==
|
||||
1;
|
||||
final homeDirChar = homeDir.toNativeUtf8().cast<Char>();
|
||||
final isInit = clashFFI.initClash(homeDirChar) == 1;
|
||||
malloc.free(homeDirChar);
|
||||
return isInit;
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
@@ -67,10 +67,12 @@ class ClashCore {
|
||||
receiver.close();
|
||||
}
|
||||
});
|
||||
final dataChar = data.toNativeUtf8().cast<Char>();
|
||||
clashFFI.validateConfig(
|
||||
data.toNativeUtf8().cast(),
|
||||
dataChar,
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(dataChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@@ -84,20 +86,23 @@ class ClashCore {
|
||||
}
|
||||
});
|
||||
final params = json.encode(updateConfigParams);
|
||||
final paramsChar = params.toNativeUtf8().cast<Char>();
|
||||
clashFFI.updateConfig(
|
||||
params.toNativeUtf8().cast(),
|
||||
paramsChar,
|
||||
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 [];
|
||||
if (proxiesRawString.isEmpty) return [];
|
||||
final proxies = (json.decode(proxiesRawString) ?? {}) as Map;
|
||||
if(proxies.isEmpty) return [];
|
||||
if (proxies.isEmpty) return [];
|
||||
final groupNames = [
|
||||
UsedProxy.GLOBAL.name,
|
||||
...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) {
|
||||
@@ -111,7 +116,8 @@ class ClashCore {
|
||||
group["all"] = ((group["all"] ?? []) as List)
|
||||
.map(
|
||||
(name) => proxies[name],
|
||||
)
|
||||
)
|
||||
.where((proxy) => proxy != null)
|
||||
.toList();
|
||||
return group;
|
||||
}).toList();
|
||||
@@ -122,14 +128,15 @@ class ClashCore {
|
||||
Future<List<ExternalProvider>> getExternalProviders() {
|
||||
final externalProvidersRaw = clashFFI.getExternalProviders();
|
||||
final externalProvidersRawString =
|
||||
externalProvidersRaw.cast<Utf8>().toDartString();
|
||||
externalProvidersRaw.cast<Utf8>().toDartString();
|
||||
clashFFI.freeCString(externalProvidersRaw);
|
||||
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;
|
||||
});
|
||||
}
|
||||
@@ -146,17 +153,24 @@ class ClashCore {
|
||||
receiver.close();
|
||||
}
|
||||
});
|
||||
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
|
||||
final providerTypeChar = providerType.toNativeUtf8().cast<Char>();
|
||||
clashFFI.updateExternalProvider(
|
||||
providerName.toNativeUtf8().cast(),
|
||||
providerType.toNativeUtf8().cast(),
|
||||
providerNameChar,
|
||||
providerTypeChar,
|
||||
receiver.sendPort.nativePort,
|
||||
);
|
||||
malloc.free(providerNameChar);
|
||||
malloc.free(providerTypeChar);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
bool changeProxy(ChangeProxyParams changeProxyParams) {
|
||||
final params = json.encode(changeProxyParams);
|
||||
return clashFFI.changeProxy(params.toNativeUtf8().cast()) == 1;
|
||||
final paramsChar = params.toNativeUtf8().cast<Char>();
|
||||
final isInit = clashFFI.changeProxy(paramsChar) == 1;
|
||||
malloc.free(paramsChar);
|
||||
return isInit;
|
||||
}
|
||||
|
||||
Future<Delay> getDelay(String proxyName) {
|
||||
@@ -172,13 +186,16 @@ class ClashCore {
|
||||
receiver.close();
|
||||
}
|
||||
});
|
||||
final delayParamsChar =
|
||||
json.encode(delayParams).toNativeUtf8().cast<Char>();
|
||||
clashFFI.asyncTestDelay(
|
||||
json.encode(delayParams).toNativeUtf8().cast(),
|
||||
delayParamsChar,
|
||||
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),
|
||||
);
|
||||
@@ -188,28 +205,33 @@ class ClashCore {
|
||||
}
|
||||
|
||||
clearEffect(String path) {
|
||||
clashFFI.clearEffect(path.toNativeUtf8().cast());
|
||||
final pathChar = path.toNativeUtf8().cast<Char>();
|
||||
clashFFI.clearEffect(pathChar);
|
||||
malloc.free(pathChar);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -234,7 +256,14 @@ class ClashCore {
|
||||
}
|
||||
|
||||
void setProcessMap(ProcessMapItem processMapItem) {
|
||||
clashFFI.setProcessMap(json.encode(processMapItem).toNativeUtf8().cast());
|
||||
final processMapItemChar =
|
||||
json.encode(processMapItem).toNativeUtf8().cast<Char>();
|
||||
clashFFI.setProcessMap(processMapItemChar);
|
||||
malloc.free(processMapItemChar);
|
||||
}
|
||||
|
||||
void setFdMap(int fd) {
|
||||
clashFFI.setFdMap(fd);
|
||||
}
|
||||
|
||||
// DateTime? getRunTime() {
|
||||
@@ -246,13 +275,16 @@ class ClashCore {
|
||||
List<Connection> getConnections() {
|
||||
final connectionsDataRaw = clashFFI.getConnections();
|
||||
final connectionsData =
|
||||
json.decode(connectionsDataRaw.cast<Utf8>().toDartString()) as Map;
|
||||
json.decode(connectionsDataRaw.cast<Utf8>().toDartString()) as Map;
|
||||
clashFFI.freeCString(connectionsDataRaw);
|
||||
final connectionsRaw = connectionsData['connections'] as List? ?? [];
|
||||
return connectionsRaw.map((e) => Connection.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
closeConnections(String id) {
|
||||
clashFFI.closeConnection(id.toNativeUtf8().cast());
|
||||
final idChar = id.toNativeUtf8().cast<Char>();
|
||||
clashFFI.closeConnection(idChar);
|
||||
malloc.free(idChar);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@ class Android {
|
||||
init() async {
|
||||
app?.onExit = () {
|
||||
clashCore.shutdown();
|
||||
print("adsadda==>");
|
||||
exit(0);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,22 +1,35 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class AppPackage{
|
||||
import 'common.dart';
|
||||
|
||||
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,21 +2,20 @@ 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(
|
||||
BaseOptions(
|
||||
headers: {"User-Agent": coreName},
|
||||
),
|
||||
);
|
||||
_dio = Dio();
|
||||
_dio.interceptors.add(InterceptorsWrapper(
|
||||
onRequest: (options, handler) {
|
||||
_syncProxy();
|
||||
@@ -45,10 +44,14 @@ 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! - 1);
|
||||
TextStyle get toMinus => copyWith(fontSize: fontSize! - 2);
|
||||
}
|
||||
|
||||
@@ -359,7 +359,9 @@ class AppController {
|
||||
}
|
||||
|
||||
addProfileFormURL(String url) async {
|
||||
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
|
||||
if (globalState.navigatorKey.currentState?.canPop() ?? false) {
|
||||
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
|
||||
}
|
||||
toProfiles();
|
||||
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
|
||||
if (commonScaffoldState?.mounted != true) return;
|
||||
@@ -421,18 +423,17 @@ class AppController {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
List<Proxy> _sortOfName(List<Proxy> proxies) {
|
||||
return List.of(proxies)
|
||||
..sort(
|
||||
(a, b) => other.sortByChar(a.name, b.name),
|
||||
(a, b) => other.sortByChar(a.name, b.name),
|
||||
);
|
||||
}
|
||||
|
||||
List<Proxy> _sortOfDelay(List<Proxy> proxies) {
|
||||
return proxies = List.of(proxies)
|
||||
..sort(
|
||||
(a, b) {
|
||||
(a, b) {
|
||||
final aDelay = appState.getDelay(a.name);
|
||||
final bDelay = appState.getDelay(b.name);
|
||||
if (aDelay == null && bDelay == null) {
|
||||
@@ -449,11 +450,11 @@ class AppController {
|
||||
);
|
||||
}
|
||||
|
||||
List<Proxy> getSortProxies(List<Proxy> proxies){
|
||||
return switch(config.proxiesSortType){
|
||||
List<Proxy> getSortProxies(List<Proxy> proxies) {
|
||||
return switch (config.proxiesSortType) {
|
||||
ProxiesSortType.none => proxies,
|
||||
ProxiesSortType.delay => _sortOfDelay(proxies),
|
||||
ProxiesSortType.name =>_sortOfName(proxies),
|
||||
ProxiesSortType.name => _sortOfName(proxies),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
|
||||
Widget _buildMoreSection() {
|
||||
final items = [
|
||||
if (false)
|
||||
if (system.isDesktop)
|
||||
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.general,
|
||||
title: appLocalizations.more,
|
||||
child: Column(
|
||||
children: [
|
||||
for (final item in items) ...[
|
||||
|
||||
@@ -53,7 +53,7 @@ class _IntranetIPState extends State<IntranetIP> {
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16).copyWith(top: 0),
|
||||
height: globalState.appController.measure.titleLargeHeight + 24 - 1,
|
||||
height: globalState.appController.measure.titleLargeHeight + 24 - 2,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: ipNotifier,
|
||||
builder: (_, value, __) {
|
||||
|
||||
@@ -137,7 +137,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
Container(
|
||||
height: globalState.appController.measure.titleLargeHeight +
|
||||
24 -
|
||||
1,
|
||||
2,
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.all(16).copyWith(top: 0),
|
||||
child: FadeBox(
|
||||
|
||||
@@ -25,7 +25,9 @@ class AddProfile extends StatelessWidget {
|
||||
final url = await Navigator.of(context)
|
||||
.push<String>(MaterialPageRoute(builder: (_) => const ScanPage()));
|
||||
if (url != null) {
|
||||
_handleAddProfileFormURL(url);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_){
|
||||
_handleAddProfileFormURL(url);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ 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';
|
||||
@@ -28,6 +29,7 @@ class ProfilesFragment extends StatefulWidget {
|
||||
|
||||
class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
final hasPadding = ValueNotifier<bool>(false);
|
||||
Function? applyConfigDebounce;
|
||||
|
||||
List<GlobalObjectKey<_ProfileItemState>> profileItemKeys = [];
|
||||
|
||||
@@ -86,12 +88,30 @@ 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>(
|
||||
@@ -154,8 +174,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
key: profileItemKeys[i],
|
||||
profile: state.profiles[i],
|
||||
groupValue: state.currentProfileId,
|
||||
onChanged:
|
||||
globalState.appController.changeProfile,
|
||||
onChanged: _changeProfile,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -437,16 +456,16 @@ class _ProfileItemState extends State<ProfileItem> {
|
||||
label: appLocalizations.update,
|
||||
iconData: Icons.sync,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: ProfileActions.delete,
|
||||
label: appLocalizations.delete,
|
||||
iconData: Icons.delete,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: ProfileActions.view,
|
||||
label: appLocalizations.view,
|
||||
iconData: Icons.visibility,
|
||||
),
|
||||
CommonPopupMenuItem(
|
||||
action: ProfileActions.delete,
|
||||
label: appLocalizations.delete,
|
||||
iconData: Icons.delete,
|
||||
),
|
||||
],
|
||||
onSelected: (ProfileActions? action) async {
|
||||
switch (action) {
|
||||
|
||||
@@ -230,17 +230,6 @@ class ProxiesExpansionPanelFragment extends StatefulWidget {
|
||||
|
||||
class _ProxiesExpansionPanelFragmentState
|
||||
extends State<ProxiesExpansionPanelFragment> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector2<AppState, Config, ProxiesSelectorState>(
|
||||
@@ -259,6 +248,7 @@ class _ProxiesExpansionPanelFragmentState
|
||||
itemBuilder: (_, index) {
|
||||
final groupName = state.groupNames[index];
|
||||
return ProxyGroupView(
|
||||
key: PageStorageKey(groupName),
|
||||
groupName: groupName,
|
||||
type: ProxiesType.expansion,
|
||||
);
|
||||
@@ -291,6 +281,7 @@ class ProxyGroupView extends StatefulWidget {
|
||||
class _ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
var isLock = false;
|
||||
final isBoundaryNotifier = ValueNotifier<bool>(false);
|
||||
final scrollController = ScrollController();
|
||||
var isEnd = false;
|
||||
|
||||
String get groupName => widget.groupName;
|
||||
@@ -384,28 +375,49 @@ class _ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: Listener(
|
||||
onPointerMove: (details) {
|
||||
double yOffset = details.delta.dy;
|
||||
if (yOffset > 0 && isEnd) {
|
||||
isBoundaryNotifier.value = false;
|
||||
} else if (yOffset < 0 && !isEnd) {
|
||||
isBoundaryNotifier.value = false;
|
||||
}
|
||||
},
|
||||
child: child,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -422,7 +434,8 @@ class _ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
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 minLines =
|
||||
innerHeight >= 200 ? (innerHeight / itemHeight).floor() : 3;
|
||||
final hasScrollable = lines > minLines;
|
||||
final height = (itemHeight + 8) * min(lines, minLines) - 8;
|
||||
return Selector<Config, Set<String>>(
|
||||
@@ -430,6 +443,7 @@ class _ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
builder: (_, currentUnfoldSet, __) {
|
||||
return CommonCard(
|
||||
child: ExpansionTile(
|
||||
childrenPadding: const EdgeInsets.all(8),
|
||||
initiallyExpanded: currentUnfoldSet.contains(groupName),
|
||||
iconColor: context.colorScheme.onSurfaceVariant,
|
||||
onExpansionChanged: (value) {
|
||||
@@ -527,12 +541,6 @@ class _ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
collapsedShape: const RoundedRectangleBorder(
|
||||
side: BorderSide.none,
|
||||
),
|
||||
childrenPadding: const EdgeInsets.only(
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
left: 8,
|
||||
right: 8,
|
||||
),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: height,
|
||||
@@ -541,62 +549,70 @@ class _ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
ValueListenableBuilder(
|
||||
valueListenable: isBoundaryNotifier,
|
||||
builder: (_, isBoundary, child) {
|
||||
return GridView.builder(
|
||||
physics: isBoundary || !hasScrollable
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: const AlwaysScrollableScrollPhysics(),
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: columns,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisExtent: _getItemHeight(proxyCardType),
|
||||
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,
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
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(
|
||||
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,
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
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,
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -609,6 +625,7 @@ class _ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
isBoundaryNotifier.dispose();
|
||||
scrollController.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -814,7 +831,12 @@ class ProxyCard extends StatelessWidget {
|
||||
groupName,
|
||||
proxy.name,
|
||||
);
|
||||
appController.changeProxy();
|
||||
clashCore.changeProxy(
|
||||
ChangeProxyParams(
|
||||
groupName: groupName,
|
||||
proxyName: proxy.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -49,11 +49,15 @@ Future<void> vpnService() async {
|
||||
isCompatible: config.isCompatible,
|
||||
selectedMap: config.currentSelectedMap,
|
||||
);
|
||||
clashMessage.addListener(ClashMessageListenerWithVpn(onTun: (String fd) {
|
||||
proxyManager.setProtect(
|
||||
int.parse(fd),
|
||||
);
|
||||
}));
|
||||
clashMessage.addListener(
|
||||
ClashMessageListenerWithVpn(
|
||||
onTun: (String fd) async {
|
||||
final fdInt = int.parse(fd);
|
||||
await proxyManager.setProtect(fdInt);
|
||||
clashCore.setFdMap(fdInt);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await globalState.init(
|
||||
appState: appState,
|
||||
|
||||
@@ -123,9 +123,10 @@ 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);
|
||||
return getRealProxyName((selectedMap.containsKey(proxyName)
|
||||
? selectedMap[proxyName]
|
||||
: group.now)) ??
|
||||
proxyName;
|
||||
}
|
||||
|
||||
String? get showProxyName {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// 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';
|
||||
@@ -235,7 +237,12 @@ class ClashConfig extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Tun get tun => _tun;
|
||||
Tun get tun {
|
||||
if (Platform.isAndroid) {
|
||||
return _tun.copyWith(enable: false);
|
||||
}
|
||||
return _tun;
|
||||
}
|
||||
|
||||
set tun(Tun value) {
|
||||
if (_tun != value) {
|
||||
@@ -282,9 +289,4 @@ 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}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,14 @@ class GlobalState {
|
||||
}) async {
|
||||
final profilePath = await appPath.getProfilePath(config.currentProfileId);
|
||||
await config.currentProfile?.checkAndUpdate();
|
||||
debugPrint("update config");
|
||||
final res = await clashCore.updateConfig(UpdateConfigParams(
|
||||
profilePath: profilePath,
|
||||
config: clashConfig,
|
||||
isPatch: isPatch,
|
||||
isCompatible: config.isCompatible,
|
||||
));
|
||||
final res = await clashCore.updateConfig(
|
||||
UpdateConfigParams(
|
||||
profilePath: profilePath,
|
||||
config: clashConfig,
|
||||
isPatch: isPatch,
|
||||
isCompatible: config.isCompatible,
|
||||
),
|
||||
);
|
||||
if (res.isNotEmpty) throw res;
|
||||
}
|
||||
|
||||
@@ -263,7 +264,7 @@ class GlobalState {
|
||||
}
|
||||
}
|
||||
|
||||
int getColumns(ViewMode viewMode,int currentColumns){
|
||||
int getColumns(ViewMode viewMode, int currentColumns) {
|
||||
final targetColumnsArray = switch (viewMode) {
|
||||
ViewMode.mobile => [2, 1],
|
||||
ViewMode.laptop => [3, 2],
|
||||
|
||||
@@ -51,8 +51,10 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
|
||||
}
|
||||
|
||||
@override
|
||||
void onTun(String fd) {
|
||||
proxyManager.setProtect(int.parse(fd));
|
||||
Future<void> onTun(String fd) async {
|
||||
final fdInt = int.parse(fd);
|
||||
await proxyManager.setProtect(fdInt);
|
||||
clashCore.setFdMap(fdInt);
|
||||
super.onTun(fd);
|
||||
}
|
||||
|
||||
@@ -73,10 +75,4 @@ 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.29
|
||||
version: 0.8.33+202407012
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user