Compare commits

..

1 Commits

Author SHA1 Message Date
chen08209
851935ed92 Support override script
Support proxies search

Support svg display

Optimize config persistence

Add some scenes auto close connections

Update core

Optimize more details
2025-06-06 19:09:36 +08:00
63 changed files with 1165 additions and 575 deletions

View File

@@ -63,10 +63,17 @@ jobs:
cache-dependency-path: |
core/go.sum
- name: Setup Flutter
- name: Setup Flutter Master
if: startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')
uses: subosito/flutter-action@v2
with:
channel: ${{ (startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')) && 'master' || 'stable' }}
channel: 'master'
cache: true
- name: Setup Flutter
if: ${{ !(startsWith(matrix.os, 'windows-11-arm') || startsWith(matrix.os, 'ubuntu-24.04-arm')) }}
uses: subosito/flutter-action@v2
with:
channel: 'stable'
cache: true
flutter-version: 3.29.3

View File

@@ -7,6 +7,9 @@
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -22,6 +25,7 @@
<application
android:name=".FlClashApplication"
android:banner="@mipmap/ic_banner"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="FlClash">
@@ -88,7 +92,7 @@
<service
android:name=".services.FlClashTileService"
android:exported="true"
android:icon="@drawable/ic_stat_name"
android:icon="@drawable/ic"
android:label="FlClash"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
tools:targetApi="n">

View File

@@ -16,7 +16,6 @@ import com.follow.clash.MainActivity
import com.follow.clash.R
import com.follow.clash.extensions.getActionPendingIntent
import com.follow.clash.models.VpnOptions
import io.flutter.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
@@ -54,7 +53,7 @@ fun Service.createFlClashNotificationBuilder(): Deferred<NotificationCompat.Buil
this@createFlClashNotificationBuilder, GlobalState.NOTIFICATION_CHANNEL
)
) {
setSmallIcon(R.drawable.ic_stat_name)
setSmallIcon(R.drawable.ic)
setContentTitle("FlClash")
setContentIntent(pendingIntent)
setCategory(NotificationCompat.CATEGORY_SERVICE)
@@ -76,9 +75,8 @@ fun Service.startForeground(notification: Notification) {
val manager = getSystemService(NotificationManager::class.java)
var channel = manager?.getNotificationChannel(GlobalState.NOTIFICATION_CHANNEL)
if (channel == null) {
Log.d("[FlClash]","createNotificationChannel===>")
channel = NotificationChannel(
GlobalState.NOTIFICATION_CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW
GlobalState.NOTIFICATION_CHANNEL, "SERVICE_CHANNEL", NotificationManager.IMPORTANCE_LOW
)
manager?.createNotificationChannel(channel)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="240dp"
android:height="240dp"
android:viewportWidth="240"
android:viewportHeight="240"
tools:ignore="VectorRaster">
<path
android:pathData="M48.1,80.89L168.44,11.41c11.08,-6.4 25.24,-2.6 31.64,8.48 0,0 0,0 0,0h0c6.4,11.08 2.6,25.24 -8.48,31.64 0,0 0,0 0,0l-120.34,69.48c-11.08,6.4 -25.24,2.6 -31.64,-8.48 0,0 0,0 0,0h0c-6.4,-11.08 -2.6,-25.24 8.48,-31.64 0,0 0,0 0,0Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M78.98,134.37l60.18,-34.74c11.07,-6.39 25.23,-2.59 31.63,8.48h0c6.4,11.07 2.61,25.24 -8.47,31.64l-60.18,34.74c-11.08,6.4 -25.24,2.6 -31.64,-8.48 0,0 0,0 0,0h0c-6.4,-11.08 -2.6,-25.24 8.48,-31.64h0Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M109.86,187.86h0c11.08,-6.4 25.24,-2.6 31.64,8.48 0,0 0,0 0,0h0c6.4,11.08 2.6,25.24 -8.48,31.64 0,0 0,0 0,0h0c-11.08,6.4 -25.24,2.6 -31.64,-8.48 0,0 0,0 0,0h0c-6.4,-11.08 -2.6,-25.24 8.48,-31.64 0,0 0,0 0,0Z"
android:fillColor="#FFFFFF"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -402,5 +402,9 @@
"redirPort": "Redir Port",
"tproxyPort": "Tproxy Port",
"portTip": "{label} must be between 1024 and 49151",
"portConflictTip": "Please enter a different port"
"portConflictTip": "Please enter a different port",
"import": "Import",
"importFile": "Import from file",
"importUrl": "Import from URL",
"autoSetSystemDns": "Auto set system DNS"
}

View File

@@ -403,5 +403,9 @@
"redirPort": "Redirポート",
"tproxyPort": "Tproxyポート",
"portTip": "{label} は 1024 から 49151 の間でなければなりません",
"portConflictTip": "別のポートを入力してください"
"portConflictTip": "別のポートを入力してください",
"import": "インポート",
"importFile": "ファイルからインポート",
"importUrl": "URLからインポート",
"autoSetSystemDns": "オートセットシステムDNS"
}

View File

@@ -403,5 +403,9 @@
"redirPort": "Redir-порт",
"tproxyPort": "Tproxy-порт",
"portTip": "{label} должен быть числом от 1024 до 49151",
"portConflictTip": "Введите другой порт"
"portConflictTip": "Введите другой порт",
"import": "Импорт",
"importFile": "Импорт из файла",
"importUrl": "Импорт по URL",
"autoSetSystemDns": "Автоматическая настройка системного DNS"
}

View File

@@ -403,5 +403,9 @@
"redirPort": "Redir端口",
"tproxyPort": "Tproxy端口",
"portTip": "{label} 必须在 1024 到 49151 之间",
"portConflictTip": "请输入不同的端口"
"portConflictTip": "请输入不同的端口",
"import": "导入",
"importFile": "通过文件导入",
"importUrl": "通过URL导入",
"autoSetSystemDns": "自动设置系统DNS"
}

View File

