diff --git a/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt b/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt index f7b609ea..9ad66119 100644 --- a/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt +++ b/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt @@ -1,6 +1,7 @@ package com.follow.clash import androidx.lifecycle.MutableLiveData +import com.follow.clash.plugins.AppPlugin import com.follow.clash.plugins.TilePlugin import io.flutter.embedding.engine.FlutterEngine import java.util.Date @@ -18,6 +19,9 @@ class GlobalState { var flutterEngine: FlutterEngine? = null fun getCurrentTilePlugin(): TilePlugin? = flutterEngine?.plugins?.get(TilePlugin::class.java) as TilePlugin? + + fun getCurrentAppPlugin(): AppPlugin? = + flutterEngine?.plugins?.get(AppPlugin::class.java) as AppPlugin? } } diff --git a/android/app/src/main/kotlin/com/follow/clash/models/Props.kt b/android/app/src/main/kotlin/com/follow/clash/models/Props.kt index 8215e39f..2b94ba57 100644 --- a/android/app/src/main/kotlin/com/follow/clash/models/Props.kt +++ b/android/app/src/main/kotlin/com/follow/clash/models/Props.kt @@ -11,7 +11,8 @@ data class AccessControl( val rejectList: List, ) -data class Props ( +data class Props( val accessControl: AccessControl?, val allowBypass: Boolean?, + val systemProxy: Boolean?, ) diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt index 3c1a6091..548a03c7 100644 --- a/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt +++ b/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt @@ -143,7 +143,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware } private suspend fun getPackages(): String { - return withContext(Dispatchers.Default){ + return withContext(Dispatchers.Default) { val packageManager = context?.packageManager val packages: List? = packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter { @@ -162,6 +162,10 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware } } + fun requestGc() { + channel.invokeMethod("gc",null) + } + override fun onAttachedToActivity(binding: ActivityPluginBinding) { activity = binding.activity; scope = CoroutineScope(Dispatchers.Default) diff --git a/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt b/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt index c2100fdf..c208237e 100644 --- a/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt +++ b/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt @@ -15,7 +15,6 @@ import androidx.core.app.NotificationCompat import com.follow.clash.GlobalState import com.follow.clash.MainActivity import com.follow.clash.R -import com.follow.clash.models.AccessControl import com.follow.clash.models.AccessControlMode import com.follow.clash.models.Props @@ -81,7 +80,7 @@ class FlClashVpnService : VpnService() { if (props?.allowBypass == true) { allowBypass() } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && props?.systemProxy == true) { setHttpProxy( ProxyInfo.buildDirectProxy( "127.0.0.1", @@ -135,14 +134,20 @@ class FlClashVpnService : VpnService() { } } + override fun onTrimMemory(level: Int) { + super.onTrimMemory(level) + GlobalState.getCurrentAppPlugin()?.requestGc() + } fun startForeground(title: String, content: String) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = - NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW) val manager = getSystemService(NotificationManager::class.java) - manager.createNotificationChannel(channel) - channel.setShowBadge(false) + var channel = manager?.getNotificationChannel(CHANNEL) + if (channel == null) { + channel = + NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW) + manager?.createNotificationChannel(channel) + } } val notification = notificationBuilder.setContentTitle(title).setContentText(content).build() diff --git a/core/Clash.Meta b/core/Clash.Meta index c038006d..fe80ddcc 160000 --- a/core/Clash.Meta +++ b/core/Clash.Meta @@ -1 +1 @@ -Subproject commit c038006dce97cad8a5f79e7ffbb93156e7674756 +Subproject commit fe80ddcc9a055a56ab7e5f3762b3c671e5b50491 diff --git a/core/common.go b/core/common.go index 3e75af7c..77f033e1 100644 --- a/core/common.go +++ b/core/common.go @@ -10,6 +10,7 @@ import ( "github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/dns" + "github.com/metacubex/mihomo/hub" "github.com/metacubex/mihomo/hub/executor" "github.com/metacubex/mihomo/listener" "github.com/metacubex/mihomo/log" @@ -325,6 +326,8 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi targetConfig.ExternalUI = "" targetConfig.Interface = "" targetConfig.ExternalUIURL = "" + targetConfig.TCPConcurrent = patchConfig.TCPConcurrent + targetConfig.UnifiedDelay = patchConfig.UnifiedDelay targetConfig.GeodataMode = false targetConfig.IPv6 = patchConfig.IPv6 targetConfig.LogLevel = patchConfig.LogLevel @@ -338,7 +341,7 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi targetConfig.Tun.Device = patchConfig.Tun.Device //targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack //targetConfig.Tun.Stack = patchConfig.Tun.Stack - targetConfig.GeodataLoader = "standard" + targetConfig.GeodataLoader = patchConfig.GeodataLoader targetConfig.Profile.StoreSelected = false if targetConfig.DNS.Enable == false { targetConfig.DNS = patchConfig.DNS @@ -410,7 +413,7 @@ func applyConfig(isPatch bool) { if isPatch { patchConfig(cfg.General) } else { - executor.ApplyConfig(cfg, true) + hub.UltraApplyConfig(cfg, true) hcCompatibleProvider(tunnel.Providers()) } } diff --git a/core/go.mod b/core/go.mod index ee3a5b57..59b970ed 100644 --- a/core/go.mod +++ b/core/go.mod @@ -17,6 +17,7 @@ require ( github.com/RyuaNerin/go-krypto v1.2.4 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect @@ -30,6 +31,9 @@ require ( github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gaukas/godicttls v0.0.4 // indirect + github.com/go-chi/chi/v5 v5.0.12 // indirect + github.com/go-chi/cors v1.2.1 // indirect + github.com/go-chi/render v1.0.3 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobwas/httphead v0.1.0 // indirect diff --git a/core/go.sum b/core/go.sum index 1534915c..2815cd11 100644 --- a/core/go.sum +++ b/core/go.sum @@ -9,6 +9,8 @@ github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4 github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= @@ -35,16 +37,25 @@ github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIF github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po= github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg= +github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c/go.mod h1:ETASDWf/FmEb6Ysrtd1QhjNedUU/ZQxBCRLh60bQ/UI= github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBEz5nGDMvswiajqh7k8ogWRlhRwKy5mY= github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4= github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA= github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok= github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= +github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= +github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= +github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= +github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -60,6 +71,7 @@ github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM= github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -69,6 +81,7 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I= +github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7spovjlY= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -83,7 +96,9 @@ github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6K github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= @@ -125,6 +140,7 @@ github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:U github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0= github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo= github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0= @@ -146,6 +162,7 @@ github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1 github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= @@ -249,6 +266,7 @@ golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= @@ -263,6 +281,7 @@ google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFW google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/core/hub.go b/core/hub.go index 60b2024f..8b63a450 100644 --- a/core/hub.go +++ b/core/hub.go @@ -60,6 +60,14 @@ func shutdownClash() bool { return true } +//export forceGc +func forceGc() { + go func() { + log.Infoln("[APP] request force GC") + runtime.GC() + }() +} + //export validateConfig func validateConfig(s *C.char, port C.longlong) { i := int64(port) diff --git a/lib/clash/core.dart b/lib/clash/core.dart index a4446e37..6975c7b3 100644 --- a/lib/clash/core.dart +++ b/lib/clash/core.dart @@ -210,6 +210,10 @@ class ClashCore { clashFFI.startTUN(fd); } + requestGc(){ + clashFFI.forceGc(); + } + void stopTun() { clashFFI.stopTun(); } diff --git a/lib/clash/generated/clash_ffi.dart b/lib/clash/generated/clash_ffi.dart index c7ba2206..ae5d33b5 100644 --- a/lib/clash/generated/clash_ffi.dart +++ b/lib/clash/generated/clash_ffi.dart @@ -893,6 +893,14 @@ class ClashFFI { _lookup>('shutdownClash'); late final _shutdownClash = _shutdownClashPtr.asFunction(); + void forceGc() { + return _forceGc(); + } + + late final _forceGcPtr = + _lookup>('forceGc'); + late final _forceGc = _forceGcPtr.asFunction(); + void validateConfig( ffi.Pointer s, int port, @@ -1122,7 +1130,7 @@ class ClashFFI { _lookup>('stopLog'); late final _stopLog = _stopLogPtr.asFunction(); - int startTUN( + void startTUN( int fd, ) { return _startTUN( @@ -1131,8 +1139,8 @@ class ClashFFI { } late final _startTUNPtr = - _lookup>('startTUN'); - late final _startTUN = _startTUNPtr.asFunction(); + _lookup>('startTUN'); + late final _startTUN = _startTUNPtr.asFunction(); int updateMarkSocketPort( int markSocketPort, diff --git a/lib/common/constant.dart b/lib/common/constant.dart index 9f8738e5..7418dc18 100644 --- a/lib/common/constant.dart +++ b/lib/common/constant.dart @@ -21,6 +21,8 @@ const repository = "chen08209/FlClash"; const defaultExternalController = "127.0.0.1:9090"; const maxMobileWidth = 600; const maxLaptopWidth = 840; +const geodataLoaderMemconservative = "memconservative"; +const geodataLoaderStandard = "standard"; final filter = ImageFilter.blur( sigmaX: 5, sigmaY: 5, diff --git a/lib/fragments/config.dart b/lib/fragments/config.dart index 03e487a0..3d3f920e 100644 --- a/lib/fragments/config.dart +++ b/lib/fragments/config.dart @@ -46,9 +46,110 @@ class _ConfigFragmentState extends State { globalState.appController.updateClashConfigDebounce(); } - @override - Widget build(BuildContext context) { - List items = [ + _buildAppSection() { + final items = [ + if (Platform.isAndroid) + Selector( + selector: (_, config) => config.allowBypass, + builder: (_, allowBypass, __) { + return ListItem.switchItem( + leading: const Icon(Icons.arrow_forward_outlined), + title: Text(appLocalizations.allowBypass), + subtitle: Text(appLocalizations.allowBypassDesc), + delegate: SwitchDelegate( + value: allowBypass, + onChanged: (bool value) async { + final appController = globalState.appController; + appController.config.allowBypass = value; + }, + ), + ); + }, + ), + if (Platform.isAndroid) + Selector( + selector: (_, config) => config.systemProxy, + builder: (_, systemProxy, __) { + return ListItem.switchItem( + leading: const Icon(Icons.settings_ethernet), + title: Text(appLocalizations.systemProxy), + subtitle: Text(appLocalizations.systemProxyDesc), + delegate: SwitchDelegate( + value: systemProxy, + onChanged: (bool value) async { + final appController = globalState.appController; + appController.config.systemProxy = value; + }, + ), + ); + }, + ), + Selector( + selector: (_, config) => config.isCompatible, + builder: (_, isCompatible, __) { + return ListItem.switchItem( + leading: const Icon(Icons.expand_outlined), + title: Text(appLocalizations.compatible), + subtitle: Text(appLocalizations.compatibleDesc), + delegate: SwitchDelegate( + value: isCompatible, + onChanged: (bool value) async { + final appController = globalState.appController; + appController.config.isCompatible = value; + await appController.updateClashConfig(isPatch: false); + await appController.updateGroups(); + appController.changeProxy(); + }, + ), + ); + }, + ), + ]; + return Section( + title: appLocalizations.app, + child: Column( + children: [ + for (final item in items) ...[ + item, + if (items.last != item) + const Divider( + height: 0, + ) + ] + ], + ), + ); + } + + _buildGeneralSection() { + final items = [ + Padding( + padding: kMaterialListPadding, + child: Selector( + selector: (_, clashConfig) => clashConfig.logLevel, + builder: (_, value, __) { + return ListItem( + leading: const Icon(Icons.info_outline), + title: Text(appLocalizations.logLevel), + trailing: SizedBox( + height: 48, + child: DropdownMenu( + width: 124, + initialSelection: value, + dropdownMenuEntries: [ + for (final logLevel in LogLevel.values) + DropdownMenuEntry( + value: logLevel, + label: logLevel.name, + ) + ], + onSelected: _updateLoglevel, + ), + ), + ); + }, + ), + ), Selector( selector: (_, clashConfig) => clashConfig.mixedPort, builder: (_, mixedPort, __) { @@ -56,9 +157,9 @@ class _ConfigFragmentState extends State { onTab: () { _modifyMixedPort(mixedPort); }, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), leading: const Icon(Icons.adjust_outlined), title: Text(appLocalizations.proxyPort), + subtitle: Text(appLocalizations.proxyPortDesc), trailing: FilledButton.tonal( onPressed: () { _modifyMixedPort(mixedPort); @@ -106,124 +207,113 @@ class _ConfigFragmentState extends State { ); }, ), - // if (system.isDesktop) - // Selector( - // selector: (_, clashConfig) => clashConfig.tun.enable, - // builder: (_, tunEnable, __) { - // return ListItem.switchItem( - // leading: const Icon(Icons.support), - // title: Text(appLocalizations.tun), - // subtitle: Text(appLocalizations.tunDesc), - // delegate: SwitchDelegate( - // value: tunEnable, - // onChanged: (bool value) async { - // final clashConfig = context.read(); - // clashConfig.tun = Tun(enable: value); - // globalState.appController.updateClashConfigDebounce(); - // }, - // ), - // ); - // }, - // ), - if (Platform.isAndroid) - Selector( - selector: (_, config) => config.allowBypass, - builder: (_, allowBypass, __) { - return ListItem.switchItem( - leading: const Icon(Icons.double_arrow), - title: Text(appLocalizations.allowBypass), - subtitle: Text(appLocalizations.allowBypassDesc), - delegate: SwitchDelegate( - value: allowBypass, - onChanged: (bool value) async { - final appController = globalState.appController; - appController.config.allowBypass = value; - }, - ), - ); - }, - ), - Selector( - selector: (_, config) => config.isCompatible, - builder: (_, isCompatible, __) { + Selector( + selector: (_, clashConfig) => clashConfig.unifiedDelay, + builder: (_, unifiedDelay, __) { return ListItem.switchItem( - leading: const Icon(Icons.expand_outlined), - title: Text(appLocalizations.compatible), - subtitle: Text(appLocalizations.compatibleDesc), + leading: const Icon(Icons.compress_outlined), + title: Text(appLocalizations.unifiedDelay), + subtitle: Text(appLocalizations.unifiedDelayDesc), delegate: SwitchDelegate( - value: isCompatible, + value: unifiedDelay, onChanged: (bool value) async { final appController = globalState.appController; - appController.config.isCompatible = value; - await appController.updateClashConfig(isPatch: false); - await appController.updateGroups(); - appController.changeProxy(); + appController.clashConfig.unifiedDelay = value; + appController.updateClashConfigDebounce(); }, ), ); }, ), - // Selector( - // selector: (_, clashConfig) => clashConfig.externalController.isNotEmpty, - // builder: (_, hasExternalController, __) { - // return ListItem.switchItem( - // leading: const Icon(Icons.api_outlined), - // title: Text(appLocalizations.externalController), - // subtitle: Text(appLocalizations.externalControllerDesc), - // delegate: SwitchDelegate( - // value: hasExternalController, - // onChanged: (bool value) async { - // final appController = globalState.appController; - // appController.clashConfig.externalController = - // value ? defaultExternalController : ''; - // await appController.updateClashConfig( - // isPatch: false, - // ); - // }, - // ), - // ); - // }, - // ), - Padding( - padding: kMaterialListPadding, - child: Selector( - selector: (_, clashConfig) => clashConfig.logLevel, - builder: (_, value, __) { - return ListItem( - leading: const Icon(Icons.info_outline), - title: Text(appLocalizations.logLevel), - trailing: SizedBox( - height: 48, - child: DropdownMenu( - width: 124, - initialSelection: value, - dropdownMenuEntries: [ - for (final logLevel in LogLevel.values) - DropdownMenuEntry( - value: logLevel, - label: logLevel.name, - ) - ], - onSelected: _updateLoglevel, - ), - ), - ); - }, - ), + Selector( + selector: (_, clashConfig) => clashConfig.tcpConcurrent, + builder: (_, tcpConcurrent, __) { + return ListItem.switchItem( + leading: const Icon(Icons.double_arrow_outlined), + title: Text(appLocalizations.tcpConcurrent), + subtitle: Text(appLocalizations.tcpConcurrentDesc), + delegate: SwitchDelegate( + value: tcpConcurrent, + onChanged: (bool value) async { + final appController = globalState.appController; + appController.clashConfig.tcpConcurrent = value; + appController.updateClashConfigDebounce(); + }, + ), + ); + }, + ), + Selector( + selector: (_, clashConfig) => + clashConfig.geodataLoader == geodataLoaderMemconservative, + builder: (_, memconservative, __) { + return ListItem.switchItem( + leading: const Icon(Icons.memory), + title: Text(appLocalizations.geodataLoader), + subtitle: Text(appLocalizations.geodataLoaderDesc), + delegate: SwitchDelegate( + value: memconservative, + onChanged: (bool value) async { + final appController = globalState.appController; + appController.clashConfig.geodataLoader = value + ? geodataLoaderMemconservative + : geodataLoaderStandard; + appController.updateClashConfigDebounce; + }, + ), + ); + }, + ), + Selector( + selector: (_, clashConfig) => clashConfig.externalController.isNotEmpty, + builder: (_, hasExternalController, __) { + return ListItem.switchItem( + leading: const Icon(Icons.api_outlined), + title: Text(appLocalizations.externalController), + subtitle: Text(appLocalizations.externalControllerDesc), + delegate: SwitchDelegate( + value: hasExternalController, + onChanged: (bool value) async { + final appController = globalState.appController; + appController.clashConfig.externalController = + value ? defaultExternalController : ''; + appController.updateClashConfigDebounce; + }, + ), + ); + }, ), ]; - return ListView.separated( + return Section( + title: appLocalizations.general, + child: Column( + children: [ + for (final item in items) ...[ + item, + if (items.last != item) + const Divider( + height: 0, + ) + ] + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + List items = [ + _buildAppSection(), + _buildGeneralSection(), + ]; + return ListView.builder( + padding: const EdgeInsets.only(bottom: 16), itemBuilder: (_, index) { return Container( alignment: Alignment.center, child: items[index], ); }, - separatorBuilder: (_, __) { - return const Divider( - height: 0, - ); - }, itemCount: items.length, ); } diff --git a/lib/fragments/profiles/edit_profile.dart b/lib/fragments/profiles/edit_profile.dart index 502261ed..004604c2 100644 --- a/lib/fragments/profiles/edit_profile.dart +++ b/lib/fragments/profiles/edit_profile.dart @@ -152,6 +152,7 @@ class _EditProfileState extends State { vertical: 16, ), child: ListView.separated( + primary: true, itemBuilder: (_, index) { return items[index]; }, diff --git a/lib/fragments/tools.dart b/lib/fragments/tools.dart index 89fdf2a3..188f9f73 100644 --- a/lib/fragments/tools.dart +++ b/lib/fragments/tools.dart @@ -166,6 +166,7 @@ class _ToolboxFragmentState extends State { delegate: OpenDelegate( title: appLocalizations.override, widget: const ConfigFragment(), + extendPageWidth: 360, ), ), ListItem.open( diff --git a/lib/l10n/arb/intl_en.arb b/lib/l10n/arb/intl_en.arb index d8103eeb..24f959e6 100644 --- a/lib/l10n/arb/intl_en.arb +++ b/lib/l10n/arb/intl_en.arb @@ -111,6 +111,7 @@ "noMoreInfoDesc": "No more info", "profileParseErrorDesc": "profile parse error", "proxyPort": "ProxyPort", + "proxyPortDesc": "Set the clash listening port", "port": "Port", "logLevel": "LogLevel", "show": "Show", @@ -163,8 +164,17 @@ "ipCheckTimeout": "Ip check timeout", "search": "Search", "allowBypass": "Allow applications to bypass VPN", - "allowBypassDesc": "Enabled to some applications can bypass VPN", + "allowBypassDesc": "Some apps can bypass VPN when turned on", "externalController": "ExternalController", - "externalControllerDesc": "Enabled to control the clash on port 9090", - "ipv6Desc": "Enabled to will allow it 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", + "geodataLoader": "Geo Low Memory Mode", + "geodataLoaderDesc": "Enabling will use the Geo low memory loader" } \ No newline at end of file diff --git a/lib/l10n/arb/intl_zh_CN.arb b/lib/l10n/arb/intl_zh_CN.arb index 814d9a5c..64746bd2 100644 --- a/lib/l10n/arb/intl_zh_CN.arb +++ b/lib/l10n/arb/intl_zh_CN.arb @@ -111,6 +111,7 @@ "noMoreInfoDesc": "暂无更多信息", "profileParseErrorDesc": "配置文件解析错误", "proxyPort": "代理端口", + "proxyPortDesc": "设置clash监听端口", "port": "端口", "logLevel": "日志等级", "show": "显示", @@ -165,6 +166,15 @@ "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并发", + "geodataLoader": "Geo低内存模式", + "geodataLoaderDesc": "开启将使用Geo低内存加载器" } \ No newline at end of file diff --git a/lib/l10n/intl/messages_en.dart b/lib/l10n/intl/messages_en.dart index 234a2e23..9b95e4ef 100644 --- a/lib/l10n/intl/messages_en.dart +++ b/lib/l10n/intl/messages_en.dart @@ -43,10 +43,11 @@ class MessageLookup extends MessageLookupByLibrary { "allowBypass": MessageLookupByLibrary.simpleMessage( "Allow applications to bypass VPN"), "allowBypassDesc": MessageLookupByLibrary.simpleMessage( - "Enabled to some applications can bypass VPN"), + "Some apps can bypass VPN when turned on"), "allowLan": MessageLookupByLibrary.simpleMessage("AllowLan"), "allowLanDesc": MessageLookupByLibrary.simpleMessage( "Allow access proxy through the LAN"), + "app": MessageLookupByLibrary.simpleMessage("App"), "appAccessControl": MessageLookupByLibrary.simpleMessage("App access control"), "application": MessageLookupByLibrary.simpleMessage("Application"), @@ -119,7 +120,7 @@ class MessageLookup extends MessageLookupByLibrary { "externalController": MessageLookupByLibrary.simpleMessage("ExternalController"), "externalControllerDesc": MessageLookupByLibrary.simpleMessage( - "Enabled to control the clash on port 9090"), + "Once enabled, the clash kernel can be controlled on port 9090"), "externalResources": MessageLookupByLibrary.simpleMessage("External resources"), "file": MessageLookupByLibrary.simpleMessage("File"), @@ -127,7 +128,12 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Directly upload profile"), "filterSystemApp": MessageLookupByLibrary.simpleMessage("Filter system app"), + "general": MessageLookupByLibrary.simpleMessage("General"), "geoData": MessageLookupByLibrary.simpleMessage("GeoData"), + "geodataLoader": + MessageLookupByLibrary.simpleMessage("Geo Low Memory Mode"), + "geodataLoaderDesc": MessageLookupByLibrary.simpleMessage( + "Enabling will use the Geo low memory loader"), "global": MessageLookupByLibrary.simpleMessage("Global"), "goDownload": MessageLookupByLibrary.simpleMessage("Go to download"), "hours": MessageLookupByLibrary.simpleMessage("Hours"), @@ -136,7 +142,7 @@ class MessageLookup extends MessageLookupByLibrary { "ipCheckTimeout": MessageLookupByLibrary.simpleMessage("Ip check timeout"), "ipv6Desc": MessageLookupByLibrary.simpleMessage( - "Enabled to will allow it 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"), @@ -205,6 +211,8 @@ class MessageLookup extends MessageLookupByLibrary { "project": MessageLookupByLibrary.simpleMessage("Project"), "proxies": MessageLookupByLibrary.simpleMessage("Proxies"), "proxyPort": MessageLookupByLibrary.simpleMessage("ProxyPort"), + "proxyPortDesc": MessageLookupByLibrary.simpleMessage( + "Set the clash listening port"), "qrcode": MessageLookupByLibrary.simpleMessage("QR code"), "qrcodeDesc": MessageLookupByLibrary.simpleMessage( "Scan QR code to obtain profile"), @@ -234,9 +242,14 @@ class MessageLookup extends MessageLookupByLibrary { "stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."), "submit": MessageLookupByLibrary.simpleMessage("Submit"), "systemProxy": MessageLookupByLibrary.simpleMessage("SystemProxy"), + "systemProxyDesc": MessageLookupByLibrary.simpleMessage( + "Attach HTTP proxy to VpnService"), "tabAnimation": MessageLookupByLibrary.simpleMessage("Tab animation"), "tabAnimationDesc": MessageLookupByLibrary.simpleMessage( "When enabled, the home tab will add a toggle animation"), + "tcpConcurrent": MessageLookupByLibrary.simpleMessage("Tcp concurrent"), + "tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage( + "Enabling it will allow tcp concurrency"), "theme": MessageLookupByLibrary.simpleMessage("Theme"), "themeColor": MessageLookupByLibrary.simpleMessage("Theme color"), "themeDesc": MessageLookupByLibrary.simpleMessage( @@ -251,6 +264,9 @@ class MessageLookup extends MessageLookupByLibrary { "unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage( "unable to update current profile"), + "unifiedDelay": MessageLookupByLibrary.simpleMessage("Unified delay"), + "unifiedDelayDesc": MessageLookupByLibrary.simpleMessage( + "Remove extra delays such as handshaking"), "unknown": MessageLookupByLibrary.simpleMessage("Unknown"), "update": MessageLookupByLibrary.simpleMessage("Update"), "upload": MessageLookupByLibrary.simpleMessage("Upload"), diff --git a/lib/l10n/intl/messages_zh_CN.dart b/lib/l10n/intl/messages_zh_CN.dart index 50688fe3..38881ed6 100644 --- a/lib/l10n/intl/messages_zh_CN.dart +++ b/lib/l10n/intl/messages_zh_CN.dart @@ -41,6 +41,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"), "allowLan": MessageLookupByLibrary.simpleMessage("局域网代理"), "allowLanDesc": MessageLookupByLibrary.simpleMessage("允许通过局域网访问代理"), + "app": MessageLookupByLibrary.simpleMessage("应用"), "appAccessControl": MessageLookupByLibrary.simpleMessage("应用访问控制"), "application": MessageLookupByLibrary.simpleMessage("应用程序"), "applicationDesc": MessageLookupByLibrary.simpleMessage("修改应用程序相关设置"), @@ -98,12 +99,16 @@ class MessageLookup extends MessageLookupByLibrary { "exit": MessageLookupByLibrary.simpleMessage("退出"), "externalController": MessageLookupByLibrary.simpleMessage("外部控制器"), "externalControllerDesc": - MessageLookupByLibrary.simpleMessage("开启后可通过9090端口控制clash内核"), + MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制clash内核"), "externalResources": MessageLookupByLibrary.simpleMessage("外部资源"), "file": MessageLookupByLibrary.simpleMessage("文件"), "fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"), "filterSystemApp": MessageLookupByLibrary.simpleMessage("过滤系统应用"), + "general": MessageLookupByLibrary.simpleMessage("基础"), "geoData": MessageLookupByLibrary.simpleMessage("地理数据"), + "geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低内存模式"), + "geodataLoaderDesc": + MessageLookupByLibrary.simpleMessage("开启将使用Geo低内存加载器"), "global": MessageLookupByLibrary.simpleMessage("全局"), "goDownload": MessageLookupByLibrary.simpleMessage("前往下载"), "hours": MessageLookupByLibrary.simpleMessage("小时"), @@ -167,6 +172,7 @@ class MessageLookup extends MessageLookupByLibrary { "project": MessageLookupByLibrary.simpleMessage("项目"), "proxies": MessageLookupByLibrary.simpleMessage("代理"), "proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"), + "proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置clash监听端口"), "qrcode": MessageLookupByLibrary.simpleMessage("二维码"), "qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"), "recovery": MessageLookupByLibrary.simpleMessage("恢复"), @@ -189,9 +195,13 @@ class MessageLookup extends MessageLookupByLibrary { "stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."), "submit": MessageLookupByLibrary.simpleMessage("提交"), "systemProxy": MessageLookupByLibrary.simpleMessage("系统代理"), + "systemProxyDesc": + MessageLookupByLibrary.simpleMessage("为VpnService附加HTTP代理"), "tabAnimation": MessageLookupByLibrary.simpleMessage("选项卡动画"), "tabAnimationDesc": MessageLookupByLibrary.simpleMessage("开启后,主页选项卡将添加切换动画"), + "tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"), + "tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许tcp并发"), "theme": MessageLookupByLibrary.simpleMessage("主题"), "themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"), "themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"), @@ -203,6 +213,8 @@ class MessageLookup extends MessageLookupByLibrary { "tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"), "unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage("无法更新当前配置文件"), + "unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"), + "unifiedDelayDesc": MessageLookupByLibrary.simpleMessage("去除握手等额外延迟"), "unknown": MessageLookupByLibrary.simpleMessage("未知"), "update": MessageLookupByLibrary.simpleMessage("更新"), "upload": MessageLookupByLibrary.simpleMessage("上传"), diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index fdfbe0d8..586e5a43 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -1170,6 +1170,16 @@ class AppLocalizations { ); } + /// `Set the clash listening port` + String get proxyPortDesc { + return Intl.message( + 'Set the clash listening port', + name: 'proxyPortDesc', + desc: '', + args: [], + ); + } + /// `Port` String get port { return Intl.message( @@ -1690,10 +1700,10 @@ class AppLocalizations { ); } - /// `Enabled to some applications can bypass VPN` + /// `Some apps can bypass VPN when turned on` String get allowBypassDesc { return Intl.message( - 'Enabled to some applications can bypass VPN', + 'Some apps can bypass VPN when turned on', name: 'allowBypassDesc', desc: '', args: [], @@ -1710,25 +1720,115 @@ class AppLocalizations { ); } - /// `Enabled to control the clash on port 9090` + /// `Once enabled, the clash kernel can be controlled on port 9090` String get externalControllerDesc { return Intl.message( - 'Enabled to control the clash on port 9090', + 'Once enabled, the clash kernel can be controlled on port 9090', name: 'externalControllerDesc', desc: '', args: [], ); } - /// `Enabled to will allow it to receive ipv6 traffic` + /// `When turned on it will be able to receive ipv6 traffic` String get ipv6Desc { return Intl.message( - 'Enabled to will allow it to receive ipv6 traffic', + 'When turned on it will be able to receive ipv6 traffic', name: 'ipv6Desc', desc: '', args: [], ); } + + /// `App` + String get app { + return Intl.message( + 'App', + name: 'app', + desc: '', + args: [], + ); + } + + /// `General` + String get general { + return Intl.message( + 'General', + name: 'general', + desc: '', + args: [], + ); + } + + /// `Attach HTTP proxy to VpnService` + String get systemProxyDesc { + return Intl.message( + 'Attach HTTP proxy to VpnService', + name: 'systemProxyDesc', + desc: '', + args: [], + ); + } + + /// `Unified delay` + String get unifiedDelay { + return Intl.message( + 'Unified delay', + name: 'unifiedDelay', + desc: '', + args: [], + ); + } + + /// `Remove extra delays such as handshaking` + String get unifiedDelayDesc { + return Intl.message( + 'Remove extra delays such as handshaking', + name: 'unifiedDelayDesc', + desc: '', + args: [], + ); + } + + /// `Tcp concurrent` + String get tcpConcurrent { + return Intl.message( + 'Tcp concurrent', + name: 'tcpConcurrent', + desc: '', + args: [], + ); + } + + /// `Enabling it will allow tcp concurrency` + String get tcpConcurrentDesc { + return Intl.message( + 'Enabling it will allow tcp concurrency', + name: 'tcpConcurrentDesc', + desc: '', + args: [], + ); + } + + /// `Geo Low Memory Mode` + String get geodataLoader { + return Intl.message( + 'Geo Low Memory Mode', + name: 'geodataLoader', + desc: '', + args: [], + ); + } + + /// `Enabling will use the Geo low memory loader` + String get geodataLoaderDesc { + return Intl.message( + 'Enabling will use the Geo low memory loader', + name: 'geodataLoaderDesc', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/models/clash_config.dart b/lib/models/clash_config.dart index 5b7daab0..d3001354 100644 --- a/lib/models/clash_config.dart +++ b/lib/models/clash_config.dart @@ -109,9 +109,12 @@ class ClashConfig extends ChangeNotifier { int _mixedPort; bool _allowLan; bool _ipv6; + String _geodataLoader; + LogLevel _logLevel; String _externalController; Mode _mode; - LogLevel _logLevel; + bool _unifiedDelay; + bool _tcpConcurrent; Tun _tun; Dns _dns; List _rules; @@ -123,15 +126,21 @@ class ClashConfig extends ChangeNotifier { bool? ipv6, LogLevel? logLevel, String? externalController, + String? geodataLoader, + bool? unifiedDelay, Tun? tun, Dns? dns, + bool? tcpConcurrent, List? rules, }) : _mixedPort = mixedPort ?? 7890, _mode = mode ?? Mode.rule, _ipv6 = ipv6 ?? false, _allowLan = allowLan ?? false, + _tcpConcurrent = tcpConcurrent ?? false, _logLevel = logLevel ?? LogLevel.info, _tun = tun ?? const Tun(), + _unifiedDelay = unifiedDelay ?? false, + _geodataLoader = geodataLoader ?? geodataLoaderMemconservative, _externalController = externalController ?? '', _dns = dns ?? Dns(), _rules = rules ?? []; @@ -195,6 +204,36 @@ class ClashConfig extends ChangeNotifier { } } + @JsonKey(name: "geodata-loader", defaultValue: geodataLoaderMemconservative) + String get geodataLoader => _geodataLoader; + + set geodataLoader(String value) { + if (_geodataLoader != value) { + _geodataLoader = value; + notifyListeners(); + } + } + + @JsonKey(name: "unified-delay", defaultValue: false) + bool get unifiedDelay => _unifiedDelay; + + set unifiedDelay(bool value) { + if (_unifiedDelay != value) { + _unifiedDelay = value; + notifyListeners(); + } + } + + @JsonKey(name: "tcp-concurrent", defaultValue: false) + bool get tcpConcurrent => _tcpConcurrent; + + set tcpConcurrent(bool value) { + if (_tcpConcurrent != value) { + _tcpConcurrent = value; + notifyListeners(); + } + } + Tun get tun => _tun; set tun(Tun value) { diff --git a/lib/models/config.dart b/lib/models/config.dart index fe1bd4f7..79a00d4b 100644 --- a/lib/models/config.dart +++ b/lib/models/config.dart @@ -29,6 +29,7 @@ class Props with _$Props { const factory Props({ AccessControl? accessControl, bool? allowBypass, + bool? systemProxy, }) = _Props; factory Props.fromJson(Map json) => @@ -54,6 +55,7 @@ class Config extends ChangeNotifier { bool _isAnimateToPage; bool _autoCheckUpdate; bool _allowBypass; + bool _systemProxy; DAV? _dav; Config() @@ -69,6 +71,7 @@ class Config extends ChangeNotifier { _isMinimizeOnExit = true, _isAccessControl = false, _autoCheckUpdate = true, + _systemProxy = false, _accessControl = const AccessControl(), _isAnimateToPage = true, _allowBypass = true; @@ -331,6 +334,18 @@ class Config extends ChangeNotifier { } } + @JsonKey(defaultValue: true) + bool get systemProxy { + return _systemProxy; + } + + set systemProxy(bool value) { + if (_systemProxy != value) { + _systemProxy = value; + notifyListeners(); + } + } + update([ Config? config, RecoveryOption recoveryOptions = RecoveryOption.all, diff --git a/lib/models/generated/clash_config.g.dart b/lib/models/generated/clash_config.g.dart index cff1c837..88b231c8 100644 --- a/lib/models/generated/clash_config.g.dart +++ b/lib/models/generated/clash_config.g.dart @@ -39,14 +39,18 @@ ClashConfig _$ClashConfigFromJson(Map json) => ClashConfig( mixedPort: (json['mixed-port'] as num?)?.toInt(), mode: $enumDecodeNullable(_$ModeEnumMap, json['mode']), allowLan: json['allow-lan'] as bool?, + ipv6: json['ipv6'] as bool? ?? false, logLevel: $enumDecodeNullable(_$LogLevelEnumMap, json['log-level']), externalController: json['external-controller'] as String? ?? '', + geodataLoader: json['geodata-loader'] as String? ?? 'memconservative', + unifiedDelay: json['unified-delay'] as bool? ?? false, tun: json['tun'] == null ? null : Tun.fromJson(json['tun'] as Map), dns: json['dns'] == null ? null : Dns.fromJson(json['dns'] as Map), + tcpConcurrent: json['tcp-concurrent'] as bool? ?? false, rules: (json['rules'] as List?)?.map((e) => e as String).toList(), ); @@ -58,6 +62,10 @@ Map _$ClashConfigToJson(ClashConfig instance) => 'allow-lan': instance.allowLan, 'log-level': _$LogLevelEnumMap[instance.logLevel]!, 'external-controller': instance.externalController, + 'ipv6': instance.ipv6, + 'geodata-loader': instance.geodataLoader, + 'unified-delay': instance.unifiedDelay, + 'tcp-concurrent': instance.tcpConcurrent, 'tun': instance.tun, 'dns': instance.dns, 'rules': instance.rules, diff --git a/lib/models/generated/config.freezed.dart b/lib/models/generated/config.freezed.dart index 0c22ee47..3ec63a17 100644 --- a/lib/models/generated/config.freezed.dart +++ b/lib/models/generated/config.freezed.dart @@ -248,6 +248,7 @@ Props _$PropsFromJson(Map json) { mixin _$Props { AccessControl? get accessControl => throw _privateConstructorUsedError; bool? get allowBypass => throw _privateConstructorUsedError; + bool? get systemProxy => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -259,7 +260,8 @@ abstract class $PropsCopyWith<$Res> { factory $PropsCopyWith(Props value, $Res Function(Props) then) = _$PropsCopyWithImpl<$Res, Props>; @useResult - $Res call({AccessControl? accessControl, bool? allowBypass}); + $Res call( + {AccessControl? accessControl, bool? allowBypass, bool? systemProxy}); $AccessControlCopyWith<$Res>? get accessControl; } @@ -279,6 +281,7 @@ class _$PropsCopyWithImpl<$Res, $Val extends Props> $Res call({ Object? accessControl = freezed, Object? allowBypass = freezed, + Object? systemProxy = freezed, }) { return _then(_value.copyWith( accessControl: freezed == accessControl @@ -289,6 +292,10 @@ class _$PropsCopyWithImpl<$Res, $Val extends Props> ? _value.allowBypass : allowBypass // ignore: cast_nullable_to_non_nullable as bool?, + systemProxy: freezed == systemProxy + ? _value.systemProxy + : systemProxy // ignore: cast_nullable_to_non_nullable + as bool?, ) as $Val); } @@ -312,7 +319,8 @@ abstract class _$$PropsImplCopyWith<$Res> implements $PropsCopyWith<$Res> { __$$PropsImplCopyWithImpl<$Res>; @override @useResult - $Res call({AccessControl? accessControl, bool? allowBypass}); + $Res call( + {AccessControl? accessControl, bool? allowBypass, bool? systemProxy}); @override $AccessControlCopyWith<$Res>? get accessControl; @@ -331,6 +339,7 @@ class __$$PropsImplCopyWithImpl<$Res> $Res call({ Object? accessControl = freezed, Object? allowBypass = freezed, + Object? systemProxy = freezed, }) { return _then(_$PropsImpl( accessControl: freezed == accessControl @@ -341,6 +350,10 @@ class __$$PropsImplCopyWithImpl<$Res> ? _value.allowBypass : allowBypass // ignore: cast_nullable_to_non_nullable as bool?, + systemProxy: freezed == systemProxy + ? _value.systemProxy + : systemProxy // ignore: cast_nullable_to_non_nullable + as bool?, )); } } @@ -348,7 +361,7 @@ class __$$PropsImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() class _$PropsImpl implements _Props { - const _$PropsImpl({this.accessControl, this.allowBypass}); + const _$PropsImpl({this.accessControl, this.allowBypass, this.systemProxy}); factory _$PropsImpl.fromJson(Map json) => _$$PropsImplFromJson(json); @@ -357,10 +370,12 @@ class _$PropsImpl implements _Props { final AccessControl? accessControl; @override final bool? allowBypass; + @override + final bool? systemProxy; @override String toString() { - return 'Props(accessControl: $accessControl, allowBypass: $allowBypass)'; + return 'Props(accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy)'; } @override @@ -371,12 +386,15 @@ class _$PropsImpl implements _Props { (identical(other.accessControl, accessControl) || other.accessControl == accessControl) && (identical(other.allowBypass, allowBypass) || - other.allowBypass == allowBypass)); + other.allowBypass == allowBypass) && + (identical(other.systemProxy, systemProxy) || + other.systemProxy == systemProxy)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash(runtimeType, accessControl, allowBypass); + int get hashCode => + Object.hash(runtimeType, accessControl, allowBypass, systemProxy); @JsonKey(ignore: true) @override @@ -395,7 +413,8 @@ class _$PropsImpl implements _Props { abstract class _Props implements Props { const factory _Props( {final AccessControl? accessControl, - final bool? allowBypass}) = _$PropsImpl; + final bool? allowBypass, + final bool? systemProxy}) = _$PropsImpl; factory _Props.fromJson(Map json) = _$PropsImpl.fromJson; @@ -404,6 +423,8 @@ abstract class _Props implements Props { @override bool? get allowBypass; @override + bool? get systemProxy; + @override @JsonKey(ignore: true) _$$PropsImplCopyWith<_$PropsImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/models/generated/config.g.dart b/lib/models/generated/config.g.dart index c6a4120e..6d3743d1 100644 --- a/lib/models/generated/config.g.dart +++ b/lib/models/generated/config.g.dart @@ -33,7 +33,8 @@ Config _$ConfigFromJson(Map json) => Config() ..isAnimateToPage = json['isAnimateToPage'] as bool? ?? true ..isCompatible = json['isCompatible'] as bool? ?? true ..autoCheckUpdate = json['autoCheckUpdate'] as bool? ?? true - ..allowBypass = json['allowBypass'] as bool? ?? true; + ..allowBypass = json['allowBypass'] as bool? ?? true + ..systemProxy = json['systemProxy'] as bool? ?? true; Map _$ConfigToJson(Config instance) => { 'profiles': instance.profiles, @@ -54,6 +55,7 @@ Map _$ConfigToJson(Config instance) => { 'isCompatible': instance.isCompatible, 'autoCheckUpdate': instance.autoCheckUpdate, 'allowBypass': instance.allowBypass, + 'systemProxy': instance.systemProxy, }; const _$ThemeModeEnumMap = { @@ -102,10 +104,12 @@ _$PropsImpl _$$PropsImplFromJson(Map json) => _$PropsImpl( : AccessControl.fromJson( json['accessControl'] as Map), allowBypass: json['allowBypass'] as bool?, + systemProxy: json['systemProxy'] as bool?, ); Map _$$PropsImplToJson(_$PropsImpl instance) => { 'accessControl': instance.accessControl, 'allowBypass': instance.allowBypass, + 'systemProxy': instance.systemProxy, }; diff --git a/lib/plugins/app.dart b/lib/plugins/app.dart index b2b68fb0..799646b2 100644 --- a/lib/plugins/app.dart +++ b/lib/plugins/app.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; +import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/models/models.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -17,10 +18,8 @@ class App { methodChannel = const MethodChannel("app"); methodChannel!.setMethodCallHandler((call) async { switch (call.method) { - case "exit": - if (onExit != null) { - await onExit!(); - } + case "gc": + clashCore.requestGc(); break; default: throw MissingPluginException(); @@ -29,10 +28,6 @@ class App { } } - setOnExit(Function() onExit) { - this.onExit = onExit; - } - factory App() { _instance ??= App._internal(); return _instance!; diff --git a/pubspec.yaml b/pubspec.yaml index d14e1546..0d86c97b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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.20 +version: 0.8.21 environment: sdk: '>=3.1.0 <4.0.0'