@@ -6,7 +6,6 @@ replace github.com/metacubex/mihomo => ./Clash.Meta
require (
github.com/metacubex/mihomo v0.0.0-00010101000000-000000000000
github.com/samber/lo v1.50.0
golang.org/x/sync v0.11.0
)
@@ -21,7 +20,7 @@ require (
github.com/cloudflare/circl v1.3.7 // indirect
github.com/coreos/go-iptables v0.8.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/ebitengine/purego v0.8.3-0.20250507171810-1638563e3615 // indirect
github.com/ebitengine/purego v0.8.3 // indirect
github.com/enfein/mieru/v3 v3.13.0 // indirect
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
@@ -51,27 +50,27 @@ require (
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect
github.com/metacubex/bart v0.19.0 // indirect
github.com/metacubex/bart v0.20.5 // indirect
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 // indirect
github.com/metacubex/chacha v0.1.2 // indirect
github.com/metacubex/fswatch v0.1.1 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
github.com/metacubex/quic-go v0.51.1-0.20250511032541-4e34341cf18b // indirect
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639 // indirect
github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 // indirect
github.com/metacubex/sing v0.5.3 // indirect
github.com/metacubex/sing-mux v0.3.2 // indirect
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2 // indirect
github.com/metacubex/sing-shadowsocks v0.2.9 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.3 // indirect
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f // indirect
github.com/metacubex/sing-shadowsocks v0.2.10 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.4 // indirect
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 // indirect
github.com/metacubex/sing-vmess v0.2.1 // indirect
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c // indirect
github.com/metacubex/sing-vmess v0.2.2 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee // indirect
github.com/metacubex/tfo-go v0.0.0-20250503140532-decbcfccbfdf // indirect
github.com/metacubex/utls v1.7.0-alpha.3 // indirect
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 // indirect
github.com/metacubex/utls v1.7.3 // indirect
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
@@ -85,6 +84,7 @@ require (
github.com/quic-go/qpack v0.4.0 // indirect
github.com/sagernet/cors v1.2.1 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/samber/lo v1.50.0 // indirect
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect

View File

@@ -26,8 +26,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/ebitengine/purego v0.8.3-0.20250507171810-1638563e3615 h1:W7mpP4uiOAbBOdDnRXT9EUdauFv7bz+ERT5rPIord00=
github.com/ebitengine/purego v0.8.3-0.20250507171810-1638563e3615/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98=
github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
@@ -97,8 +97,8 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY=
github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM=
github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc=
@@ -111,35 +111,35 @@ github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
github.com/metacubex/quic-go v0.51.1-0.20250511032541-4e34341cf18b h1:8oDU32eJ+RRhl1FodGgPfxQxtoBAiD9D40XG2XtU/SE=
github.com/metacubex/quic-go v0.51.1-0.20250511032541-4e34341cf18b/go.mod h1:9R1NOzCgTcWsdWvOMlmtMuF0uKzuOpsfvEf7U3I8zM0=
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639 h1:L+1brQNzBhCCxWlicwfK1TlceemCRmrDE4HmcVHc29w=
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639/go.mod h1:Kc6h++Q/zf3AxcUCevJhJwgrskJumv+pZdR8g/E/10k=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 h1:m4nSxvw46JEgxMzzmnXams+ebwabcry4Ydep/zNiesQ=
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.3 h1:QWdN16WFKMk06x4nzkc8SvZ7y2x+TLQrpkPoHs+WSVM=
github.com/metacubex/sing v0.5.3/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2 h1:wfmYgtECbEYo1slMtyo+2kMqscYYDSjU/TVgS3018F4=
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2/go.mod h1:P1kd57U6XXmXv9PbwWdznUGT0k9bKgFJXF0fEORbIlk=
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00=
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA=
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0=
github.com/metacubex/sing-shadowsocks2 v0.2.3/go.mod h1:/WNy/Q8ahLCoPRriWuFZFD0Jy+JNp1MEQl28Zw6SaF8=
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f h1:mP3vIm+9hRFI0C0Vl3pE0NESF/L85FDbuB0tGgUii6I=
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
github.com/metacubex/sing-shadowsocks v0.2.10 h1:Pr7LDbjMANIQHl07zWgl1vDuhpsfDQUpZ8cX6DPabfg=
github.com/metacubex/sing-shadowsocks v0.2.10/go.mod h1:MtRM0ZZjR0kaDOzy9zWSt6/4/UlrnsNBq+1FNAF4vBk=
github.com/metacubex/sing-shadowsocks2 v0.2.4 h1:Ec0x3hHR7xkld5Z09IGh16wtUUpBb2HgqZ9DExd8Q7s=
github.com/metacubex/sing-shadowsocks2 v0.2.4/go.mod h1:WP8+S0kqtnSbX1vlIpo5i8Irm/ijZITEPBcZ26B5unY=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 h1:TAwL91XPa6x1QK55CRm+VTzPvLPUfEr/uFDnOZArqEU=
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
github.com/metacubex/sing-vmess v0.2.1 h1:I6gM3VUjtvJ15D805EUbNH+SRBuqzJeFnuIbKYUsWZ0=
github.com/metacubex/sing-vmess v0.2.1/go.mod h1:DsODWItJtOMZNna8Qhheg8r3tUivrcO3vWgaTYKnfTo=
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c h1:Y6jk7AH5BEg9Dsvczrf/KokYsvxeKSZZlCLHg+hC4ro=
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
github.com/metacubex/sing-vmess v0.2.2 h1:nG6GIKF1UOGmlzs+BIetdGHkFZ20YqFVIYp5Htqzp+4=
github.com/metacubex/sing-vmess v0.2.2/go.mod h1:CVDNcdSLVYFgTHQlubr88d8CdqupAUDqLjROos+H9xk=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20250503140532-decbcfccbfdf h1:LwID1wz4tzypidd412dd4dC1H0m1TgRCQ/XvRvMJDFM=
github.com/metacubex/tfo-go v0.0.0-20250503140532-decbcfccbfdf/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.7.0-alpha.3 h1:cp1cEMUnoifiWrGHRzo+nCwPRveN9yPD8QaRFmfcYxA=
github.com/metacubex/utls v1.7.0-alpha.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.7.3 h1:yDcMEWojFh+t8rU9X0HPcZDPAoFze/rIIyssqivzj8A=
github.com/metacubex/utls v1.7.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=

View File

@@ -66,6 +66,11 @@ class ClashCore {
Future<bool> init() async {
await initGeo();
if (globalState.config.appSetting.openLogs) {
clashCore.startLog();
} else {
clashCore.stopLog();
}
final homeDirPath = await appPath.homeDirPath;
return await clashInterface.init(
InitParams(

View File

@@ -6,7 +6,7 @@ import 'dart:isolate';
import 'dart:ui';
import 'package:ffi/ffi.dart';
import 'package:fl_clash/common/constant.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/service.dart';
@@ -240,18 +240,19 @@ class ClashLibHandler {
return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
}
// Map<String, dynamic> getConfig(String path) {
// final pathChar = path.toNativeUtf8().cast<Char>();
// final configRaw = clashFFI.getConfig(pathChar);
// final configString = configRaw.cast<Utf8>().toDartString();
// if (configString.isEmpty) {
// return {};
// }
// final config = json.decode(configString);
// malloc.free(pathChar);
// clashFFI.freeCString(configRaw);
// return config;
// }
Future<Map<String, dynamic>> getConfig(String id) async {
final path = await appPath.getProfilePath(id);
final pathChar = path.toNativeUtf8().cast<Char>();
final configRaw = clashFFI.getConfig(pathChar);
final configString = configRaw.cast<Utf8>().toDartString();
if (configString.isEmpty) {
return {};
}
final config = json.decode(configString);
malloc.free(pathChar);
clashFFI.freeCString(configRaw);
return config;
}
Future<String> quickStart(
InitParams initParams,
@@ -289,4 +290,4 @@ ClashLib? get clashLib =>
Platform.isAndroid && !globalState.isService ? ClashLib() : null;
ClashLibHandler? get clashLibHandler =>
Platform.isAndroid ? ClashLibHandler() : null;
Platform.isAndroid && globalState.isService ? ClashLibHandler() : null;

View File

@@ -11,7 +11,6 @@ export 'future.dart';
export 'http.dart';
export 'icons.dart';
export 'iterable.dart';
export 'javascript.dart';
export 'keyboard.dart';
export 'launch.dart';
export 'link.dart';

View File

@@ -18,6 +18,7 @@ class FlClashHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
final client = super.createHttpClient(context);
client.badCertificateCallback = (_, __, ___) => true;
client.findProxy = handleFindProxy;
return client;
}

View File

@@ -1,18 +0,0 @@
import 'package:flutter_js/flutter_js.dart';
class Javascript {
static Javascript? _instance;
Javascript._internal();
JavascriptRuntime get runTime {
return getJavascriptRuntime();
}
factory Javascript() {
_instance ??= Javascript._internal();
return _instance!;
}
}
final js = Javascript();

View File

@@ -1,11 +1,10 @@
import 'dart:async';
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'constant.dart';
class AppPath {
static AppPath? _instance;
Completer<Directory> dataDir = Completer();
@@ -78,17 +77,28 @@ class AppPath {
return join(directory, "$id.yaml");
}
Future<String> getProvidersPath(String id, {String filePath = ""}) async {
Future<String> getProvidersDirPath(String id) async {
final directory = await profilesPath;
final path = join(
return join(
directory,
"providers",
id,
);
if (filePath.isNotEmpty) {
return join(path, filePath);
}
return path;
}
Future<String> getProvidersFilePath(
String id,
String type,
String url,
) async {
final directory = await profilesPath;
return join(
directory,
"providers",
id,
type,
url.toMd5(),
);
}
Future<String> get tempPath async {

View File

@@ -1,6 +1,8 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'print.dart';
extension StringExtension on String {
@@ -45,6 +47,10 @@ extension StringExtension on String {
}
}
bool get isSvg {
return endsWith(".svg");
}
bool get isRegex {
try {
RegExp(this);
@@ -55,6 +61,11 @@ extension StringExtension on String {
}
}
String toMd5() {
final bytes = utf8.encode(this);
return md5.convert(bytes).toString();
}
// bool containsToLower(String target) {
// return toLowerCase().contains(target);
// }

View File

@@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
class System {
static System? _instance;
List<String>? originDns;
System._internal();
@@ -104,6 +105,100 @@ class System {
return AuthorizeCode.error;
}
Future<String?> getMacOSDefaultServiceName() async {
if (!Platform.isMacOS) {
return null;
}
final result = await Process.run('route', ['-n', 'get', 'default']);
final output = result.stdout.toString();
final deviceLine = output
.split('\n')
.firstWhere((s) => s.contains('interface:'), orElse: () => "");
final lineSplits = deviceLine.trim().split(' ');
if (lineSplits.length != 2) {
return null;
}
final device = lineSplits[1];
final serviceResult = await Process.run(
'networksetup',
['-listnetworkserviceorder'],
);
final serviceResultOutput = serviceResult.stdout.toString();
final currentService = serviceResultOutput.split('\n\n').firstWhere(
(s) => s.contains("Device: $device"),
orElse: () => "",
);
if (currentService.isEmpty) {
return null;
}
final currentServiceNameLine = currentService.split("\n").firstWhere(
(line) => RegExp(r'^\(\d+\).*').hasMatch(line),
orElse: () => "");
final currentServiceNameLineSplits =
currentServiceNameLine.trim().split(' ');
if (currentServiceNameLineSplits.length < 2) {
return null;
}
return currentServiceNameLineSplits[1];
}
Future<List<String>?> getMacOSOriginDns() async {
if (!Platform.isMacOS) {
return null;
}
final deviceServiceName = await getMacOSDefaultServiceName();
if (deviceServiceName == null) {
return null;
}
final result = await Process.run(
'networksetup',
['-getdnsservers', deviceServiceName],
);
final output = result.stdout.toString().trim();
if (output.startsWith("There aren't any DNS Servers set on")) {
originDns = [];
} else {
originDns = output.split("\n");
}
return originDns;
}
setMacOSDns(bool restore) async {
if (!Platform.isMacOS) {
return;
}
final serviceName = await getMacOSDefaultServiceName();
if (serviceName == null) {
return;
}
List<String>? nextDns;
if (restore) {
nextDns = originDns;
} else {
final originDns = await system.getMacOSOriginDns();
if (originDns == null) {
return;
}
final needAddDns = "223.5.5.5";
if (originDns.contains(needAddDns)) {
return;
}
nextDns = List.from(originDns)..add(needAddDns);
}
if (nextDns == null) {
return;
}
await Process.run(
'networksetup',
[
'-setdnsservers',
serviceName,
if (nextDns.isNotEmpty) ...nextDns,
if (nextDns.isEmpty) "Empty",
],
);
}
back() async {
await app?.moveTaskToBack();
await window?.hide();

View File

@@ -9,7 +9,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:lpinyin/lpinyin.dart';
import 'package:yaml/yaml.dart';
class Utils {
Color? getDelayColor(int? delay) {
@@ -337,51 +336,51 @@ class Utils {
);
}
dynamic convertYamlNode(dynamic node) {
if (node is YamlMap) {
final map = <String, dynamic>{};
YamlNode? mergeKeyNode;
for (final entry in node.nodes.entries) {
if (entry.key is YamlScalar &&
(entry.key as YamlScalar).value == '<<') {
mergeKeyNode = entry.value;
break;
}
}
if (mergeKeyNode != null) {
final mergeValue = mergeKeyNode.value;
if (mergeValue is YamlMap) {
map.addAll(convertYamlNode(mergeValue) as Map<String, dynamic>);
} else if (mergeValue is YamlList) {
for (final node in mergeValue.nodes) {
if (node.value is YamlMap) {
map.addAll(convertYamlNode(node.value) as Map<String, dynamic>);
}
}
}
}
node.nodes.forEach((key, value) {
String stringKey;
if (key is YamlScalar) {
stringKey = key.value.toString();
} else {
stringKey = key.toString();
}
map[stringKey] = convertYamlNode(value.value);
});
return map;
} else if (node is YamlList) {
final list = <dynamic>[];
for (final item in node.nodes) {
list.add(convertYamlNode(item.value));
}
return list;
} else if (node is YamlScalar) {
return node.value;
}
return node;
}
// dynamic convertYamlNode(dynamic node) {
// if (node is YamlMap) {
// final map = <String, dynamic>{};
// YamlNode? mergeKeyNode;
// for (final entry in node.nodes.entries) {
// if (entry.key is YamlScalar &&
// (entry.key as YamlScalar).value == '<<') {
// mergeKeyNode = entry.value;
// break;
// }
// }
// if (mergeKeyNode != null) {
// final mergeValue = mergeKeyNode.value;
// if (mergeValue is YamlMap) {
// map.addAll(convertYamlNode(mergeValue) as Map<String, dynamic>);
// } else if (mergeValue is YamlList) {
// for (final node in mergeValue.nodes) {
// if (node.value is YamlMap) {
// map.addAll(convertYamlNode(node.value) as Map<String, dynamic>);
// }
// }
// }
// }
//
// node.nodes.forEach((key, value) {
// String stringKey;
// if (key is YamlScalar) {
// stringKey = key.value.toString();
// } else {
// stringKey = key.toString();
// }
// map[stringKey] = convertYamlNode(value.value);
// });
// return map;
// } else if (node is YamlList) {
// final list = <dynamic>[];
// for (final item in node.nodes) {
// list.add(convertYamlNode(item.value));
// }
// return list;
// } else if (node is YamlScalar) {
// return node.value;
// }
// return node;
// }
FutureOr<T> handleWatch<T>(Function function) async {
if (kDebugMode) {

View File

@@ -22,7 +22,6 @@ import 'models/models.dart';
import 'views/profiles/override_profile.dart';
class AppController {
bool lastTunEnable = false;
int? lastProfileModified;
final BuildContext context;
@@ -263,29 +262,31 @@ class AppController {
if (res.isError) {
return;
}
lastTunEnable = res.data == true;
final realTunEnable = _ref.read(realTunEnableProvider);
final message = await clashCore.updateConfig(
updateParams.copyWith.tun(
enable: lastTunEnable,
enable: realTunEnable,
),
);
if (message.isNotEmpty) throw message;
}
Future<Result<bool>> _requestAdmin(bool enableTun) async {
if (enableTun != lastTunEnable && lastTunEnable == false) {
final realTunEnable = _ref.read(realTunEnableProvider);
if (enableTun != realTunEnable && realTunEnable == false) {
final code = await system.authorizeCore();
switch (code) {
case AuthorizeCode.none:
return Result.success(enableTun);
case AuthorizeCode.success:
await restartCore();
return Result.error("");
case AuthorizeCode.none:
break;
case AuthorizeCode.error:
enableTun = false;
return Result.success(false);
break;
}
}
_ref.read(realTunEnableProvider.notifier).value = enableTun;
return Result.success(enableTun);
}
@@ -304,8 +305,8 @@ class AppController {
if (res.isError) {
return;
}
lastTunEnable = res.data == true;
final realPatchConfig = patchConfig.copyWith.tun(enable: lastTunEnable);
final realTunEnable = _ref.read(realTunEnableProvider);
final realPatchConfig = patchConfig.copyWith.tun(enable: realTunEnable);
final params = await globalState.getSetupParams(
pathConfig: realPatchConfig,
);
@@ -443,6 +444,7 @@ class AppController {
});
try {
await savePreferences();
await system.setMacOSDns(true);
await proxy?.stopProxy();
await clashCore.shutdown();
await clashService?.destroy();
@@ -774,14 +776,14 @@ class AppController {
clearEffect(String profileId) async {
final profilePath = await appPath.getProfilePath(profileId);
final providersPath = await appPath.getProvidersPath(profileId);
final providersDirPath = await appPath.getProvidersDirPath(profileId);
return await Isolate.run(() async {
final profileFile = File(profilePath);
final isExists = await profileFile.exists();
if (isExists) {
profileFile.delete(recursive: true);
}
final providersFileDir = File(providersPath);
final providersFileDir = File(providersDirPath);
final providersFileIsExists = await providersFileDir.exists();
if (providersFileIsExists) {
providersFileDir.delete(recursive: true);
@@ -1031,6 +1033,7 @@ class AppController {
_ref.read(overrideDnsProvider.notifier).value = config.overrideDns;
_ref.read(networkSettingProvider.notifier).value = config.networkProps;
_ref.read(hotKeyActionsProvider.notifier).value = config.hotKeyActions;
_ref.read(scriptStateProvider.notifier).value = config.scriptProps;
}
final currentProfile = _ref.read(currentProfileProvider);
if (currentProfile == null) {

View File

@@ -499,3 +499,8 @@ enum Language {
yaml,
javaScript,
}
enum ImportOption {
file,
url,
}

View File

@@ -123,6 +123,9 @@ class MessageLookup extends MessageLookupByLibrary {
"autoRunDesc": MessageLookupByLibrary.simpleMessage(
"Auto run when the application is opened",
),
"autoSetSystemDns": MessageLookupByLibrary.simpleMessage(
"Auto set system DNS",
),
"autoUpdate": MessageLookupByLibrary.simpleMessage("Auto update"),
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage(
"Auto update interval (minutes)",
@@ -323,7 +326,10 @@ class MessageLookup extends MessageLookupByLibrary {
"Icon configuration",
),
"iconStyle": MessageLookupByLibrary.simpleMessage("Icon style"),
"import": MessageLookupByLibrary.simpleMessage("Import"),
"importFile": MessageLookupByLibrary.simpleMessage("Import from file"),
"importFromURL": MessageLookupByLibrary.simpleMessage("Import from URL"),
"importUrl": MessageLookupByLibrary.simpleMessage("Import from URL"),
"infiniteTime": MessageLookupByLibrary.simpleMessage("Long term effective"),
"init": MessageLookupByLibrary.simpleMessage("Init"),
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage(

View File

@@ -93,6 +93,7 @@ class MessageLookup extends MessageLookupByLibrary {
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage("システムの自動起動に従う"),
"autoRun": MessageLookupByLibrary.simpleMessage("自動実行"),
"autoRunDesc": MessageLookupByLibrary.simpleMessage("アプリ起動時に自動実行"),
"autoSetSystemDns": MessageLookupByLibrary.simpleMessage("オートセットシステムDNS"),
"autoUpdate": MessageLookupByLibrary.simpleMessage("自動更新"),
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自動更新間隔(分)"),
"backup": MessageLookupByLibrary.simpleMessage("バックアップ"),
@@ -245,7 +246,10 @@ class MessageLookup extends MessageLookupByLibrary {
"icon": MessageLookupByLibrary.simpleMessage("アイコン"),
"iconConfiguration": MessageLookupByLibrary.simpleMessage("アイコン設定"),
"iconStyle": MessageLookupByLibrary.simpleMessage("アイコンスタイル"),
"import": MessageLookupByLibrary.simpleMessage("インポート"),
"importFile": MessageLookupByLibrary.simpleMessage("ファイルからインポート"),
"importFromURL": MessageLookupByLibrary.simpleMessage("URLからインポート"),
"importUrl": MessageLookupByLibrary.simpleMessage("URLからインポート"),
"infiniteTime": MessageLookupByLibrary.simpleMessage("長期有効"),
"init": MessageLookupByLibrary.simpleMessage("初期化"),
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("正しいホットキーを入力"),

View File

@@ -120,6 +120,9 @@ class MessageLookup extends MessageLookupByLibrary {
"autoRunDesc": MessageLookupByLibrary.simpleMessage(
"Автоматический запуск при открытии приложения",
),
"autoSetSystemDns": MessageLookupByLibrary.simpleMessage(
"Автоматическая настройка системного DNS",
),
"autoUpdate": MessageLookupByLibrary.simpleMessage("Автообновление"),
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage(
"Интервал автообновления (минуты)",
@@ -342,7 +345,10 @@ class MessageLookup extends MessageLookupByLibrary {
"Конфигурация иконки",
),
"iconStyle": MessageLookupByLibrary.simpleMessage("Стиль иконки"),
"import": MessageLookupByLibrary.simpleMessage("Импорт"),
"importFile": MessageLookupByLibrary.simpleMessage("Импорт из файла"),
"importFromURL": MessageLookupByLibrary.simpleMessage("Импорт из URL"),
"importUrl": MessageLookupByLibrary.simpleMessage("Импорт по URL"),
"infiniteTime": MessageLookupByLibrary.simpleMessage(
"Долгосрочное действие",
),

View File

@@ -87,6 +87,7 @@ class MessageLookup extends MessageLookupByLibrary {
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage("跟随系统自启动"),
"autoRun": MessageLookupByLibrary.simpleMessage("自动运行"),
"autoRunDesc": MessageLookupByLibrary.simpleMessage("应用打开时自动运行"),
"autoSetSystemDns": MessageLookupByLibrary.simpleMessage("自动设置系统DNS"),
"autoUpdate": MessageLookupByLibrary.simpleMessage("自动更新"),
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自动更新间隔(分钟)"),
"backup": MessageLookupByLibrary.simpleMessage("备份"),
@@ -221,7 +222,10 @@ class MessageLookup extends MessageLookupByLibrary {
"icon": MessageLookupByLibrary.simpleMessage("图片"),
"iconConfiguration": MessageLookupByLibrary.simpleMessage("图片配置"),
"iconStyle": MessageLookupByLibrary.simpleMessage("图标样式"),
"import": MessageLookupByLibrary.simpleMessage("导入"),
"importFile": MessageLookupByLibrary.simpleMessage("通过文件导入"),
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
"importUrl": MessageLookupByLibrary.simpleMessage("通过URL导入"),
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
"init": MessageLookupByLibrary.simpleMessage("初始化"),
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("请输入正确的快捷键"),

View File

@@ -3104,6 +3104,41 @@ class AppLocalizations {
args: [],
);
}
/// `Import`
String get import {
return Intl.message('Import', name: 'import', desc: '', args: []);
}
/// `Import from file`
String get importFile {
return Intl.message(
'Import from file',
name: 'importFile',
desc: '',
args: [],
);
}
/// `Import from URL`
String get importUrl {
return Intl.message(
'Import from URL',
name: 'importUrl',
desc: '',
args: [],
);
}
/// `Auto set system DNS`
String get autoSetSystemDns {
return Intl.message(
'Auto set system DNS',
name: 'autoSetSystemDns',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -41,10 +41,34 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
},
fireImmediately: true,
);
ref.listenManual(configStateProvider, (prev, next) {
if (prev != next) {
globalState.appController.savePreferencesDebounce();
}
});
ref.listenManual(
autoSetSystemDnsStateProvider,
(prev, next) async {
if (prev == next) {
return;
}
if (next.a == true && next.b == true) {
system.setMacOSDns(false);
} else {
system.setMacOSDns(true);
}
},
);
}
@override
void dispose() {
reassemble() {
super.reassemble();
}
@override
void dispose() async {
await system.setMacOSDns(true);
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

View File

@@ -57,7 +57,6 @@ class _ClashContainerState extends ConsumerState<ClashManager>
clashCore.stopLog();
}
},
fireImmediately: true,
);
}

View File

@@ -102,9 +102,10 @@ class _WindowContainerState extends ConsumerState<WindowManager>
}
@override
Future<void> onTaskbarCreated() async {
// globalState.appController.updateTray(true);
super.onTaskbarCreated();
void onWindowRestore() {
commonPrint.log("restore");
render?.resume();
super.onWindowRestore();
}
@override

View File

@@ -32,6 +32,7 @@ class AppState with _$AppState {
required FixedList<Traffic> traffics,
required Traffic totalTraffic,
@Default("") String proxiesQuery,
@Default(false) bool realTunEnable,
}) = _AppState;
}

View File

@@ -152,7 +152,8 @@ class NetworkProps with _$NetworkProps {
const factory NetworkProps({
@Default(true) bool systemProxy,
@Default(defaultBypassDomain) List<String> bypassDomain,
@Default(RouteMode.bypassPrivate) RouteMode routeMode,
@Default(RouteMode.config) RouteMode routeMode,
@Default(true) bool autoSetSystemDns,
}) = _NetworkProps;
factory NetworkProps.fromJson(Map<String, Object?>? json) =>

View File

@@ -36,6 +36,7 @@ mixin _$AppState {
FixedList<Traffic> get traffics => throw _privateConstructorUsedError;
Traffic get totalTraffic => throw _privateConstructorUsedError;
String get proxiesQuery => throw _privateConstructorUsedError;
bool get realTunEnable => throw _privateConstructorUsedError;
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@@ -68,7 +69,8 @@ abstract class $AppStateCopyWith<$Res> {
FixedList<Log> logs,
FixedList<Traffic> traffics,
Traffic totalTraffic,
String proxiesQuery});
String proxiesQuery,
bool realTunEnable});
}
/// @nodoc
@@ -105,6 +107,7 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
Object? traffics = null,
Object? totalTraffic = null,
Object? proxiesQuery = null,
Object? realTunEnable = null,
}) {
return _then(_value.copyWith(
isInit: null == isInit
@@ -183,6 +186,10 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
? _value.proxiesQuery
: proxiesQuery // ignore: cast_nullable_to_non_nullable
as String,
realTunEnable: null == realTunEnable
? _value.realTunEnable
: realTunEnable // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
@@ -214,7 +221,8 @@ abstract class _$$AppStateImplCopyWith<$Res>
FixedList<Log> logs,
FixedList<Traffic> traffics,
Traffic totalTraffic,
String proxiesQuery});
String proxiesQuery,
bool realTunEnable});
}
/// @nodoc
@@ -249,6 +257,7 @@ class __$$AppStateImplCopyWithImpl<$Res>
Object? traffics = null,
Object? totalTraffic = null,
Object? proxiesQuery = null,
Object? realTunEnable = null,
}) {
return _then(_$AppStateImpl(
isInit: null == isInit
@@ -327,6 +336,10 @@ class __$$AppStateImplCopyWithImpl<$Res>
? _value.proxiesQuery
: proxiesQuery // ignore: cast_nullable_to_non_nullable
as String,
realTunEnable: null == realTunEnable
? _value.realTunEnable
: realTunEnable // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@@ -353,7 +366,8 @@ class _$AppStateImpl implements _AppState {
required this.logs,
required this.traffics,
required this.totalTraffic,
this.proxiesQuery = ""})
this.proxiesQuery = "",
this.realTunEnable = false})
: _packages = packages,
_delayMap = delayMap,
_groups = groups,
@@ -431,10 +445,13 @@ class _$AppStateImpl implements _AppState {
@override
@JsonKey()
final String proxiesQuery;
@override
@JsonKey()
final bool realTunEnable;
@override
String toString() {
return 'AppState(isInit: $isInit, backBlock: $backBlock, pageLabel: $pageLabel, packages: $packages, sortNum: $sortNum, viewSize: $viewSize, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, proxiesQuery: $proxiesQuery)';
return 'AppState(isInit: $isInit, backBlock: $backBlock, pageLabel: $pageLabel, packages: $packages, sortNum: $sortNum, viewSize: $viewSize, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, proxiesQuery: $proxiesQuery, realTunEnable: $realTunEnable)';
}
@override
@@ -470,7 +487,9 @@ class _$AppStateImpl implements _AppState {
(identical(other.totalTraffic, totalTraffic) ||
other.totalTraffic == totalTraffic) &&
(identical(other.proxiesQuery, proxiesQuery) ||
other.proxiesQuery == proxiesQuery));
other.proxiesQuery == proxiesQuery) &&
(identical(other.realTunEnable, realTunEnable) ||
other.realTunEnable == realTunEnable));
}
@override
@@ -494,7 +513,8 @@ class _$AppStateImpl implements _AppState {
logs,
traffics,
totalTraffic,
proxiesQuery
proxiesQuery,
realTunEnable
]);
/// Create a copy of AppState
@@ -526,7 +546,8 @@ abstract class _AppState implements AppState {
required final FixedList<Log> logs,
required final FixedList<Traffic> traffics,
required final Traffic totalTraffic,
final String proxiesQuery}) = _$AppStateImpl;
final String proxiesQuery,
final bool realTunEnable}) = _$AppStateImpl;
@override
bool get isInit;
@@ -566,6 +587,8 @@ abstract class _AppState implements AppState {
Traffic get totalTraffic;
@override
String get proxiesQuery;
@override
bool get realTunEnable;
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.

View File

@@ -1325,6 +1325,7 @@ mixin _$NetworkProps {
bool get systemProxy => throw _privateConstructorUsedError;
List<String> get bypassDomain => throw _privateConstructorUsedError;
RouteMode get routeMode => throw _privateConstructorUsedError;
bool get autoSetSystemDns => throw _privateConstructorUsedError;
/// Serializes this NetworkProps to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -1342,7 +1343,11 @@ abstract class $NetworkPropsCopyWith<$Res> {
NetworkProps value, $Res Function(NetworkProps) then) =
_$NetworkPropsCopyWithImpl<$Res, NetworkProps>;
@useResult
$Res call({bool systemProxy, List<String> bypassDomain, RouteMode routeMode});
$Res call(
{bool systemProxy,
List<String> bypassDomain,
RouteMode routeMode,
bool autoSetSystemDns});
}
/// @nodoc
@@ -1363,6 +1368,7 @@ class _$NetworkPropsCopyWithImpl<$Res, $Val extends NetworkProps>
Object? systemProxy = null,
Object? bypassDomain = null,
Object? routeMode = null,
Object? autoSetSystemDns = null,
}) {
return _then(_value.copyWith(
systemProxy: null == systemProxy
@@ -1377,6 +1383,10 @@ class _$NetworkPropsCopyWithImpl<$Res, $Val extends NetworkProps>
? _value.routeMode
: routeMode // ignore: cast_nullable_to_non_nullable
as RouteMode,
autoSetSystemDns: null == autoSetSystemDns
? _value.autoSetSystemDns
: autoSetSystemDns // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
@@ -1389,7 +1399,11 @@ abstract class _$$NetworkPropsImplCopyWith<$Res>
__$$NetworkPropsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool systemProxy, List<String> bypassDomain, RouteMode routeMode});
$Res call(
{bool systemProxy,
List<String> bypassDomain,
RouteMode routeMode,
bool autoSetSystemDns});
}
/// @nodoc
@@ -1408,6 +1422,7 @@ class __$$NetworkPropsImplCopyWithImpl<$Res>
Object? systemProxy = null,
Object? bypassDomain = null,
Object? routeMode = null,
Object? autoSetSystemDns = null,
}) {
return _then(_$NetworkPropsImpl(
systemProxy: null == systemProxy
@@ -1422,6 +1437,10 @@ class __$$NetworkPropsImplCopyWithImpl<$Res>
? _value.routeMode
: routeMode // ignore: cast_nullable_to_non_nullable
as RouteMode,
autoSetSystemDns: null == autoSetSystemDns
? _value.autoSetSystemDns
: autoSetSystemDns // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@@ -1432,7 +1451,8 @@ class _$NetworkPropsImpl implements _NetworkProps {
const _$NetworkPropsImpl(
{this.systemProxy = true,
final List<String> bypassDomain = defaultBypassDomain,
this.routeMode = RouteMode.bypassPrivate})
this.routeMode = RouteMode.config,
this.autoSetSystemDns = true})
: _bypassDomain = bypassDomain;
factory _$NetworkPropsImpl.fromJson(Map<String, dynamic> json) =>
@@ -1453,10 +1473,13 @@ class _$NetworkPropsImpl implements _NetworkProps {
@override
@JsonKey()
final RouteMode routeMode;
@override
@JsonKey()
final bool autoSetSystemDns;
@override
String toString() {
return 'NetworkProps(systemProxy: $systemProxy, bypassDomain: $bypassDomain, routeMode: $routeMode)';
return 'NetworkProps(systemProxy: $systemProxy, bypassDomain: $bypassDomain, routeMode: $routeMode, autoSetSystemDns: $autoSetSystemDns)';
}
@override
@@ -1469,13 +1492,19 @@ class _$NetworkPropsImpl implements _NetworkProps {
const DeepCollectionEquality()
.equals(other._bypassDomain, _bypassDomain) &&
(identical(other.routeMode, routeMode) ||
other.routeMode == routeMode));
other.routeMode == routeMode) &&
(identical(other.autoSetSystemDns, autoSetSystemDns) ||
other.autoSetSystemDns == autoSetSystemDns));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, systemProxy,
const DeepCollectionEquality().hash(_bypassDomain), routeMode);
int get hashCode => Object.hash(
runtimeType,
systemProxy,
const DeepCollectionEquality().hash(_bypassDomain),
routeMode,
autoSetSystemDns);
/// Create a copy of NetworkProps
/// with the given fields replaced by the non-null parameter values.
@@ -1497,7 +1526,8 @@ abstract class _NetworkProps implements NetworkProps {
const factory _NetworkProps(
{final bool systemProxy,
final List<String> bypassDomain,
final RouteMode routeMode}) = _$NetworkPropsImpl;
final RouteMode routeMode,
final bool autoSetSystemDns}) = _$NetworkPropsImpl;
factory _NetworkProps.fromJson(Map<String, dynamic> json) =
_$NetworkPropsImpl.fromJson;
@@ -1508,6 +1538,8 @@ abstract class _NetworkProps implements NetworkProps {
List<String> get bypassDomain;
@override
RouteMode get routeMode;
@override
bool get autoSetSystemDns;
/// Create a copy of NetworkProps
/// with the given fields replaced by the non-null parameter values.

View File

@@ -160,7 +160,8 @@ _$NetworkPropsImpl _$$NetworkPropsImplFromJson(Map<String, dynamic> json) =>
.toList() ??
defaultBypassDomain,
routeMode: $enumDecodeNullable(_$RouteModeEnumMap, json['routeMode']) ??
RouteMode.bypassPrivate,
RouteMode.config,
autoSetSystemDns: json['autoSetSystemDns'] as bool? ?? true,
);
Map<String, dynamic> _$$NetworkPropsImplToJson(_$NetworkPropsImpl instance) =>
@@ -168,6 +169,7 @@ Map<String, dynamic> _$$NetworkPropsImplToJson(_$NetworkPropsImpl instance) =>
'systemProxy': instance.systemProxy,
'bypassDomain': instance.bypassDomain,
'routeMode': _$RouteModeEnumMap[instance.routeMode]!,
'autoSetSystemDns': instance.autoSetSystemDns,
};
const _$RouteModeEnumMap = {

View File

@@ -111,10 +111,25 @@ class _EditorPageState extends ConsumerState<EditorPage> {
_findController.findMode();
}
_handleRemoteDownload() async {
_handleImport() async {
final option = await globalState.showCommonDialog<ImportOption>(
child: _ImportOptionsDialog(),
);
if (option == null) {
return;
}
if (option == ImportOption.file) {
final file = await picker.pickerFile();
if (file == null) {
return;
}
final res = String.fromCharCodes(file.bytes?.toList() ?? []);
_controller.text = res;
return;
}
final url = await globalState.showCommonDialog(
child: InputDialog(
title: appLocalizations.download,
title: "导入",
value: "",
labelText: appLocalizations.url,
validator: (value) {
@@ -186,7 +201,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
),
if (widget.supportRemoteDownload)
IconButton(
onPressed: _handleRemoteDownload,
onPressed: _handleImport,
icon: Icon(
Icons.arrow_downward,
),
@@ -236,6 +251,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
padding: EdgeInsets.only(
right: 16,
),
autocompleteSymbols: true,
focusNode: _focusNode,
scrollbarBuilder: (context, child, details) {
return CommonScrollBar(
@@ -662,7 +678,45 @@ class _NoInputBorder extends InputBorder {
double gapExtent = 0.0,
double gapPercentage = 0.0,
TextDirection? textDirection,
}) {
// Do not paint.
}) {}
}
class _ImportOptionsDialog extends StatefulWidget {
const _ImportOptionsDialog();
@override
State<_ImportOptionsDialog> createState() => _ImportOptionsDialogState();
}
class _ImportOptionsDialogState extends State<_ImportOptionsDialog> {
_handleOnTab(ImportOption value) {
Navigator.of(context).pop(value);
}
@override
Widget build(BuildContext context) {
return CommonDialog(
title: appLocalizations.import,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 16,
),
child: Wrap(
children: [
ListItem(
onTap: () {
_handleOnTab(ImportOption.url);
},
title: Text(appLocalizations.importUrl),
),
ListItem(
onTap: () {
_handleOnTab(ImportOption.file);
},
title: Text(appLocalizations.importFile),
)
],
),
);
}
}

View File

@@ -8,6 +8,21 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'generated/app.g.dart';
@riverpod
class RealTunEnable extends _$RealTunEnable with AutoDisposeNotifierMixin {
@override
bool build() {
return globalState.appState.realTunEnable;
}
@override
onUpdate(value) {
globalState.appState = globalState.appState.copyWith(
realTunEnable: value,
);
}
}
@riverpod
class Logs extends _$Logs with AutoDisposeNotifierMixin {
@override

View File

@@ -285,6 +285,10 @@ class ScriptState extends _$ScriptState with AutoDisposeNotifierMixin {
currentId: nextId,
);
}
isExits(String label) {
return state.scripts.indexWhere((item) => item.label == label) != -1;
}
}
@riverpod

View File

@@ -70,6 +70,22 @@ final viewHeightProvider = AutoDisposeProvider<double>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ViewHeightRef = AutoDisposeProviderRef<double>;
String _$realTunEnableHash() => r'a4e995c86deca4c8307966470e69d93d64a40df6';
/// See also [RealTunEnable].
@ProviderFor(RealTunEnable)
final realTunEnableProvider =
AutoDisposeNotifierProvider<RealTunEnable, bool>.internal(
RealTunEnable.new,
name: r'realTunEnableProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$realTunEnableHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$RealTunEnable = AutoDisposeNotifier<bool>;
String _$logsHash() => r'56fb8aa9d62a97b026b749d204576a7384084737';
/// See also [Logs].

View File

@@ -178,7 +178,7 @@ final proxiesStyleSettingProvider =
);
typedef _$ProxiesStyleSetting = AutoDisposeNotifier<ProxiesStyle>;
String _$scriptStateHash() => r'16d669009ffb233d95b2cb206cf771342ebc1cc1';
String _$scriptStateHash() => r'884581c71fd5afa3c9d34f31625d967cf561cdbe';
/// See also [ScriptState].
@ProviderFor(ScriptState)

View File

@@ -6,6 +6,22 @@ part of '../state.dart';
// RiverpodGenerator
// **************************************************************************
String _$configStateHash() => r'1f4ea3cc8f6461ba734e7e0c5d7295bfa4fd5afb';
/// See also [configState].
@ProviderFor(configState)
final configStateProvider = AutoDisposeProvider<Config>.internal(
configState,
name: r'configStateProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$configStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ConfigStateRef = AutoDisposeProviderRef<Config>;
String _$currentGroupsStateHash() =>
r'6222c006e1970e7435268d32903b9019cf1a4351';
@@ -1959,11 +1975,12 @@ class _GenColorSchemeProviderElement
bool get ignoreConfig => (origin as GenColorSchemeProvider).ignoreConfig;
}
String _$needSetupHash() => r'1116c73bb2964321de63bdca631a983d31e30d69';
String _$needSetupHash() => r'3668e8dc9f40a9bea45c94321804eb3afa0e7c51';
/// See also [needSetup].
@ProviderFor(needSetup)
final needSetupProvider = AutoDisposeProvider<VM2>.internal(
final needSetupProvider =
AutoDisposeProvider<VM3<String?, String?, Dns?>>.internal(
needSetup,
name: r'needSetupProvider',
debugGetCreateSourceHash:
@@ -1974,7 +1991,26 @@ final needSetupProvider = AutoDisposeProvider<VM2>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef NeedSetupRef = AutoDisposeProviderRef<VM2>;
typedef NeedSetupRef = AutoDisposeProviderRef<VM3<String?, String?, Dns?>>;
String _$autoSetSystemDnsStateHash() =>
r'2e0976e079100325b1ca797285df48a94c2c066c';
/// See also [autoSetSystemDnsState].
@ProviderFor(autoSetSystemDnsState)
final autoSetSystemDnsStateProvider =
AutoDisposeProvider<VM2<bool, bool>>.internal(
autoSetSystemDnsState,
name: r'autoSetSystemDnsStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$autoSetSystemDnsStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef AutoSetSystemDnsStateRef = AutoDisposeProviderRef<VM2<bool, bool>>;
String _$profileOverrideStateHash() =>
r'fa26570a355ab39e27b1f93d1d2f358717065592';

View File

@@ -12,6 +12,38 @@ import 'config.dart';
part 'generated/state.g.dart';
@riverpod
Config configState(Ref ref) {
final themeProps = ref.watch(themeSettingProvider);
final patchClashConfig = ref.watch(patchClashConfigProvider);
final appSetting = ref.watch(appSettingProvider);
final profiles = ref.watch(profilesProvider);
final currentProfileId = ref.watch(currentProfileIdProvider);
final overrideDns = ref.watch(overrideDnsProvider);
final networkProps = ref.watch(networkSettingProvider);
final vpnProps = ref.watch(vpnSettingProvider);
final proxiesStyle = ref.watch(proxiesStyleSettingProvider);
final scriptProps = ref.watch(scriptStateProvider);
final hotKeyActions = ref.watch(hotKeyActionsProvider);
final dav = ref.watch(appDAVSettingProvider);
final windowProps = ref.watch(windowSettingProvider);
return Config(
dav: dav,
windowProps: windowProps,
hotKeyActions: hotKeyActions,
scriptProps: scriptProps,
proxiesStyle: proxiesStyle,
vpnProps: vpnProps,
networkProps: networkProps,
overrideDns: overrideDns,
currentProfileId: currentProfileId,
profiles: profiles,
appSetting: appSetting,
themeProps: themeProps,
patchClashConfig: patchClashConfig,
);
}
@riverpod
GroupsState currentGroupsState(Ref ref) {
final mode =
@@ -590,9 +622,34 @@ ColorScheme genColorScheme(
}
@riverpod
VM2 needSetup(Ref ref) {
VM3<String?, String?, Dns?> needSetup(Ref ref) {
final profileId = ref.watch(currentProfileIdProvider);
final content = ref.watch(
scriptStateProvider.select((state) => state.currentScript?.content));
return VM2(a: profileId, b: content);
final overrideDns = ref.watch(overrideDnsProvider);
final dns = overrideDns == true
? ref.watch(patchClashConfigProvider.select(
(state) => state.dns,
))
: null;
return VM3(
a: profileId,
b: content,
c: dns,
);
}
@riverpod
VM2<bool, bool> autoSetSystemDnsState(Ref ref) {
final isStart = ref.watch(runTimeProvider.select((state) => state != null));
final realTunEnable = ref.watch(realTunEnableProvider);
final autoSetSystemDns = ref.watch(
networkSettingProvider.select(
(state) => state.autoSetSystemDns,
),
);
return VM2(
a: isStart ? realTunEnable : false,
b: autoSetSystemDns,
);
}

View File

@@ -1,8 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ffi' show Pointer;
import 'dart:io';
import 'dart:isolate';
import 'package:animations/animations.dart';
import 'package:dio/dio.dart';
@@ -15,10 +13,10 @@ import 'package:fl_clash/plugins/service.dart';
import 'package:fl_clash/widgets/dialog.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart';
import 'package:flutter_js/flutter_js.dart';
import 'package:material_color_utilities/palettes/core_palette.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:yaml/yaml.dart';
import 'common/common.dart';
import 'controller.dart';
@@ -193,26 +191,26 @@ class GlobalState {
);
}
Future<Map<String, dynamic>> getProfileMap(String id) async {
final profilePath = await appPath.getProfilePath(id);
final res = await Isolate.run<Result<dynamic>>(() async {
try {
final file = File(profilePath);
if (!await file.exists()) {
return Result.error("");
}
final value = await file.readAsString();
return Result.success(utils.convertYamlNode(loadYaml(value)));
} catch (e) {
return Result.error(e.toString());
}
});
if (res.isSuccess) {
return res.data as Map<String, dynamic>;
} else {
throw res.message;
}
}
// Future<Map<String, dynamic>> getProfileMap(String id) async {
// final profilePath = await appPath.getProfilePath(id);
// final res = await Isolate.run<Result<dynamic>>(() async {
// try {
// final file = File(profilePath);
// if (!await file.exists()) {
// return Result.error("");
// }
// final value = await file.readAsString();
// return Result.success(utils.convertYamlNode(loadYaml(value)));
// } catch (e) {
// return Result.error(e.toString());
// }
// });
// if (res.isSuccess) {
// return res.data as Map<String, dynamic>;
// } else {
// throw res.message;
// }
// }
Future<T?> showCommonDialog<T>({
required Widget child,
@@ -315,18 +313,21 @@ class GlobalState {
return {};
}
final profileId = profile.id;
final rawConfig =
await handleEvaluate(await globalState.getProfileMap(profileId));
final configMap = await getProfileConfig(profileId);
final rawConfig = await handleEvaluate(configMap);
final routeAddress =
config.networkProps.routeMode == RouteMode.bypassPrivate
? defaultBypassPrivateRouteAddress
: patchConfig.tun.routeAddress;
final realPatchConfig = patchConfig.copyWith.tun(
autoRoute: routeAddress.isEmpty ? true : false,
routeAddress: routeAddress,
);
final realPatchConfig = !system.isDesktop
? patchConfig.copyWith.tun(
autoRoute: routeAddress.isEmpty ? true : false,
routeAddress: routeAddress,
)
: patchConfig.copyWith.tun(
autoRoute: true,
routeAddress: [],
);
rawConfig["external-controller"] = realPatchConfig.externalController.value;
rawConfig["external-ui"] = "";
rawConfig["interface-name"] = "";
@@ -371,9 +372,15 @@ class GlobalState {
final proxyProviders = rawConfig["proxy-providers"] as Map;
for (final key in proxyProviders.keys) {
final proxyProvider = proxyProviders[key];
if (proxyProvider["path"] != null) {
proxyProvider["path"] = await appPath.getProvidersPath(profile.id,
filePath: proxyProvider["path"]);
if (proxyProvider["type"] != "http") {
continue;
}
if (proxyProvider["url"] != null) {
proxyProvider["path"] = await appPath.getProvidersFilePath(
profile.id,
"proxies",
proxyProvider["url"],
);
}
}
}
@@ -382,9 +389,15 @@ class GlobalState {
final ruleProviders = rawConfig["rule-providers"] as Map;
for (final key in ruleProviders.keys) {
final ruleProvider = ruleProviders[key];
if (ruleProvider["path"] != null) {
ruleProvider["path"] = await appPath.getProvidersPath(profile.id,
filePath: ruleProvider["path"]);
if (ruleProvider["type"] != "http") {
continue;
}
if (ruleProvider["url"] != null) {
ruleProvider["path"] = await appPath.getProvidersFilePath(
profile.id,
"rules",
ruleProvider["url"],
);
}
}
}
@@ -415,11 +428,8 @@ class GlobalState {
}
}
var rules = [];
// if (rawConfig["rule"] != null) {
// rules.addAll(rawConfig["rule"]);
// }
if (rawConfig["rules"] != null) {
rules.addAll(rawConfig["rules"]);
rules = rawConfig["rules"];
}
rawConfig.remove("rules");
@@ -428,13 +438,23 @@ class GlobalState {
if (overrideData.rule.type == OverrideRuleType.override) {
rules = overrideData.runningRule;
} else {
rules.addAll(overrideData.runningRule);
rules = [...overrideData.runningRule, ...rules];
}
}
rawConfig["rule"] = rules;
return rawConfig;
}
Future<Map<String, dynamic>> getProfileConfig(String profileId) async {
final configMap = await switch (clashLibHandler != null) {
true => clashLibHandler!.getConfig(profileId),
false => clashCore.getConfig(profileId),
};
configMap["rules"] = configMap["rule"];
configMap.remove("rule");
return configMap;
}
Future<Map<String, dynamic>> handleEvaluate(
Map<String, dynamic> config,
) async {
@@ -442,9 +462,12 @@ class GlobalState {
if (currentScript == null) {
return config;
}
if (config["proxy-providers"] == null) {
config["proxy-providers"] = {};
}
final configJs = json.encode(config);
final runtime = js.runTime;
final res = await js.runTime.evaluateAsync("""
final runtime = getJavascriptRuntime();
final res = await runtime.evaluateAsync("""
${currentScript.content}
main($configJs)
""");

View File

@@ -47,15 +47,6 @@ class AboutView extends StatelessWidget {
_checkUpdate(context);
},
),
ListItem(
title: Text(appLocalizations.contactMe),
onTap: () {
globalState.showMessage(
title: appLocalizations.contactMe,
message: TextSpan(text: "chen08209@gmail.com"),
);
},
),
ListItem(
title: const Text("Telegram"),
onTap: () {

View File

@@ -601,7 +601,8 @@ class _PortDialogState extends ConsumerState<_PortDialog> {
.numberTip(appLocalizations.mixedPort);
}
if (port < 1024 || port > 49151) {
return appLocalizations.mixedPort;
return appLocalizations
.portTip(appLocalizations.mixedPort);
}
final ports = [
_portController.text,

View File

@@ -155,6 +155,29 @@ class Ipv6Item extends ConsumerWidget {
}
}
class AutoSetSystemDnsItem extends ConsumerWidget {
const AutoSetSystemDnsItem({super.key});
@override
Widget build(BuildContext context, ref) {
final autoSetSystemDns = ref.watch(
networkSettingProvider.select((state) => state.autoSetSystemDns));
return ListItem.switchItem(
title: Text(appLocalizations.autoSetSystemDns),
delegate: SwitchDelegate(
value: autoSetSystemDns,
onChanged: (bool value) async {
ref.read(networkSettingProvider.notifier).updateState(
(state) => state.copyWith(
autoSetSystemDns: value,
),
);
},
),
);
}
}
class TunStackItem extends ConsumerWidget {
const TunStackItem({super.key});
@@ -349,9 +372,12 @@ final networkItems = [
title: appLocalizations.options,
items: [
if (system.isDesktop) const TUNItem(),
if (Platform.isMacOS) const AutoSetSystemDnsItem(),
const TunStackItem(),
const RouteModeItem(),
const RouteAddressItem(),
if (!system.isDesktop) ...[
const RouteModeItem(),
const RouteAddressItem(),
]
],
),
];

View File

@@ -1,5 +1,6 @@
import 'dart:math';
import 'package:defer_pointer/defer_pointer.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/providers/providers.dart';
@@ -9,6 +10,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'widgets/start_button.dart';
typedef _IsEditWidgetBuilder = Widget Function(bool isEdit);
class DashboardView extends ConsumerStatefulWidget {
const DashboardView({super.key});
@@ -18,6 +21,8 @@ class DashboardView extends ConsumerStatefulWidget {
class _DashboardViewState extends ConsumerState<DashboardView> with PageMixin {
final key = GlobalKey<SuperGridState>();
final _isEditNotifier = ValueNotifier<bool>(false);
final _addedWidgetsNotifier = ValueNotifier<List<GridItem>>([]);
@override
initState() {
@@ -33,115 +38,282 @@ class _DashboardViewState extends ConsumerState<DashboardView> with PageMixin {
return super.initState();
}
@override
dispose() {
_isEditNotifier.dispose();
super.dispose();
}
@override
Widget? get floatingActionButton => const StartButton();
Widget _buildIsEdit(_IsEditWidgetBuilder builder) {
return ValueListenableBuilder(
valueListenable: _isEditNotifier,
builder: (_, isEdit, ___) {
return builder(isEdit);
},
);
}
@override
List<Widget> get actions => [
ValueListenableBuilder(
valueListenable: key.currentState!.addedChildrenNotifier,
builder: (_, addedChildren, child) {
return ValueListenableBuilder(
valueListenable: key.currentState!.isEditNotifier,
builder: (_, isEdit, child) {
if (!isEdit || addedChildren.isEmpty) {
return Container();
}
return child!;
},
child: child,
);
},
child: IconButton(
onPressed: () {
key.currentState!.showAddModal();
},
icon: Icon(
Icons.add_circle,
),
),
),
_buildIsEdit((isEdit) {
return isEdit
? ValueListenableBuilder(
valueListenable: _addedWidgetsNotifier,
builder: (_, addedChildren, child) {
if (addedChildren.isEmpty) {
return Container();
}
return child!;
},
child: IconButton(
onPressed: () {
_showAddWidgetsModal();
},
icon: Icon(
Icons.add_circle,
),
),
)
: SizedBox();
}),
IconButton(
icon: ValueListenableBuilder(
valueListenable: key.currentState!.isEditNotifier,
builder: (_, isEdit, ___) {
return isEdit
? SystemBackBlock(
child: CommonPopScope(
child: Icon(Icons.save),
onPop: () {
key.currentState!.isEditNotifier.value =
!key.currentState!.isEditNotifier.value;
return false;
},
),
)
: Icon(
Icons.edit,
);
},
),
onPressed: () {
key.currentState!.isEditNotifier.value =
!key.currentState!.isEditNotifier.value;
},
icon: _buildIsEdit((isEdit) {
return isEdit
? Icon(Icons.save)
: Icon(
Icons.edit,
);
}),
onPressed: _handleUpdateIsEdit,
),
];
_handleSave(List<GridItem> girdItems, WidgetRef ref) {
final dashboardWidgets = girdItems
.map(
(item) => DashboardWidget.getDashboardWidget(item),
)
.toList();
ref.read(appSettingProvider.notifier).updateState(
(state) => state.copyWith(dashboardWidgets: dashboardWidgets),
_showAddWidgetsModal() {
showSheet(
builder: (_, type) {
return ValueListenableBuilder(
valueListenable: _addedWidgetsNotifier,
builder: (_, value, __) {
return AdaptiveSheetScaffold(
type: type,
body: _AddDashboardWidgetModal(
items: value,
onAdd: (gridItem) {
key.currentState?.handleAdd(gridItem);
},
),
title: appLocalizations.add,
);
},
);
},
context: context,
);
}
_handleUpdateIsEdit() {
if (_isEditNotifier.value == true) {
_handleSave();
}
_isEditNotifier.value = !_isEditNotifier.value;
}
_handleSave() {
final children = key.currentState?.children;
if (children == null) {
return;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
final dashboardWidgets = children
.map(
(item) => DashboardWidget.getDashboardWidget(item),
)
.toList();
ref.read(appSettingProvider.notifier).updateState(
(state) => state.copyWith(dashboardWidgets: dashboardWidgets),
);
});
}
@override
Widget build(BuildContext context) {
final dashboardState = ref.watch(dashboardStateProvider);
final columns = max(4 * ((dashboardState.viewWidth / 320).ceil()), 8);
final spacing = 16.ap;
final children = [
...dashboardState.dashboardWidgets
.where(
(item) => item.platforms.contains(
SupportPlatform.currentPlatform,
),
)
.map(
(item) => item.widget,
),
];
WidgetsBinding.instance.addPostFrameCallback((_) {
_addedWidgetsNotifier.value = DashboardWidget.values
.where(
(item) =>
!children.contains(item.widget) &&
item.platforms.contains(
SupportPlatform.currentPlatform,
),
)
.map((item) => item.widget)
.toList();
});
return Align(
alignment: Alignment.topCenter,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16).copyWith(
bottom: 88,
),
child: SuperGrid(
key: key,
crossAxisCount: columns,
crossAxisSpacing: 16.ap,
mainAxisSpacing: 16.ap,
children: [
...dashboardState.dashboardWidgets
.where(
(item) => item.platforms.contains(
SupportPlatform.currentPlatform,
),
)
.map(
(item) => item.widget,
),
],
onSave: (girdItems) {
_handleSave(girdItems, ref);
},
addedItemsBuilder: (girdItems) {
return DashboardWidget.values
.where(
(item) =>
!girdItems.contains(item.widget) &&
item.platforms.contains(
SupportPlatform.currentPlatform,
padding: const EdgeInsets.all(16).copyWith(
bottom: 88,
),
child: _buildIsEdit((isEdit) {
return isEdit
? SystemBackBlock(
child: CommonPopScope(
child: SuperGrid(
key: key,
crossAxisCount: columns,
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
children: [
...dashboardState.dashboardWidgets
.where(
(item) => item.platforms.contains(
SupportPlatform.currentPlatform,
),
)
.map(
(item) => item.widget,
),
],
onUpdate: () {
_handleSave();
},
),
)
.map((item) => item.widget)
.toList();
},
onPop: () {
_handleUpdateIsEdit();
return false;
},
),
)
: Grid(
crossAxisCount: columns,
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
children: children,
);
})),
);
}
}
class _AddDashboardWidgetModal extends StatelessWidget {
final List<GridItem> items;
final Function(GridItem item) onAdd;
const _AddDashboardWidgetModal({
required this.items,
required this.onAdd,
});
@override
Widget build(BuildContext context) {
return DeferredPointerHandler(
child: SingleChildScrollView(
padding: EdgeInsets.all(
16,
),
child: Grid(
crossAxisCount: 8,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
children: items
.map(
(item) => item.wrap(
builder: (child) {
return _AddedContainer(
onAdd: () {
onAdd(item);
},
child: child,
);
},
),
)
.toList(),
),
),
);
}
}
class _AddedContainer extends StatefulWidget {
final Widget child;
final VoidCallback onAdd;
const _AddedContainer({
required this.child,
required this.onAdd,
});
@override
State<_AddedContainer> createState() => _AddedContainerState();
}
class _AddedContainerState extends State<_AddedContainer> {
@override
void initState() {
super.initState();
}
@override
void didUpdateWidget(_AddedContainer oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.child != widget.child) {}
}
_handleAdd() async {
widget.onAdd();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
ActivateBox(
child: widget.child,
),
Positioned(
top: -8,
right: -8,
child: DeferPointer(
child: SizedBox(
width: 24,
height: 24,
child: IconButton.filled(
iconSize: 20,
padding: EdgeInsets.all(2),
onPressed: _handleAdd,
icon: Icon(
Icons.add,
),
),
),
),
)
],
);
}
}

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/views/config/network.dart';
@@ -23,6 +25,7 @@ class TUNButton extends StatelessWidget {
generateSection(
items: [
if (system.isDesktop) const TUNItem(),
if (Platform.isMacOS) const AutoSetSystemDnsItem(),
const TunStackItem(),
],
),

View File

@@ -136,12 +136,9 @@ class _ScriptsViewState extends ConsumerState<ScriptsView> {
return appLocalizations.emptyTip(appLocalizations.name);
}
if (value != script?.label) {
final index = ref
.read(scriptStateProvider.select((state) => state.scripts))
.indexWhere(
(item) => item.label == value,
);
if (index != -1) {
final isExits =
ref.read(scriptStateProvider.notifier).isExits(value);
if (isExits) {
return appLocalizations.existsTip(
appLocalizations.name,
);
@@ -156,6 +153,18 @@ class _ScriptsViewState extends ConsumerState<ScriptsView> {
}
newScript = newScript.copyWith(label: res);
}
if (newScript.label != script?.label) {
final isExits =
ref.read(scriptStateProvider.notifier).isExits(newScript.label);
if (isExits) {
globalState.showMessage(
message: TextSpan(
text: appLocalizations.existsTip(appLocalizations.name),
),
);
return;
}
}
ref.read(scriptStateProvider.notifier).setScript(newScript);
if (mounted) {
Navigator.of(context).pop();

View File

@@ -1,6 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:fl_clash/common/common.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_svg/svg.dart';
class CommonTargetIcon extends StatelessWidget {
final String src;
@@ -33,11 +34,23 @@ class CommonTargetIcon extends StatelessWidget {
},
);
}
return CachedNetworkImage(
imageUrl: src,
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
errorWidget: (_, __, ___) => _defaultIcon(),
return FutureBuilder(
future: DefaultCacheManager().getSingleFile(src),
builder: (_, snapshot) {
final data = snapshot.data;
if (data == null) {
return SizedBox();
}
return src.isSvg
? SvgPicture.file(
data,
errorBuilder: (_, __, ___) => _defaultIcon(),
)
: Image.file(
data,
errorBuilder: (_, __, ___) => _defaultIcon(),
);
},
);
}

View File

@@ -422,6 +422,7 @@ class ListItem<T> extends StatelessWidget {
value: radioDelegate.value,
groupValue: radioDelegate.groupValue,
onChanged: radioDelegate.onChanged,
toggleable: true,
),
trailing: trailing,
);

View File

@@ -7,7 +7,6 @@ import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/widgets/activate_box.dart';
import 'package:fl_clash/widgets/card.dart';
import 'package:fl_clash/widgets/grid.dart';
import 'package:fl_clash/widgets/sheet.dart';
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
@@ -18,8 +17,7 @@ class SuperGrid extends StatefulWidget {
final double mainAxisSpacing;
final double crossAxisSpacing;
final int crossAxisCount;
final void Function(List<GridItem> newChildren)? onSave;
final List<GridItem> Function(List<GridItem> newChildren)? addedItemsBuilder;
final VoidCallback? onUpdate;
const SuperGrid({
super.key,
@@ -27,8 +25,7 @@ class SuperGrid extends StatefulWidget {
this.crossAxisCount = 1,
this.mainAxisSpacing = 0,
this.crossAxisSpacing = 0,
this.onSave,
this.addedItemsBuilder,
this.onUpdate,
});
@override
@@ -37,7 +34,7 @@ class SuperGrid extends StatefulWidget {
class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
final ValueNotifier<List<GridItem>> _childrenNotifier = ValueNotifier([]);
final ValueNotifier<List<GridItem>> addedChildrenNotifier = ValueNotifier([]);
List<GridItem> children = [];
int get length => _childrenNotifier.value.length;
List<int> _tempIndexList = [];
@@ -49,8 +46,6 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
List<Offset> _offsets = [];
Offset _parentOffset = Offset.zero;
EdgeDraggingAutoScroller? _edgeDraggingAutoScroller;
final ValueNotifier<bool> isEditNotifier = ValueNotifier(false);
Map<int, Tween<Offset>> _transformTweenMap = {};
final ValueNotifier<bool> _animating = ValueNotifier(false);
@@ -95,35 +90,6 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
_containerSize = context.size!;
}
showAddModal() {
if (!isEditNotifier.value) {
return;
}
showSheet(
builder: (_, type) {
return ValueListenableBuilder(
valueListenable: addedChildrenNotifier,
builder: (_, value, __) {
return AdaptiveSheetScaffold(
type: type,
body: _AddedWidgetsModal(
items: value,
onAdd: (gridItem) {
_childrenNotifier.value = List.from(_childrenNotifier.value)
..add(
gridItem,
);
},
),
title: appLocalizations.add,
);
},
);
},
context: context,
);
}
_initState() {
_transformController.value = 0;
_sizes = List.generate(length, (index) => Size.zero);
@@ -139,22 +105,18 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
_targetIndex = -1;
}
_handleChildrenNotifierChange() {
addedChildrenNotifier.value = widget.addedItemsBuilder != null
? widget.addedItemsBuilder!(_childrenNotifier.value)
: [];
}
@override
void initState() {
super.initState();
_childrenNotifier.addListener(() {
children = _childrenNotifier.value;
if (widget.onUpdate != null) {
widget.onUpdate!();
}
});
_childrenNotifier.value = widget.children;
_childrenNotifier.addListener(_handleChildrenNotifierChange);
isEditNotifier.addListener(_handleIsEditChange);
_fakeDragWidgetController = AnimationController.unbounded(
vsync: this,
duration: commonDuration,
@@ -164,6 +126,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
vsync: this,
duration: Duration(milliseconds: 120),
);
_shakeAnimation = Tween<double>(
begin: -0.012,
end: 0.012,
@@ -182,15 +145,11 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
_initState();
}
_handleIsEditChange() async {
_handleChildrenNotifierChange();
if (isEditNotifier.value == false) {
if (widget.onSave != null) {
await _transformCompleter?.future;
await Future.delayed(commonDuration);
widget.onSave!(_childrenNotifier.value);
}
}
handleAdd(GridItem gridItem) {
_childrenNotifier.value = List.from(_childrenNotifier.value)
..add(
gridItem,
);
}
@override
@@ -305,6 +264,9 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
}
_handleDragEnd(DraggableDetails details) async {
final children = List<GridItem>.from(_childrenNotifier.value);
children.insert(_targetIndex, children.removeAt(_dragIndexNotifier.value));
this.children = children;
debouncer.cancel(FunctionTag.handleWill);
if (_targetIndex == -1) {
return;
@@ -334,8 +296,6 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
_fakeDragWidgetAnimation = null;
_transformTweenMap.clear();
_transformAnimationMap.clear();
final children = List<GridItem>.from(_childrenNotifier.value);
children.insert(_targetIndex, children.removeAt(_dragIndexNotifier.value));
_childrenNotifier.value = children;
_initState();
}
@@ -385,17 +345,15 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
_initState();
}
Widget _wrapTransform(Widget rawChild, int index) {
Widget _buildTransform(Widget rawChild, int index) {
return ValueListenableBuilder(
valueListenable: _animating,
builder: (_, animating, child) {
if (animating) {
if (_dragIndexNotifier.value == index) {
return _sizeBoxWrap(
Container(),
index,
);
}
if (animating && _dragIndexNotifier.value == index) {
return _buildSizeBox(
Container(),
index,
);
}
return child!;
},
@@ -442,7 +400,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
return nextOffset;
}
Widget _sizeBoxWrap(Widget child, int index) {
Widget _buildSizeBox(Widget child, int index) {
return ValueListenableBuilder(
valueListenable: _dragWidgetSizeNotifier,
builder: (_, size, child) {
@@ -455,7 +413,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
);
}
Widget _ignoreWrap(Widget child) {
Widget _buildInactivate(Widget child) {
return ValueListenableBuilder(
valueListenable: _animating,
builder: (_, animating, child) {
@@ -471,7 +429,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
);
}
Widget _shakeWrap(Widget child) {
Widget _buildShake(Widget child) {
final random = 0.7 + Random().nextDouble() * 0.3;
_shakeController.stop();
_shakeController.repeat(reverse: true);
@@ -487,7 +445,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
);
}
Widget _draggableWrap({
Widget _buildDraggable({
required Widget childWhenDragging,
required Widget feedback,
required Widget item,
@@ -523,7 +481,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
if (dragIndex == index) {
return child!;
}
return _shakeWrap(
return _buildShake(
_DeletableContainer(
onDelete: () {
_handleDelete(index);
@@ -566,16 +524,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
},
child: shakeTarget,
);
return ValueListenableBuilder(
valueListenable: isEditNotifier,
builder: (_, isEdit, child) {
if (!isEdit) {
return item;
}
return child!;
},
child: draggableChild,
);
return draggableChild;
}
Widget _builderItem(int index) {
@@ -590,7 +539,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
final childWhenDragging = ActivateBox(
child: Opacity(
opacity: 0.6,
child: _sizeBoxWrap(
child: _buildSizeBox(
CommonCard(
child: child,
),
@@ -599,7 +548,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
),
);
final feedback = ActivateBox(
child: _sizeBoxWrap(
child: _buildSizeBox(
CommonCard(
child: Material(
elevation: 6,
@@ -609,8 +558,8 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
index,
),
);
return _wrapTransform(
_draggableWrap(
return _buildTransform(
_buildDraggable(
childWhenDragging: childWhenDragging,
feedback: feedback,
item: child,
@@ -631,7 +580,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
if (!animating || _fakeDragWidgetAnimation == null || index == -1) {
return Container();
}
return _sizeBoxWrap(
return _buildSizeBox(
AnimatedBuilder(
animation: _fakeDragWidgetAnimation!,
builder: (_, child) {
@@ -658,10 +607,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
_transformController.dispose();
_dragIndexNotifier.dispose();
_animating.dispose();
_childrenNotifier.removeListener(_handleChildrenNotifierChange);
_childrenNotifier.dispose();
isEditNotifier.removeListener(_handleIsEditChange);
isEditNotifier.dispose();
super.dispose();
}
@@ -670,7 +616,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
return DeferredPointerHandler(
child: Stack(
children: [
_ignoreWrap(
_buildInactivate(
ValueListenableBuilder(
valueListenable: _childrenNotifier,
builder: (_, children, __) {
@@ -694,46 +640,6 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
}
}
class _AddedWidgetsModal extends StatelessWidget {
final List<GridItem> items;
final Function(GridItem item) onAdd;
const _AddedWidgetsModal({
required this.items,
required this.onAdd,
});
@override
Widget build(BuildContext context) {
return DeferredPointerHandler(
child: SingleChildScrollView(
padding: EdgeInsets.all(
16,
),
child: Grid(
crossAxisCount: 8,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
children: items
.map(
(item) => item.wrap(
builder: (child) {
return _AddedContainer(
onAdd: () {
onAdd(item);
},
child: child,
);
},
),
)
.toList(),
),
),
);
}
}
class _DeletableContainer extends StatefulWidget {
final Widget child;
final VoidCallback onDelete;
@@ -841,68 +747,3 @@ class _DeletableContainerState extends State<_DeletableContainer>
);
}
}
class _AddedContainer extends StatefulWidget {
final Widget child;
final VoidCallback onAdd;
const _AddedContainer({
required this.child,
required this.onAdd,
});
@override
State<_AddedContainer> createState() => _AddedContainerState();
}
class _AddedContainerState extends State<_AddedContainer> {
@override
void initState() {
super.initState();
}
@override
void didUpdateWidget(_AddedContainer oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.child != widget.child) {}
}
_handleAdd() async {
widget.onAdd();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
ActivateBox(
child: widget.child,
),
Positioned(
top: -8,
right: -8,
child: DeferPointer(
child: SizedBox(
width: 24,
height: 24,
child: IconButton.filled(
iconSize: 20,
padding: EdgeInsets.all(2),
onPressed: _handleAdd,
icon: Icon(
Icons.add,
),
),
),
),
)
],
);
}
}

View File

@@ -81,6 +81,24 @@ static gboolean my_application_local_command_line(GApplication* application, gch
return TRUE;
}
// Implements GApplication::startup.
static void my_application_startup(GApplication* application) {
//MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application startup.
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
}
// Implements GApplication::shutdown.
static void my_application_shutdown(GApplication* application) {
//MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application shutdown.
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
@@ -91,12 +109,21 @@ static void my_application_dispose(GObject* object) {
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
nullptr)); }
// Set the program name to the application ID, which helps various systems
// like GTK and desktop environments map this running application to its
// corresponding .desktop file. This ensures better integration by allowing
// the application to be recognized beyond its binary name.
g_set_prgname(APPLICATION_ID);
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
nullptr));
}

View File

@@ -40,7 +40,7 @@ PODS:
- FlutterMacOS
- window_ext (0.0.1):
- FlutterMacOS
- window_manager (0.2.0):
- window_manager (0.5.0):
- FlutterMacOS
DEPENDENCIES:
@@ -128,7 +128,7 @@ SPEC CHECKSUMS:
tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
window_ext: 4afef727fe428b30c68ce800ba92e890fd329f63
window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c
window_manager: b729e31d38fb04905235df9ea896128991cad99e
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367

View File

@@ -161,30 +161,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.9.5"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
url: "https://pub.dev"
source: hosted
version: "3.4.1"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
url: "https://pub.dev"
source: hosted
version: "4.1.1"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
characters:
dependency: transitive
description:
@@ -274,7 +250,7 @@ packages:
source: hosted
version: "0.3.4+2"
crypto:
dependency: "direct dev"
dependency: "direct main"
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
@@ -479,7 +455,7 @@ packages:
source: sdk
version: "0.0.0"
flutter_cache_manager:
dependency: transitive
dependency: "direct main"
description:
name: flutter_cache_manager
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
@@ -524,6 +500,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.6.1"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1
url: "https://pub.dev"
source: hosted
version: "2.1.0"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -886,14 +870,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.5.0"
octo_image:
dependency: transitive
description:
name: octo_image
sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
package_config:
dependency: transitive
description:
@@ -926,6 +902,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
path_provider:
dependency: "direct main"
description:
@@ -1474,6 +1458,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.5.1"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de"
url: "https://pub.dev"
source: hosted
version: "1.1.18"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
url: "https://pub.dev"
source: hosted
version: "1.1.13"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "557a315b7d2a6dbb0aaaff84d857967ce6bdc96a63dc6ee2a57ce5a6ee5d3331"
url: "https://pub.dev"
source: hosted
version: "1.1.17"
vector_math:
dependency: transitive
description:
@@ -1557,10 +1565,10 @@ packages:
dependency: "direct main"
description:
name: window_manager
sha256: "732896e1416297c63c9e3fb95aea72d0355f61390263982a47fd519169dc5059"
sha256: "51d50168ab267d344b975b15390426b1243600d436770d3f13de67e55b05ec16"
url: "https://pub.dev"
source: hosted
version: "0.4.3"
version: "0.5.0"
xdg_directories:
dependency: transitive
description:
@@ -1578,7 +1586,7 @@ packages:
source: hosted
version: "6.5.0"
yaml:
dependency: "direct main"
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce

View File

@@ -1,7 +1,7 @@
name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none'
version: 0.8.85+202505291
version: 0.8.85+202506063
environment:
sdk: '>=3.1.0 <4.0.0'
@@ -14,7 +14,7 @@ dependencies:
path_provider: ^2.1.0
path: ^1.9.0
shared_preferences: ^2.5.3
window_manager: ^0.4.3
window_manager: ^0.5.0
dynamic_color: ^1.7.0
proxy:
path: plugins/proxy
@@ -42,7 +42,6 @@ dependencies:
archive: ^3.6.1
lpinyin: ^2.0.3
emoji_regex: ^0.0.5
cached_network_image: ^3.4.0
hotkey_manager: ^0.2.3
uni_platform: ^0.1.3
device_info_plus: ^11.3.3
@@ -57,7 +56,10 @@ dependencies:
git:
url: https://github.com/chen08209/flutter_js
ref: master
yaml: ^3.1.3
# yaml: ^3.1.3
flutter_svg: ^2.1.0
flutter_cache_manager: ^3.4.1
crypto: ^3.0.3
dev_dependencies:
flutter_test:
sdk: flutter
@@ -68,9 +70,8 @@ dev_dependencies:
args: ^2.4.2
freezed: ^2.5.1
riverpod_generator: ^2.6.3
custom_lint: ^0.7.0
riverpod_lint: ^2.6.3
crypto: ^3.0.3
custom_lint: ^0.7.0
flutter:
uses-material-design: true

View File

@@ -1,4 +1,5 @@
fn main() {
let version = std::env::var("TOKEN").unwrap_or_default();
println!("cargo:rustc-env=TOKEN={}", version);
println!("cargo:rerun-if-env-changed=TOKEN");
}