Compare commits

...

27 Commits

Author SHA1 Message Date
chen08209
f2aa8851ae Remove request validate certificate
Sync core
2024-07-18 21:28:27 +08:00
chen08209
ec2890cab2 Fix windows error 2024-07-18 17:27:29 +08:00
chen08209
ca946c1b06 Fix setup.dart error 2024-07-18 16:39:28 +08:00
chen08209
3bc3172723 Fix android system proxy not effective
Add macos arm64
2024-07-18 16:33:53 +08:00
chen08209
82be4cc45f Optimize proxies page
Support mouse drag scroll

Adjust desktop ui
2024-07-17 14:52:15 +08:00
chen08209
2c3f4ae8a8 Revert "Fix android vpn issues"
This reverts commit 891977408e.
2024-07-17 14:51:52 +08:00
chen08209
891977408e Fix android vpn issues 2024-07-15 16:19:58 +08:00
chen08209
5292f34e8d Fix android vpn issues 2024-07-15 16:18:51 +08:00
chen08209
1c54db6bf3 Rollback partial modification 2024-07-15 16:14:19 +08:00
chen08209
a4b5f4abdb Fix the problem that ui can't be synchronized when android vpn is occupied by an external
Override default socksPort,port
2024-07-15 13:47:06 +08:00
chen08209
aa4ffbe4fb Fix fab issues 2024-07-14 01:37:44 +08:00
chen08209
3d25298639 Update version 2024-07-14 00:05:42 +08:00
chen08209
1765576d09 Fix the problem that vpn cannot be started in some cases
Fix the problem that geodata url does not take effect
2024-07-14 00:01:18 +08:00
chen08209
2dd45062f1 Update ua
Fix change outbound mode without check ip issues
2024-07-13 20:43:28 +08:00
chen08209
c6407984ac Separate android ui and vpn 2024-07-13 20:43:21 +08:00
chen08209
53af86238e Fix url validate issues 2
Add android hidden from the recent task

Add geoip file

Support modify geoData URL
2024-07-13 20:43:18 +08:00
chen08209
b20d9edec2 Fix url validate issues
Fix check ip performance problem

Optimize resources page
2024-07-07 13:37:08 +08:00
chen08209
5c3a0c576d Add ua selector
Support modify test url

Optimize android proxy

Fix the error that async proxy provider could not selected the proxy
2024-07-04 09:55:06 +08:00
chen08209
6dcb466fd3 Fix android proxy error 2024-07-01 20:57:24 +08:00
chen08209
acbcec358b Fix submit error 2024-07-01 19:51:11 +08:00
chen08209
a923549ddf Add windows tun
Optimize android proxy

Optimize change profile

Update application ua

Optimize delay test
2024-07-01 19:41:57 +08:00
chen08209
07bd21580b Fix android repeated request notification issues 2024-06-28 21:16:47 +08:00
chen08209
57ceb64a5e Fix memory overflow issues 2024-06-28 07:49:06 +08:00
chen08209
713e83d9d8 Optimize proxies expansion panel 2
Fix android scan qrcode error
2024-06-27 19:39:49 +08:00
chen08209
5e3b0e4929 Optimize proxies expansion panel
Fix text error
2024-06-27 15:54:10 +08:00
chen08209
0389b6eb29 Optimize proxy
Optimize delayed sorting performance

Add expansion panel proxies page

Support to adjust the proxy card size

Support to adjust proxies columns number
2024-06-26 16:04:30 +08:00
chen08209
8f22cbf746 Fix autoRun show issues
Fix Android 10 issues

Optimize ip show
2024-06-23 03:07:52 +08:00
130 changed files with 11421 additions and 3200 deletions

View File

@@ -15,17 +15,37 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
- platform: windows - platform: windows
os: windows-latest os: windows-latest
arch: amd64
- platform: linux - platform: linux
os: ubuntu-latest os: ubuntu-latest
arch: amd64
- platform: macos - platform: macos
os: macos-13 os: macos-13
arch: amd64
- platform: macos
os: macos-latest
arch: arm64
steps: steps:
- name: Setup Mingw64
if: startsWith(matrix.platform,'windows')
uses: msys2/setup-msys2@v2
with:
msystem: mingw64
install: mingw-w64-x86_64-gcc
update: true
- name: Set Mingw64 Env
if: startsWith(matrix.platform,'windows')
run: |
echo "${{ runner.temp }}\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Check Matrix - name: Check Matrix
run: | run: |
echo "Running on ${{ matrix.os }}" echo "Running on ${{ matrix.os }}"
echo "Arch: ${{ runner.arch }}" echo "Arch: ${{ runner.arch }}"
gcc --version gcc --version
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -52,10 +72,10 @@ jobs:
if: startsWith(matrix.platform,'android') if: startsWith(matrix.platform,'android')
run: | run: |
echo "${{ secrets.KEYSTORE }}" | base64 --decode > android/app/keystore.jks echo "${{ secrets.KEYSTORE }}" | base64 --decode > android/app/keystore.jks
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/local.properties echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/local.properties
echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/local.properties echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/local.properties
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/local.properties echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/local.properties
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
@@ -75,13 +95,12 @@ jobs:
run: flutter pub get run: flutter pub get
- name: Setup - name: Setup
run: | run: dart setup.dart ${{ matrix.platform }} ${{ matrix.arch && format('--arch {0}', matrix.arch) }}
dart setup.dart ${{ matrix.platform }}
- name: Upload - name: Upload
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: artifact-${{ matrix.platform }} name: artifact-${{ matrix.platform }}${{ matrix.arch && format('-{0}', matrix.arch) }}
path: ./dist path: ./dist
retention-days: 1 retention-days: 1
overwrite: true overwrite: true

View File

@@ -22,8 +22,10 @@
<application <application
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:networkSecurityConfig="@xml/network_security_config"
android:extractNativeLibs="true" android:extractNativeLibs="true"
android:label="FlClash"> android:label="FlClash"
tools:targetApi="n">
<activity <activity
android:name="com.follow.clash.MainActivity" android:name="com.follow.clash.MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"

View File

@@ -1,10 +1,15 @@
package com.follow.clash package com.follow.clash
import android.content.Context
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.follow.clash.plugins.AppPlugin import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ProxyPlugin
import com.follow.clash.plugins.TilePlugin import com.follow.clash.plugins.TilePlugin
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import java.util.Date import io.flutter.embedding.engine.dart.DartExecutor
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
enum class RunState { enum class RunState {
START, START,
@@ -12,16 +17,46 @@ enum class RunState {
STOP STOP
} }
class GlobalState {
companion object {
val runState: MutableLiveData<RunState> = MutableLiveData<RunState>(RunState.STOP)
var runTime: Date? = null
var flutterEngine: FlutterEngine? = null
fun getCurrentTilePlugin(): TilePlugin? =
flutterEngine?.plugins?.get(TilePlugin::class.java) as TilePlugin?
fun getCurrentAppPlugin(): AppPlugin? = object GlobalState {
flutterEngine?.plugins?.get(AppPlugin::class.java) as AppPlugin?
private val lock = ReentrantLock()
val runState: MutableLiveData<RunState> = MutableLiveData<RunState>(RunState.STOP)
var flutterEngine: FlutterEngine? = null
private var serviceEngine: FlutterEngine? = null
fun getCurrentAppPlugin(): AppPlugin? {
val currentEngine = if (flutterEngine != null) flutterEngine else serviceEngine
return currentEngine?.plugins?.get(AppPlugin::class.java) as AppPlugin?
}
fun getCurrentTitlePlugin(): TilePlugin? {
val currentEngine = if (flutterEngine != null) flutterEngine else serviceEngine
return currentEngine?.plugins?.get(TilePlugin::class.java) as TilePlugin?
}
fun destroyServiceEngine() {
serviceEngine?.destroy()
serviceEngine = null
}
fun initServiceEngine(context: Context) {
if (serviceEngine != null) return
lock.withLock {
destroyServiceEngine()
serviceEngine = FlutterEngine(context)
serviceEngine?.plugins?.add(ProxyPlugin())
serviceEngine?.plugins?.add(AppPlugin())
serviceEngine?.plugins?.add(TilePlugin())
val vpnService = DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
"vpnService"
)
serviceEngine?.dartExecutor?.executeDartEntrypoint(
vpnService,
)
}
} }
} }

View File

@@ -10,7 +10,6 @@ import io.flutter.embedding.engine.FlutterEngine
class MainActivity : FlutterActivity() { class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GlobalState.flutterEngine?.destroy()
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
flutterEngine.plugins.add(AppPlugin()) flutterEngine.plugins.add(AppPlugin())
flutterEngine.plugins.add(ProxyPlugin()) flutterEngine.plugins.add(ProxyPlugin())

View File

@@ -2,27 +2,27 @@ package com.follow.clash.plugins
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.Uri
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat.getSystemService
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.graphics.drawable.toBitmap import com.follow.clash.GlobalState
import com.follow.clash.extensions.getBase64 import com.follow.clash.extensions.getBase64
import com.follow.clash.extensions.getInetSocketAddress
import com.follow.clash.extensions.getProtocol import com.follow.clash.extensions.getProtocol
import com.follow.clash.models.Process
import com.follow.clash.models.Package import com.follow.clash.models.Package
import com.follow.clash.models.Process
import com.google.gson.Gson import com.google.gson.Gson
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.Result
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
@@ -30,6 +30,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.net.InetSocketAddress import java.net.InetSocketAddress
class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware { class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware {
private var activity: Activity? = null private var activity: Activity? = null
@@ -47,31 +48,41 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
private val iconMap = mutableMapOf<String, String?>() private val iconMap = mutableMapOf<String, String?>()
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
scope = CoroutineScope(Dispatchers.Default)
context = flutterPluginBinding.applicationContext; context = flutterPluginBinding.applicationContext;
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "app") channel = MethodChannel(flutterPluginBinding.binaryMessenger, "app")
channel.setMethodCallHandler(this) channel.setMethodCallHandler(this)
} }
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null) channel.setMethodCallHandler(null)
scope.cancel()
} }
private fun tip(message: String?) { private fun tip(message: String?) {
if (toast != null) { if(GlobalState.flutterEngine == null){
toast!!.cancel() if (toast != null) {
toast!!.cancel()
}
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT)
toast!!.show()
} }
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT)
toast!!.show()
} }
@RequiresApi(Build.VERSION_CODES.Q) override fun onMethodCall(call: MethodCall, result: Result) {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) { when (call.method) {
"moveTaskToBack" -> { "moveTaskToBack" -> {
activity?.moveTaskToBack(true) activity?.moveTaskToBack(true)
result.success(true); result.success(true);
} }
"updateExcludeFromRecents" -> {
val value = call.argument<Boolean>("value")
updateExcludeFromRecents(value)
result.success(true);
}
"getPackages" -> { "getPackages" -> {
scope.launch { scope.launch {
result.success(getPackages()) result.success(getPackages())
@@ -116,7 +127,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
} }
scope.launch { scope.launch {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q){ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
result.success(null) result.success(null)
return@withContext return@withContext
} }
@@ -151,7 +162,6 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
val message = call.argument<String>("message") val message = call.argument<String>("message")
tip(message) tip(message)
result.success(true) result.success(true)
} }
else -> { else -> {
@@ -160,6 +170,24 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
} }
} }
private fun updateExcludeFromRecents(value: Boolean?) {
if (context == null) return
val am = getSystemService(context!!, ActivityManager::class.java)
val task = am?.appTasks?.firstOrNull {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
it.taskInfo.taskId == activity?.taskId
} else {
it.taskInfo.id == activity?.taskId
}
}
when (value) {
true -> task?.setExcludeFromRecents(value)
false -> task?.setExcludeFromRecents(value)
null -> task?.setExcludeFromRecents(false)
}
}
private suspend fun getPackageIcon(packageName: String): String? { private suspend fun getPackageIcon(packageName: String): String? {
val packageManager = context?.packageManager val packageManager = context?.packageManager
if (iconMap[packageName] == null) { if (iconMap[packageName] == null) {
@@ -199,11 +227,10 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
override fun onAttachedToActivity(binding: ActivityPluginBinding) { override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity; activity = binding.activity;
scope = CoroutineScope(Dispatchers.Default)
} }
override fun onDetachedFromActivityForConfigChanges() { override fun onDetachedFromActivityForConfigChanges() {
activity = null; activity = null
} }
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
@@ -212,7 +239,6 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
override fun onDetachedFromActivity() { override fun onDetachedFromActivity() {
channel.invokeMethod("exit", null) channel.invokeMethod("exit", null)
scope.cancel() activity = null
activity = null;
} }
} }

View File

@@ -1,7 +1,6 @@
package com.follow.clash.plugins package com.follow.clash.plugins
import android.Manifest import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
@@ -9,8 +8,7 @@ import android.content.Intent
import android.content.ServiceConnection import android.content.ServiceConnection
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.VpnService import android.net.VpnService
import android.os.Build.VERSION import android.os.Build
import android.os.Build.VERSION_CODES
import android.os.IBinder import android.os.IBinder
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@@ -25,37 +23,36 @@ import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import java.util.Date
class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware { class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware {
private lateinit var flutterMethodChannel: MethodChannel
val VPN_PERMISSION_REQUEST_CODE = 1001 val VPN_PERMISSION_REQUEST_CODE = 1001
val NOTIFICATION_PERMISSION_REQUEST_CODE = 1002 val NOTIFICATION_PERMISSION_REQUEST_CODE = 1002
private lateinit var flutterMethodChannel: MethodChannel
private var activity: Activity? = null private var activity: Activity? = null
private var context: Context? = null private var context: Context? = null
private var flClashVpnService: FlClashVpnService? = null private var flClashVpnService: FlClashVpnService? = null
private var isBound = false private var port: Int = 7890
private var port: Int? = null
private var props: Props? = null private var props: Props? = null
private lateinit var title: String private var isBlockNotification: Boolean = false
private lateinit var content: String private var isStart: Boolean = false
private val connection = object : ServiceConnection { private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) { override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as FlClashVpnService.LocalBinder val binder = service as FlClashVpnService.LocalBinder
flClashVpnService = binder.getService() flClashVpnService = binder.getService()
port?.let { startVpn(it) } if (isStart) {
isBound = true startVpn()
} else {
flClashVpnService?.initServiceEngine()
}
} }
override fun onServiceDisconnected(arg0: ComponentName) { override fun onServiceDisconnected(arg: ComponentName) {
flClashVpnService = null flClashVpnService = null
isBound = false
} }
} }
@@ -70,21 +67,29 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
} }
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) {
"StartProxy" -> { "initService" -> {
port = call.argument<Int>("port") isStart = false
val args = call.argument<String>("args") initService()
props = requestNotificationsPermission()
if (args != null) Gson().fromJson(args, Props::class.java) else null
handleStartVpn()
result.success(true) result.success(true)
} }
"StopProxy" -> { "startProxy" -> {
isStart = true
port = call.argument<Int>("port")!!
val args = call.argument<String>("args")
props =
if (args != null) Gson().fromJson(args, Props::class.java) else null
startVpn()
result.success(true)
}
"stopProxy" -> {
stopVpn() stopVpn()
result.success(true) result.success(true)
} }
"SetProtect" -> { "setProtect" -> {
val fd = call.argument<Int>("fd") val fd = call.argument<Int>("fd")
if (fd != null) { if (fd != null) {
flClashVpnService?.protect(fd) flClashVpnService?.protect(fd)
@@ -94,14 +99,10 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
} }
} }
"GetRunTimeStamp" -> {
result.success(GlobalState.runTime?.time)
}
"startForeground" -> { "startForeground" -> {
title = call.argument<String>("title") as String val title = call.argument<String>("title") as String
content = call.argument<String>("content") as String val content = call.argument<String>("content") as String
requestNotificationsPermission() startForeground(title, content)
result.success(true) result.success(true)
} }
@@ -110,65 +111,41 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
} }
} }
private fun handleStartVpn() { private fun initService() {
val intent = VpnService.prepare(context) val intent = VpnService.prepare(context)
if (intent != null) { if (intent != null) {
activity?.startActivityForResult(intent, VPN_PERMISSION_REQUEST_CODE) activity?.startActivityForResult(intent, VPN_PERMISSION_REQUEST_CODE)
} else { } else {
bindService() if (flClashVpnService != null) {
flClashVpnService!!.initServiceEngine()
} else {
bindService()
}
} }
} }
private fun startVpn(port: Int) { private fun startVpn() {
if (GlobalState.runState.value == RunState.START) return; if (flClashVpnService == null) {
flClashVpnService?.start(port, props) bindService()
return
}
if (GlobalState.runState.value == RunState.START) return
GlobalState.runState.value = RunState.START GlobalState.runState.value = RunState.START
GlobalState.runTime = Date() flutterMethodChannel.invokeMethod("started", flClashVpnService?.start(port, props))
startAfter()
} }
private fun stopVpn() { private fun stopVpn() {
if (GlobalState.runState.value == RunState.STOP) return if (GlobalState.runState.value == RunState.STOP) return
GlobalState.runState.value = RunState.STOP
flClashVpnService?.stop() flClashVpnService?.stop()
unbindService() GlobalState.destroyServiceEngine()
GlobalState.runState.value = RunState.STOP;
GlobalState.runTime = null;
} }
@SuppressLint("ForegroundServiceType") private fun startForeground(title: String, content: String) {
private fun startForeground() {
if (GlobalState.runState.value != RunState.START) return if (GlobalState.runState.value != RunState.START) return
flClashVpnService?.startForeground(title, content) flClashVpnService?.startForeground(title, content)
} }
private fun requestNotificationsPermission() {
if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
val permission = context?.let {
ContextCompat.checkSelfPermission(
it,
Manifest.permission.POST_NOTIFICATIONS
)
}
if (permission == PackageManager.PERMISSION_GRANTED) {
startForeground()
} else {
activity?.let {
ActivityCompat.requestPermissions(
it,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
NOTIFICATION_PERMISSION_REQUEST_CODE
)
}
}
} else {
startForeground()
}
}
private fun startAfter() {
flutterMethodChannel.invokeMethod("startAfter", flClashVpnService?.fd)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) { override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity activity = binding.activity
binding.addActivityResultListener(::onActivityResult) binding.addActivityResultListener(::onActivityResult)
@@ -183,7 +160,7 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
stopVpn() stopVpn()
} }
} }
return true; return true
} }
private fun onRequestPermissionsResultListener( private fun onRequestPermissionsResultListener(
@@ -192,16 +169,33 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
grantResults: IntArray grantResults: IntArray
): Boolean { ): Boolean {
if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) { if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { isBlockNotification = true
startForeground()
}
} }
return true; return false
} }
private fun requestNotificationsPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val permission = context?.let {
ContextCompat.checkSelfPermission(
it,
Manifest.permission.POST_NOTIFICATIONS
)
}
if (permission != PackageManager.PERMISSION_GRANTED) {
if (isBlockNotification) return
if (activity == null) return
ActivityCompat.requestPermissions(
activity!!,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
NOTIFICATION_PERMISSION_REQUEST_CODE
)
}
}
}
override fun onDetachedFromActivityForConfigChanges() { override fun onDetachedFromActivityForConfigChanges() {
activity = null; activity = null
} }
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
@@ -209,7 +203,6 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
} }
override fun onDetachedFromActivity() { override fun onDetachedFromActivity() {
stopVpn()
activity = null activity = null
} }
@@ -217,11 +210,4 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
val intent = Intent(context, FlClashVpnService::class.java) val intent = Intent(context, FlClashVpnService::class.java)
context?.bindService(intent, connection, Context.BIND_AUTO_CREATE) context?.bindService(intent, connection, Context.BIND_AUTO_CREATE)
} }
private fun unbindService() {
if (isBound) {
context?.unbindService(connection)
isBound = false
}
}
} }

View File

@@ -1,3 +1,4 @@
package com.follow.clash.plugins package com.follow.clash.plugins
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin

View File

@@ -1,9 +1,9 @@
package com.follow.clash.services package com.follow.clash.services
import android.annotation.SuppressLint
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.IBinder
import android.service.quicksettings.Tile import android.service.quicksettings.Tile
import android.service.quicksettings.TileService import android.service.quicksettings.TileService
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
@@ -11,14 +11,9 @@ import androidx.lifecycle.Observer
import com.follow.clash.GlobalState import com.follow.clash.GlobalState
import com.follow.clash.RunState import com.follow.clash.RunState
import com.follow.clash.TempActivity import com.follow.clash.TempActivity
import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ProxyPlugin
import com.follow.clash.plugins.TilePlugin
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
@RequiresApi(Build.VERSION_CODES.N)
class FlClashTileService : TileService() { class FlClashTileService : TileService() {
private val observer = Observer<RunState> { runState -> private val observer = Observer<RunState> { runState ->
@@ -62,42 +57,25 @@ class FlClashTileService : TileService() {
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startActivityAndCollapse(pendingIntent) startActivityAndCollapse(pendingIntent)
}else{ } else {
startActivityAndCollapse(intent) startActivityAndCollapse(intent)
} }
} }
private var flutterEngine: FlutterEngine? = null;
private fun initFlutterEngine() {
flutterEngine = FlutterEngine(this)
flutterEngine?.plugins?.add(ProxyPlugin())
flutterEngine?.plugins?.add(TilePlugin())
flutterEngine?.plugins?.add(AppPlugin())
GlobalState.flutterEngine = flutterEngine
if (flutterEngine?.dartExecutor?.isExecutingDart != true) {
val vpnService = DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
"vpnService"
)
flutterEngine?.dartExecutor?.executeDartEntrypoint(vpnService)
}
}
override fun onClick() { override fun onClick() {
super.onClick() super.onClick()
activityTransfer() activityTransfer()
val currentTilePlugin = GlobalState.getCurrentTilePlugin()
if (GlobalState.runState.value == RunState.STOP) { if (GlobalState.runState.value == RunState.STOP) {
GlobalState.runState.value = RunState.PENDING GlobalState.runState.value = RunState.PENDING
if(currentTilePlugin == null){ val titlePlugin = GlobalState.getCurrentTitlePlugin()
initFlutterEngine() if (titlePlugin != null) {
}else{ titlePlugin.handleStart()
currentTilePlugin.handleStart() } else {
GlobalState.initServiceEngine(applicationContext)
} }
} else if(GlobalState.runState.value == RunState.START){ } else if (GlobalState.runState.value == RunState.START) {
GlobalState.runState.value = RunState.PENDING GlobalState.runState.value = RunState.PENDING
currentTilePlugin?.handleStop() GlobalState.getCurrentTitlePlugin()?.handleStop()
} }
} }

View File

@@ -11,20 +11,22 @@ import android.net.VpnService
import android.os.Binder import android.os.Binder
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.Parcel
import android.os.RemoteException
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.follow.clash.GlobalState import com.follow.clash.GlobalState
import com.follow.clash.MainActivity import com.follow.clash.MainActivity
import com.follow.clash.R import com.follow.clash.R
import com.follow.clash.models.AccessControlMode import com.follow.clash.models.AccessControlMode
import com.follow.clash.models.Props import com.follow.clash.models.Props
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class FlClashVpnService : VpnService() { class FlClashVpnService : VpnService() {
private val CHANNEL = "FlClash" private val CHANNEL = "FlClash"
var fd: Int? = null
private val notificationId: Int = 1 private val notificationId: Int = 1
private val passList = listOf( private val passList = listOf(
@@ -47,12 +49,13 @@ class FlClashVpnService : VpnService() {
"192.168.*" "192.168.*"
) )
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onCreate() {
return START_STICKY super.onCreate()
initServiceEngine()
} }
fun start(port: Int, props: Props?) { fun start(port: Int, props: Props?): Int? {
fd = with(Builder()) { return with(Builder()) {
addAddress("172.16.0.1", 30) addAddress("172.16.0.1", 30)
setMtu(9000) setMtu(9000)
addRoute("0.0.0.0", 0) addRoute("0.0.0.0", 0)
@@ -98,7 +101,6 @@ class FlClashVpnService : VpnService() {
stopForeground() stopForeground()
} }
private val notificationBuilder: NotificationCompat.Builder by lazy { private val notificationBuilder: NotificationCompat.Builder by lazy {
val intent = Intent(this, MainActivity::class.java) val intent = Intent(this, MainActivity::class.java)
@@ -117,7 +119,6 @@ class FlClashVpnService : VpnService() {
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT
) )
} }
with(NotificationCompat.Builder(this, CHANNEL)) { with(NotificationCompat.Builder(this, CHANNEL)) {
setSmallIcon(R.drawable.ic_stat_name) setSmallIcon(R.drawable.ic_stat_name)
setContentTitle("FlClash") setContentTitle("FlClash")
@@ -130,10 +131,14 @@ class FlClashVpnService : VpnService() {
setOngoing(true) setOngoing(true)
setShowWhen(false) setShowWhen(false)
setOnlyAlertOnce(true) setOnlyAlertOnce(true)
setAutoCancel(true); setAutoCancel(true)
} }
} }
fun initServiceEngine() {
GlobalState.initServiceEngine(applicationContext)
}
override fun onTrimMemory(level: Int) { override fun onTrimMemory(level: Int) {
super.onTrimMemory(level) super.onTrimMemory(level)
GlobalState.getCurrentAppPlugin()?.requestGc() GlobalState.getCurrentAppPlugin()?.requestGc()
@@ -168,14 +173,28 @@ class FlClashVpnService : VpnService() {
inner class LocalBinder : Binder() { inner class LocalBinder : Binder() {
fun getService(): FlClashVpnService = this@FlClashVpnService fun getService(): FlClashVpnService = this@FlClashVpnService
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
try {
val isSuccess = super.onTransact(code, data, reply, flags)
if (!isSuccess) {
CoroutineScope(Dispatchers.Main).launch {
GlobalState.getCurrentTitlePlugin()?.handleStop()
}
}
return isSuccess
} catch (e: RemoteException) {
throw e
}
}
} }
override fun onBind(intent: Intent): IBinder { override fun onBind(intent: Intent): IBinder {
return binder return binder
} }
override fun onUnbind(intent: Intent?): Boolean { override fun onUnbind(intent: Intent?): Boolean {
GlobalState.getCurrentTilePlugin()?.handleStop();
return super.onUnbind(intent) return super.onUnbind(intent)
} }

View File

@@ -7,4 +7,8 @@
<certificates src="user" /> <certificates src="user" />
</trust-anchors> </trust-anchors>
</base-config> </base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
<domain includeSubdomains="true">127.0.0.1</domain>
</domain-config>
</network-security-config> </network-security-config>

BIN
assets/data/GeoIP.dat Normal file

Binary file not shown.

View File

@@ -4,6 +4,7 @@ import "C"
import ( import (
"github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/adapter/outboundgroup"
ap "github.com/metacubex/mihomo/adapter/provider" ap "github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
@@ -20,6 +21,7 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"sync"
"syscall" "syscall"
"time" "time"
) )
@@ -59,11 +61,17 @@ type ruleProviderSchema struct {
Interval int `provider:"interval,omitempty"` Interval int `provider:"interval,omitempty"`
} }
type ConfigExtendedParams struct {
IsPatch bool `json:"is-patch"`
IsCompatible bool `json:"is-compatible"`
SelectedMap map[string]string `json:"selected-map"`
TestURL *string `json:"test-url"`
}
type GenerateConfigParams struct { type GenerateConfigParams struct {
ProfilePath *string `json:"profile-path"` ProfilePath *string `json:"profile-path"`
Config *config.RawConfig `json:"config" ` Config config.RawConfig `json:"config" `
IsPatch *bool `json:"is-patch"` Params ConfigExtendedParams `json:"params"`
IsCompatible *bool `json:"is-compatible"`
} }
type ChangeProxyParams struct { type ChangeProxyParams struct {
@@ -76,26 +84,11 @@ type TestDelayParams struct {
Timeout int64 `json:"timeout"` Timeout int64 `json:"timeout"`
} }
type Delay struct {
Name string `json:"name"`
Value int32 `json:"value"`
}
type Process struct {
Id int64 `json:"id"`
Metadata *constant.Metadata `json:"metadata"`
}
type ProcessMapItem struct { type ProcessMapItem struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Value string `json:"value"` Value string `json:"value"`
} }
type Now struct {
Name string `json:"name"`
Value string `json:"value"`
}
type ExternalProvider struct { type ExternalProvider struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
@@ -170,9 +163,9 @@ func getRawConfigWithPath(path *string) *config.RawConfig {
} }
} }
func decorationConfig(profilePath *string, cfg config.RawConfig, compatible bool) *config.RawConfig { func decorationConfig(profilePath *string, cfg config.RawConfig) *config.RawConfig {
prof := getRawConfigWithPath(profilePath) prof := getRawConfigWithPath(profilePath)
overwriteConfig(prof, cfg, compatible) overwriteConfig(prof, cfg)
return prof return prof
} }
@@ -322,14 +315,14 @@ func generateProxyGroupAndRule(proxyGroup *[]map[string]any, rule *[]string) {
*rule = computedRule *rule = computedRule
} }
func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig, compatible bool) { func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig) {
targetConfig.ExternalController = patchConfig.ExternalController targetConfig.ExternalController = patchConfig.ExternalController
targetConfig.ExternalUI = "" targetConfig.ExternalUI = ""
targetConfig.Interface = "" targetConfig.Interface = ""
targetConfig.ExternalUIURL = "" targetConfig.ExternalUIURL = ""
targetConfig.TCPConcurrent = patchConfig.TCPConcurrent targetConfig.TCPConcurrent = patchConfig.TCPConcurrent
targetConfig.UnifiedDelay = patchConfig.UnifiedDelay targetConfig.UnifiedDelay = patchConfig.UnifiedDelay
targetConfig.GeodataMode = false //targetConfig.GeodataMode = false
targetConfig.IPv6 = patchConfig.IPv6 targetConfig.IPv6 = patchConfig.IPv6
targetConfig.LogLevel = patchConfig.LogLevel targetConfig.LogLevel = patchConfig.LogLevel
targetConfig.Port = 0 targetConfig.Port = 0
@@ -340,10 +333,12 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
targetConfig.Mode = patchConfig.Mode targetConfig.Mode = patchConfig.Mode
targetConfig.Tun.Enable = patchConfig.Tun.Enable targetConfig.Tun.Enable = patchConfig.Tun.Enable
targetConfig.Tun.Device = patchConfig.Tun.Device targetConfig.Tun.Device = patchConfig.Tun.Device
//targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
//targetConfig.Tun.Stack = patchConfig.Tun.Stack targetConfig.Tun.Stack = patchConfig.Tun.Stack
targetConfig.GeodataLoader = patchConfig.GeodataLoader targetConfig.GeodataLoader = patchConfig.GeodataLoader
targetConfig.Profile.StoreSelected = false targetConfig.Profile.StoreSelected = false
targetConfig.GeoXUrl = patchConfig.GeoXUrl
targetConfig.GlobalUA = patchConfig.GlobalUA
if targetConfig.DNS.Enable == false { if targetConfig.DNS.Enable == false {
targetConfig.DNS = patchConfig.DNS targetConfig.DNS = patchConfig.DNS
} }
@@ -352,7 +347,7 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
//} else if runtime.GOOS == "windows" { //} else if runtime.GOOS == "windows" {
// targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder) // targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder)
//} //}
if compatible == false { if configParams.IsCompatible == false {
targetConfig.ProxyProvider = make(map[string]map[string]any) targetConfig.ProxyProvider = make(map[string]map[string]any)
targetConfig.RuleProvider = make(map[string]map[string]any) targetConfig.RuleProvider = make(map[string]map[string]any)
generateProxyGroupAndRule(&targetConfig.ProxyGroup, &targetConfig.Rule) generateProxyGroupAndRule(&targetConfig.ProxyGroup, &targetConfig.Rule)
@@ -388,16 +383,48 @@ func patchConfig(general *config.General) {
resolver.DisableIPv6 = !general.IPv6 resolver.DisableIPv6 = !general.IPv6
} }
func applyConfig(isPatch bool) { func patchSelectGroup() {
mapping := configParams.SelectedMap
if mapping == nil {
return
}
for name, proxy := range tunnel.ProxiesWithProviders() {
outbound, ok := proxy.(*adapter.Proxy)
if !ok {
continue
}
selector, ok := outbound.ProxyAdapter.(outboundgroup.SelectAble)
if !ok {
continue
}
selected, exist := mapping[name]
if !exist {
continue
}
selector.ForceSet(selected)
}
}
var applyLock sync.Mutex
func applyConfig() {
applyLock.Lock()
defer applyLock.Unlock()
cfg, err := config.ParseRawConfig(currentConfig) cfg, err := config.ParseRawConfig(currentConfig)
if err != nil { if err != nil {
cfg, _ = config.ParseRawConfig(config.DefaultRawConfig()) cfg, _ = config.ParseRawConfig(config.DefaultRawConfig())
} }
if isPatch { if configParams.TestURL != nil {
constant.DefaultTestURL = *configParams.TestURL
}
if configParams.IsPatch {
patchConfig(cfg.General) patchConfig(cfg.General)
} else { } else {
executor.Shutdown()
runtime.GC() runtime.GC()
hub.UltraApplyConfig(cfg, true) hub.UltraApplyConfig(cfg, true)
patchSelectGroup()
} }
} }

View File

@@ -1,6 +1,7 @@
package dart_bridge package dart_bridge
/* /*
#include <stdlib.h>
#include "stdint.h" #include "stdint.h"
#include "include/dart_api_dl.h" #include "include/dart_api_dl.h"
#include "include/dart_api_dl.c" #include "include/dart_api_dl.c"
@@ -24,14 +25,16 @@ func InitDartApi(api unsafe.Pointer) {
} }
} }
func SendToPort(port int64, msg string) { func SendToPort(port int64, msg string) bool {
var obj C.Dart_CObject var obj C.Dart_CObject
obj._type = C.Dart_CObject_kString obj._type = C.Dart_CObject_kString
msgString := C.CString(msg) msgString := C.CString(msg)
defer C.free(unsafe.Pointer(msgString))
ptr := unsafe.Pointer(&obj.value[0]) ptr := unsafe.Pointer(&obj.value[0])
*(**C.char)(ptr) = msgString *(**C.char)(ptr) = msgString
isSuccess := C.GoDart_PostCObject(C.Dart_Port_DL(port), &obj) isSuccess := C.GoDart_PostCObject(C.Dart_Port_DL(port), &obj)
if !isSuccess { if !isSuccess {
fmt.Println("ERROR: post to port ", port, " failed", msg) return false
} }
return true
} }

View File

@@ -1,38 +0,0 @@
package dart_bridge
import "encoding/json"
var Port *int64
type MessageType string
const (
Log MessageType = "log"
Tun MessageType = "tun"
Delay MessageType = "delay"
Now MessageType = "now"
Process MessageType = "process"
Request MessageType = "request"
Run MessageType = "run"
)
type Message struct {
Type MessageType `json:"type"`
Data interface{} `json:"data"`
}
func (message *Message) Json() (string, error) {
data, err := json.Marshal(message)
return string(data), err
}
func SendMessage(message Message) {
if Port == nil {
return
}
s, err := message.Json()
if err != nil {
return
}
SendToPort(*Port, s)
}

View File

@@ -7,8 +7,8 @@ replace github.com/metacubex/mihomo => ./Clash.Meta
require ( require (
github.com/Kr328/tun2socket v0.0.0-20220414050025-d07c78d06d34 github.com/Kr328/tun2socket v0.0.0-20220414050025-d07c78d06d34
github.com/metacubex/mihomo v1.17.1 github.com/metacubex/mihomo v1.17.1
github.com/miekg/dns v1.1.59 github.com/miekg/dns v1.1.61
golang.org/x/net v0.25.0 golang.org/x/net v0.26.0
golang.org/x/sync v0.7.0 golang.org/x/sync v0.7.0
) )
@@ -31,7 +31,7 @@ require (
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect github.com/gaukas/godicttls v0.0.4 // indirect
github.com/go-chi/chi/v5 v5.0.12 // indirect github.com/go-chi/chi/v5 v5.0.14 // indirect
github.com/go-chi/cors v1.2.1 // indirect github.com/go-chi/cors v1.2.1 // indirect
github.com/go-chi/render v1.0.3 // indirect github.com/go-chi/render v1.0.3 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
@@ -44,10 +44,10 @@ require (
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49 // indirect github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
@@ -55,13 +55,14 @@ require (
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect
github.com/metacubex/quic-go v0.43.2-0.20240518033621-2c3d14c6b38e // indirect github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e // indirect
github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 // indirect github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 // indirect
github.com/metacubex/sing-shadowsocks v0.2.6 // indirect github.com/metacubex/sing-shadowsocks v0.2.6 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.0 // indirect github.com/metacubex/sing-shadowsocks2 v0.2.0 // indirect
github.com/metacubex/sing-tun v0.2.7-0.20240512075008-89e7c6208eec // indirect github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e // indirect
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f // indirect github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f // indirect
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 // indirect github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a // indirect
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 // indirect github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 // indirect
github.com/metacubex/utls v1.6.6 // indirect github.com/metacubex/utls v1.6.6 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect github.com/mroth/weightedrand/v2 v2.1.0 // indirect
@@ -71,18 +72,19 @@ require (
github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect github.com/puzpuzpuz/xsync/v3 v3.2.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/sing v0.3.8 // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/sing v0.5.0-alpha.10 // indirect
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 // indirect github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e // indirect github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e // indirect
github.com/samber/lo v1.39.0 // indirect github.com/samber/lo v1.39.0 // indirect
github.com/shirou/gopsutil/v3 v3.24.4 // indirect github.com/shirou/gopsutil/v3 v3.24.5 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
@@ -91,21 +93,20 @@ require (
github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.6.1 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/vishvananda/netns v0.0.4 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zhangyunhao116/fastrand v0.4.0 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.23.0 // indirect golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/mod v0.17.0 // indirect golang.org/x/mod v0.18.0 // indirect
golang.org/x/sys v0.20.0 // indirect golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.0 // indirect golang.org/x/tools v0.22.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect lukechampine.com/blake3 v1.3.0 // indirect
) )

View File

@@ -48,8 +48,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 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 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= 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.14 h1:PyEwo2Vudraa0x/Wl6eDRRW2NXBvekgfxyydcM0WGE0=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.14/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= 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/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 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
@@ -75,7 +75,6 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= 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/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= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
@@ -85,16 +84,16 @@ github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7s
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= 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= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49 h1:/OuvSMGT9+xnyZ+7MZQ1zdngaCCAdPoSw8B/uurZ7pg= github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6 h1:dh8D8FksyMhD64mRMbUhZHWYJfNoNMCxfVq6eexleMw=
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic= github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -113,26 +112,28 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
github.com/metacubex/quic-go v0.43.2-0.20240518033621-2c3d14c6b38e h1:Nzwe08FNIJpExWpy9iXkG336dN/8nJqn69yijB7vJ8g= github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e h1:bLYn3GuRvWDcBDAkIv5kUYIhzHwafDVq635BuybnKqI=
github.com/metacubex/quic-go v0.43.2-0.20240518033621-2c3d14c6b38e/go.mod h1:uXHODgJFUfUnkkCMWLd5Er6L5QY/LFRZb9LD5jyyhsk= github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e/go.mod h1:Yza2H7Ax1rxWPUcJx0vW+oAt9EsPuSiyQFhFabUPzwU=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 h1:Wr4g1HCb5Z/QIFwFiVNjO2qL+dRu25+Mdn9xtAZZ+ew= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 h1:Wr4g1HCb5Z/QIFwFiVNjO2qL+dRu25+Mdn9xtAZZ+ew=
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ= github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ=
github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg=
github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A= github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A=
github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8= github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8=
github.com/metacubex/sing-tun v0.2.7-0.20240512075008-89e7c6208eec h1:K4Wq3GOdLZ/xcqwyzAt4kmYQrjokyKQ3u/Xh5Yft14U= github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e h1:o+zohxPRo45P35fS9u1zfdBgr+L/7S0ObGU6YjbVBIc=
github.com/metacubex/sing-tun v0.2.7-0.20240512075008-89e7c6208eec/go.mod h1:4VsMwZH1IlgPGFK1ZbBomZ/B2MYkTgs2+gnBAr5GOIo= github.com/metacubex/sing-tun v0.2.7-0.20240627012306-9d1f5fc0b45e/go.mod h1:WwJGbCx7bQcBzuQXiDOJvZH27R0kIjKNNlISIWsL6kM=
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ=
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 h1:AGyIB55UfQm/0ZH0HtQO9u3l//yjtHUpjeRjjPGfGRI= github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a h1:NpSGclHJUYndUwBmyIpFBSoBVg8PoVX7QQKhYg0DjM0=
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo= github.com/metacubex/sing-wireguard v0.0.0-20240618022557-a6efaa37127a/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c=
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8= github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo= github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
@@ -155,8 +156,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4= github.com/puzpuzpuz/xsync/v3 v3.2.0 h1:9AzuUeF88YC5bK8u2vEG1Fpvu4wgpM1wfPIExfaaDxQ=
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/puzpuzpuz/xsync/v3 v3.2.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
@@ -165,11 +166,13 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 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 h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= 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= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.3.8 h1:gm4JKalPhydMYX2zFOTnnd4TXtM/16WFRqSjMepYQQk= github.com/sagernet/sing v0.5.0-alpha.10 h1:kuHl10gpjbKQAdQfyogQU3u0CVnpqC3wrAHe/+BFaXc=
github.com/sagernet/sing v0.3.8/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI= github.com/sagernet/sing v0.5.0-alpha.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14= github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
@@ -180,8 +183,8 @@ github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2F
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE= github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8= github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -195,15 +198,9 @@ github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
@@ -215,14 +212,12 @@ github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zhangyunhao116/fastrand v0.4.0 h1:86QB6Y+GGgLZRFRDCjMmAS28QULwspK9sgL5d1Bx3H4=
github.com/zhangyunhao116/fastrand v0.4.0/go.mod h1:vIyo6EyBhjGKpZv6qVlkPl4JVAklpMM4DSKzbAkMguA=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
@@ -231,18 +226,18 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
@@ -262,26 +257,24 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
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.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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,5 +1,8 @@
package main package main
/*
#include <stdlib.h>
*/
import "C" import "C"
import ( import (
bridge "core/dart-bridge" bridge "core/dart-bridge"
@@ -10,6 +13,7 @@ import (
"github.com/metacubex/mihomo/adapter/provider" "github.com/metacubex/mihomo/adapter/provider"
"github.com/metacubex/mihomo/common/structure" "github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/geodata"
"github.com/metacubex/mihomo/component/mmdb" "github.com/metacubex/mihomo/component/mmdb"
"github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant"
@@ -28,8 +32,12 @@ import (
var currentConfig = config.DefaultRawConfig() var currentConfig = config.DefaultRawConfig()
var configParams = ConfigExtendedParams{}
var isInit = false var isInit = false
var currentProfileName = ""
//export initClash //export initClash
func initClash(homeDirStr *C.char) bool { func initClash(homeDirStr *C.char) bool {
if !isInit { if !isInit {
@@ -68,11 +76,21 @@ func forceGc() {
}() }()
} }
//export setCurrentProfileName
func setCurrentProfileName(s *C.char) {
currentProfileName = C.GoString(s)
}
//export getCurrentProfileName
func getCurrentProfileName() *C.char {
return C.CString(currentProfileName)
}
//export validateConfig //export validateConfig
func validateConfig(s *C.char, port C.longlong) { func validateConfig(s *C.char, port C.longlong) {
i := int64(port) i := int64(port)
bytes := []byte(C.GoString(s))
go func() { go func() {
bytes := []byte(C.GoString(s))
_, err := config.UnmarshalRawConfig(bytes) _, err := config.UnmarshalRawConfig(bytes)
if err != nil { if err != nil {
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
@@ -85,21 +103,18 @@ func validateConfig(s *C.char, port C.longlong) {
//export updateConfig //export updateConfig
func updateConfig(s *C.char, port C.longlong) { func updateConfig(s *C.char, port C.longlong) {
i := int64(port) i := int64(port)
paramsString := C.GoString(s)
go func() { go func() {
paramsString := C.GoString(s)
var params = &GenerateConfigParams{} var params = &GenerateConfigParams{}
err := json.Unmarshal([]byte(paramsString), params) err := json.Unmarshal([]byte(paramsString), params)
if err != nil { if err != nil {
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
return return
} }
prof := decorationConfig(params.ProfilePath, *params.Config, *params.IsCompatible) configParams = params.Params
prof := decorationConfig(params.ProfilePath, params.Config)
currentConfig = prof currentConfig = prof
if *params.IsPatch { applyConfig()
applyConfig(true)
} else {
applyConfig(false)
}
bridge.SendToPort(i, "") bridge.SendToPort(i, "")
}() }()
} }
@@ -147,30 +162,32 @@ func getProxies() *C.char {
} }
//export changeProxy //export changeProxy
func changeProxy(s *C.char) bool { func changeProxy(s *C.char) {
paramsString := C.GoString(s)
go func() { go func() {
paramsString := C.GoString(s)
var params = &ChangeProxyParams{} var params = &ChangeProxyParams{}
err := json.Unmarshal([]byte(paramsString), params) err := json.Unmarshal([]byte(paramsString), params)
if err != nil { if err != nil {
log.Infoln("Unmarshal ChangeProxyParams %v", err) log.Infoln("Unmarshal ChangeProxyParams %v", err)
} }
groupName := *params.GroupName
proxyName := *params.ProxyName
proxies := tunnel.ProxiesWithProviders() proxies := tunnel.ProxiesWithProviders()
proxy := proxies[*params.GroupName] group, ok := proxies[groupName]
if proxy == nil { if !ok {
return return
} }
log.Infoln("change proxy %s", proxy.Name()) adapterProxy := group.(*adapter.Proxy)
adapterProxy := proxy.(*adapter.Proxy)
selector, ok := adapterProxy.ProxyAdapter.(*outboundgroup.Selector) selector, ok := adapterProxy.ProxyAdapter.(*outboundgroup.Selector)
if !ok { if !ok {
return return
} }
if err := selector.Set(*params.ProxyName); err != nil {
return err = selector.Set(proxyName)
if err == nil {
log.Infoln("[Selector] %s selected %s", groupName, proxyName)
} }
}() }()
return true
} }
//export getTraffic //export getTraffic
@@ -211,8 +228,8 @@ func resetTraffic() {
//export asyncTestDelay //export asyncTestDelay
func asyncTestDelay(s *C.char, port C.longlong) { func asyncTestDelay(s *C.char, port C.longlong) {
i := int64(port) i := int64(port)
paramsString := C.GoString(s)
go func() { go func() {
paramsString := C.GoString(s)
var params = &TestDelayParams{} var params = &TestDelayParams{}
err := json.Unmarshal([]byte(paramsString), params) err := json.Unmarshal([]byte(paramsString), params)
if err != nil { if err != nil {
@@ -296,7 +313,6 @@ func closeConnections() bool {
//export closeConnection //export closeConnection
func closeConnection(id *C.char) bool { func closeConnection(id *C.char) bool {
connectionId := C.GoString(id) connectionId := C.GoString(id)
err := statistic.DefaultManager.Get(connectionId).Close() err := statistic.DefaultManager.Get(connectionId).Close()
if err != nil { if err != nil {
return false return false
@@ -307,10 +323,13 @@ func closeConnection(id *C.char) bool {
//export getProviders //export getProviders
func getProviders() *C.char { func getProviders() *C.char {
data, err := json.Marshal(tunnel.Providers()) data, err := json.Marshal(tunnel.Providers())
var msg *C.char
if err != nil { if err != nil {
return C.CString("") msg = C.CString("")
return msg
} }
return C.CString(string(data)) msg = C.CString(string(data))
return msg
} }
//export getProvider //export getProvider
@@ -360,10 +379,9 @@ func getExternalProviders() *C.char {
//export updateExternalProvider //export updateExternalProvider
func updateExternalProvider(providerName *C.char, providerType *C.char, port C.longlong) { func updateExternalProvider(providerName *C.char, providerType *C.char, port C.longlong) {
i := int64(port) i := int64(port)
providerNameString := C.GoString(providerName)
providerTypeString := C.GoString(providerType)
go func() { go func() {
providerNameString := C.GoString(providerName)
providerTypeString := C.GoString(providerType)
switch providerTypeString { switch providerTypeString {
case "Proxy": case "Proxy":
providers := tunnel.Providers() providers := tunnel.Providers()
@@ -379,34 +397,49 @@ func updateExternalProvider(providerName *C.char, providerType *C.char, port C.l
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
return return
} }
case "GeoIp": case "MMDB":
err := mmdb.DownloadMMDB(constant.Path.Resolve(providerNameString)) err := mmdb.DownloadMMDB(constant.Path.Resolve(providerNameString))
if err != nil { if err != nil {
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
return return
} }
case "GeoSite":
err := mmdb.DownloadGeoSite(constant.Path.Resolve(providerNameString))
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
case "ASN": case "ASN":
err := mmdb.DownloadASN(constant.Path.Resolve(providerNameString)) err := mmdb.DownloadASN(constant.Path.Resolve(providerNameString))
if err != nil { if err != nil {
bridge.SendToPort(i, err.Error()) bridge.SendToPort(i, err.Error())
return return
} }
case "GeoIp":
err := geodata.DownloadGeoIP(constant.Path.Resolve(providerNameString))
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
case "GeoSite":
err := geodata.DownloadGeoSite(constant.Path.Resolve(providerNameString))
if err != nil {
bridge.SendToPort(i, err.Error())
return
}
} }
bridge.SendToPort(i, "") bridge.SendToPort(i, "")
}() }()
} }
//export initNativeApiBridge //export initNativeApiBridge
func initNativeApiBridge(api unsafe.Pointer, port C.longlong) { func initNativeApiBridge(api unsafe.Pointer) {
bridge.InitDartApi(api) bridge.InitDartApi(api)
}
//export initMessage
func initMessage(port C.longlong) {
i := int64(port) i := int64(port)
bridge.Port = &i Port = i
}
//export freeCString
func freeCString(s *C.char) {
C.free(unsafe.Pointer(s))
} }
func init() { func init() {
@@ -419,15 +452,21 @@ func init() {
} else { } else {
delayData.Value = int32(delay) delayData.Value = int32(delay)
} }
bridge.SendMessage(bridge.Message{ SendMessage(Message{
Type: bridge.Delay, Type: DelayMessage,
Data: delayData, Data: delayData,
}) })
} }
statistic.DefaultRequestNotify = func(c statistic.Tracker) { statistic.DefaultRequestNotify = func(c statistic.Tracker) {
bridge.SendMessage(bridge.Message{ SendMessage(Message{
Type: bridge.Request, Type: RequestMessage,
Data: c, Data: c,
}) })
} }
executor.DefaultProxyProviderLoadedHook = func(providerName string) {
SendMessage(Message{
Type: LoadedMessage,
Data: providerName,
})
}
} }

View File

@@ -2,7 +2,6 @@ package main
import "C" import "C"
import ( import (
bridge "core/dart-bridge"
"github.com/metacubex/mihomo/common/observable" "github.com/metacubex/mihomo/common/observable"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
) )
@@ -21,11 +20,11 @@ func startLog() {
if logData.LogLevel < log.Level() { if logData.LogLevel < log.Level() {
continue continue
} }
message := &bridge.Message{ message := &Message{
Type: bridge.Log, Type: LogMessage,
Data: logData, Data: logData,
} }
bridge.SendMessage(*message) SendMessage(*message)
} }
}() }()
} }

77
core/message.go Normal file
View File

@@ -0,0 +1,77 @@
package main
import (
bridge "core/dart-bridge"
"encoding/json"
"github.com/metacubex/mihomo/constant"
)
var Port int64
var ServicePort int64
type MessageType string
const (
LogMessage MessageType = "log"
ProtectMessage MessageType = "protect"
DelayMessage MessageType = "delay"
ProcessMessage MessageType = "process"
RequestMessage MessageType = "request"
StartedMessage MessageType = "started"
LoadedMessage MessageType = "loaded"
)
type Delay struct {
Name string `json:"name"`
Value int32 `json:"value"`
}
type Process struct {
Id int64 `json:"id"`
Metadata *constant.Metadata `json:"metadata"`
}
type Message struct {
Type MessageType `json:"type"`
Data interface{} `json:"data"`
}
func (message *Message) Json() (string, error) {
data, err := json.Marshal(message)
return string(data), err
}
func SendMessage(message Message) {
s, err := message.Json()
if err != nil {
return
}
if handler, ok := messageHandlers[message.Type]; ok {
handler(s)
} else {
sendToPort(s)
}
}
var messageHandlers = map[MessageType]func(string) bool{
ProtectMessage: sendToServicePort,
ProcessMessage: sendToServicePort,
StartedMessage: conditionalSend,
LoadedMessage: conditionalSend,
}
func sendToPort(s string) bool {
return bridge.SendToPort(Port, s)
}
func sendToServicePort(s string) bool {
return bridge.SendToPort(ServicePort, s)
}
func conditionalSend(s string) bool {
isSuccess := sendToPort(s)
if !isSuccess {
return sendToServicePort(s)
}
return isSuccess
}

42
core/platform/limit.go Normal file
View File

@@ -0,0 +1,42 @@
//go:build android
package platform
import "syscall"
var nullFd int
var maxFdCount int
func init() {
fd, err := syscall.Open("/dev/null", syscall.O_WRONLY, 0644)
if err != nil {
panic(err.Error())
}
nullFd = fd
var limit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
maxFdCount = 1024
} else {
maxFdCount = int(limit.Cur)
}
maxFdCount = maxFdCount / 4 * 3
}
func ShouldBlockConnection() bool {
fd, err := syscall.Dup(nullFd)
if err != nil {
return true
}
_ = syscall.Close(fd)
if fd > maxFdCount {
return true
}
return false
}

View File

@@ -4,7 +4,6 @@ package main
import "C" import "C"
import ( import (
bridge "core/dart-bridge"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/metacubex/mihomo/component/process" "github.com/metacubex/mihomo/component/process"
@@ -43,8 +42,8 @@ func init() {
timeout := time.After(200 * time.Millisecond) timeout := time.After(200 * time.Millisecond)
bridge.SendMessage(bridge.Message{ SendMessage(Message{
Type: bridge.Process, Type: ProcessMessage,
Data: Process{ Data: Process{
Id: id, Id: id,
Metadata: metadata, Metadata: metadata,
@@ -71,8 +70,8 @@ func setProcessMap(s *C.char) {
if s == nil { if s == nil {
return return
} }
paramsString := C.GoString(s)
go func() { go func() {
paramsString := C.GoString(s)
var processMapItem = &ProcessMapItem{} var processMapItem = &ProcessMapItem{}
err := json.Unmarshal([]byte(paramsString), processMapItem) err := json.Unmarshal([]byte(paramsString), processMapItem)
if err == nil { if err == nil {

43
core/status.go Normal file
View File

@@ -0,0 +1,43 @@
//go:build android
package main
import "C"
import (
"encoding/json"
"fmt"
)
type AccessControl struct {
Mode string `json:"mode"`
AcceptList []string `json:"acceptList"`
RejectList []string `json:"rejectList"`
IsFilterSystemApp bool `json:"isFilterSystemApp"`
}
type AndroidProps struct {
AccessControl *AccessControl `json:"accessControl"`
AllowBypass bool `json:"allowBypass"`
SystemProxy bool `json:"systemProxy"`
}
var androidProps AndroidProps
//export getAndroidProps
func getAndroidProps() *C.char {
data, err := json.Marshal(androidProps)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
}
return C.CString(string(data))
}
//export setAndroidProps
func setAndroidProps(s *C.char) {
paramsString := C.GoString(s)
err := json.Unmarshal([]byte(paramsString), &androidProps)
if err != nil {
return
}
}

View File

@@ -4,20 +4,42 @@ package main
import "C" import "C"
import ( import (
"core/platform"
t "core/tun" t "core/tun"
"errors"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
"strconv"
"sync" "sync"
"sync/atomic"
"syscall" "syscall"
"time" "time"
) )
var tunLock sync.Mutex var tunLock sync.Mutex
var tun *t.Tun var tun *t.Tun
var runTime *time.Time
type FdMap struct {
m sync.Map
}
func (cm *FdMap) Store(key int64) {
cm.m.Store(key, struct{}{})
}
func (cm *FdMap) Load(key int64) bool {
_, ok := cm.m.Load(key)
return ok
}
var fdMap FdMap
//export startTUN //export startTUN
func startTUN(fd C.int) { func startTUN(fd C.int, port C.longlong) {
i := int64(port)
ServicePort = i
go func() { go func() {
tunLock.Lock() tunLock.Lock()
defer tunLock.Unlock() defer tunLock.Unlock()
@@ -26,6 +48,7 @@ func startTUN(fd C.int) {
tun.Close() tun.Close()
tun = nil tun = nil
} }
f := int(fd) f := int(fd)
gateway := "172.16.0.1/30" gateway := "172.16.0.1/30"
portal := "172.16.0.2" portal := "172.16.0.2"
@@ -43,15 +66,34 @@ func startTUN(fd C.int) {
tempTun.Closer = closer tempTun.Closer = closer
tun = tempTun tun = tempTun
now := time.Now()
runTime = &now
SendMessage(Message{
Type: StartedMessage,
Data: strconv.FormatInt(runTime.UnixMilli(), 10),
})
}() }()
} }
//export getRunTime
func getRunTime() *C.char {
if runTime == nil {
return C.CString("")
}
return C.CString(strconv.FormatInt(runTime.UnixMilli(), 10))
}
//export stopTun //export stopTun
func stopTun() { func stopTun() {
go func() { go func() {
tunLock.Lock() tunLock.Lock()
defer tunLock.Unlock() defer tunLock.Unlock()
runTime = nil
if tun != nil { if tun != nil {
tun.Close() tun.Close()
tun = nil tun = nil
@@ -59,12 +101,60 @@ func stopTun() {
}() }()
} }
var errBlocked = errors.New("blocked")
//export setFdMap
func setFdMap(fd C.long) {
fdInt := int64(fd)
go func() {
fdMap.Store(fdInt)
}()
}
type Fd struct {
Id int64 `json:"id"`
Value int64 `json:"value"`
}
func markSocket(fd Fd) {
SendMessage(Message{
Type: ProtectMessage,
Data: fd,
})
}
var fdCounter int64 = 0
func init() { func init() {
dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error { dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error {
if platform.ShouldBlockConnection() {
return errBlocked
}
return conn.Control(func(fd uintptr) { return conn.Control(func(fd uintptr) {
if tun != nil { if tun == nil {
tun.MarkSocket(int(fd)) return
time.Sleep(time.Millisecond * 100) }
fdInt := int64(fd)
timeout := time.After(100 * time.Millisecond)
id := atomic.AddInt64(&fdCounter, 1)
markSocket(Fd{
Id: id,
Value: fdInt,
})
for {
select {
case <-timeout:
return
default:
exists := fdMap.Load(id)
if exists {
return
}
time.Sleep(10 * time.Millisecond)
}
} }
}) })
} }

View File

@@ -5,7 +5,6 @@ package tun
import "C" import "C"
import ( import (
"context" "context"
bridge "core/dart-bridge"
"encoding/binary" "encoding/binary"
"github.com/Kr328/tun2socket" "github.com/Kr328/tun2socket"
"github.com/Kr328/tun2socket/nat" "github.com/Kr328/tun2socket/nat"
@@ -19,7 +18,6 @@ import (
"io" "io"
"net" "net"
"os" "os"
"strconv"
"time" "time"
) )
@@ -186,19 +184,3 @@ func Start(fd int, gateway, portal, dns string) (io.Closer, error) {
return stack, nil return stack, nil
} }
func (t *Tun) MarkSocket(fd int) {
_ = t.Limit.Acquire(context.Background(), 1)
defer t.Limit.Release(1)
if t.Closed {
return
}
message := &bridge.Message{
Type: bridge.Tun,
Data: strconv.Itoa(fd),
}
bridge.SendMessage(*message)
}

View File

@@ -115,7 +115,6 @@ class ApplicationState extends State<Application> {
lightColorScheme: lightDynamic, lightColorScheme: lightDynamic,
darkColorScheme: darkDynamic, darkColorScheme: darkDynamic,
); );
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
globalState.appController.updateSystemColorSchemes(systemColorSchemes); globalState.appController.updateSystemColorSchemes(systemColorSchemes);
}); });
@@ -130,7 +129,6 @@ class ApplicationState extends State<Application> {
httpTimeoutDuration, httpTimeoutDuration,
(timer) async { (timer) async {
await globalState.appController.updateGroups(); await globalState.appController.updateGroups();
globalState.appController.appState.sortNum++;
}, },
); );
} }
@@ -158,6 +156,7 @@ class ApplicationState extends State<Application> {
GlobalCupertinoLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate GlobalWidgetsLocalizations.delegate
], ],
scrollBehavior: BaseScrollBehavior(),
title: appName, title: appName,
locale: other.getLocaleForString(state.locale), locale: other.getLocaleForString(state.locale),
supportedLocales: supportedLocales:
@@ -165,7 +164,6 @@ class ApplicationState extends State<Application> {
themeMode: state.themeMode, themeMode: state.themeMode,
theme: ThemeData( theme: ThemeData(
useMaterial3: true, useMaterial3: true,
fontFamily: '',
pageTransitionsTheme: _pageTransitionsTheme, pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme( colorScheme: _getAppColorScheme(
brightness: Brightness.light, brightness: Brightness.light,
@@ -175,7 +173,6 @@ class ApplicationState extends State<Application> {
), ),
darkTheme: ThemeData( darkTheme: ThemeData(
useMaterial3: true, useMaterial3: true,
fontFamily: '',
pageTransitionsTheme: _pageTransitionsTheme, pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme( colorScheme: _getAppColorScheme(
brightness: Brightness.dark, brightness: Brightness.dark,

View File

@@ -35,7 +35,6 @@ class ClashCore {
clashFFI = ClashFFI(lib); clashFFI = ClashFFI(lib);
clashFFI.initNativeApiBridge( clashFFI.initNativeApiBridge(
NativeApi.initializeApiDLData, NativeApi.initializeApiDLData,
receiver.sendPort.nativePort,
); );
} }
@@ -45,10 +44,10 @@ class ClashCore {
} }
bool init(String homeDir) { bool init(String homeDir) {
return clashFFI.initClash( final homeDirChar = homeDir.toNativeUtf8().cast<Char>();
homeDir.toNativeUtf8().cast(), final isInit = clashFFI.initClash(homeDirChar) == 1;
) == malloc.free(homeDirChar);
1; return isInit;
} }
shutdown() { shutdown() {
@@ -67,10 +66,12 @@ class ClashCore {
receiver.close(); receiver.close();
} }
}); });
final dataChar = data.toNativeUtf8().cast<Char>();
clashFFI.validateConfig( clashFFI.validateConfig(
data.toNativeUtf8().cast(), dataChar,
receiver.sendPort.nativePort, receiver.sendPort.nativePort,
); );
malloc.free(dataChar);
return completer.future; return completer.future;
} }
@@ -84,20 +85,45 @@ class ClashCore {
} }
}); });
final params = json.encode(updateConfigParams); final params = json.encode(updateConfigParams);
final paramsChar = params.toNativeUtf8().cast<Char>();
clashFFI.updateConfig( clashFFI.updateConfig(
params.toNativeUtf8().cast(), paramsChar,
receiver.sendPort.nativePort, receiver.sendPort.nativePort,
); );
malloc.free(paramsChar);
return completer.future; return completer.future;
} }
initMessage() {
clashFFI.initMessage(
receiver.sendPort.nativePort,
);
}
setProfileName(String profileName) {
final profileNameChar = profileName.toNativeUtf8().cast<Char>();
clashFFI.setCurrentProfileName(
profileNameChar,
);
malloc.free(profileNameChar);
}
getProfileName() {
final currentProfileNameRaw = clashFFI.getCurrentProfileName();
final currentProfileName =
currentProfileNameRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(currentProfileNameRaw);
return currentProfileName;
}
Future<List<Group>> getProxiesGroups() { Future<List<Group>> getProxiesGroups() {
final proxiesRaw = clashFFI.getProxies(); final proxiesRaw = clashFFI.getProxies();
final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString(); final proxiesRawString = proxiesRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(proxiesRaw);
return Isolate.run<List<Group>>(() { return Isolate.run<List<Group>>(() {
if(proxiesRawString.isEmpty) return []; if (proxiesRawString.isEmpty) return [];
final proxies = json.decode(proxiesRawString) as Map; final proxies = (json.decode(proxiesRawString) ?? {}) as Map;
if(proxies.isEmpty) return []; if (proxies.isEmpty) return [];
final groupNames = [ final groupNames = [
UsedProxy.GLOBAL.name, UsedProxy.GLOBAL.name,
...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) { ...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) {
@@ -111,7 +137,8 @@ class ClashCore {
group["all"] = ((group["all"] ?? []) as List) group["all"] = ((group["all"] ?? []) as List)
.map( .map(
(name) => proxies[name], (name) => proxies[name],
) )
.where((proxy) => proxy != null)
.toList(); .toList();
return group; return group;
}).toList(); }).toList();
@@ -122,14 +149,15 @@ class ClashCore {
Future<List<ExternalProvider>> getExternalProviders() { Future<List<ExternalProvider>> getExternalProviders() {
final externalProvidersRaw = clashFFI.getExternalProviders(); final externalProvidersRaw = clashFFI.getExternalProviders();
final externalProvidersRawString = final externalProvidersRawString =
externalProvidersRaw.cast<Utf8>().toDartString(); externalProvidersRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(externalProvidersRaw);
return Isolate.run<List<ExternalProvider>>(() { return Isolate.run<List<ExternalProvider>>(() {
final externalProviders = final externalProviders =
(json.decode(externalProvidersRawString) as List<dynamic>) (json.decode(externalProvidersRawString) as List<dynamic>)
.map( .map(
(item) => ExternalProvider.fromJson(item), (item) => ExternalProvider.fromJson(item),
) )
.toList(); .toList();
return externalProviders; return externalProviders;
}); });
} }
@@ -146,17 +174,23 @@ class ClashCore {
receiver.close(); receiver.close();
} }
}); });
final providerNameChar = providerName.toNativeUtf8().cast<Char>();
final providerTypeChar = providerType.toNativeUtf8().cast<Char>();
clashFFI.updateExternalProvider( clashFFI.updateExternalProvider(
providerName.toNativeUtf8().cast(), providerNameChar,
providerType.toNativeUtf8().cast(), providerTypeChar,
receiver.sendPort.nativePort, receiver.sendPort.nativePort,
); );
malloc.free(providerNameChar);
malloc.free(providerTypeChar);
return completer.future; return completer.future;
} }
bool changeProxy(ChangeProxyParams changeProxyParams) { changeProxy(ChangeProxyParams changeProxyParams) {
final params = json.encode(changeProxyParams); final params = json.encode(changeProxyParams);
return clashFFI.changeProxy(params.toNativeUtf8().cast()) == 1; final paramsChar = params.toNativeUtf8().cast<Char>();
clashFFI.changeProxy(paramsChar);
malloc.free(paramsChar);
} }
Future<Delay> getDelay(String proxyName) { Future<Delay> getDelay(String proxyName) {
@@ -172,13 +206,16 @@ class ClashCore {
receiver.close(); receiver.close();
} }
}); });
final delayParamsChar =
json.encode(delayParams).toNativeUtf8().cast<Char>();
clashFFI.asyncTestDelay( clashFFI.asyncTestDelay(
json.encode(delayParams).toNativeUtf8().cast(), delayParamsChar,
receiver.sendPort.nativePort, receiver.sendPort.nativePort,
); );
malloc.free(delayParamsChar);
Future.delayed(httpTimeoutDuration + moreDuration, () { Future.delayed(httpTimeoutDuration + moreDuration, () {
receiver.close(); receiver.close();
if(!completer.isCompleted){ if (!completer.isCompleted) {
completer.complete( completer.complete(
Delay(name: proxyName, value: -1), Delay(name: proxyName, value: -1),
); );
@@ -188,28 +225,48 @@ class ClashCore {
} }
clearEffect(String path) { clearEffect(String path) {
clashFFI.clearEffect(path.toNativeUtf8().cast()); final pathChar = path.toNativeUtf8().cast<Char>();
clashFFI.clearEffect(pathChar);
malloc.free(pathChar);
} }
VersionInfo getVersionInfo() { VersionInfo getVersionInfo() {
final versionInfoRaw = clashFFI.getVersionInfo(); final versionInfoRaw = clashFFI.getVersionInfo();
final versionInfo = json.decode(versionInfoRaw.cast<Utf8>().toDartString()); final versionInfo = json.decode(versionInfoRaw.cast<Utf8>().toDartString());
clashFFI.freeCString(versionInfoRaw);
return VersionInfo.fromJson(versionInfo); return VersionInfo.fromJson(versionInfo);
} }
setProps(Props props) {
final propsChar = json.encode(props).toNativeUtf8().cast<Char>();
clashFFI.setAndroidProps(propsChar);
malloc.free(propsChar);
}
Props getProps() {
final androidPropsRaw = clashFFI.getAndroidProps();
final androidProps = json.decode(
androidPropsRaw.cast<Utf8>().toDartString(),
);
clashFFI.freeCString(androidPropsRaw);
return Props.fromJson(androidProps);
}
Traffic getTraffic() { Traffic getTraffic() {
final trafficRaw = clashFFI.getTraffic(); final trafficRaw = clashFFI.getTraffic();
final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString()); final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString());
clashFFI.freeCString(trafficRaw);
return Traffic.fromMap(trafficMap); return Traffic.fromMap(trafficMap);
} }
Traffic getTotalTraffic() { Traffic getTotalTraffic() {
final trafficRaw = clashFFI.getTotalTraffic(); final trafficRaw = clashFFI.getTotalTraffic();
final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString()); final trafficMap = json.decode(trafficRaw.cast<Utf8>().toDartString());
clashFFI.freeCString(trafficRaw);
return Traffic.fromMap(trafficMap); return Traffic.fromMap(trafficMap);
} }
void resetTraffic(){ void resetTraffic() {
clashFFI.resetTraffic(); clashFFI.resetTraffic();
} }
@@ -221,8 +278,9 @@ class ClashCore {
clashFFI.stopLog(); clashFFI.stopLog();
} }
startTun(int fd) { startTun(int fd, int port) {
clashFFI.startTUN(fd); if (!Platform.isAndroid) return;
clashFFI.startTUN(fd, port);
} }
requestGc() { requestGc() {
@@ -234,25 +292,37 @@ class ClashCore {
} }
void setProcessMap(ProcessMapItem processMapItem) { void setProcessMap(ProcessMapItem processMapItem) {
clashFFI.setProcessMap(json.encode(processMapItem).toNativeUtf8().cast()); final processMapItemChar =
json.encode(processMapItem).toNativeUtf8().cast<Char>();
clashFFI.setProcessMap(processMapItemChar);
malloc.free(processMapItemChar);
} }
// DateTime? getRunTime() { void setFdMap(int fd) {
// final runTimeString = clashFFI.getRunTime().cast<Utf8>().toDartString(); clashFFI.setFdMap(fd);
// if (runTimeString.isEmpty) return null; }
// return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
// } DateTime? getRunTime() {
final runTimeRaw = clashFFI.getRunTime();
final runTimeString = runTimeRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(runTimeRaw);
if (runTimeString.isEmpty) return null;
return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
}
List<Connection> getConnections() { List<Connection> getConnections() {
final connectionsDataRaw = clashFFI.getConnections(); final connectionsDataRaw = clashFFI.getConnections();
final connectionsData = final connectionsData =
json.decode(connectionsDataRaw.cast<Utf8>().toDartString()) as Map; json.decode(connectionsDataRaw.cast<Utf8>().toDartString()) as Map;
clashFFI.freeCString(connectionsDataRaw);
final connectionsRaw = connectionsData['connections'] as List? ?? []; final connectionsRaw = connectionsData['connections'] as List? ?? [];
return connectionsRaw.map((e) => Connection.fromJson(e)).toList(); return connectionsRaw.map((e) => Connection.fromJson(e)).toList();
} }
closeConnections(String id) { closeConnections(String id) {
clashFFI.closeConnection(id.toNativeUtf8().cast()); final idChar = id.toNativeUtf8().cast<Char>();
clashFFI.closeConnection(idChar);
malloc.free(idChar);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -7,22 +7,6 @@ import 'package:flutter/foundation.dart';
import 'core.dart'; import 'core.dart';
abstract mixin class ClashMessageListener {
void onLog(Log log) {}
void onTun(String fd) {}
void onDelay(Delay delay) {}
void onProcess(Process process) {}
void onRequest(Connection connection) {}
void onNow(Now now) {}
void onRun(String runTime) {}
}
class ClashMessage { class ClashMessage {
StreamSubscription? subscription; StreamSubscription? subscription;
@@ -32,29 +16,23 @@ class ClashMessage {
subscription = null; subscription = null;
} }
subscription = ClashCore.receiver.listen((message) { subscription = ClashCore.receiver.listen((message) {
final m = Message.fromJson(json.decode(message)); final m = AppMessage.fromJson(json.decode(message));
for (final ClashMessageListener listener in _listeners) { for (final AppMessageListener listener in _listeners) {
switch (m.type) { switch (m.type) {
case MessageType.log: case AppMessageType.log:
listener.onLog(Log.fromJson(m.data)); listener.onLog(Log.fromJson(m.data));
break; break;
case MessageType.tun: case AppMessageType.delay:
listener.onTun(m.data);
break;
case MessageType.delay:
listener.onDelay(Delay.fromJson(m.data)); listener.onDelay(Delay.fromJson(m.data));
break; break;
case MessageType.process: case AppMessageType.request:
listener.onProcess(Process.fromJson(m.data));
break;
case MessageType.now:
listener.onNow(Now.fromJson(m.data));
break;
case MessageType.request:
listener.onRequest(Connection.fromJson(m.data)); listener.onRequest(Connection.fromJson(m.data));
break; break;
case MessageType.run: case AppMessageType.started:
listener.onRun(m.data); listener.onStarted(m.data);
break;
case AppMessageType.loaded:
listener.onLoaded(m.data);
break; break;
} }
} }
@@ -63,18 +41,18 @@ class ClashMessage {
static final ClashMessage instance = ClashMessage._(); static final ClashMessage instance = ClashMessage._();
final ObserverList<ClashMessageListener> _listeners = final ObserverList<AppMessageListener> _listeners =
ObserverList<ClashMessageListener>(); ObserverList<AppMessageListener>();
bool get hasListeners { bool get hasListeners {
return _listeners.isNotEmpty; return _listeners.isNotEmpty;
} }
void addListener(ClashMessageListener listener) { void addListener(AppMessageListener listener) {
_listeners.add(listener); _listeners.add(listener);
} }
void removeListener(ClashMessageListener listener) { void removeListener(AppMessageListener listener) {
_listeners.remove(listener); _listeners.remove(listener);
} }
} }

View File

@@ -17,6 +17,7 @@ class ClashService {
} }
const geoFileNameList = [ const geoFileNameList = [
mmdbFileName, mmdbFileName,
geoIpFileName,
geoSiteFileName, geoSiteFileName,
asnFileName, asnFileName,
]; ];

View File

@@ -1,15 +1,10 @@
import 'dart:io'; import 'dart:io';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/plugins/app.dart';
class Android { class Android {
init() async { init() async {
app?.onExit = () { app?.onExit = () {};
clashCore.shutdown();
print("adsadda==>");
exit(0);
};
} }
} }

View File

@@ -22,4 +22,7 @@ export 'string.dart';
export 'app_localizations.dart'; export 'app_localizations.dart';
export 'function.dart'; export 'function.dart';
export 'package.dart'; export 'package.dart';
export 'measure.dart'; export 'measure.dart';
export 'service.dart';
export 'iterable.dart';
export 'scroll.dart';

View File

@@ -1,5 +1,7 @@
import 'dart:ui'; import 'dart:ui';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
const appName = "FlClash"; const appName = "FlClash";
@@ -7,10 +9,22 @@ const coreName = "clash.meta";
const packageName = "FlClash"; const packageName = "FlClash";
const httpTimeoutDuration = Duration(milliseconds: 5000); const httpTimeoutDuration = Duration(milliseconds: 5000);
const moreDuration = Duration(milliseconds: 100); const moreDuration = Duration(milliseconds: 100);
const animateDuration = Duration(milliseconds: 100);
const defaultUpdateDuration = Duration(days: 1); const defaultUpdateDuration = Duration(days: 1);
const mmdbFileName = "geoip.metadb"; const mmdbFileName = "geoip.metadb";
const geoSiteFileName = "GeoSite.dat";
const asnFileName = "ASN.mmdb"; const asnFileName = "ASN.mmdb";
const geoIpFileName = "GeoIP.dat";
const geoSiteFileName = "GeoSite.dat";
const GeoXMap defaultGeoXMap = {
"mmdb":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
"asn":
"https://github.com/xishang0128/geoip/releases/download/latest/GeoLite2-ASN.mmdb",
"geoip":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoIP.dat",
"geosite":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat"
};
const profilesDirectoryName = "profiles"; const profilesDirectoryName = "profiles";
const localhost = "127.0.0.1"; const localhost = "127.0.0.1";
const clashConfigKey = "clash_config"; const clashConfigKey = "clash_config";
@@ -23,10 +37,17 @@ const maxMobileWidth = 600;
const maxLaptopWidth = 840; const maxLaptopWidth = 840;
const geodataLoaderMemconservative = "memconservative"; const geodataLoaderMemconservative = "memconservative";
const geodataLoaderStandard = "standard"; const geodataLoaderStandard = "standard";
const defaultTestUrl = "https://www.gstatic.com/generate_204";
final filter = ImageFilter.blur( final filter = ImageFilter.blur(
sigmaX: 5, sigmaX: 5,
sigmaY: 5, sigmaY: 5,
tileMode: TileMode.mirror, tileMode: TileMode.mirror,
); );
const viewModeColumnsMap = {
ViewMode.mobile: [2, 1],
ViewMode.laptop: [3, 2],
ViewMode.desktop: [4, 3],
};
const defaultPrimaryColor = Colors.brown; const defaultPrimaryColor = Colors.brown;

View File

@@ -7,8 +7,12 @@ extension BuildContextExtension on BuildContext {
return findAncestorStateOfType<CommonScaffoldState>(); return findAncestorStateOfType<CommonScaffoldState>();
} }
Size get appSize{
return MediaQuery.of(this).size;
}
double get width { double get width {
return MediaQuery.of(this).size.width; return appSize.width;
} }
ColorScheme get colorScheme => Theme.of(this).colorScheme; ColorScheme get colorScheme => Theme.of(this).colorScheme;

13
lib/common/iterable.dart Normal file
View File

@@ -0,0 +1,13 @@
extension IterableExt<T> on Iterable<T> {
Iterable<T> separated(T separator) sync* {
final iterator = this.iterator;
if (!iterator.moveNext()) return;
yield iterator.current;
while (iterator.moveNext()) {
yield separator;
yield iterator.current;
}
}
}

View File

@@ -2,6 +2,7 @@ import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:fl_clash/common/app_localizations.dart';
import 'package:fl_clash/common/constant.dart'; import 'package:fl_clash/common/constant.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -39,6 +40,9 @@ class Other {
} }
final diff = timeStamp / 1000; final diff = timeStamp / 1000;
final inHours = (diff / 3600).floor(); final inHours = (diff / 3600).floor();
if (inHours > 99) {
return "99:59:59";
}
final inMinutes = (diff / 60 % 60).floor(); final inMinutes = (diff / 60 % 60).floor();
final inSeconds = (diff % 60).floor(); final inSeconds = (diff % 60).floor();
@@ -171,7 +175,7 @@ class Other {
} }
List<String> parseReleaseBody(String? body) { List<String> parseReleaseBody(String? body) {
if(body == null) return []; if (body == null) return [];
const pattern = r'- (.+?)\. \[.+?\]'; const pattern = r'- (.+?)\. \[.+?\]';
final regex = RegExp(pattern); final regex = RegExp(pattern);
return regex return regex
@@ -181,11 +185,29 @@ class Other {
.toList(); .toList();
} }
ViewMode getViewMode(double viewWidth){ ViewMode getViewMode(double viewWidth) {
if (viewWidth <= maxMobileWidth) return ViewMode.mobile; if (viewWidth <= maxMobileWidth) return ViewMode.mobile;
if (viewWidth <= maxLaptopWidth) return ViewMode.laptop; if (viewWidth <= maxLaptopWidth) return ViewMode.laptop;
return ViewMode.desktop; return ViewMode.desktop;
} }
int getColumns(ViewMode viewMode, int currentColumns) {
final targetColumnsArray = viewModeColumnsMap[viewMode]!;
if (targetColumnsArray.contains(currentColumns)) {
return currentColumns;
}
return targetColumnsArray.first;
}
String getColumnsTextForInt(int number){
return switch(number){
1 => appLocalizations.oneColumn,
2 => appLocalizations.twoColumns,
3 => appLocalizations.threeColumns,
4 => appLocalizations.fourColumns,
int() => throw UnimplementedError(),
};
}
} }
final other = Other(); final other = Other();

View File

@@ -1,22 +1,13 @@
import 'dart:async'; import 'dart:io';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
class AppPackage{ import 'common.dart';
static AppPackage? _instance; extension PackageInfoExtension on PackageInfo {
Completer<PackageInfo> packageInfoCompleter = Completer(); String get ua => [
"$appName/v$version",
AppPackage._internal() { "clash-verge",
PackageInfo.fromPlatform().then( "Platform/${Platform.operatingSystem}",
(value) => packageInfoCompleter.complete(value), ].join(" ");
);
}
factory AppPackage() {
_instance ??= AppPackage._internal();
return _instance!;
}
} }
final appPackage = AppPackage();

View File

@@ -1,29 +1,22 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
class Picker { class Picker {
Future<PlatformFile?> pickerConfigFile() async { Future<PlatformFile?> pickerConfigFile() async {
FilePickerResult? filePickerResult; final filePickerResult = await FilePicker.platform.pickFiles(
if (Platform.isAndroid) { withData: true,
filePickerResult = await FilePicker.platform.pickFiles( allowMultiple: false,
withData: true, );
allowMultiple: false, return filePickerResult?.files.first;
); }
} else {
filePickerResult = await FilePicker.platform.pickFiles( Future<PlatformFile?> pickerGeoDataFile() async {
withData: true, final filePickerResult = await FilePicker.platform.pickFiles(
type: FileType.custom, withData: true,
allowedExtensions: ['yaml', 'txt', 'conf'], allowMultiple: false,
); );
} return filePickerResult?.files.first;
final file = filePickerResult?.files.first;
if (file == null) {
return null;
}
return file;
} }
Future<String?> pickerConfigQRCode() async { Future<String?> pickerConfigQRCode() async {

View File

@@ -15,8 +15,8 @@ class ProxyManager {
DateTime? get startTime => _proxy.startTime; DateTime? get startTime => _proxy.startTime;
Future<bool?> startProxy({required int port, String? args}) async { Future<bool?> startProxy({required int port}) async {
return await _proxy.startProxy(port, args); return await _proxy.startProxy(port);
} }
Future<bool?> stopProxy() async { Future<bool?> stopProxy() async {
@@ -28,20 +28,6 @@ class ProxyManager {
return await (_proxy as Proxy).updateStartTime(); return await (_proxy as Proxy).updateStartTime();
} }
Future<bool?> setProtect(int fd) async {
if (_proxy is! Proxy) return null;
return await (_proxy as Proxy).setProtect(fd);
}
Future<bool?> startForeground({
required String title,
required String content,
}) async {
if (_proxy is! Proxy) return null;
return await (_proxy as Proxy)
.startForeground(title: title, content: content);
}
factory ProxyManager() { factory ProxyManager() {
_instance ??= ProxyManager._internal(); _instance ??= ProxyManager._internal();
return _instance!; return _instance!;

View File

@@ -12,20 +12,21 @@ class Request {
bool _isStart = false; bool _isStart = false;
Request() { Request() {
_dio = Dio( _dio = Dio();
BaseOptions( _dio.options = BaseOptions(
headers: {"User-Agent": coreName}, headers: {"User-Agent": globalState.appController.clashConfig.globalUa},
);
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
_updateAdapter();
return handler.next(options); // 继续请求
},
), ),
); );
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
_syncProxy();
return handler.next(options); // 继续请求
},
));
} }
_syncProxy() { _updateAdapter() {
final port = globalState.appController.clashConfig.mixedPort; final port = globalState.appController.clashConfig.mixedPort;
final isStart = globalState.appController.appState.isStart; final isStart = globalState.appController.appState.isStart;
if (_port != port || isStart != _isStart) { if (_port != port || isStart != _isStart) {
@@ -40,6 +41,7 @@ class Request {
}; };
return client; return client;
}, },
validateCertificate: (_, __, ___) => true,
); );
} }
} }
@@ -68,8 +70,7 @@ class Request {
if (response.statusCode != 200) return null; if (response.statusCode != 200) return null;
final data = response.data as Map<String, dynamic>; final data = response.data as Map<String, dynamic>;
final remoteVersion = data['tag_name']; final remoteVersion = data['tag_name'];
final packageInfo = await appPackage.packageInfoCompleter.future; final version = globalState.packageInfo.version;
final version = packageInfo.version;
final hasUpdate = final hasUpdate =
other.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0; other.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0;
if (!hasUpdate) return null; if (!hasUpdate) return null;

16
lib/common/scroll.dart Normal file
View File

@@ -0,0 +1,16 @@
import 'dart:ui';
import 'package:fl_clash/common/common.dart';
import 'package:flutter/material.dart';
class BaseScrollBehavior extends MaterialScrollBehavior {
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.invertedStylus,
PointerDeviceKind.trackpad,
if (system.isDesktop) PointerDeviceKind.mouse,
PointerDeviceKind.unknown,
};
}

110
lib/common/service.dart Normal file
View File

@@ -0,0 +1,110 @@
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';
typedef CreateServiceNative = IntPtr Function(
IntPtr hSCManager,
Pointer<Utf16> lpServiceName,
Pointer<Utf16> lpDisplayName,
Uint32 dwDesiredAccess,
Uint32 dwServiceType,
Uint32 dwStartType,
Uint32 dwErrorControl,
Pointer<Utf16> lpBinaryPathName,
Pointer<Utf16> lpLoadOrderGroup,
Pointer<Uint32> lpdwTagId,
Pointer<Utf16> lpDependencies,
Pointer<Utf16> lpServiceStartName,
Pointer<Utf16> lpPassword,
);
typedef CreateServiceDart = int Function(
int hSCManager,
Pointer<Utf16> lpServiceName,
Pointer<Utf16> lpDisplayName,
int dwDesiredAccess,
int dwServiceType,
int dwStartType,
int dwErrorControl,
Pointer<Utf16> lpBinaryPathName,
Pointer<Utf16> lpLoadOrderGroup,
Pointer<Uint32> lpdwTagId,
Pointer<Utf16> lpDependencies,
Pointer<Utf16> lpServiceStartName,
Pointer<Utf16> lpPassword,
);
const _SERVICE_ALL_ACCESS = 0xF003F;
const _SERVICE_WIN32_OWN_PROCESS = 0x00000010;
const _SERVICE_AUTO_START = 0x00000002;
const _SERVICE_ERROR_NORMAL = 0x00000001;
typedef GetLastErrorNative = Uint32 Function();
typedef GetLastErrorDart = int Function();
class Service {
static Service? _instance;
late DynamicLibrary _advapi32;
Service._internal() {
_advapi32 = DynamicLibrary.open('advapi32.dll');
}
factory Service() {
_instance ??= Service._internal();
return _instance!;
}
Future<void> createService() async {
final int scManager = OpenSCManager(nullptr, nullptr, _SERVICE_ALL_ACCESS);
if (scManager == 0) return;
final serviceName = 'FlClash Service'.toNativeUtf16();
final displayName = 'FlClash Service'.toNativeUtf16();
final binaryPathName = "C:\\Application\\Clash.Verge_1.6.6_x64_portable\\resources\\clash-verge-service.exe".toNativeUtf16();
final createService =
_advapi32.lookupFunction<CreateServiceNative, CreateServiceDart>(
'CreateServiceW',
);
final getLastError = DynamicLibrary.open('kernel32.dll')
.lookupFunction<GetLastErrorNative, GetLastErrorDart>('GetLastError');
final serviceHandle = createService(
scManager,
serviceName,
displayName,
_SERVICE_ALL_ACCESS,
_SERVICE_WIN32_OWN_PROCESS,
_SERVICE_AUTO_START,
_SERVICE_ERROR_NORMAL,
binaryPathName,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
);
print("serviceHandle $serviceHandle");
final errorCode = GetLastError();
print('Error code: $errorCode');
final result = StartService(serviceHandle, 0, nullptr);
if (result == 0) {
print('Failed to start the service.');
} else {
print('Service started successfully.');
}
calloc.free(serviceName);
calloc.free(displayName);
calloc.free(binaryPathName);
}
}
final service = Platform.isWindows ? Service() : null;

View File

@@ -1,10 +1,5 @@
extension StringExtension on String { extension StringExtension on String {
bool get isUrl { bool get isUrl {
try { return RegExp(r'^(http|https|ftp)://').hasMatch(this);
Uri.parse(this);
return true;
} catch (e) {
return false;
}
} }
} }

View File

@@ -2,20 +2,13 @@ import 'package:flutter/material.dart';
import 'color.dart'; import 'color.dart';
extension TextStyleExtension on TextStyle { extension TextStyleExtension on TextStyle {
toLight() { TextStyle get toLight => copyWith(color: color?.toLight());
return copyWith(color: color?.toLight());
}
toLighter() { TextStyle get toLighter => copyWith(color: color?.toLighter());
return copyWith(color: color?.toLighter());
}
TextStyle get toSoftBold => copyWith(fontWeight: FontWeight.w500);
toSoftBold() { TextStyle get toBold => copyWith(fontWeight: FontWeight.bold);
return copyWith(fontWeight: FontWeight.w500);
}
toBold() { TextStyle get toMinus => copyWith(fontSize: fontSize! - 2);
return copyWith(fontWeight: FontWeight.bold); }
}
}

View File

@@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:fl_clash/models/config.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:windows_single_instance/windows_single_instance.dart'; import 'package:windows_single_instance/windows_single_instance.dart';
@@ -8,7 +9,7 @@ import 'protocol.dart';
import 'system.dart'; import 'system.dart';
class Window { class Window {
init() async { init(WindowProps props) async {
if (Platform.isWindows) { if (Platform.isWindows) {
await WindowsSingleInstance.ensureSingleInstance([], "FlClash"); await WindowsSingleInstance.ensureSingleInstance([], "FlClash");
protocol.register("clash"); protocol.register("clash");
@@ -16,11 +17,17 @@ class Window {
protocol.register("flclash"); protocol.register("flclash");
} }
await windowManager.ensureInitialized(); await windowManager.ensureInitialized();
WindowOptions windowOptions = const WindowOptions( WindowOptions windowOptions = WindowOptions(
size: Size(1000, 600), size: Size(props.width, props.height),
minimumSize: Size(400, 600), minimumSize: const Size(380, 600),
center: true,
); );
if (props.left != null || props.top != null) {
await windowManager.setPosition(
Offset(props.left ?? 0, props.top ?? 0),
);
} else {
await windowManager.setAlignment(Alignment.center);
}
await windowManager.waitUntilReadyToShow(windowOptions, () async { await windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.setPreventClose(true); await windowManager.setPreventClose(true);
}); });

View File

@@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -18,6 +17,7 @@ class AppController {
late ClashConfig clashConfig; late ClashConfig clashConfig;
late Measure measure; late Measure measure;
late Function updateClashConfigDebounce; late Function updateClashConfigDebounce;
late Function addCheckIpNumDebounce;
AppController(this.context) { AppController(this.context) {
appState = context.read<AppState>(); appState = context.read<AppState>();
@@ -26,6 +26,9 @@ class AppController {
updateClashConfigDebounce = debounce<Function()>(() async { updateClashConfigDebounce = debounce<Function()>(() async {
await updateClashConfig(); await updateClashConfig();
}); });
addCheckIpNumDebounce = debounce(() {
appState.checkIpNum++;
});
measure = Measure.of(context); measure = Measure.of(context);
} }
@@ -67,19 +70,10 @@ class AppController {
updateTraffic() { updateTraffic() {
globalState.updateTraffic( globalState.updateTraffic(
config: config,
appState: appState, appState: appState,
); );
} }
changeProxy() {
globalState.changeProxy(
appState: appState,
config: config,
clashConfig: clashConfig,
);
}
addProfile(Profile profile) async { addProfile(Profile profile) async {
config.setProfile(profile); config.setProfile(profile);
if (config.currentProfileId != null) return; if (config.currentProfileId != null) return;
@@ -126,6 +120,14 @@ class AppController {
}); });
} }
Future rawApplyProfile() async {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
}
changeProfile(String? value) async { changeProfile(String? value) async {
if (value == config.currentProfileId) return; if (value == config.currentProfileId) return;
config.currentProfileId = value; config.currentProfileId = value;
@@ -267,6 +269,7 @@ class AppController {
} }
init() async { init() async {
updateLogStatus();
if (!config.silentLaunch) { if (!config.silentLaunch) {
window?.show(); window?.show();
} }
@@ -290,14 +293,13 @@ class AppController {
} }
afterInit() async { afterInit() async {
if (config.autoRun) { await proxyManager.updateStartTime();
if (proxyManager.isStart) {
await updateSystemProxy(true); await updateSystemProxy(true);
} else { } else {
await proxyManager.updateStartTime(); await updateSystemProxy(config.autoRun);
await updateSystemProxy(proxyManager.isStart);
} }
autoUpdateProfiles(); autoUpdateProfiles();
updateLogStatus();
autoCheckUpdate(); autoCheckUpdate();
} }
@@ -360,7 +362,9 @@ class AppController {
} }
addProfileFormURL(String url) async { addProfileFormURL(String url) async {
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst); if (globalState.navigatorKey.currentState?.canPop() ?? false) {
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
}
toProfiles(); toProfiles();
final commonScaffoldState = globalState.homeScaffoldKey.currentState; final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return; if (commonScaffoldState?.mounted != true) return;
@@ -406,9 +410,47 @@ class AppController {
addProfileFormURL(url); addProfileFormURL(url);
} }
int get columns =>
other.getColumns(appState.viewMode, config.proxiesColumns);
updateViewWidth(double width) { updateViewWidth(double width) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
appState.viewWidth = width; appState.viewWidth = width;
}); });
} }
List<Proxy> _sortOfName(List<Proxy> proxies) {
return List.of(proxies)
..sort(
(a, b) => other.sortByChar(a.name, b.name),
);
}
List<Proxy> _sortOfDelay(List<Proxy> proxies) {
return proxies = List.of(proxies)
..sort(
(a, b) {
final aDelay = appState.getDelay(a.name);
final bDelay = appState.getDelay(b.name);
if (aDelay == null && bDelay == null) {
return 0;
}
if (aDelay == null || aDelay == -1) {
return 1;
}
if (bDelay == null || bDelay == -1) {
return -1;
}
return aDelay.compareTo(bDelay);
},
);
}
List<Proxy> getSortProxies(List<Proxy> proxies) {
return switch (config.proxiesSortType) {
ProxiesSortType.none => proxies,
ProxiesSortType.delay => _sortOfDelay(proxies),
ProxiesSortType.name => _sortOfName(proxies),
};
}
} }

View File

@@ -56,7 +56,20 @@ enum ProfileType { file, url }
enum ResultType { success, error } enum ResultType { success, error }
enum MessageType { log, tun, delay, process, now, request, run } enum AppMessageType {
log,
delay,
request,
started,
loaded,
}
enum ServiceMessageType {
protect,
process,
started,
loaded,
}
enum FindProcessMode { always, off } enum FindProcessMode { always, off }
@@ -65,7 +78,10 @@ enum RecoveryOption {
onlyProfiles, onlyProfiles,
} }
enum ChipType { enum ChipType { action, delete }
action,
delete, enum CommonCardType { plain, filled }
}
enum ProxiesType { tab, list }
enum ProxyCardType { expand, shrink, min }

View File

@@ -1,7 +1,6 @@
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class AboutFragment extends StatelessWidget { class AboutFragment extends StatelessWidget {
@@ -49,16 +48,9 @@ class AboutFragment extends StatelessWidget {
appName, appName,
style: Theme.of(context).textTheme.headlineSmall, style: Theme.of(context).textTheme.headlineSmall,
), ),
FutureBuilder<PackageInfo>( Text(
future: appPackage.packageInfoCompleter.future, globalState.packageInfo.version,
builder: (_, package) { style: Theme.of(context).textTheme.labelLarge,
final version = package.data?.version;
if (version == null) return Container();
return Text(
version,
style: Theme.of(context).textTheme.labelLarge,
);
},
) )
], ],
) )

View File

@@ -35,7 +35,6 @@ class _AccessFragmentState extends State<AccessFragment> {
}); });
} }
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
@@ -112,55 +111,48 @@ class _AccessFragmentState extends State<AccessFragment> {
); );
} }
_buildSelectedAllButton({ Widget _buildSelectedAllButton({
required bool isAccessControl, required bool isAccessControl,
required bool isSelectedAll, required bool isSelectedAll,
required List<String> allValueList, required List<String> allValueList,
}) { }) {
WidgetsBinding.instance.addPostFrameCallback((_) { final tooltip = isSelectedAll
final tooltip = isSelectedAll ? appLocalizations.cancelSelectAll
? appLocalizations.cancelSelectAll : appLocalizations.selectAll;
: appLocalizations.selectAll; return AbsorbPointer(
final commonScaffoldState = absorbing: !isAccessControl,
context.findAncestorStateOfType<CommonScaffoldState>(); child: FloatingActionButton(
commonScaffoldState?.floatingActionButton = DisabledMask( tooltip: tooltip,
status: !isAccessControl, onPressed: () {
child: AbsorbPointer( final config = globalState.appController.config;
absorbing: !isAccessControl, final isAccept =
child: FloatingActionButton ( config.accessControl.mode == AccessControlMode.acceptSelected;
tooltip: tooltip,
onPressed: () {
final config = globalState.appController.config;
final isAccept =
config.accessControl.mode == AccessControlMode.acceptSelected;
if (isSelectedAll) { if (isSelectedAll) {
config.accessControl = switch (isAccept) { config.accessControl = switch (isAccept) {
true => config.accessControl.copyWith( true => config.accessControl.copyWith(
acceptList: [], acceptList: [],
), ),
false => config.accessControl.copyWith( false => config.accessControl.copyWith(
rejectList: [], rejectList: [],
), ),
}; };
} else { } else {
config.accessControl = switch (isAccept) { config.accessControl = switch (isAccept) {
true => config.accessControl.copyWith( true => config.accessControl.copyWith(
acceptList: allValueList, acceptList: allValueList,
), ),
false => config.accessControl.copyWith( false => config.accessControl.copyWith(
rejectList: allValueList, rejectList: allValueList,
), ),
}; };
} }
}, },
child: isSelectedAll child: isSelectedAll
? const Icon(Icons.deselect) ? const Icon(Icons.deselect)
: const Icon(Icons.select_all), : const Icon(Icons.select_all),
), ),
), );
);
});
} }
Widget _buildPackageList() { Widget _buildPackageList() {
@@ -213,137 +205,141 @@ class _AccessFragmentState extends State<AccessFragment> {
accessControlMode == AccessControlMode.acceptSelected accessControlMode == AccessControlMode.acceptSelected
? appLocalizations.accessControlAllowDesc ? appLocalizations.accessControlAllowDesc
: appLocalizations.accessControlNotAllowDesc; : appLocalizations.accessControlNotAllowDesc;
_buildSelectedAllButton(
isAccessControl: isAccessControl,
isSelectedAll: valueList.length == packageNameList.length,
allValueList: packageNameList,
);
return DisabledMask( return DisabledMask(
status: !isAccessControl, status: !isAccessControl,
child: Column( child: FloatLayout(
children: [ floatingWidget: FloatWrapper(
AbsorbPointer( child: _buildSelectedAllButton(
absorbing: !isAccessControl, isAccessControl: isAccessControl,
child: Padding( isSelectedAll: valueList.length == packageNameList.length,
padding: const EdgeInsets.only( allValueList: packageNameList,
top: 4, ),
bottom: 4, ),
left: 16, child: Column(
right: 8, children: [
), AbsorbPointer(
child: Row( absorbing: !isAccessControl,
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: Padding(
mainAxisSize: MainAxisSize.max, padding: const EdgeInsets.only(
children: [ top: 4,
Expanded( bottom: 4,
child: IntrinsicHeight( left: 16,
child: Column( right: 8,
mainAxisSize: MainAxisSize.max, ),
crossAxisAlignment: CrossAxisAlignment.start, child: Row(
children: [ mainAxisAlignment: MainAxisAlignment.spaceBetween,
Expanded( mainAxisSize: MainAxisSize.max,
child: Row( children: [
children: [ Expanded(
Flexible( child: IntrinsicHeight(
child: Text( child: Column(
appLocalizations.selected, mainAxisSize: MainAxisSize.max,
style: Theme.of(context) crossAxisAlignment: CrossAxisAlignment.start,
.textTheme children: [
.labelLarge Expanded(
?.copyWith( child: Row(
color: Theme.of(context) children: [
.colorScheme Flexible(
.primary, child: Text(
), appLocalizations.selected,
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(
color: Theme.of(context)
.colorScheme
.primary,
),
),
), ),
), const Flexible(
const Flexible( child: SizedBox(
child: SizedBox( width: 8,
width: 8, ),
), ),
), Flexible(
Flexible( child: Text(
child: Text( "${valueList.length}",
"${valueList.length}", style: Theme.of(context)
style: Theme.of(context) .textTheme
.textTheme .labelLarge
.labelLarge ?.copyWith(
?.copyWith( color: Theme.of(context)
color: Theme.of(context) .colorScheme
.colorScheme .primary,
.primary, ),
), ),
), ),
), ],
], ),
), ),
), Flexible(
Flexible( child: Text(describe),
child: Text(describe), )
) ],
], ),
), ),
), ),
), Row(
Row( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end, children: [
children: [ Flexible(
Flexible( child: _buildSearchButton(currentPackages)),
child: _buildSearchButton(currentPackages)), Flexible(child: _buildFilterSystemAppButton()),
Flexible(child: _buildFilterSystemAppButton()), Flexible(child: _buildAppProxyModePopup()),
Flexible(child: _buildAppProxyModePopup()), ],
], ),
), ],
], ),
), ),
), ),
), Expanded(
Expanded( flex: 1,
flex: 1, child: FadeBox(
child: FadeBox( key: const Key("fade_box"),
key: const Key("fade_box"), child: currentPackages.isEmpty
child: currentPackages.isEmpty ? const Center(
? const Center( child: CircularProgressIndicator(),
child: CircularProgressIndicator(), )
) : ListView.builder(
: ListView.builder( itemCount: currentPackages.length,
itemCount: currentPackages.length, itemBuilder: (_, index) {
itemBuilder: (_, index) { final package = currentPackages[index];
final package = currentPackages[index]; return PackageListItem(
return PackageListItem( key: Key(package.packageName),
key: Key(package.packageName), package: package,
package: package, value:
value: valueList.contains(package.packageName),
valueList.contains(package.packageName), isActive: isAccessControl,
isActive: isAccessControl, onChanged: (value) {
onChanged: (value) { if (value == true) {
if (value == true) { valueList.add(package.packageName);
valueList.add(package.packageName); } else {
} else { valueList.remove(package.packageName);
valueList.remove(package.packageName); }
} final config =
final config = globalState.appController.config;
globalState.appController.config; if (accessControlMode ==
if (accessControlMode == AccessControlMode.acceptSelected) {
AccessControlMode.acceptSelected) { config.accessControl =
config.accessControl = config.accessControl.copyWith(
config.accessControl.copyWith( acceptList: valueList,
acceptList: valueList, );
); } else {
} else { config.accessControl =
config.accessControl = config.accessControl.copyWith(
config.accessControl.copyWith( rejectList: valueList,
rejectList: valueList, );
); }
} },
}, );
); },
}, ),
), ),
), ),
), ],
], ),
), ),
); );
}, },

View File

@@ -89,6 +89,24 @@ class ApplicationSettingFragment extends StatelessWidget {
); );
}, },
), ),
if (Platform.isAndroid)
Selector<Config, bool>(
selector: (_, config) => config.isExclude,
builder: (_, isExclude, child) {
return ListItem.switchItem(
leading: const Icon(Icons.visibility_off),
title: Text(appLocalizations.exclude),
subtitle: Text(appLocalizations.excludeDesc),
delegate: SwitchDelegate(
value: isExclude,
onChanged: (value) {
final config = context.read<Config>();
config.isExclude = value;
},
),
);
},
),
if (Platform.isAndroid) if (Platform.isAndroid)
Selector<Config, bool>( Selector<Config, bool>(
selector: (_, config) => config.isAnimateToPage, selector: (_, config) => config.isAnimateToPage,

View File

@@ -6,7 +6,6 @@ import 'package:fl_clash/models/dav.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/fade_box.dart'; import 'package:fl_clash/widgets/fade_box.dart';
import 'package:fl_clash/widgets/list.dart'; import 'package:fl_clash/widgets/list.dart';
import 'package:fl_clash/widgets/section.dart';
import 'package:fl_clash/widgets/text.dart'; import 'package:fl_clash/widgets/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -34,9 +33,9 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
final res = await commonScaffoldState?.loadingRun<bool>(() async { final res = await commonScaffoldState?.loadingRun<bool>(() async {
return await _client?.backup(); return await _client?.backup();
}); });
if(res != true) return; if (res != true) return;
globalState.showMessage( globalState.showMessage(
title: appLocalizations.recovery, title: appLocalizations.backup,
message: TextSpan(text: appLocalizations.backupSuccess), message: TextSpan(text: appLocalizations.backupSuccess),
); );
} }
@@ -46,7 +45,7 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
final res = await commonScaffoldState?.loadingRun<bool>(() async { final res = await commonScaffoldState?.loadingRun<bool>(() async {
return await _client?.recovery(recoveryOption: recoveryOption); return await _client?.recovery(recoveryOption: recoveryOption);
}); });
if(res != true) return; if (res != true) return;
globalState.showMessage( globalState.showMessage(
title: appLocalizations.recovery, title: appLocalizations.recovery,
message: TextSpan(text: appLocalizations.recoverySuccess), message: TextSpan(text: appLocalizations.recoverySuccess),
@@ -69,26 +68,22 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
if (dav == null) { if (dav == null) {
return ListView( return ListView(
children: [ children: [
Section( ListHeader(
title: appLocalizations.account, title: appLocalizations.account,
child: Builder( ),
builder: (_) { ListItem(
return ListItem( leading: const Icon(Icons.account_box),
leading: const Icon(Icons.account_box), title: Text(appLocalizations.noInfo),
title: Text(appLocalizations.noInfo), subtitle: Text(appLocalizations.pleaseBindWebDAV),
subtitle: Text(appLocalizations.pleaseBindWebDAV), trailing: FilledButton.tonal(
trailing: FilledButton.tonal( onPressed: () {
onPressed: () { _showAddWebDAV(dav);
_showAddWebDAV(dav);
},
child: Text(
appLocalizations.bind,
),
),
);
}, },
child: Text(
appLocalizations.bind,
),
), ),
) ),
], ],
); );
} }
@@ -96,62 +91,60 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
final pingFuture = _client!.pingCompleter.future; final pingFuture = _client!.pingCompleter.future;
return ListView( return ListView(
children: [ children: [
Section( ListHeader(title: appLocalizations.account),
title: appLocalizations.account, ListItem(
child: ListItem( leading: const Icon(Icons.account_box),
leading: const Icon(Icons.account_box), title: TooltipText(
title: TooltipText( text: Text(
text: Text( dav.user,
dav.user, maxLines: 1,
maxLines: 1, overflow: TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis,
),
), ),
subtitle: Padding( ),
padding: const EdgeInsets.symmetric(vertical: 4), subtitle: Padding(
child: Row( padding: const EdgeInsets.symmetric(vertical: 4),
crossAxisAlignment: CrossAxisAlignment.center, child: Row(
children: [ crossAxisAlignment: CrossAxisAlignment.center,
Text(appLocalizations.connectivity), children: [
FutureBuilder<bool>( Text(appLocalizations.connectivity),
future: pingFuture, FutureBuilder<bool>(
builder: (_, snapshot) { future: pingFuture,
return Center( builder: (_, snapshot) {
child: FadeBox( return Center(
key: const Key("fade_box_1"), child: FadeBox(
child: snapshot.connectionState == key: const Key("fade_box_1"),
ConnectionState.waiting child: snapshot.connectionState ==
? const SizedBox( ConnectionState.waiting
width: 12, ? const SizedBox(
height: 12, width: 12,
child: CircularProgressIndicator( height: 12,
strokeWidth: 1, child: CircularProgressIndicator(
), strokeWidth: 1,
)
: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: snapshot.data == true
? Colors.green
: Colors.red,
),
width: 12,
height: 12,
), ),
), )
); : Container(
}, decoration: BoxDecoration(
), shape: BoxShape.circle,
], color: snapshot.data == true
), ? Colors.green
: Colors.red,
),
width: 12,
height: 12,
),
),
);
},
),
],
), ),
trailing: FilledButton.tonal( ),
onPressed: () { trailing: FilledButton.tonal(
_showAddWebDAV(dav); onPressed: () {
}, _showAddWebDAV(dav);
child: Text( },
appLocalizations.edit, child: Text(
), appLocalizations.edit,
), ),
), ),
), ),
@@ -161,22 +154,21 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
return FadeBox( return FadeBox(
key: const Key("fade_box_2"), key: const Key("fade_box_2"),
child: snapshot.data == true child: snapshot.data == true
? Section( ? Column(
title: appLocalizations.backupAndRecovery, children: [
child: Column( ListHeader(
children: [ title: appLocalizations.backupAndRecovery),
ListItem( ListItem(
onTab: _backup, onTab: _backup,
title: Text(appLocalizations.backup), title: Text(appLocalizations.backup),
subtitle: Text(appLocalizations.backupDesc), subtitle: Text(appLocalizations.backupDesc),
), ),
ListItem( ListItem(
onTab: _handleRecovery, onTab: _handleRecovery,
title: Text(appLocalizations.recovery), title: Text(appLocalizations.recovery),
subtitle: Text(appLocalizations.recoveryDesc), subtitle: Text(appLocalizations.recoveryDesc),
), ),
], ],
),
) )
: Container(), : Container(),
); );
@@ -228,7 +220,6 @@ class _WebDAVFormDialogState extends State<WebDAVFormDialog> {
Navigator.pop(context); Navigator.pop(context);
} }
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();

View File

@@ -39,81 +39,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
} }
} }
Widget _buildAppSection() {
final items = [
if (Platform.isAndroid)
Selector<Config, bool>(
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<Config, bool>(
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<Config, bool>(
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,
)
]
],
),
);
}
_showLogLevelDialog(LogLevel value) { _showLogLevelDialog(LogLevel value) {
globalState.showCommonDialog( globalState.showCommonDialog(
child: AlertDialog( child: AlertDialog(
@@ -150,238 +75,364 @@ class _ConfigFragmentState extends State<ConfigFragment> {
); );
} }
Widget _buildGeneralSection() { _showUaDialog(String? value) {
final items = [ const uas = [
Selector<ClashConfig, LogLevel>( null,
selector: (_, clashConfig) => clashConfig.logLevel, "clash-verge/v1.6.6",
builder: (_, value, __) { "ClashforWindows/0.19.23",
return ListItem(
leading: const Icon(Icons.info_outline),
title: Text(appLocalizations.logLevel),
subtitle: Text(value.name),
onTab: () {
_showLogLevelDialog(value);
},
);
},
),
Selector<ClashConfig, int>(
selector: (_, clashConfig) => clashConfig.mixedPort,
builder: (_, mixedPort, __) {
return ListItem(
onTab: () {
_modifyMixedPort(mixedPort);
},
leading: const Icon(Icons.adjust_outlined),
title: Text(appLocalizations.proxyPort),
subtitle: Text(appLocalizations.proxyPortDesc),
trailing: FilledButton.tonal(
onPressed: () {
_modifyMixedPort(mixedPort);
},
child: Text(
"$mixedPort",
),
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.ipv6,
builder: (_, ipv6, __) {
return ListItem.switchItem(
leading: const Icon(Icons.water_outlined),
title: const Text("Ipv6"),
subtitle: Text(appLocalizations.ipv6Desc),
delegate: SwitchDelegate(
value: ipv6,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.clashConfig.ipv6 = value;
appController.updateClashConfigDebounce();
},
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.allowLan,
builder: (_, allowLan, __) {
return ListItem.switchItem(
leading: const Icon(Icons.device_hub),
title: Text(appLocalizations.allowLan),
subtitle: Text(appLocalizations.allowLanDesc),
delegate: SwitchDelegate(
value: allowLan,
onChanged: (bool value) async {
final clashConfig = context.read<ClashConfig>();
clashConfig.allowLan = value;
globalState.appController.updateClashConfigDebounce();
},
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.unifiedDelay,
builder: (_, unifiedDelay, __) {
return ListItem.switchItem(
leading: const Icon(Icons.compress_outlined),
title: Text(appLocalizations.unifiedDelay),
subtitle: Text(appLocalizations.unifiedDelayDesc),
delegate: SwitchDelegate(
value: unifiedDelay,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.clashConfig.unifiedDelay = value;
appController.updateClashConfigDebounce();
},
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) =>
clashConfig.findProcessMode == FindProcessMode.always,
builder: (_, findProcess, __) {
return ListItem.switchItem(
leading: const Icon(Icons.polymer_outlined),
title: Text(appLocalizations.findProcessMode),
subtitle: Text(appLocalizations.findProcessModeDesc),
delegate: SwitchDelegate(
value: findProcess,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.clashConfig.findProcessMode =
value ? FindProcessMode.always : FindProcessMode.off;
appController.updateClashConfigDebounce();
},
),
);
},
),
Selector<ClashConfig, bool>(
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<ClashConfig, bool>(
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<ClashConfig, bool>(
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 Section( globalState.showCommonDialog(
title: appLocalizations.general, child: AlertDialog(
child: Column( title: const Text("UA"),
children: [ contentPadding: const EdgeInsets.symmetric(
for (final item in items) ...[ horizontal: 8,
item, vertical: 16,
if (items.last != item) ),
const Divider( content: SizedBox(
height: 0, width: 250,
) child: Wrap(
] children: [
], for (final ua in uas)
ListItem.radio(
delegate: RadioDelegate<String?>(
value: ua,
groupValue: value,
onChanged: (String? value) {
final appController = globalState.appController;
appController.clashConfig.globalRealUa = value;
appController.updateClashConfigDebounce();
Navigator.of(context).pop();
},
),
title: Text(ua ?? appLocalizations.defaultText),
)
],
),
),
), ),
); );
} }
Widget _buildMoreSection() { _modifyTestUrl(String testUrl) async {
final items = [ final newTestUrl = await globalState.showCommonDialog<String>(
if (false) child: TestUrlFormDialog(
Selector<ClashConfig, bool>( testUrl: testUrl,
selector: (_, clashConfig) => clashConfig.tun.enable, ),
builder: (_, tunEnable, __) { );
if (newTestUrl != null && newTestUrl != testUrl && mounted) {
try {
if (!newTestUrl.isUrl) {
throw "Invalid url";
}
globalState.appController.config.testUrl = newTestUrl;
globalState.appController.updateClashConfigDebounce();
} catch (e) {
globalState.showMessage(
title: appLocalizations.testUrl,
message: TextSpan(
text: e.toString(),
),
);
}
}
}
List<Widget> _buildAppSection() {
return generateSection(
title: appLocalizations.app,
items: [
if (Platform.isAndroid)
Selector<Config, bool>(
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<Config, bool>(
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<Config, bool>(
selector: (_, config) => config.isCompatible,
builder: (_, isCompatible, __) {
return ListItem.switchItem( return ListItem.switchItem(
leading: const Icon( leading: const Icon(Icons.expand_outlined),
Icons.important_devices_outlined title: Text(appLocalizations.compatible),
), subtitle: Text(appLocalizations.compatibleDesc),
title: Text(appLocalizations.tun),
subtitle: Text(appLocalizations.tunDesc),
delegate: SwitchDelegate( delegate: SwitchDelegate(
value: tunEnable, value: isCompatible,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.config.isCompatible = value;
await appController.applyProfile();
},
),
);
},
),
],
);
}
List<Widget> _buildGeneralSection() {
return generateSection(
title: appLocalizations.general,
items: [
Selector<ClashConfig, LogLevel>(
selector: (_, clashConfig) => clashConfig.logLevel,
builder: (_, value, __) {
return ListItem(
leading: const Icon(Icons.info_outline),
title: Text(appLocalizations.logLevel),
subtitle: Text(value.name),
onTab: () {
_showLogLevelDialog(value);
},
);
},
),
Selector<ClashConfig, String?>(
selector: (_, clashConfig) => clashConfig.globalRealUa,
builder: (_, value, __) {
return ListItem(
leading: const Icon(Icons.computer_outlined),
title: const Text("UA"),
subtitle: Text(value ?? appLocalizations.defaultText),
onTab: () {
_showUaDialog(value);
},
);
},
),
Selector<Config, String>(
selector: (_, config) => config.testUrl,
builder: (_, value, __) {
return ListItem(
leading: const Icon(Icons.timeline),
title: Text(appLocalizations.testUrl),
subtitle: Text(value),
onTab: () {
_modifyTestUrl(value);
},
);
},
),
Selector<ClashConfig, int>(
selector: (_, clashConfig) => clashConfig.mixedPort,
builder: (_, mixedPort, __) {
return ListItem(
onTab: () {
_modifyMixedPort(mixedPort);
},
leading: const Icon(Icons.adjust_outlined),
title: Text(appLocalizations.proxyPort),
subtitle: Text(appLocalizations.proxyPortDesc),
trailing: FilledButton.tonal(
onPressed: () {
_modifyMixedPort(mixedPort);
},
child: Text(
"$mixedPort",
),
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.ipv6,
builder: (_, ipv6, __) {
return ListItem.switchItem(
leading: const Icon(Icons.water_outlined),
title: const Text("IPv6"),
subtitle: Text(appLocalizations.ipv6Desc),
delegate: SwitchDelegate(
value: ipv6,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.clashConfig.ipv6 = value;
appController.updateClashConfigDebounce();
},
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.allowLan,
builder: (_, allowLan, __) {
return ListItem.switchItem(
leading: const Icon(Icons.device_hub),
title: Text(appLocalizations.allowLan),
subtitle: Text(appLocalizations.allowLanDesc),
delegate: SwitchDelegate(
value: allowLan,
onChanged: (bool value) async { onChanged: (bool value) async {
final clashConfig = context.read<ClashConfig>(); final clashConfig = context.read<ClashConfig>();
clashConfig.tun = Tun(enable: value); clashConfig.allowLan = value;
globalState.appController.updateClashConfigDebounce(); globalState.appController.updateClashConfigDebounce();
}, },
), ),
); );
}, },
), ),
]; Selector<ClashConfig, bool>(
if(items.isEmpty) return Container(); selector: (_, clashConfig) => clashConfig.unifiedDelay,
return Section( builder: (_, unifiedDelay, __) {
title: appLocalizations.general, return ListItem.switchItem(
child: Column( leading: const Icon(Icons.compress_outlined),
children: [ title: Text(appLocalizations.unifiedDelay),
for (final item in items) ...[ subtitle: Text(appLocalizations.unifiedDelayDesc),
item, delegate: SwitchDelegate(
if (items.last != item) value: unifiedDelay,
const Divider( onChanged: (bool value) async {
height: 0, final appController = globalState.appController;
) appController.clashConfig.unifiedDelay = value;
] appController.updateClashConfigDebounce();
], },
), ),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) =>
clashConfig.findProcessMode == FindProcessMode.always,
builder: (_, findProcess, __) {
return ListItem.switchItem(
leading: const Icon(Icons.polymer_outlined),
title: Text(appLocalizations.findProcessMode),
subtitle: Text(appLocalizations.findProcessModeDesc),
delegate: SwitchDelegate(
value: findProcess,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.clashConfig.findProcessMode =
value ? FindProcessMode.always : FindProcessMode.off;
appController.updateClashConfigDebounce();
},
),
);
},
),
Selector<ClashConfig, bool>(
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<ClashConfig, bool>(
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<ClashConfig, bool>(
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();
},
),
);
},
),
],
);
}
List<Widget> _buildMoreSection() {
return generateSection(
title: appLocalizations.more,
items: [
if (system.isDesktop)
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.tun.enable,
builder: (_, tunEnable, __) {
return ListItem.switchItem(
leading: const Icon(Icons.important_devices_outlined),
title: Text(appLocalizations.tun),
subtitle: Text(appLocalizations.tunDesc),
delegate: SwitchDelegate(
value: tunEnable,
onChanged: (bool value) async {
final clashConfig = context.read<ClashConfig>();
clashConfig.tun = Tun(enable: value);
globalState.appController.updateClashConfigDebounce();
},
),
);
},
),
],
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Widget> items = [ List<Widget> items = [
_buildAppSection(), ..._buildAppSection(),
_buildGeneralSection(), ..._buildGeneralSection(),
_buildMoreSection(), ..._buildMoreSection(),
]; ];
return ListView.builder( return ListView.builder(
padding: const EdgeInsets.only(bottom: 32), padding: const EdgeInsets.only(bottom: 32),
@@ -414,7 +465,7 @@ class _MixedPortFormDialogState extends State<MixedPortFormDialog> {
portController = TextEditingController(text: "${widget.mixedPort}"); portController = TextEditingController(text: "${widget.mixedPort}");
} }
_handleAddProfileFormURL() async { _handleUpdate() async {
final port = portController.value.text; final port = portController.value.text;
if (port.isEmpty) return; if (port.isEmpty) return;
Navigator.of(context).pop<String>(port); Navigator.of(context).pop<String>(port);
@@ -440,7 +491,64 @@ class _MixedPortFormDialogState extends State<MixedPortFormDialog> {
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: _handleAddProfileFormURL, onPressed: _handleUpdate,
child: Text(appLocalizations.submit),
)
],
);
}
}
class TestUrlFormDialog extends StatefulWidget {
final String testUrl;
const TestUrlFormDialog({
super.key,
required this.testUrl,
});
@override
State<TestUrlFormDialog> createState() => _TestUrlFormDialogState();
}
class _TestUrlFormDialogState extends State<TestUrlFormDialog> {
late TextEditingController testUrlController;
@override
void initState() {
super.initState();
testUrlController = TextEditingController(text: widget.testUrl);
}
_handleUpdate() async {
final testUrl = testUrlController.value.text;
if (testUrl.isEmpty) return;
Navigator.of(context).pop<String>(testUrl);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(appLocalizations.testUrl),
content: SizedBox(
width: 300,
child: Wrap(
runSpacing: 16,
children: [
TextField(
maxLines: 5,
minLines: 1,
controller: testUrlController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
),
],
),
),
actions: [
TextButton(
onPressed: _handleUpdate,
child: Text(appLocalizations.submit), child: Text(appLocalizations.submit),
) )
], ],

View File

@@ -1,11 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/widgets/widgets.dart'; import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -63,9 +61,6 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
}, },
icon: const Icon(Icons.search), icon: const Icon(Icons.search),
), ),
const SizedBox(
width: 8,
)
]; ];
}, },
); );
@@ -139,8 +134,8 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
vertical: 16, vertical: 16,
), ),
child: Wrap( child: Wrap(
runSpacing: 8, runSpacing: 6,
spacing: 8, spacing: 6,
children: [ children: [
for (final keyword in state.keywords) for (final keyword in state.keywords)
CommonChip( CommonChip(
@@ -162,7 +157,12 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
key: Key(connection.id), key: Key(connection.id),
connection: connection, connection: connection,
onClick: _addKeyword, onClick: _addKeyword,
onBlock: _handleBlockConnection, trailing: IconButton(
icon: const Icon(Icons.block),
onPressed: () {
_handleBlockConnection(connection.id);
},
),
); );
}, },
separatorBuilder: (BuildContext context, int index) { separatorBuilder: (BuildContext context, int index) {
@@ -181,112 +181,6 @@ class _ConnectionsFragmentState extends State<ConnectionsFragment> {
} }
} }
class ConnectionItem extends StatelessWidget {
final Connection connection;
final Function(String)? onClick;
final Function(String)? onBlock;
const ConnectionItem({
super.key,
required this.connection,
this.onClick,
this.onBlock,
});
Future<ImageProvider?> _getPackageIcon(Connection connection) async {
return await app?.getPackageIcon(connection.metadata.process);
}
String _getRequestText(Metadata metadata) {
var text = "${metadata.network}:://";
final ips = [
metadata.host,
metadata.destinationIP,
].where((ip) => ip.isNotEmpty);
text += ips.join("/");
text += ":${metadata.destinationPort}";
return text;
}
String _getSourceText(Connection connection) {
final metadata = connection.metadata;
if (metadata.process.isEmpty) {
return connection.start.lastUpdateTimeDesc;
}
return "${metadata.process} · ${connection.start.lastUpdateTimeDesc}";
}
@override
Widget build(BuildContext context) {
return ListItem(
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
leading: Platform.isAndroid
? Container(
margin: const EdgeInsets.only(top: 4),
width: 48,
height: 48,
child: FutureBuilder<ImageProvider?>(
future: _getPackageIcon(connection),
builder: (_, snapshot) {
if (!snapshot.hasData && snapshot.data == null) {
return Container();
} else {
return Image(
image: snapshot.data!,
gaplessPlayback: true,
width: 48,
height: 48,
);
}
},
),
)
: null,
title: Text(
_getRequestText(connection.metadata),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
Text(
_getSourceText(connection),
),
const SizedBox(
height: 12,
),
Wrap(
runSpacing: 8,
spacing: 8,
children: [
for (final chain in connection.chains)
CommonChip(
label: chain,
onPressed: () {
if (onClick == null) return;
onClick!(chain);
},
),
],
),
const SizedBox(
height: 12,
),
],
),
trailing: IconButton(
icon: const Icon(Icons.block),
onPressed: () {
if (onBlock == null) return;
onBlock!(connection.id);
},
),
);
}
}
class ConnectionsSearchDelegate extends SearchDelegate { class ConnectionsSearchDelegate extends SearchDelegate {
ValueNotifier<ConnectionsAndKeywords> connectionsNotifier; ValueNotifier<ConnectionsAndKeywords> connectionsNotifier;
@@ -332,11 +226,11 @@ class ConnectionsSearchDelegate extends SearchDelegate {
); );
} }
_handleBlockConnection(String id) { _handleBlockConnection(String id) {
clashCore.closeConnections(id); clashCore.closeConnections(id);
connectionsNotifier.value = connectionsNotifier.value connectionsNotifier.value = connectionsNotifier.value.copyWith(
.copyWith(connections: clashCore.getConnections()); connections: clashCore.getConnections(),
);
} }
@override @override
@@ -394,8 +288,8 @@ class ConnectionsSearchDelegate extends SearchDelegate {
vertical: 16, vertical: 16,
), ),
child: Wrap( child: Wrap(
runSpacing: 8, runSpacing: 6,
spacing: 8, spacing: 6,
children: [ children: [
for (final keyword in state.keywords) for (final keyword in state.keywords)
CommonChip( CommonChip(
@@ -416,7 +310,12 @@ class ConnectionsSearchDelegate extends SearchDelegate {
key: Key(connection.id), key: Key(connection.id),
connection: connection, connection: connection,
onClick: _addKeyword, onClick: _addKeyword,
onBlock: _handleBlockConnection, trailing: IconButton(
icon: const Icon(Icons.block),
onPressed: () {
_handleBlockConnection(connection.id);
},
),
); );
}, },
separatorBuilder: (BuildContext context, int index) { separatorBuilder: (BuildContext context, int index) {

View File

@@ -13,6 +13,7 @@ class CoreInfo extends StatelessWidget {
selector: (_, appState) => appState.versionInfo, selector: (_, appState) => appState.versionInfo,
builder: (_, versionInfo, __) { builder: (_, versionInfo, __) {
return CommonCard( return CommonCard(
onPressed: () {},
info: Info( info: Info(
label: appLocalizations.coreInfo, label: appLocalizations.coreInfo,
iconData: Icons.memory, iconData: Icons.memory,
@@ -31,7 +32,7 @@ class CoreInfo extends StatelessWidget {
style: context style: context
.textTheme .textTheme
.titleMedium .titleMedium
?.toSoftBold(), ?.toSoftBold,
), ),
), ),
const SizedBox( const SizedBox(
@@ -44,7 +45,7 @@ class CoreInfo extends StatelessWidget {
style: context style: context
.textTheme .textTheme
.titleLarge .titleLarge
?.toSoftBold(), ?.toSoftBold,
), ),
), ),
], ],

View File

@@ -56,7 +56,7 @@ class _DashboardFragmentState extends State<DashboardFragment> {
), ),
GridItem( GridItem(
crossAxisCellCount: isDesktop ? 4 : 6, crossAxisCellCount: isDesktop ? 4 : 6,
child: const IntranetIp(), child: const IntranetIP(),
), ),
], ],
); );

View File

@@ -5,21 +5,21 @@ import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart'; import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class IntranetIp extends StatefulWidget { class IntranetIP extends StatefulWidget {
const IntranetIp({super.key}); const IntranetIP({super.key});
@override @override
State<IntranetIp> createState() => _IntranetIpState(); State<IntranetIP> createState() => _IntranetIPState();
} }
class _IntranetIpState extends State<IntranetIp> { class _IntranetIPState extends State<IntranetIP> {
final ipNotifier = ValueNotifier<String>(""); final ipNotifier = ValueNotifier<String>("");
Future<String?> getLocalIpAddress() async { Future<String?> getLocalIpAddress() async {
List<NetworkInterface> interfaces = await NetworkInterface.list(); List<NetworkInterface> interfaces = await NetworkInterface.list();
for (final interface in interfaces) { for (final interface in interfaces) {
for (final address in interface.addresses) { for (final address in interface.addresses) {
if (address.type == InternetAddressType.IPv4 && !address.isLoopback) { if (!address.isLoopback) {
return address.address; return address.address;
} }
} }
@@ -45,12 +45,15 @@ class _IntranetIpState extends State<IntranetIp> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CommonCard( return CommonCard(
info: Info( info: Info(
label: appLocalizations.intranetIp, label: appLocalizations.intranetIP,
iconData: Icons.devices, iconData: Icons.devices,
), ),
onPressed: (){
},
child: Container( child: Container(
padding: const EdgeInsets.all(16).copyWith(top: 0), padding: const EdgeInsets.all(16).copyWith(top: 0),
height: globalState.appController.measure.titleLargeHeight + 16, height: globalState.appController.measure.titleLargeHeight + 24 - 2,
child: ValueListenableBuilder( child: ValueListenableBuilder(
valueListenable: ipNotifier, valueListenable: ipNotifier,
builder: (_, value, __) { builder: (_, value, __) {
@@ -65,7 +68,7 @@ class _IntranetIpState extends State<IntranetIp> {
child: TooltipText( child: TooltipText(
text: Text( text: Text(
value, value,
style: context.textTheme.titleLarge?.toSoftBold(), style: context.textTheme.titleLarge?.toSoftBold.toMinus,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -74,8 +77,11 @@ class _IntranetIpState extends State<IntranetIp> {
], ],
) )
: const Padding( : const Padding(
padding: EdgeInsets.all(4), padding: EdgeInsets.all(2),
child: CircularProgressIndicator(), child: AspectRatio(
aspectRatio: 1,
child: CircularProgressIndicator(),
),
), ),
); );
}, },

View File

@@ -52,6 +52,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
isInit: appState.isInit, isInit: appState.isInit,
selectedMap: appState.selectedMap, selectedMap: appState.selectedMap,
isStart: appState.isStart, isStart: appState.isStart,
checkIpNum: appState.checkIpNum,
); );
}, },
builder: (_, state, __) { builder: (_, state, __) {
@@ -78,6 +79,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
valueListenable: ipInfoNotifier, valueListenable: ipInfoNotifier,
builder: (_, ipInfo, __) { builder: (_, ipInfo, __) {
return CommonCard( return CommonCard(
onPressed: () {},
child: Column( child: Column(
children: [ children: [
Flexible( Flexible(
@@ -134,8 +136,9 @@ class _NetworkDetectionState extends State<NetworkDetection> {
), ),
), ),
Container( Container(
height: height: globalState.appController.measure.titleLargeHeight +
globalState.appController.measure.titleLargeHeight + 24, 24 -
2,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
padding: const EdgeInsets.all(16).copyWith(top: 0), padding: const EdgeInsets.all(16).copyWith(top: 0),
child: FadeBox( child: FadeBox(
@@ -150,7 +153,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
text: Text( text: Text(
ipInfo.ip, ipInfo.ip,
style: context.textTheme.titleLarge style: context.textTheme.titleLarge
?.toSoftBold(), ?.toSoftBold.toMinus,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -164,9 +167,10 @@ class _NetworkDetectionState extends State<NetworkDetection> {
if (timeout) { if (timeout) {
return Text( return Text(
"timeout", "timeout",
style: context.textTheme.titleMedium style: context.textTheme.titleLarge
?.copyWith(color: Colors.red) ?.copyWith(color: Colors.red)
.toSoftBold(), .toSoftBold
.toMinus,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
); );

View File

@@ -47,8 +47,8 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
final showValue = value.showValue; final showValue = value.showValue;
final showUnit = "${value.showUnit}/s"; final showUnit = "${value.showUnit}/s";
final titleLargeSoftBold = final titleLargeSoftBold =
Theme.of(context).textTheme.titleLarge?.toSoftBold(); Theme.of(context).textTheme.titleLarge?.toSoftBold;
final bodyMedium = Theme.of(context).textTheme.bodySmall?.toLight(); final bodyMedium = Theme.of(context).textTheme.bodySmall?.toLight;
final valueText = Text( final valueText = Text(
showValue, showValue,
style: titleLargeSoftBold, style: titleLargeSoftBold,
@@ -75,7 +75,7 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
Flexible( Flexible(
child: Text( child: Text(
label, label,
style: Theme.of(context).textTheme.titleSmall?.toSoftBold(), style: Theme.of(context).textTheme.titleSmall?.toSoftBold,
), ),
), ),
], ],
@@ -111,6 +111,7 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CommonCard( return CommonCard(
onPressed: () {},
info: Info( info: Info(
label: appLocalizations.networkSpeed, label: appLocalizations.networkSpeed,
iconData: Icons.speed, iconData: Icons.speed,

View File

@@ -13,18 +13,10 @@ class OutboundMode extends StatelessWidget {
_changeMode(BuildContext context, Mode? value) async { _changeMode(BuildContext context, Mode? value) async {
final appController = globalState.appController; final appController = globalState.appController;
final clashConfig = appController.clashConfig; final clashConfig = appController.clashConfig;
final config = appController.config;
if (value == null || clashConfig.mode == value) return; if (value == null || clashConfig.mode == value) return;
clashConfig.mode = value; clashConfig.mode = value;
await appController.updateClashConfig(); await appController.updateClashConfig();
if (!config.isCompatible) { appController.addCheckIpNumDebounce();
final proxySelected = config.currentSelectedMap[GroupName.Proxy.name];
final globalSelected = config.currentSelectedMap[GroupName.GLOBAL.name];
if (proxySelected != null && globalSelected == null) {
config.updateCurrentSelectedMap(GroupName.GLOBAL.name, proxySelected);
}
}
appController.changeProxy();
} }
@override @override
@@ -33,6 +25,7 @@ class OutboundMode extends StatelessWidget {
selector: (_, clashConfig) => clashConfig.mode, selector: (_, clashConfig) => clashConfig.mode,
builder: (_, mode, __) { builder: (_, mode, __) {
return CommonCard( return CommonCard(
onPressed: () {},
info: Info( info: Info(
label: appLocalizations.outboundMode, label: appLocalizations.outboundMode,
iconData: Icons.call_split, iconData: Icons.call_split,
@@ -63,11 +56,8 @@ class OutboundMode extends StatelessWidget {
), ),
title: Text( title: Text(
Intl.message(item.name), Intl.message(item.name),
style: Theme style:
.of(context) Theme.of(context).textTheme.titleMedium?.toSoftBold,
.textTheme
.titleMedium
?.toSoftBold(),
), ),
), ),
], ],

View File

@@ -13,19 +13,17 @@ class StartButton extends StatefulWidget {
class _StartButtonState extends State<StartButton> class _StartButtonState extends State<StartButton>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
bool isStart = false;
bool isInit = false;
late AnimationController _controller; late AnimationController _controller;
bool isStart = false;
@override @override
void initState() { void initState() {
isStart = globalState.appController.appState.isStart; super.initState();
_controller = AnimationController( _controller = AnimationController(
vsync: this, vsync: this,
value: isStart ? 1 : 0, value: 0,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
); );
super.initState();
} }
@override @override
@@ -35,9 +33,12 @@ class _StartButtonState extends State<StartButton>
} }
handleSwitchStart() { handleSwitchStart() {
isStart = !isStart; final appController = globalState.appController;
updateController(); if (isStart == appController.appState.isStart) {
updateSystemProxy(); isStart = !isStart;
updateController();
appController.updateSystemProxy(isStart);
}
} }
updateController() { updateController() {
@@ -48,11 +49,18 @@ class _StartButtonState extends State<StartButton>
} }
} }
updateSystemProxy() { Widget _updateControllerContainer(Widget child) {
WidgetsBinding.instance.addPostFrameCallback((_) async { return Selector<AppState, bool>(
final appController = globalState.appController; selector: (_, appState) => appState.isStart,
await appController.updateSystemProxy(isStart); builder: (_, isStart, child) {
}); if(isStart != this.isStart){
this.isStart = isStart;
updateController();
}
return child!;
},
child: child,
);
} }
@override @override
@@ -72,8 +80,7 @@ class _StartButtonState extends State<StartButton>
other.getTimeDifference( other.getTimeDifference(
DateTime.now(), DateTime.now(),
), ),
style: style: Theme.of(context).textTheme.titleMedium?.toSoftBold,
Theme.of(context).textTheme.titleMedium?.toSoftBold(),
), ),
) )
.width + .width +
@@ -119,24 +126,14 @@ class _StartButtonState extends State<StartButton>
child: child, child: child,
); );
}, },
child: Selector<AppState, bool>( child: _updateControllerContainer(
selector: (_, appState) => appState.runTime != null, Selector<AppState, int?>(
builder: (_, isRun, child) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (isStart != isRun) {
isStart = isRun;
updateController();
}
});
return child!;
},
child: Selector<AppState, int?>(
selector: (_, appState) => appState.runTime, selector: (_, appState) => appState.runTime,
builder: (_, int? value, __) { builder: (_, int? value, __) {
final text = other.getTimeText(value); final text = other.getTimeText(value);
return Text( return Text(
text, text,
style: Theme.of(context).textTheme.titleMedium?.toSoftBold(), style: Theme.of(context).textTheme.titleMedium?.toSoftBold,
); );
}, },
), ),

View File

@@ -42,7 +42,7 @@ class TrafficUsage extends StatelessWidget {
), ),
Text( Text(
trafficValue.showUnit, trafficValue.showUnit,
style: context.textTheme.labelMedium?.toLight(), style: context.textTheme.labelMedium?.toLight,
), ),
], ],
); );
@@ -51,6 +51,7 @@ class TrafficUsage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CommonCard( return CommonCard(
onPressed: () {},
info: Info( info: Info(
label: appLocalizations.trafficUsage, label: appLocalizations.trafficUsage,
iconData: Icons.data_saver_off, iconData: Icons.data_saver_off,

View File

@@ -1,4 +1,4 @@
export 'proxies.dart'; export 'proxies/proxies.dart';
export 'dashboard/dashboard.dart'; export 'dashboard/dashboard.dart';
export 'tools.dart'; export 'tools.dart';
export 'profiles/profiles.dart'; export 'profiles/profiles.dart';

View File

@@ -72,9 +72,6 @@ class _LogsFragmentState extends State<LogsFragment> {
}, },
icon: const Icon(Icons.search), icon: const Icon(Icons.search),
), ),
const SizedBox(
width: 8,
)
]; ];
}); });
} }
@@ -269,8 +266,8 @@ class LogsSearchDelegate extends SearchDelegate {
vertical: 16, vertical: 16,
), ),
child: Wrap( child: Wrap(
runSpacing: 8, runSpacing: 6,
spacing: 8, spacing: 6,
children: [ children: [
for (final keyword in state.keywords) for (final keyword in state.keywords)
CommonChip( CommonChip(
@@ -328,26 +325,23 @@ class _LogItemState extends State<LogItem> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final log = widget.log; final log = widget.log;
return ListTile( return ListItem(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
title: SelectableText(log.payload ?? ''), title: SelectableText(log.payload ?? ''),
subtitle: Column( subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( SelectableText(
padding: const EdgeInsets.only( "${log.dateTime}",
top: 8, style: context.textTheme.bodySmall
), ?.copyWith(color: context.colorScheme.primary),
child: SelectableText(
"${log.dateTime}",
style: context.textTheme.bodySmall
?.copyWith(color: context.colorScheme.primary),
),
), ),
const SizedBox(height: 8,),
Container( Container(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(
vertical: 8,
),
child: CommonChip( child: CommonChip(
onPressed: () { onPressed: () {
if (widget.onClick == null) return; if (widget.onClick == null) return;

View File

@@ -25,7 +25,9 @@ class AddProfile extends StatelessWidget {
final url = await Navigator.of(context) final url = await Navigator.of(context)
.push<String>(MaterialPageRoute(builder: (_) => const ScanPage())); .push<String>(MaterialPageRoute(builder: (_) => const ScanPage()));
if (url != null) { if (url != null) {
_handleAddProfileFormURL(url); WidgetsBinding.instance.addPostFrameCallback((_){
_handleAddProfileFormURL(url);
});
} }
} }
@@ -91,7 +93,8 @@ class _URLFormDialogState extends State<URLFormDialog> {
runSpacing: 16, runSpacing: 16,
children: [ children: [
TextField( TextField(
maxLines: null, maxLines: 5,
minLines: 1,
controller: urlController, controller: urlController,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),

View File

@@ -94,7 +94,8 @@ class _EditProfileState extends State<EditProfile> {
ListItem( ListItem(
title: TextFormField( title: TextFormField(
controller: urlController, controller: urlController,
maxLines: null, maxLines: 5,
minLines: 1,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: appLocalizations.url, labelText: appLocalizations.url,

View File

@@ -28,6 +28,7 @@ class ProfilesFragment extends StatefulWidget {
class _ProfilesFragmentState extends State<ProfilesFragment> { class _ProfilesFragmentState extends State<ProfilesFragment> {
final hasPadding = ValueNotifier<bool>(false); final hasPadding = ValueNotifier<bool>(false);
Function? applyConfigDebounce;
List<GlobalObjectKey<_ProfileItemState>> profileItemKeys = []; List<GlobalObjectKey<_ProfileItemState>> profileItemKeys = [];
@@ -55,7 +56,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
_updateProfiles() async { _updateProfiles() async {
final updateProfiles = profileItemKeys.map<Future>( final updateProfiles = profileItemKeys.map<Future>(
(key) async => await key.currentState?.updateProfile(false)); (key) async => await key.currentState?.updateProfile(false));
final result = await Future.wait(updateProfiles); await Future.wait(updateProfiles);
} }
_initScaffoldState() { _initScaffoldState() {
@@ -71,101 +72,113 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
}, },
icon: const Icon(Icons.sync), icon: const Icon(Icons.sync),
), ),
const SizedBox(
width: 8,
)
]; ];
commonScaffoldState?.floatingActionButton = FloatingActionButton(
heroTag: null,
onPressed: _handleShowAddExtendPage,
child: const Icon(
Icons.add,
),
);
}, },
); );
} }
@override
void initState() {
super.initState();
}
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
hasPadding.dispose(); hasPadding.dispose();
} }
_changeProfile(String? id) async {
final appController = globalState.appController;
final config = appController.config;
if (id == config.currentProfileId) return;
config.currentProfileId = id;
applyConfigDebounce ??= debounce<Function()>(() async {
await appController.applyProfile();
appController.appState.delayMap = {};
appController.saveConfigPreferences();
});
applyConfigDebounce!();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Selector<AppState, bool>( return FloatLayout(
selector: (_, appState) => appState.currentLabel == 'profiles', floatingWidget: FloatWrapper(
builder: (_, isCurrent, child) { child: FloatingActionButton(
if (isCurrent) { heroTag: null,
_initScaffoldState(); onPressed: _handleShowAddExtendPage,
} child: const Icon(
return child!; Icons.add,
}, ),
child: Selector2<AppState, Config, ProfilesSelectorState>(
selector: (_, appState, config) => ProfilesSelectorState(
profiles: config.profiles,
currentProfileId: config.currentProfileId,
viewMode: appState.viewMode,
), ),
builder: (context, state, child) { ),
if (state.profiles.isEmpty) { child: Selector<AppState, bool>(
return NullStatus( selector: (_, appState) => appState.currentLabel == 'profiles',
label: appLocalizations.nullProfileDesc, builder: (_, isCurrent, child) {
); if (isCurrent) {
_initScaffoldState();
} }
profileItemKeys = state.profiles return child!;
.map((profile) => GlobalObjectKey<_ProfileItemState>(profile.id)) },
.toList(); child: Selector2<AppState, Config, ProfilesSelectorState>(
final columns = _getColumns(state.viewMode); selector: (_, appState, config) => ProfilesSelectorState(
final isMobile = state.viewMode == ViewMode.mobile; profiles: config.profiles,
return Align( currentProfileId: config.currentProfileId,
alignment: Alignment.topCenter, viewMode: appState.viewMode,
child: NotificationListener<ScrollNotification>( ),
onNotification: (scrollNotification) { builder: (context, state, child) {
WidgetsBinding.instance.addPostFrameCallback((_) { if (state.profiles.isEmpty) {
return NullStatus(
label: appLocalizations.nullProfileDesc,
);
}
profileItemKeys = state.profiles
.map(
(profile) => GlobalObjectKey<_ProfileItemState>(profile.id))
.toList();
final columns = _getColumns(state.viewMode);
return Align(
alignment: Alignment.topCenter,
child: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
hasPadding.value = hasPadding.value =
scrollNotification.metrics.maxScrollExtent > 0; scrollNotification.metrics.maxScrollExtent > 0;
}); return true;
return true;
},
child: ValueListenableBuilder(
valueListenable: hasPadding,
builder: (_, hasPadding, __) {
return SingleChildScrollView(
padding: !isMobile
? EdgeInsets.only(
left: 16,
right: 16,
top: 16,
bottom: 16 + (hasPadding ? 56 : 0),
)
: EdgeInsets.only(
bottom: 0 + (hasPadding ? 56 : 0),
),
child: Grid(
mainAxisSpacing: isMobile ? 8 : 16,
crossAxisSpacing: 16,
crossAxisCount: columns,
children: [
for (int i = 0; i < state.profiles.length; i++)
GridItem(
child: ProfileItem(
key: profileItemKeys[i],
profile: state.profiles[i],
groupValue: state.currentProfileId,
onChanged:
globalState.appController.changeProfile,
),
),
],
),
);
}, },
child: ValueListenableBuilder(
valueListenable: hasPadding,
builder: (_, hasPadding, __) {
return SingleChildScrollView(
padding: EdgeInsets.only(
left: 16,
right: 16,
top: 16,
bottom: 16 + (hasPadding ? 72 : 0),
),
child: Grid(
mainAxisSpacing: 16,
crossAxisSpacing: 16,
crossAxisCount: columns,
children: [
for (int i = 0; i < state.profiles.length; i++)
GridItem(
child: ProfileItem(
key: profileItemKeys[i],
profile: state.profiles[i],
groupValue: state.currentProfileId,
onChanged: _changeProfile,
),
),
],
),
);
},
),
), ),
), );
); },
}, ),
), ),
); );
} }
@@ -201,11 +214,18 @@ class _ProfileItemState extends State<ProfileItem> {
Future updateProfile([isSingle = true]) async { Future updateProfile([isSingle = true]) async {
isUpdating.value = true; isUpdating.value = true;
try { try {
await globalState.appController.updateProfile(widget.profile); final appController = globalState.appController;
await appController.updateProfile(widget.profile);
if (widget.profile.id == appController.config.currentProfile?.id &&
!appController.appState.isStart) {
globalState.appController.rawApplyProfile();
}
} catch (e) { } catch (e) {
isUpdating.value = false; isUpdating.value = false;
if (!isSingle) { if (!isSingle) {
return e.toString(); return e.toString();
} else {
rethrow;
} }
} }
isUpdating.value = false; isUpdating.value = false;
@@ -245,15 +265,17 @@ class _ProfileItemState extends State<ProfileItem> {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Flexible(
profile.label ?? profile.id, child: Text(
style: textTheme.titleMedium, profile.label ?? profile.id,
maxLines: 1, style: textTheme.titleMedium,
overflow: TextOverflow.ellipsis, maxLines: 1,
overflow: TextOverflow.ellipsis,
),
), ),
Text( Text(
profile.lastUpdateDate?.lastUpdateTimeDesc ?? '', profile.lastUpdateDate?.lastUpdateTimeDesc ?? '',
style: textTheme.labelMedium?.toLight(), style: textTheme.labelMedium?.toLight,
), ),
], ],
), ),
@@ -284,7 +306,7 @@ class _ProfileItemState extends State<ProfileItem> {
), ),
Text( Text(
"$useShow / $totalShow", "$useShow / $totalShow",
style: textTheme.labelMedium?.toLight(), style: textTheme.labelMedium?.toLight,
), ),
const SizedBox( const SizedBox(
height: 2, height: 2,
@@ -293,14 +315,14 @@ class _ProfileItemState extends State<ProfileItem> {
children: [ children: [
Text( Text(
appLocalizations.expirationTime, appLocalizations.expirationTime,
style: textTheme.labelMedium?.toLighter(), style: textTheme.labelMedium?.toLighter,
), ),
const SizedBox( const SizedBox(
width: 4, width: 4,
), ),
Text( Text(
expireShow, expireShow,
style: textTheme.labelMedium?.toLighter(), style: textTheme.labelMedium?.toLighter,
), ),
], ],
) )
@@ -393,16 +415,7 @@ class _ProfileItemState extends State<ProfileItem> {
final profile = widget.profile; final profile = widget.profile;
final groupValue = widget.groupValue; final groupValue = widget.groupValue;
final onChanged = widget.onChanged; final onChanged = widget.onChanged;
return Selector<AppState, ViewMode>( return CommonCard(
selector: (_, appState) => appState.viewMode,
builder: (_, viewMode, child) {
if (viewMode == ViewMode.mobile) {
return child!;
}
return CommonCard(
child: child!,
);
},
child: ListItem.radio( child: ListItem.radio(
key: Key(profile.id), key: Key(profile.id),
horizontalTitleGap: 16, horizontalTitleGap: 16,
@@ -437,16 +450,16 @@ class _ProfileItemState extends State<ProfileItem> {
label: appLocalizations.update, label: appLocalizations.update,
iconData: Icons.sync, iconData: Icons.sync,
), ),
CommonPopupMenuItem(
action: ProfileActions.view,
label: appLocalizations.view,
iconData: Icons.visibility,
),
CommonPopupMenuItem( CommonPopupMenuItem(
action: ProfileActions.delete, action: ProfileActions.delete,
label: appLocalizations.delete, label: appLocalizations.delete,
iconData: Icons.delete, iconData: Icons.delete,
), ),
CommonPopupMenuItem(
action: ProfileActions.view,
label: "查看",
iconData: Icons.visibility,
),
], ],
onSelected: (ProfileActions? action) async { onSelected: (ProfileActions? action) async {
switch (action) { switch (action) {

View File

@@ -1,520 +0,0 @@
import 'package:collection/collection.dart';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../enum/enum.dart';
import '../models/models.dart';
import '../common/common.dart';
import '../widgets/widgets.dart';
class ProxiesFragment extends StatefulWidget {
const ProxiesFragment({super.key});
@override
State<ProxiesFragment> createState() => _ProxiesFragmentState();
}
class _ProxiesFragmentState extends State<ProxiesFragment>
with TickerProviderStateMixin {
TabController? _tabController;
_initActions() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>();
final items = [
CommonPopupMenuItem(
action: ProxiesSortType.none,
label: appLocalizations.defaultSort,
iconData: Icons.sort,
),
CommonPopupMenuItem(
action: ProxiesSortType.delay,
label: appLocalizations.delaySort,
iconData: Icons.network_ping),
CommonPopupMenuItem(
action: ProxiesSortType.name,
label: appLocalizations.nameSort,
iconData: Icons.sort_by_alpha),
];
commonScaffoldState?.actions = [
Selector<Config, ProxiesSortType>(
selector: (_, config) => config.proxiesSortType,
builder: (_, proxiesSortType, __) {
return CommonPopupMenu<ProxiesSortType>.radio(
items: items,
onSelected: (value) {
final config = context.read<Config>();
config.proxiesSortType = value;
},
selectedValue: proxiesSortType,
);
},
),
const SizedBox(
width: 8,
)
];
});
}
_handleTabControllerChange() {
final indexIsChanging = _tabController?.indexIsChanging ?? false;
if (indexIsChanging) return;
final index = _tabController?.index;
if (index == null) return;
final appController = globalState.appController;
final currentGroups = appController.appState.currentGroups;
if (currentGroups.length > index) {
appController.config.updateCurrentGroupName(currentGroups[index].name);
}
}
@override
void dispose() {
super.dispose();
_tabController?.dispose();
}
@override
Widget build(BuildContext context) {
return Selector<AppState, bool>(
selector: (_, appState) => appState.currentLabel == 'proxies',
builder: (_, isCurrent, child) {
if (isCurrent) {
_initActions();
}
return child!;
},
child: Selector2<AppState, Config, ProxiesSelectorState>(
selector: (_, appState, config) {
final currentGroups = appState.currentGroups;
final groupNames = currentGroups.map((e) => e.name).toList();
return ProxiesSelectorState(
groupNames: groupNames,
currentGroupName: config.currentGroupName,
);
},
shouldRebuild: (prev, next) {
if (!const ListEquality<String>()
.equals(prev.groupNames, next.groupNames)) {
_tabController?.removeListener(_handleTabControllerChange);
_tabController?.dispose();
_tabController = null;
return true;
}
return false;
},
builder: (_, state, __) {
final index = state.groupNames.indexWhere(
(item) => item == state.currentGroupName,
);
_tabController ??= TabController(
length: state.groupNames.length,
initialIndex: index == -1 ? 0 : index,
vsync: this,
)..addListener(_handleTabControllerChange);
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TabBar(
controller: _tabController,
padding: const EdgeInsets.symmetric(horizontal: 16),
dividerColor: Colors.transparent,
isScrollable: true,
tabAlignment: TabAlignment.start,
overlayColor:
const WidgetStatePropertyAll(Colors.transparent),
tabs: [
for (final groupName in state.groupNames)
Tab(
text: groupName,
),
],
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
for (final groupName in state.groupNames)
KeepContainer(
key: ObjectKey(groupName),
child: ProxiesTabView(
groupName: groupName,
),
),
],
),
)
],
);
},
),
);
}
}
class ProxiesTabView extends StatelessWidget {
final String groupName;
const ProxiesTabView({
super.key,
required this.groupName,
});
List<Proxy> _sortOfName(List<Proxy> proxies) {
return List.of(proxies)
..sort(
(a, b) => other.sortByChar(a.name, b.name),
);
}
List<Proxy> _sortOfDelay(BuildContext context, List<Proxy> proxies) {
final appState = context.read<AppState>();
return proxies = List.of(proxies)
..sort(
(a, b) {
final aDelay = appState.getDelay(a.name);
final bDelay = appState.getDelay(b.name);
if (aDelay == null && bDelay == null) {
return 0;
}
if (aDelay == null || aDelay == -1) {
return 1;
}
if (bDelay == null || bDelay == -1) {
return -1;
}
return aDelay.compareTo(bDelay);
},
);
}
_getProxies(
BuildContext context,
List<Proxy> proxies,
ProxiesSortType proxiesSortType,
) {
if (proxiesSortType == ProxiesSortType.delay) {
return _sortOfDelay(context, proxies);
}
if (proxiesSortType == ProxiesSortType.name) return _sortOfName(proxies);
return proxies;
}
double _getItemHeight(BuildContext context) {
final measure = globalState.appController.measure;
return 12 * 2 +
measure.bodyMediumHeight * 2 +
measure.bodySmallHeight +
measure.labelSmallHeight +
8 * 2;
}
int _getColumns(ViewMode viewMode) {
switch (viewMode) {
case ViewMode.mobile:
return 2;
case ViewMode.laptop:
return 3;
case ViewMode.desktop:
return 4;
}
}
_delayTest(List<Proxy> proxies) async {
for (final proxy in proxies) {
final appController = globalState.appController;
final proxyName =
appController.appState.getRealProxyName(proxy.name) ?? proxy.name;
globalState.appController.setDelay(
Delay(
name: proxyName,
value: 0,
),
);
clashCore.getDelay(proxyName).then((delay) {
globalState.appController.setDelay(delay);
});
}
await Future.delayed(httpTimeoutDuration + moreDuration);
}
@override
Widget build(BuildContext context) {
return Selector2<AppState, Config, ProxiesTabViewSelectorState>(
selector: (_, appState, config) {
return ProxiesTabViewSelectorState(
proxiesSortType: config.proxiesSortType,
sortNum: appState.sortNum,
group: appState.getGroupWithName(groupName)!,
viewMode: appState.viewMode,
);
},
builder: (_, state, __) {
final proxies = _getProxies(
context,
state.group.all,
state.proxiesSortType,
);
return DelayTestButtonContainer(
onClick: () async {
await _delayTest(
state.group.all,
);
},
child: Align(
alignment: Alignment.topCenter,
child: GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: _getColumns(state.viewMode),
mainAxisSpacing: 8,
crossAxisSpacing: 8,
mainAxisExtent: _getItemHeight(context),
),
itemCount: proxies.length,
itemBuilder: (_, index) {
final proxy = proxies[index];
return ProxyCard(
key: ValueKey('$groupName.${proxy.name}'),
proxy: proxy,
groupName: groupName,
);
},
),
),
);
},
);
}
}
class ProxyCard extends StatelessWidget {
final String groupName;
final Proxy proxy;
const ProxyCard({
super.key,
required this.groupName,
required this.proxy,
});
@override
Widget build(BuildContext context) {
final measure = globalState.appController.measure;
return Selector3<AppState, Config, ClashConfig, ProxiesCardSelectorState>(
selector: (_, appState, config, clashConfig) {
final group = appState.getGroupWithName(groupName)!;
bool isSelected = config.currentSelectedMap[group.name] == proxy.name ||
(config.currentSelectedMap[group.name] == null &&
group.now == proxy.name);
return ProxiesCardSelectorState(
isSelected: isSelected,
);
},
builder: (_, state, __) {
return CommonCard(
isSelected: state.isSelected,
onPressed: () {
final appController = globalState.appController;
final group = appController.appState.getGroupWithName(groupName)!;
if (group.type != GroupType.Selector) {
globalState.showSnackBar(
context,
message: appLocalizations.notSelectedTip,
);
return;
}
globalState.appController.config.updateCurrentSelectedMap(
groupName,
proxy.name,
);
globalState.appController.changeProxy();
},
selectWidget: Container(
alignment: Alignment.topRight,
margin: const EdgeInsets.all(8),
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.secondaryContainer,
),
child: const SelectIcon(),
),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: measure.bodyMediumHeight * 2,
child: Text(
proxy.name,
maxLines: 2,
style: context.textTheme.bodyMedium?.copyWith(
overflow: TextOverflow.ellipsis,
),
),
),
const SizedBox(
height: 8,
),
SizedBox(
height: measure.bodySmallHeight,
child: Selector<AppState, String>(
selector: (context, appState) => appState.getDesc(
proxy.type,
proxy.name,
),
builder: (_, desc, __) {
return TooltipText(
text: Text(
desc,
style: context.textTheme.bodySmall?.copyWith(
overflow: TextOverflow.ellipsis,
color:
context.textTheme.bodySmall?.color?.toLight(),
),
),
);
},
),
),
const SizedBox(
height: 8,
),
SizedBox(
height: measure.labelSmallHeight,
child: Selector<AppState, int?>(
selector: (context, appState) => appState.getDelay(
proxy.name,
),
builder: (_, delay, __) {
return FadeBox(
child: Builder(
builder: (_) {
if (delay == null) {
return Container();
}
if (delay == 0) {
return SizedBox(
height: measure.labelSmallHeight,
width: measure.labelSmallHeight,
child: const CircularProgressIndicator(
strokeWidth: 2,
),
);
}
return Text(
delay > 0 ? '$delay ms' : "Timeout",
style: context.textTheme.labelSmall?.copyWith(
overflow: TextOverflow.ellipsis,
color: other.getDelayColor(
delay,
),
),
);
},
),
);
},
),
),
],
),
),
);
},
);
}
}
class DelayTestButtonContainer extends StatefulWidget {
final Widget child;
final Future Function() onClick;
const DelayTestButtonContainer({
super.key,
required this.child,
required this.onClick,
});
@override
State<DelayTestButtonContainer> createState() =>
_DelayTestButtonContainerState();
}
class _DelayTestButtonContainerState extends State<DelayTestButtonContainer>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scale;
_healthcheck() async {
_controller.forward();
await widget.onClick();
_controller.reverse();
}
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 200,
),
);
_scale = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0,
1,
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
_controller.reverse();
return FloatLayout(
floatingWidget: FloatWrapper(
child: AnimatedBuilder(
animation: _controller,
builder: (_, child) {
return SizedBox(
width: 56,
height: 56,
child: Transform.scale(
scale: _scale.value,
child: child,
),
);
},
child: FloatingActionButton(
heroTag: null,
onPressed: _healthcheck,
child: const Icon(Icons.network_ping),
),
),
),
child: widget.child,
);
}
}

View File

@@ -0,0 +1,192 @@
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ProxyCard extends StatelessWidget {
final String groupName;
final Proxy proxy;
final bool isSelected;
final CommonCardType style;
final ProxyCardType type;
const ProxyCard({
super.key,
required this.groupName,
required this.proxy,
required this.isSelected,
this.style = CommonCardType.plain,
required this.type,
});
Measure get measure => globalState.appController.measure;
Widget _buildDelayText() {
return SizedBox(
height: measure.labelSmallHeight,
child: Selector<AppState, int?>(
selector: (context, appState) => appState.getDelay(
proxy.name,
),
builder: (context, delay, __) {
return FadeBox(
child: Builder(
builder: (_) {
if (delay == null) {
return Container();
}
if (delay == 0) {
return SizedBox(
height: measure.labelSmallHeight,
width: measure.labelSmallHeight,
child: const CircularProgressIndicator(
strokeWidth: 2,
),
);
}
return Text(
delay > 0 ? '$delay ms' : "Timeout",
style: context.textTheme.labelSmall?.copyWith(
overflow: TextOverflow.ellipsis,
color: other.getDelayColor(
delay,
),
),
);
},
),
);
},
),
);
}
Widget _buildProxyNameText(BuildContext context) {
if (type == ProxyCardType.min) {
return SizedBox(
height: measure.bodyMediumHeight * 1,
child: Text(
proxy.name,
maxLines: 1,
style: context.textTheme.bodyMedium?.copyWith(
overflow: TextOverflow.ellipsis,
),
),
);
} else {
return SizedBox(
height: measure.bodyMediumHeight * 2,
child: Text(
proxy.name,
maxLines: 2,
style: context.textTheme.bodyMedium?.copyWith(
overflow: TextOverflow.ellipsis,
),
),
);
}
}
_changeProxy(BuildContext context) {
final appController = globalState.appController;
final group = appController.appState.getGroupWithName(groupName)!;
if (group.type != GroupType.Selector) {
globalState.showSnackBar(
context,
message: appLocalizations.notSelectedTip,
);
return;
}
globalState.appController.config.updateCurrentSelectedMap(
groupName,
proxy.name,
);
clashCore.changeProxy(
ChangeProxyParams(
groupName: groupName,
proxyName: proxy.name,
),
);
}
@override
Widget build(BuildContext context) {
final measure = globalState.appController.measure;
final delayText = _buildDelayText();
final proxyNameText = _buildProxyNameText(context);
return CommonCard(
type: style,
key: key,
onPressed: () {
_changeProxy(context);
},
isSelected: isSelected,
child: Container(
padding: const EdgeInsets.all(12),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
proxyNameText,
const SizedBox(
height: 8,
),
if (type == ProxyCardType.expand) ...[
SizedBox(
height: measure.bodySmallHeight,
child: Selector<AppState, String>(
selector: (context, appState) => appState.getDesc(
proxy.type,
proxy.name,
),
builder: (_, desc, __) {
return TooltipText(
text: Text(
desc,
style: context.textTheme.bodySmall?.copyWith(
overflow: TextOverflow.ellipsis,
color: context.textTheme.bodySmall?.color?.toLight(),
),
),
);
},
),
),
const SizedBox(
height: 8,
),
delayText,
] else
SizedBox(
height: measure.bodySmallHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
flex: 1,
child: TooltipText(
text: Text(
proxy.type,
style: context.textTheme.bodySmall?.copyWith(
overflow: TextOverflow.ellipsis,
color:
context.textTheme.bodySmall?.color?.toLight(),
),
),
),
),
delayText,
],
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,404 @@
import 'dart:math';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'card.dart';
class ProxyGroupView extends StatefulWidget {
final String groupName;
final ProxiesType type;
const ProxyGroupView({
super.key,
required this.groupName,
required this.type,
});
@override
State<ProxyGroupView> createState() => _ProxyGroupViewState();
}
class _ProxyGroupViewState extends State<ProxyGroupView> {
var isLock = false;
final scrollController = ScrollController();
var isEnd = false;
String get groupName => widget.groupName;
ProxiesType get type => widget.type;
double _getItemHeight(ProxyCardType proxyCardType) {
final measure = globalState.appController.measure;
final baseHeight =
12 * 2 + measure.bodyMediumHeight * 2 + measure.bodySmallHeight + 8;
return switch(proxyCardType){
ProxyCardType.expand => baseHeight + measure.labelSmallHeight + 8,
ProxyCardType.shrink => baseHeight,
ProxyCardType.min => baseHeight - measure.bodyMediumHeight,
};
}
_delayTest(List<Proxy> proxies) async {
if (isLock) return;
isLock = true;
final appController = globalState.appController;
for (final proxy in proxies) {
final proxyName =
appController.appState.getRealProxyName(proxy.name) ?? proxy.name;
globalState.appController.setDelay(
Delay(
name: proxyName,
value: 0,
),
);
clashCore.getDelay(proxyName).then((delay) {
globalState.appController.setDelay(delay);
});
}
await Future.delayed(httpTimeoutDuration + moreDuration);
appController.appState.sortNum++;
isLock = false;
}
Widget _currentProxyNameBuilder({
required Widget Function(String) builder,
}) {
return Selector2<AppState, Config, String>(
selector: (_, appState, config) {
final group = appState.getGroupWithName(groupName)!;
return config.currentSelectedMap[groupName] ?? group.now ?? '';
},
builder: (_, value, ___) {
return builder(value);
},
);
}
Widget _buildTabGroupView({
required List<Proxy> proxies,
required int columns,
required ProxyCardType proxyCardType,
}) {
final sortedProxies = globalState.appController.getSortProxies(
proxies,
);
return DelayTestButtonContainer(
onClick: () async {
await _delayTest(
proxies,
);
},
child: Align(
alignment: Alignment.topCenter,
child: GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
mainAxisExtent: _getItemHeight(proxyCardType),
),
itemCount: sortedProxies.length,
itemBuilder: (_, index) {
final proxy = sortedProxies[index];
return _currentProxyNameBuilder(builder: (value) {
return ProxyCard(
type: proxyCardType,
key: ValueKey('$groupName.${proxy.name}'),
isSelected: value == proxy.name,
proxy: proxy,
groupName: groupName,
);
});
},
),
),
);
}
Widget _buildExpansionGroupView({
required List<Proxy> proxies,
required int columns,
required ProxyCardType proxyCardType,
}) {
final sortedProxies = globalState.appController.getSortProxies(
proxies,
);
final group =
globalState.appController.appState.getGroupWithName(groupName)!;
final itemHeight = _getItemHeight(proxyCardType);
final innerHeight = context.appSize.height - 200;
final lines = (sortedProxies.length / columns).ceil();
final minLines =
innerHeight >= 200 ? (innerHeight / itemHeight).floor() : 3;
final height = (itemHeight + 8) * min(lines, minLines) - 8;
return Selector<Config, Set<String>>(
selector: (_, config) => config.currentUnfoldSet,
builder: (_, currentUnfoldSet, __) {
return CommonCard(
child: ExpansionTile(
childrenPadding: const EdgeInsets.all(8),
initiallyExpanded: currentUnfoldSet.contains(groupName),
iconColor: context.colorScheme.onSurfaceVariant,
onExpansionChanged: (value) {
final tempUnfoldSet = Set<String>.from(currentUnfoldSet);
if (value) {
tempUnfoldSet.add(groupName);
} else {
tempUnfoldSet.remove(groupName);
}
globalState.appController.config.updateCurrentUnfoldSet(
tempUnfoldSet,
);
},
controlAffinity: ListTileControlAffinity.trailing,
title: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
flex: 1,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(groupName),
const SizedBox(
height: 4,
),
Flexible(
flex: 1,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
group.type.name,
style: context.textTheme.labelMedium?.toLight,
),
Flexible(
flex: 1,
child: _currentProxyNameBuilder(
builder: (value) {
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
if (value.isNotEmpty) ...[
Icon(
Icons.arrow_right,
color: context
.colorScheme.onSurfaceVariant,
),
Flexible(
flex: 1,
child: Text(
overflow: TextOverflow.ellipsis,
value,
style: context
.textTheme.labelMedium?.toLight,
),
),
]
],
);
},
),
),
],
),
),
const SizedBox(
height: 4,
),
],
),
),
IconButton(
icon: Icon(
Icons.network_ping,
size: 20,
color: context.colorScheme.onSurfaceVariant,
),
onPressed: () {
_delayTest(sortedProxies);
},
),
],
),
shape: const RoundedRectangleBorder(
side: BorderSide.none,
),
collapsedShape: const RoundedRectangleBorder(
side: BorderSide.none,
),
children: [
SizedBox(
height: height,
child: GridView.builder(
key: widget.key,
controller: scrollController,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
mainAxisExtent: _getItemHeight(proxyCardType),
),
itemCount: sortedProxies.length,
itemBuilder: (_, index) {
final proxy = sortedProxies[index];
return _currentProxyNameBuilder(
builder: (value) {
return ProxyCard(
style: CommonCardType.filled,
type: proxyCardType,
isSelected: value == proxy.name,
key: ValueKey('$groupName.${proxy.name}'),
proxy: proxy,
groupName: groupName,
);
},
);
},
),
),
],
),
);
},
);
}
@override
void dispose() {
super.dispose();
scrollController.dispose();
}
@override
Widget build(BuildContext context) {
return Selector2<AppState, Config, ProxyGroupSelectorState>(
selector: (_, appState, config) {
final group = appState.getGroupWithName(groupName)!;
return ProxyGroupSelectorState(
proxyCardType: config.proxyCardType,
proxiesSortType: config.proxiesSortType,
columns: globalState.appController.columns,
sortNum: appState.sortNum,
proxies: group.all,
);
},
builder: (_, state, __) {
final proxies = state.proxies;
final columns = state.columns;
final proxyCardType = state.proxyCardType;
return switch (type) {
ProxiesType.tab => _buildTabGroupView(
proxies: proxies,
columns: columns,
proxyCardType: proxyCardType,
),
ProxiesType.list => _buildExpansionGroupView(
proxies: proxies,
columns: columns,
proxyCardType: proxyCardType,
),
};
},
);
}
}
class DelayTestButtonContainer extends StatefulWidget {
final Widget child;
final Future Function() onClick;
const DelayTestButtonContainer({
super.key,
required this.child,
required this.onClick,
});
@override
State<DelayTestButtonContainer> createState() =>
_DelayTestButtonContainerState();
}
class _DelayTestButtonContainerState extends State<DelayTestButtonContainer>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scale;
_healthcheck() async {
_controller.forward();
await widget.onClick();
_controller.reverse();
}
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 200,
),
);
_scale = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0,
1,
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
_controller.reverse();
return FloatLayout(
floatingWidget: FloatWrapper(
child: AnimatedBuilder(
animation: _controller.view,
builder: (_, child) {
return SizedBox(
width: 56,
height: 56,
child: Transform.scale(
scale: _scale.value,
child: child,
),
);
},
child: FloatingActionButton(
heroTag: null,
onPressed: _healthcheck,
child: const Icon(Icons.network_ping),
),
),
),
child: widget.child,
);
}
}

View File

@@ -0,0 +1,50 @@
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'group.dart';
class ProxiesListFragment extends StatefulWidget {
const ProxiesListFragment({super.key});
@override
State<ProxiesListFragment> createState() =>
_ProxiesListFragmentState();
}
class _ProxiesListFragmentState
extends State<ProxiesListFragment> {
@override
Widget build(BuildContext context) {
return Selector2<AppState, Config, ProxiesSelectorState>(
selector: (_, appState, config) {
final currentGroups = appState.currentGroups;
final groupNames = currentGroups.map((e) => e.name).toList();
return ProxiesSelectorState(
groupNames: groupNames,
currentGroupName: config.currentGroupName,
);
},
builder: (_, state, __) {
return ListView.separated(
padding: const EdgeInsets.all(16),
itemCount: state.groupNames.length,
itemBuilder: (_, index) {
final groupName = state.groupNames[index];
return ProxyGroupView(
key: PageStorageKey(groupName),
groupName: groupName,
type: ProxiesType.list,
);
},
separatorBuilder: (BuildContext context, int index) {
return const SizedBox(
height: 16,
);
},
);
},
);
}
}

View File

@@ -0,0 +1,65 @@
import 'package:fl_clash/common/app_localizations.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/fragments/proxies/list.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'setting.dart';
import 'tab.dart';
class ProxiesFragment extends StatefulWidget {
const ProxiesFragment({super.key});
@override
State<ProxiesFragment> createState() => _ProxiesFragmentState();
}
class _ProxiesFragmentState extends State<ProxiesFragment> {
_initActions() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [
IconButton(
onPressed: () {
showSheet(
title: appLocalizations.proxiesSetting,
context: context,
builder: (context) {
return const ProxiesSettingWidget();
},
);
},
icon: const Icon(
Icons.tune,
),
)
];
});
}
@override
Widget build(BuildContext context) {
return Selector<AppState, bool>(
selector: (_, appState) => appState.currentLabel == 'proxies',
builder: (_, isCurrent, child) {
if (isCurrent) {
_initActions();
}
return child!;
},
child: Selector<Config, ProxiesType>(
selector: (_, config) => config.proxiesType,
builder: (_, proxiesType, __) {
return switch (proxiesType) {
ProxiesType.tab => const ProxiesTabFragment(),
ProxiesType.list => const ProxiesListFragment(),
};
},
),
);
}
}

View File

@@ -0,0 +1,262 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
class ProxiesSettingWidget extends StatelessWidget {
const ProxiesSettingWidget({super.key});
IconData _getIconWithProxiesType(ProxiesType type) {
return switch (type) {
ProxiesType.tab => Icons.view_carousel,
ProxiesType.list => Icons.view_list,
};
}
IconData _getIconWithProxiesSortType(ProxiesSortType type) {
return switch (type) {
ProxiesSortType.none => Icons.sort,
ProxiesSortType.delay => Icons.network_ping,
ProxiesSortType.name => Icons.sort_by_alpha,
};
}
String _getStringProxiesSortType(ProxiesSortType type) {
return switch (type) {
ProxiesSortType.none => appLocalizations.defaultText,
ProxiesSortType.delay => appLocalizations.delay,
ProxiesSortType.name => appLocalizations.name,
};
}
List<Widget> _buildStyleSetting() {
return generateSection(
title: appLocalizations.style,
items: [
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Selector<Config, ProxiesType>(
selector: (_, config) => config.proxiesType,
builder: (_, proxiesType, __) {
final config = globalState.appController.config;
return Wrap(
spacing: 16,
children: [
for (final item in ProxiesType.values)
SettingInfoCard(
Info(
label: Intl.message(item.name),
iconData: _getIconWithProxiesType(item),
),
isSelected: proxiesType == item,
onPressed: () {
config.proxiesType = item;
},
)
],
);
},
),
)
],
);
}
List<Widget> _buildSortSetting() {
return generateSection(
title: appLocalizations.sort,
items: [
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Selector<Config, ProxiesSortType>(
selector: (_, config) => config.proxiesSortType,
builder: (_, proxiesSortType, __) {
final config = globalState.appController.config;
return Wrap(
spacing: 16,
children: [
for (final item in ProxiesSortType.values)
SettingInfoCard(
Info(
label: _getStringProxiesSortType(item),
iconData: _getIconWithProxiesSortType(item),
),
isSelected: proxiesSortType == item,
onPressed: () {
config.proxiesSortType = item;
},
),
],
);
},
),
),
],
);
}
List<Widget> _buildSizeSetting() {
return generateSection(
title: appLocalizations.size,
items: [
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Selector<Config, ProxyCardType>(
selector: (_, config) => config.proxyCardType,
builder: (_, proxyCardType, __) {
final config = globalState.appController.config;
return Wrap(
spacing: 16,
children: [
for (final item in ProxyCardType.values)
SettingTextCard(
Intl.message(item.name),
isSelected: item == proxyCardType,
onPressed: () {
config.proxyCardType = item;
},
)
],
);
},
),
)
],
);
}
List<Widget> _buildColumnsSetting() {
return generateSection(
title: appLocalizations.columns,
items: [
SingleChildScrollView(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
scrollDirection: Axis.horizontal,
child: Selector2<AppState, Config, ColumnsSelectorState>(
selector: (_, appState, config) => ColumnsSelectorState(
columns: config.proxiesColumns,
viewMode: appState.viewMode,
),
builder: (_, state, __) {
final config = globalState.appController.config;
final targetColumnsArray = viewModeColumnsMap[state.viewMode]!;
final currentColumns = other.getColumns(
state.viewMode,
state.columns,
);
return Wrap(
spacing: 16,
children: [
for (final item in targetColumnsArray)
SettingTextCard(
other.getColumnsTextForInt(item),
isSelected: item == currentColumns,
onPressed: () {
config.proxiesColumns = item;
},
)
],
);
},
),
),
],
);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 32),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
..._buildStyleSetting(),
..._buildSortSetting(),
..._buildColumnsSetting(),
..._buildSizeSetting(),
],
),
);
}
}
class SettingInfoCard extends StatelessWidget {
final Info info;
final bool? isSelected;
final VoidCallback onPressed;
const SettingInfoCard(
this.info, {
super.key,
this.isSelected,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return CommonCard(
isSelected: isSelected,
onPressed: onPressed,
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Flexible(
child: Icon(info.iconData),
),
const SizedBox(
width: 8,
),
Flexible(
child: Text(
info.label,
style: context.textTheme.bodyMedium,
),
),
],
),
),
);
}
}
class SettingTextCard extends StatelessWidget {
final String text;
final bool? isSelected;
final VoidCallback onPressed;
const SettingTextCard(
this.text, {
super.key,
this.isSelected,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return CommonCard(
onPressed: onPressed,
isSelected: isSelected,
child: Padding(
padding: const EdgeInsets.all(12),
child: Text(
text,
style: context.textTheme.bodyMedium,
),
),
);
}
}

View File

@@ -0,0 +1,219 @@
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/fragments/proxies/setting.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'group.dart';
class ProxiesTabFragment extends StatefulWidget {
const ProxiesTabFragment({super.key});
@override
State<ProxiesTabFragment> createState() => _ProxiesTabFragmentState();
}
class _ProxiesTabFragmentState extends State<ProxiesTabFragment>
with TickerProviderStateMixin {
TabController? _tabController;
final hasMoreButtonNotifier = ValueNotifier<bool>(false);
@override
void dispose() {
super.dispose();
_tabController?.dispose();
}
_buildMoreButton() {
return Selector<AppState, bool>(
selector: (_, appState) => appState.viewMode == ViewMode.mobile,
builder: (_, value, ___) {
return IconButton(
onPressed: _showMoreMenu,
icon: value
? const Icon(
Icons.expand_more,
)
: const Icon(
Icons.chevron_right,
),
);
},
);
}
_showMoreMenu() {
showSheet(
context: context,
width: 380,
isScrollControlled: false,
builder: (context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Selector2<AppState, Config, ProxiesSelectorState>(
selector: (_, appState, config) {
final currentGroups = appState.currentGroups;
final groupNames = currentGroups.map((e) => e.name).toList();
return ProxiesSelectorState(
groupNames: groupNames,
currentGroupName: config.currentGroupName,
);
},
builder: (_, state, __) {
return SizedBox(
width: double.infinity,
child: Wrap(
alignment: WrapAlignment.center,
runSpacing: 8,
spacing: 8,
children: [
for (final groupName in state.groupNames)
SettingTextCard(
groupName,
onPressed: () {
final index = state.groupNames
.indexWhere((item) => item == groupName);
if (index == -1) return;
_tabController?.animateTo(index);
globalState.appController.config
.updateCurrentGroupName(
groupName,
);
},
isSelected: groupName == state.currentGroupName,
)
],
),
);
},
),
);
},
title: appLocalizations.proxyGroup,
);
}
@override
Widget build(BuildContext context) {
return Selector2<AppState, Config, ProxiesSelectorState>(
selector: (_, appState, config) {
final currentGroups = appState.currentGroups;
final groupNames = currentGroups.map((e) => e.name).toList();
return ProxiesSelectorState(
groupNames: groupNames,
currentGroupName: config.currentGroupName,
);
},
shouldRebuild: (prev, next) {
if (!const ListEquality<String>()
.equals(prev.groupNames, next.groupNames)) {
_tabController?.dispose();
_tabController = null;
return true;
}
return false;
},
builder: (_, state, __) {
final index = state.groupNames.indexWhere(
(item) => item == state.currentGroupName,
);
_tabController ??= TabController(
length: state.groupNames.length,
initialIndex: index == -1 ? 0 : index,
vsync: this,
);
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
NotificationListener<ScrollMetricsNotification>(
onNotification: (scrollNotification) {
hasMoreButtonNotifier.value =
scrollNotification.metrics.maxScrollExtent > 0;
return true;
},
child: ValueListenableBuilder(
valueListenable: hasMoreButtonNotifier,
builder: (_, value, child) {
return Stack(
alignment: AlignmentDirectional.centerStart,
children: [
TabBar(
controller: _tabController,
padding: EdgeInsets.only(
left: 16,
right: 16 + (value ? 16 : 0),
),
onTap: (index) {
final appController = globalState.appController;
final currentGroups =
appController.appState.currentGroups;
if (currentGroups.length > index) {
appController.config.updateCurrentGroupName(
currentGroups[index].name,
);
}
},
dividerColor: Colors.transparent,
isScrollable: true,
tabAlignment: TabAlignment.start,
overlayColor:
const WidgetStatePropertyAll(Colors.transparent),
tabs: [
for (final groupName in state.groupNames)
Tab(
text: groupName,
),
],
),
if (value)
Positioned(
right: 0,
child: child!,
),
],
);
},
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
context.colorScheme.surface.withOpacity(0.1),
context.colorScheme.surface,
],
stops: const [
0.0,
0.1
]),
),
child: _buildMoreButton(),
),
),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
for (final groupName in state.groupNames)
KeepContainer(
key: ObjectKey(groupName),
child: ProxyGroupView(
groupName: groupName,
type: ProxiesType.tab,
),
),
],
),
)
],
);
},
);
}
}

View File

@@ -1,11 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart'; import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -68,9 +66,6 @@ class _RequestsFragmentState extends State<RequestsFragment> {
}, },
icon: const Icon(Icons.search), icon: const Icon(Icons.search),
), ),
const SizedBox(
width: 8,
)
]; ];
}, },
); );
@@ -137,8 +132,8 @@ class _RequestsFragmentState extends State<RequestsFragment> {
vertical: 16, vertical: 16,
), ),
child: Wrap( child: Wrap(
runSpacing: 8, runSpacing: 6,
spacing: 8, spacing: 6,
children: [ children: [
for (final keyword in state.keywords) for (final keyword in state.keywords)
CommonChip( CommonChip(
@@ -156,7 +151,7 @@ class _RequestsFragmentState extends State<RequestsFragment> {
controller: _scrollController, controller: _scrollController,
itemBuilder: (_, index) { itemBuilder: (_, index) {
final connection = connections[index]; final connection = connections[index];
return RequestItem( return ConnectionItem(
key: Key(connection.id), key: Key(connection.id),
connection: connection, connection: connection,
onClick: _addKeyword, onClick: _addKeyword,
@@ -178,103 +173,6 @@ class _RequestsFragmentState extends State<RequestsFragment> {
} }
} }
class RequestItem extends StatelessWidget {
final Connection connection;
final Function(String)? onClick;
const RequestItem({
super.key,
required this.connection,
this.onClick,
});
Future<ImageProvider?> _getPackageIcon(Connection connection) async {
return await app?.getPackageIcon(connection.metadata.process);
}
String _getRequestText(Metadata metadata) {
var text = "${metadata.network}:://";
final ips = [
metadata.host,
metadata.destinationIP,
].where((ip) => ip.isNotEmpty);
text += ips.join("/");
text += ":${metadata.destinationPort}";
return text;
}
String _getSourceText(Connection connection) {
final metadata = connection.metadata;
if (metadata.process.isEmpty) {
return connection.start.lastUpdateTimeDesc;
}
return "${metadata.process} · ${connection.start.lastUpdateTimeDesc}";
}
@override
Widget build(BuildContext context) {
return ListItem(
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
leading: Platform.isAndroid
? Container(
margin: const EdgeInsets.only(top: 4),
width: 48,
height: 48,
child: FutureBuilder<ImageProvider?>(
future: _getPackageIcon(connection),
builder: (_, snapshot) {
if (!snapshot.hasData && snapshot.data == null) {
return Container();
} else {
return Image(
image: snapshot.data!,
gaplessPlayback: true,
width: 48,
height: 48,
);
}
},
),
)
: null,
title: Text(
_getRequestText(connection.metadata),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
Text(
_getSourceText(connection),
),
const SizedBox(
height: 12,
),
Wrap(
runSpacing: 8,
spacing: 8,
children: [
for (final chain in connection.chains)
CommonChip(
label: chain,
onPressed: () {
if (onClick == null) return;
onClick!(chain);
},
),
],
),
const SizedBox(
height: 12,
),
],
),
);
}
}
class RequestsSearchDelegate extends SearchDelegate { class RequestsSearchDelegate extends SearchDelegate {
ValueNotifier<ConnectionsAndKeywords> requestsNotifier; ValueNotifier<ConnectionsAndKeywords> requestsNotifier;
@@ -375,8 +273,8 @@ class RequestsSearchDelegate extends SearchDelegate {
vertical: 16, vertical: 16,
), ),
child: Wrap( child: Wrap(
runSpacing: 8, runSpacing: 6,
spacing: 8, spacing: 6,
children: [ children: [
for (final keyword in state.keywords) for (final keyword in state.keywords)
CommonChip( CommonChip(
@@ -393,7 +291,7 @@ class RequestsSearchDelegate extends SearchDelegate {
child: ListView.separated( child: ListView.separated(
itemBuilder: (_, index) { itemBuilder: (_, index) {
final connection = _results[index]; final connection = _results[index];
return RequestItem( return ConnectionItem(
key: Key(connection.id), key: Key(connection.id),
connection: connection, connection: connection,
onClick: (value) { onClick: (value) {

View File

@@ -2,22 +2,37 @@ import 'dart:io';
import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/ffi.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart'; import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:path/path.dart' hide context; import 'package:path/path.dart' hide context;
import 'package:provider/provider.dart';
@immutable @immutable
class GeoItem { class GeoItem {
final String label; final String label;
final String key;
final String fileName; final String fileName;
const GeoItem({ const GeoItem({
required this.label, required this.label,
required this.key,
required this.fileName, required this.fileName,
}); });
} }
@immutable
class FileInfo {
final String size;
final DateTime lastModified;
const FileInfo({
required this.size,
required this.lastModified,
});
}
class Resources extends StatefulWidget { class Resources extends StatefulWidget {
const Resources({super.key}); const Resources({super.key});
@@ -26,147 +41,473 @@ class Resources extends StatefulWidget {
} }
class _ResourcesState extends State<Resources> { class _ResourcesState extends State<Resources> {
_updateExternalProvider( List<ExternalProvider> externalProviders = [];
String providerName,
String providerType, List<GlobalObjectKey<_ProviderItemState>> providerItemKeys = [];
) async {
final commonScaffoldState = context.commonScaffoldState; @override
await commonScaffoldState?.loadingRun(() async { void initState() {
final message = await clashCore.updateExternalProvider( super.initState();
providerName: providerName, WidgetsBinding.instance.addPostFrameCallback((_) {
providerType: providerType, _syncExternalProviders();
);
if (message.isNotEmpty) throw message;
}); });
setState(() {});
} }
Future<DateTime> _getGeoFileLastModified(String fileName) async { _syncExternalProviders() async {
final homePath = await appPath.getHomeDirPath(); externalProviders = await clashCore.getExternalProviders();
return await File(join(homePath, fileName)).lastModified(); if (mounted) {
setState(() {});
}
} }
Widget _buildExternalProviderSection() { _updateProviders() async {
return FutureBuilder<List<ExternalProvider>>( final updateProviders = providerItemKeys.map<Future>(
future: () async { (key) async => await key.currentState?.updateProvider(false),
await Future.delayed(const Duration(milliseconds: 200));
return await clashCore.getExternalProviders();
}(),
builder: (_, snapshot) {
return Center(
child: FadeBox(
key: const Key("external_providers"),
child: snapshot.data == null || snapshot.data!.isEmpty
? Container()
: Section(
title: appLocalizations.externalResources,
child: Column(
children: [
for (final externalProvider in snapshot.data!)
ListItem(
title: Text(externalProvider.name),
subtitle: Text(
"${externalProvider.type} (${externalProvider.vehicleType})",
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
externalProvider.updateAt.lastUpdateTimeDesc,
style: context.textTheme.bodyMedium,
),
const Padding(
padding: EdgeInsets.only(left: 12,right: 4),
child: VerticalDivider(
endIndent: 6,
width: 4,
indent: 6,
),
),
externalProvider.vehicleType == "HTTP"
? IconButton(
icon: const Icon(Icons.sync),
onPressed: () {
_updateExternalProvider(
externalProvider.name,
externalProvider.type,
);
},
)
: Container(),
],
),
)
],
),
),
),
);
},
); );
await Future.wait(updateProviders);
_syncExternalProviders();
} }
Widget _buildGeoDataSection() { List<Widget> _buildExternalProviderSection() {
List<GlobalObjectKey<_ProviderItemState>> keys = [];
final res = generateInfoSection(
info: Info(
iconData: Icons.source,
label: appLocalizations.externalResources,
),
actions: [
IconButton.filledTonal(
onPressed: () {
_updateProviders();
},
padding: const EdgeInsets.all(4),
iconSize: 20,
icon: const Icon(
Icons.sync,
),
)
],
items: externalProviders.map(
(externalProvider) {
final key =
GlobalObjectKey<_ProviderItemState>(externalProvider.name);
keys.add(key);
return ProviderItem(
key: key,
provider: externalProvider,
onUpdated: () {
_syncExternalProviders();
},
);
},
),
);
providerItemKeys = keys;
return res;
}
List<Widget> _buildGeoDataSection() {
const geoItems = <GeoItem>[ const geoItems = <GeoItem>[
GeoItem(label: "GeoIp", fileName: mmdbFileName), GeoItem(
GeoItem(label: "GeoSite", fileName: geoSiteFileName), label: "GeoIp",
GeoItem(label: "ASN", fileName: asnFileName), fileName: geoIpFileName,
key: "geoip",
),
GeoItem(label: "GeoSite", fileName: geoSiteFileName, key: "geosite"),
GeoItem(
label: "MMDB",
fileName: mmdbFileName,
key: "mmdb",
),
GeoItem(label: "ASN", fileName: asnFileName, key: "asn"),
]; ];
return Section(
title: appLocalizations.geoData, return generateInfoSection(
child: Column( info: Info(
children: [ iconData: Icons.storage,
for (final geoItem in geoItems) label: appLocalizations.geoData,
ListItem( ),
title: Text(geoItem.label), items: geoItems.map(
subtitle: FutureBuilder<DateTime>( (geoItem) => GeoDataListItem(
future: () async { geoItem: geoItem,
await Future.delayed(const Duration(milliseconds: 200)); ),
return await _getGeoFileLastModified(geoItem.fileName);
}(),
builder: (_, snapshot) {
return Container(
alignment: Alignment.centerLeft,
height: 24,
child: FadeBox(
key: Key("fade_box_${geoItem.label}"),
child: snapshot.data == null
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: Text(
snapshot.data!.lastUpdateTimeDesc,
),
),
);
},
),
trailing: IconButton(
icon: const Icon(Icons.sync),
onPressed: () {
_updateExternalProvider(
geoItem.fileName,
geoItem.label,
);
},
),
),
],
), ),
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView( return generateListView(
children: [ [
_buildGeoDataSection(), ..._buildGeoDataSection(),
_buildExternalProviderSection(), ..._buildExternalProviderSection(),
], ],
); );
} }
} }
class GeoDataListItem extends StatefulWidget {
final GeoItem geoItem;
const GeoDataListItem({
super.key,
required this.geoItem,
});
@override
State<GeoDataListItem> createState() => _GeoDataListItemState();
}
class _GeoDataListItemState extends State<GeoDataListItem> {
final isUpdating = ValueNotifier<bool>(false);
GeoItem get geoItem => widget.geoItem;
_updateUrl(String url) async {
final newUrl = await globalState.showCommonDialog<String>(
child: UpdateGeoUrlFormDialog(
title: geoItem.label,
url: url,
),
);
if (newUrl != null && newUrl != url && mounted) {
try {
if (!newUrl.isUrl) {
throw "Invalid url";
}
final appController = globalState.appController;
appController.clashConfig.geoXUrl =
Map.from(appController.clashConfig.geoXUrl)..[geoItem.key] = newUrl;
appController.updateClashConfigDebounce();
} catch (e) {
globalState.showMessage(
title: geoItem.label,
message: TextSpan(
text: e.toString(),
),
);
}
}
}
Future<FileInfo> _getGeoFileLastModified(String fileName) async {
final homePath = await appPath.getHomeDirPath();
final file = File(join(homePath, fileName));
final lastModified = await file.lastModified();
final size = await file.length();
return FileInfo(
size: TrafficValue(value: size).show,
lastModified: lastModified,
);
}
// _uploadGeoFile(String fileName) async {
// final res = await picker.pickerGeoDataFile();
// if (res == null || res.bytes == null) return;
// final homePath = await appPath.getHomeDirPath();
// final file = File(join(homePath, fileName));
// await file.writeAsBytes(
// res.bytes!,
// flush: true,
// );
// setState(() {});
// }
String _buildFileInfoDesc(FileInfo fileInfo) {
return "${fileInfo.size} · ${fileInfo.lastModified.lastUpdateTimeDesc}";
}
Widget _buildSubtitle(String url) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 4,
),
FutureBuilder<FileInfo>(
future: _getGeoFileLastModified(geoItem.fileName),
builder: (_, snapshot) {
return SizedBox(
height: 24,
child: FadeBox(
key: Key("fade_box_${geoItem.label}"),
child: snapshot.data == null
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: Text(
_buildFileInfoDesc(snapshot.data!),
),
),
);
},
),
Text(
url,
style: context.textTheme.bodyMedium?.toLight,
),
const SizedBox(
height: 8,
),
const SizedBox(
height: 8,
),
Wrap(
runSpacing: 6,
spacing: 12,
children: [
CommonChip(
avatar: const Icon(Icons.edit),
label: appLocalizations.edit,
onPressed: () {
_updateUrl(url);
},
),
CommonChip(
avatar: const Icon(Icons.sync),
label: appLocalizations.sync,
onPressed: () {
_handleUpdateGeoDataItem();
},
),
],
),
],
);
}
_handleUpdateGeoDataItem() async {
await globalState.safeRun<void>(updateGeoDateItem);
setState(() {});
}
updateGeoDateItem() async {
isUpdating.value = true;
try {
final message = await clashCore.updateExternalProvider(
providerName: geoItem.fileName,
providerType: geoItem.label,
);
if (message.isNotEmpty) throw message;
} catch (e) {
isUpdating.value = false;
rethrow;
}
isUpdating.value = false;
return null;
}
@override
void dispose() {
super.dispose();
isUpdating.dispose();
}
@override
Widget build(BuildContext context) {
return ListItem(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
title: Text(geoItem.label),
subtitle: Selector<ClashConfig, String>(
selector: (_, clashConfig) => clashConfig.geoXUrl[geoItem.key]!,
builder: (_, value, __) {
return _buildSubtitle(value);
},
),
trailing: SizedBox(
height: 48,
width: 48,
child: ValueListenableBuilder(
valueListenable: isUpdating,
builder: (_, isUpdating, ___) {
return FadeBox(
child: isUpdating
? const Padding(
padding: EdgeInsets.all(8),
child: CircularProgressIndicator(),
)
: const SizedBox(),
);
},
),
),
);
}
}
class ProviderItem extends StatefulWidget {
final ExternalProvider provider;
final Function onUpdated;
const ProviderItem({
super.key,
required this.provider,
required this.onUpdated,
});
@override
State<ProviderItem> createState() => _ProviderItemState();
}
class _ProviderItemState extends State<ProviderItem> {
final isUpdating = ValueNotifier<bool>(false);
ExternalProvider get provider => widget.provider;
_handleUpdateProfile() async {
await globalState.safeRun<void>(updateProvider);
widget.onUpdated();
}
updateProvider([isSingle = true]) async {
if (provider.vehicleType != "HTTP") return;
isUpdating.value = true;
try {
final message = await clashCore.updateExternalProvider(
providerName: provider.name,
providerType: provider.type,
);
if (message.isNotEmpty) throw message;
} catch (e) {
isUpdating.value = false;
if (!isSingle) {
return e.toString();
} else {
rethrow;
}
}
isUpdating.value = false;
return null;
}
String _buildProviderDesc() {
return "${provider.type} (${provider.vehicleType}) · ${provider.updateAt.lastUpdateTimeDesc}";
}
@override
void dispose() {
super.dispose();
isUpdating.dispose();
}
Widget _buildSubtitle() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 4,
),
Text(
_buildProviderDesc(),
),
if (provider.vehicleType == "HTTP") ...[
const SizedBox(
height: 8,
),
CommonChip(
avatar: const Icon(Icons.sync),
label: appLocalizations.sync,
onPressed: () {
_handleUpdateProfile();
},
),
],
],
);
}
@override
Widget build(BuildContext context) {
return ListItem(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
title: Text(provider.name),
subtitle: _buildSubtitle(),
trailing: SizedBox(
height: 48,
width: 48,
child: ValueListenableBuilder(
valueListenable: isUpdating,
builder: (_, isUpdating, ___) {
return FadeBox(
child: isUpdating
? const Padding(
padding: EdgeInsets.all(8),
child: CircularProgressIndicator(),
)
: const SizedBox(),
);
},
),
),
);
}
}
class UpdateGeoUrlFormDialog extends StatefulWidget {
final String title;
final String url;
const UpdateGeoUrlFormDialog({
super.key,
required this.title,
required this.url,
});
@override
State<UpdateGeoUrlFormDialog> createState() => _UpdateGeoUrlFormDialogState();
}
class _UpdateGeoUrlFormDialogState extends State<UpdateGeoUrlFormDialog> {
late TextEditingController urlController;
@override
void initState() {
super.initState();
urlController = TextEditingController(text: widget.url);
}
_handleUpdate() async {
final url = urlController.value.text;
if (url.isEmpty) return;
Navigator.of(context).pop<String>(url);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(widget.title),
content: SizedBox(
width: 300,
child: Wrap(
runSpacing: 16,
children: [
TextField(
maxLines: 5,
minLines: 1,
controller: urlController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
),
],
),
),
actions: [
TextButton(
onPressed: _handleUpdate,
child: Text(appLocalizations.submit),
)
],
);
}
}

View File

@@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
@@ -21,8 +23,95 @@ class ThemeModeItem {
class ThemeFragment extends StatelessWidget { class ThemeFragment extends StatelessWidget {
const ThemeFragment({super.key}); const ThemeFragment({super.key});
Widget _themeModeCheckBox({ Widget _itemCard({
required BuildContext context, required BuildContext context,
required Info info,
required Widget child,
}) {
return Padding(
padding: const EdgeInsets.only(
top: 16,
),
child: Wrap(
runSpacing: 16,
children: [
InfoHeader(
info: info,
),
child,
],
),
);
}
@override
Widget build(BuildContext context) {
final previewCard = Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: CommonCard(
onPressed: (){
},
info: Info(
label: appLocalizations.preview,
iconData: Icons.looks,
),
child: Container(
height: 200,
),
),
);
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
previewCard,
const ThemeColorsBox(),
],
),
);
}
}
class ItemCard extends StatelessWidget {
final Widget child;
final Info info;
const ItemCard({
super.key,
required this.info,
required this.child,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(
top: 16,
),
child: Wrap(
runSpacing: 16,
children: [
InfoHeader(
info: info,
),
child,
],
),
);
}
}
class ThemeColorsBox extends StatefulWidget {
const ThemeColorsBox({super.key});
@override
State<ThemeColorsBox> createState() => _ThemeColorsBoxState();
}
class _ThemeColorsBoxState extends State<ThemeColorsBox> {
Widget _themeModeCheckBox({
bool? isSelected, bool? isSelected,
required ThemeModeItem themeModeItem, required ThemeModeItem themeModeItem,
}) { }) {
@@ -32,7 +121,7 @@ class ThemeFragment extends StatelessWidget {
globalState.appController.config.themeMode = themeModeItem.themeMode; globalState.appController.config.themeMode = themeModeItem.themeMode;
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal:16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@@ -55,7 +144,6 @@ class ThemeFragment extends StatelessWidget {
} }
Widget _primaryColorCheckBox({ Widget _primaryColorCheckBox({
required BuildContext context,
bool? isSelected, bool? isSelected,
Color? color, Color? color,
}) { }) {
@@ -68,28 +156,8 @@ class ThemeFragment extends StatelessWidget {
); );
} }
Widget _itemCard({ @override
required BuildContext context, Widget build(BuildContext context) {
required Info info,
required Widget child,
}) {
return Padding(
padding: const EdgeInsets.only(
top: 16,
),
child: Wrap(
runSpacing: 16,
children: [
InfoHeader(
info: info,
),
child,
],
),
);
}
Widget _getThemeCard(BuildContext context) {
List<ThemeModeItem> themeModeItems = [ List<ThemeModeItem> themeModeItems = [
ThemeModeItem( ThemeModeItem(
iconData: Icons.auto_mode, iconData: Icons.auto_mode,
@@ -118,8 +186,7 @@ class ThemeFragment extends StatelessWidget {
]; ];
return Column( return Column(
children: [ children: [
_itemCard( ItemCard(
context: context,
info: Info( info: Info(
label: appLocalizations.themeMode, label: appLocalizations.themeMode,
iconData: Icons.brightness_high, iconData: Icons.brightness_high,
@@ -137,7 +204,6 @@ class ThemeFragment extends StatelessWidget {
final themeModeItem = themeModeItems[index]; final themeModeItem = themeModeItems[index];
return _themeModeCheckBox( return _themeModeCheckBox(
isSelected: themeMode == themeModeItem.themeMode, isSelected: themeMode == themeModeItem.themeMode,
context: context,
themeModeItem: themeModeItem, themeModeItem: themeModeItem,
); );
}, },
@@ -151,8 +217,7 @@ class ThemeFragment extends StatelessWidget {
}, },
), ),
), ),
_itemCard( ItemCard(
context: context,
info: Info( info: Info(
label: appLocalizations.themeColor, label: appLocalizations.themeColor,
iconData: Icons.palette, iconData: Icons.palette,
@@ -172,7 +237,6 @@ class ThemeFragment extends StatelessWidget {
itemBuilder: (_, index) { itemBuilder: (_, index) {
final primaryColor = primaryColors[index]; final primaryColor = primaryColors[index];
return _primaryColorCheckBox( return _primaryColorCheckBox(
context: context,
isSelected: currentPrimaryColor == primaryColor?.value, isSelected: currentPrimaryColor == primaryColor?.value,
color: primaryColor, color: primaryColor,
); );
@@ -191,30 +255,4 @@ class ThemeFragment extends StatelessWidget {
], ],
); );
} }
@override
Widget build(BuildContext context) {
final themeCard = _getThemeCard(context);
final previewCard = Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: CommonCard(
info: Info(
label: appLocalizations.preview,
iconData: Icons.looks,
),
child: Container(
height: 200,
),
),
);
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
previewCard,
themeCard,
],
),
);
}
} }

View File

@@ -57,138 +57,120 @@ class _ToolboxFragmentState extends State<ToolsFragment> {
return Intl.message(locale.toString()); return Intl.message(locale.toString());
} }
Widget _getOtherList() { List<Widget> _getOtherList() {
List<Widget> items = [ return generateSection(
ListItem.open( title: appLocalizations.other,
leading: const Icon(Icons.info), items: [
title: Text(appLocalizations.about), ListItem.open(
delegate: OpenDelegate( leading: const Icon(Icons.info),
title: appLocalizations.about, title: Text(appLocalizations.about),
widget: const AboutFragment(), delegate: OpenDelegate(
title: appLocalizations.about,
widget: const AboutFragment(),
),
), ),
),
];
return Column(
mainAxisSize: MainAxisSize.min,
children: [
for (final item in items) ...[
item,
if (item != items.last)
const Divider(
height: 0,
),
]
], ],
); );
} }
Widget _getSettingList() { List<Widget> _getSettingList() {
List<Widget> items = [ return generateSection(
Selector<Config, String?>( title: appLocalizations.settings,
selector: (_, config) => config.locale, items: [
builder: (_, localeString, __) { Selector<Config, String?>(
final subTitle = localeString ?? appLocalizations.defaultText; selector: (_, config) => config.locale,
final currentLocale = other.getLocaleForString(localeString); builder: (_, localeString, __) {
return ListTile( final subTitle = localeString ?? appLocalizations.defaultText;
leading: const Icon(Icons.language_outlined), final currentLocale = other.getLocaleForString(localeString);
title: Text(appLocalizations.language), return ListTile(
subtitle: Text(Intl.message(subTitle)), leading: const Icon(Icons.language_outlined),
onTap: () { title: Text(appLocalizations.language),
globalState.showCommonDialog( subtitle: Text(Intl.message(subTitle)),
child: AlertDialog( onTap: () {
title: Text(appLocalizations.language), globalState.showCommonDialog(
contentPadding: const EdgeInsets.symmetric( child: AlertDialog(
horizontal: 8, title: Text(appLocalizations.language),
vertical: 16, contentPadding: const EdgeInsets.symmetric(
), horizontal: 8,
content: SizedBox( vertical: 16,
width: 250, ),
child: Wrap( content: SizedBox(
children: [ width: 250,
for (final locale in [ child: Wrap(
null, children: [
...AppLocalizations.delegate.supportedLocales for (final locale in [
]) null,
ListItem.radio( ...AppLocalizations.delegate.supportedLocales
delegate: RadioDelegate<Locale?>( ])
value: locale, ListItem.radio(
groupValue: currentLocale, delegate: RadioDelegate<Locale?>(
onChanged: (Locale? value) { value: locale,
final config = context.read<Config>(); groupValue: currentLocale,
config.locale = value?.toString(); onChanged: (Locale? value) {
Navigator.of(context).pop(); final config = context.read<Config>();
}, config.locale = value?.toString();
), Navigator.of(context).pop();
title: Text(_getLocaleString(locale)), },
) ),
], title: Text(_getLocaleString(locale)),
)
],
),
), ),
), ),
), );
); },
}, );
); },
},
),
ListItem.open(
leading: const Icon(Icons.style),
title: Text(appLocalizations.theme),
subtitle: Text(appLocalizations.themeDesc),
delegate: OpenDelegate(
title: appLocalizations.theme,
widget: const ThemeFragment(),
extendPageWidth: 360,
), ),
),
ListItem.open(
leading: const Icon(Icons.cloud_sync),
title: Text(appLocalizations.backupAndRecovery),
subtitle: Text(appLocalizations.backupAndRecoveryDesc),
delegate: OpenDelegate(
title: appLocalizations.backupAndRecovery,
widget: const BackupAndRecovery(),
),
),
if (Platform.isAndroid)
ListItem.open( ListItem.open(
leading: const Icon(Icons.view_list), leading: const Icon(Icons.style),
title: Text(appLocalizations.accessControl), title: Text(appLocalizations.theme),
subtitle: Text(appLocalizations.accessControlDesc), subtitle: Text(appLocalizations.themeDesc),
delegate: OpenDelegate( delegate: OpenDelegate(
title: appLocalizations.appAccessControl, title: appLocalizations.theme,
widget: const AccessFragment(), widget: const ThemeFragment(),
extendPageWidth: 360,
), ),
), ),
ListItem.open( ListItem.open(
leading: const Icon(Icons.edit), leading: const Icon(Icons.cloud_sync),
title: Text(appLocalizations.override), title: Text(appLocalizations.backupAndRecovery),
subtitle: Text(appLocalizations.overrideDesc), subtitle: Text(appLocalizations.backupAndRecoveryDesc),
delegate: OpenDelegate( delegate: OpenDelegate(
title: appLocalizations.override, title: appLocalizations.backupAndRecovery,
widget: const ConfigFragment(), widget: const BackupAndRecovery(),
extendPageWidth: 360, ),
), ),
), if (Platform.isAndroid)
ListItem.open( ListItem.open(
leading: const Icon(Icons.settings_applications), leading: const Icon(Icons.view_list),
title: Text(appLocalizations.application), title: Text(appLocalizations.accessControl),
subtitle: Text(appLocalizations.applicationDesc), subtitle: Text(appLocalizations.accessControlDesc),
delegate: OpenDelegate( delegate: OpenDelegate(
title: appLocalizations.application, title: appLocalizations.appAccessControl,
widget: const ApplicationSettingFragment(), widget: const AccessFragment(),
),
),
];
return Column(
mainAxisSize: MainAxisSize.min,
children: [
for (final item in items) ...[
item,
if (item != items.last)
const Divider(
height: 0,
), ),
] ),
ListItem.open(
leading: const Icon(Icons.edit),
title: Text(appLocalizations.override),
subtitle: Text(appLocalizations.overrideDesc),
delegate: OpenDelegate(
title: appLocalizations.override,
widget: const ConfigFragment(),
extendPageWidth: 360,
),
),
ListItem.open(
leading: const Icon(Icons.settings_applications),
title: Text(appLocalizations.application),
subtitle: Text(appLocalizations.applicationDesc),
delegate: OpenDelegate(
title: appLocalizations.application,
widget: const ApplicationSettingFragment(),
),
),
], ],
); );
} }
@@ -216,20 +198,16 @@ class _ToolboxFragmentState extends State<ToolsFragment> {
if (state.navigationItems.isEmpty) { if (state.navigationItems.isEmpty) {
return Container(); return Container();
} }
return Section( return Column(
title: appLocalizations.more, children: [
child: _buildNavigationMenu(state.navigationItems), ListHeader(title: appLocalizations.more),
_buildNavigationMenu(state.navigationItems)
],
); );
}, },
), ),
Section( ..._getSettingList(),
title: appLocalizations.settings, ..._getOtherList(),
child: _getSettingList(),
),
Section(
title: appLocalizations.other,
child: _getOtherList(),
),
]; ];
return ListView.builder( return ListView.builder(
itemCount: items.length, itemCount: items.length,

View File

@@ -37,7 +37,7 @@
"overrideDesc": "Override Proxy related config", "overrideDesc": "Override Proxy related config",
"allowLan": "AllowLan", "allowLan": "AllowLan",
"allowLanDesc": "Allow access proxy through the LAN", "allowLanDesc": "Allow access proxy through the LAN",
"tun": "Tun mode", "tun": "TUN mode",
"tunDesc": "only effective in administrator mode", "tunDesc": "only effective in administrator mode",
"minimizeOnExit": "Minimize on exit", "minimizeOnExit": "Minimize on exit",
"minimizeOnExitDesc": "Modify the default system exit event", "minimizeOnExitDesc": "Modify the default system exit event",
@@ -111,7 +111,7 @@
"noMoreInfoDesc": "No more info", "noMoreInfoDesc": "No more info",
"profileParseErrorDesc": "profile parse error", "profileParseErrorDesc": "profile parse error",
"proxyPort": "ProxyPort", "proxyPort": "ProxyPort",
"proxyPortDesc": "Set the clash listening port", "proxyPortDesc": "Set the Clash listening port",
"port": "Port", "port": "Port",
"logLevel": "LogLevel", "logLevel": "LogLevel",
"show": "Show", "show": "Show",
@@ -161,20 +161,19 @@
"checking": "Checking...", "checking": "Checking...",
"country": "Country", "country": "Country",
"checkError": "Check error", "checkError": "Check error",
"ipCheckTimeout": "Ip check timeout",
"search": "Search", "search": "Search",
"allowBypass": "Allow applications to bypass VPN", "allowBypass": "Allow applications to bypass VPN",
"allowBypassDesc": "Some apps can bypass VPN when turned on", "allowBypassDesc": "Some apps can bypass VPN when turned on",
"externalController": "ExternalController", "externalController": "ExternalController",
"externalControllerDesc": "Once enabled, the clash kernel can be controlled on port 9090", "externalControllerDesc": "Once enabled, the Clash kernel can be controlled on port 9090",
"ipv6Desc": "When turned on it will be able to receive ipv6 traffic", "ipv6Desc": "When turned on it will be able to receive IPv6 traffic",
"app": "App", "app": "App",
"general": "General", "general": "General",
"systemProxyDesc": "Attach HTTP proxy to VpnService", "systemProxyDesc": "Attach HTTP proxy to VpnService",
"unifiedDelay": "Unified delay", "unifiedDelay": "Unified delay",
"unifiedDelayDesc": "Remove extra delays such as handshaking", "unifiedDelayDesc": "Remove extra delays such as handshaking",
"tcpConcurrent": "Tcp concurrent", "tcpConcurrent": "TCP concurrent",
"tcpConcurrentDesc": "Enabling it will allow tcp concurrency", "tcpConcurrentDesc": "Enabling it will allow TCP concurrency",
"geodataLoader": "Geo Low Memory Mode", "geodataLoader": "Geo Low Memory Mode",
"geodataLoaderDesc": "Enabling will use the Geo low memory loader", "geodataLoaderDesc": "Enabling will use the Geo low memory loader",
"requests": "Requests", "requests": "Requests",
@@ -187,10 +186,30 @@
"connections": "Connections", "connections": "Connections",
"connectionsDesc": "View current connection", "connectionsDesc": "View current connection",
"nullRequestsDesc": "No requests", "nullRequestsDesc": "No requests",
"nullConnectionsDesc": "No Connections", "nullConnectionsDesc": "No connections",
"intranetIp": "Intranet IP", "intranetIP": "Intranet IP",
"view": "View", "view": "View",
"cut": "Cut", "cut": "Cut",
"copy": "Copy", "copy": "Copy",
"paste": "Paste" "paste": "Paste",
"testUrl": "Test url",
"sync": "Sync",
"exclude": "Hidden from recent tasks",
"excludeDesc": "When the app is in the background, the app is hidden from the recent task",
"oneColumn": "One column",
"twoColumns": "Two columns",
"threeColumns": "Three columns",
"fourColumns": "Four columns",
"expand": "Standard",
"shrink": "Shrink",
"min": "Min",
"tab": "Tab",
"list": "List",
"delay": "Delay",
"style": "Style",
"size": "Size",
"sort": "Sort",
"columns": "Columns",
"proxiesSetting": "Proxies setting",
"proxyGroup": "Proxy group"
} }

View File

@@ -37,7 +37,7 @@
"overrideDesc": "覆写代理相关配置", "overrideDesc": "覆写代理相关配置",
"allowLan": "局域网代理", "allowLan": "局域网代理",
"allowLanDesc": "允许通过局域网访问代理", "allowLanDesc": "允许通过局域网访问代理",
"tun": "Tun模式", "tun": "TUN模式",
"tunDesc": "仅在管理员模式生效", "tunDesc": "仅在管理员模式生效",
"minimizeOnExit": "退出时最小化", "minimizeOnExit": "退出时最小化",
"minimizeOnExitDesc": "修改系统默认退出事件", "minimizeOnExitDesc": "修改系统默认退出事件",
@@ -111,7 +111,7 @@
"noMoreInfoDesc": "暂无更多信息", "noMoreInfoDesc": "暂无更多信息",
"profileParseErrorDesc": "配置文件解析错误", "profileParseErrorDesc": "配置文件解析错误",
"proxyPort": "代理端口", "proxyPort": "代理端口",
"proxyPortDesc": "设置clash监听端口", "proxyPortDesc": "设置Clash监听端口",
"port": "端口", "port": "端口",
"logLevel": "日志等级", "logLevel": "日志等级",
"show": "显示", "show": "显示",
@@ -161,20 +161,19 @@
"checking": "检测中...", "checking": "检测中...",
"country": "区域", "country": "区域",
"checkError": "检测失败", "checkError": "检测失败",
"ipCheckTimeout": "Ip检测超时",
"search": "搜索", "search": "搜索",
"allowBypass": "允许应用绕过vpn", "allowBypass": "允许应用绕过VPN",
"allowBypassDesc": "开启后部分应用可绕过VPN", "allowBypassDesc": "开启后部分应用可绕过VPN",
"externalController": "外部控制器", "externalController": "外部控制器",
"externalControllerDesc": "开启后将可以通过9090端口控制clash内核", "externalControllerDesc": "开启后将可以通过9090端口控制Clash内核",
"ipv6Desc": "开启后将可以接收ipv6流量", "ipv6Desc": "开启后将可以接收IPv6流量",
"app": "应用", "app": "应用",
"general": "基础", "general": "基础",
"systemProxyDesc": "为VpnService附加HTTP代理", "systemProxyDesc": "为VpnService附加HTTP代理",
"unifiedDelay": "统一延迟", "unifiedDelay": "统一延迟",
"unifiedDelayDesc": "去除握手等额外延迟", "unifiedDelayDesc": "去除握手等额外延迟",
"tcpConcurrent": "TCP并发", "tcpConcurrent": "TCP并发",
"tcpConcurrentDesc": "开启后允许tcp并发", "tcpConcurrentDesc": "开启后允许TCP并发",
"geodataLoader": "Geo低内存模式", "geodataLoader": "Geo低内存模式",
"geodataLoaderDesc": "开启将使用Geo低内存加载器", "geodataLoaderDesc": "开启将使用Geo低内存加载器",
"requests": "请求", "requests": "请求",
@@ -188,9 +187,29 @@
"connectionsDesc": "查看当前连接", "connectionsDesc": "查看当前连接",
"nullRequestsDesc": "暂无请求", "nullRequestsDesc": "暂无请求",
"nullConnectionsDesc": "暂无连接", "nullConnectionsDesc": "暂无连接",
"intranetIp": "内网 IP", "intranetIP": "内网 IP",
"view": "查看", "view": "查看",
"cut": "剪切", "cut": "剪切",
"copy": "复制", "copy": "复制",
"paste": "粘贴" "paste": "粘贴",
"testUrl": "测速链接",
"sync": "同步",
"exclude": "从最近任务中隐藏",
"excludeDesc": "应用在后台时,从最近任务中隐藏应用",
"oneColumn": "一列",
"twoColumns": "两列",
"threeColumns": "三列",
"fourColumns": "四列",
"expand": "标准",
"shrink": "紧凑",
"min": "最小",
"tab": "标签页",
"list": "列表",
"delay": "延迟",
"style": "风格",
"size": "尺寸",
"sort": "排序",
"columns": "列数",
"proxiesSetting": "代理设置",
"proxyGroup": "代理组"
} }

View File

@@ -87,6 +87,7 @@ class MessageLookup extends MessageLookupByLibrary {
"checkUpdateError": MessageLookupByLibrary.simpleMessage( "checkUpdateError": MessageLookupByLibrary.simpleMessage(
"The current application is already the latest version"), "The current application is already the latest version"),
"checking": MessageLookupByLibrary.simpleMessage("Checking..."), "checking": MessageLookupByLibrary.simpleMessage("Checking..."),
"columns": MessageLookupByLibrary.simpleMessage("Columns"),
"compatible": "compatible":
MessageLookupByLibrary.simpleMessage("Compatibility mode"), MessageLookupByLibrary.simpleMessage("Compatibility mode"),
"compatibleDesc": MessageLookupByLibrary.simpleMessage( "compatibleDesc": MessageLookupByLibrary.simpleMessage(
@@ -107,6 +108,7 @@ class MessageLookup extends MessageLookupByLibrary {
"days": MessageLookupByLibrary.simpleMessage("Days"), "days": MessageLookupByLibrary.simpleMessage("Days"),
"defaultSort": MessageLookupByLibrary.simpleMessage("Sort by default"), "defaultSort": MessageLookupByLibrary.simpleMessage("Sort by default"),
"defaultText": MessageLookupByLibrary.simpleMessage("Default"), "defaultText": MessageLookupByLibrary.simpleMessage("Default"),
"delay": MessageLookupByLibrary.simpleMessage("Delay"),
"delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"), "delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"),
"delete": MessageLookupByLibrary.simpleMessage("Delete"), "delete": MessageLookupByLibrary.simpleMessage("Delete"),
"desc": MessageLookupByLibrary.simpleMessage( "desc": MessageLookupByLibrary.simpleMessage(
@@ -121,13 +123,18 @@ class MessageLookup extends MessageLookupByLibrary {
"download": MessageLookupByLibrary.simpleMessage("Download"), "download": MessageLookupByLibrary.simpleMessage("Download"),
"edit": MessageLookupByLibrary.simpleMessage("Edit"), "edit": MessageLookupByLibrary.simpleMessage("Edit"),
"en": MessageLookupByLibrary.simpleMessage("English"), "en": MessageLookupByLibrary.simpleMessage("English"),
"exclude":
MessageLookupByLibrary.simpleMessage("Hidden from recent tasks"),
"excludeDesc": MessageLookupByLibrary.simpleMessage(
"When the app is in the background, the app is hidden from the recent task"),
"exit": MessageLookupByLibrary.simpleMessage("Exit"), "exit": MessageLookupByLibrary.simpleMessage("Exit"),
"expand": MessageLookupByLibrary.simpleMessage("Standard"),
"expirationTime": "expirationTime":
MessageLookupByLibrary.simpleMessage("Expiration time"), MessageLookupByLibrary.simpleMessage("Expiration time"),
"externalController": "externalController":
MessageLookupByLibrary.simpleMessage("ExternalController"), MessageLookupByLibrary.simpleMessage("ExternalController"),
"externalControllerDesc": MessageLookupByLibrary.simpleMessage( "externalControllerDesc": MessageLookupByLibrary.simpleMessage(
"Once enabled, the clash kernel can be controlled on port 9090"), "Once enabled, the Clash kernel can be controlled on port 9090"),
"externalResources": "externalResources":
MessageLookupByLibrary.simpleMessage("External resources"), MessageLookupByLibrary.simpleMessage("External resources"),
"file": MessageLookupByLibrary.simpleMessage("File"), "file": MessageLookupByLibrary.simpleMessage("File"),
@@ -138,6 +145,7 @@ class MessageLookup extends MessageLookupByLibrary {
"findProcessMode": MessageLookupByLibrary.simpleMessage("Find process"), "findProcessMode": MessageLookupByLibrary.simpleMessage("Find process"),
"findProcessModeDesc": MessageLookupByLibrary.simpleMessage( "findProcessModeDesc": MessageLookupByLibrary.simpleMessage(
"There is a risk of flashback after opening"), "There is a risk of flashback after opening"),
"fourColumns": MessageLookupByLibrary.simpleMessage("Four columns"),
"general": MessageLookupByLibrary.simpleMessage("General"), "general": MessageLookupByLibrary.simpleMessage("General"),
"geoData": MessageLookupByLibrary.simpleMessage("GeoData"), "geoData": MessageLookupByLibrary.simpleMessage("GeoData"),
"geodataLoader": "geodataLoader":
@@ -152,20 +160,20 @@ class MessageLookup extends MessageLookupByLibrary {
"infiniteTime": "infiniteTime":
MessageLookupByLibrary.simpleMessage("Long term effective"), MessageLookupByLibrary.simpleMessage("Long term effective"),
"init": MessageLookupByLibrary.simpleMessage("Init"), "init": MessageLookupByLibrary.simpleMessage("Init"),
"intranetIp": MessageLookupByLibrary.simpleMessage("Intranet IP"), "intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
"ipCheckTimeout":
MessageLookupByLibrary.simpleMessage("Ip check timeout"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage( "ipv6Desc": MessageLookupByLibrary.simpleMessage(
"When turned on it will be able to receive ipv6 traffic"), "When turned on it will be able to receive IPv6 traffic"),
"just": MessageLookupByLibrary.simpleMessage("Just"), "just": MessageLookupByLibrary.simpleMessage("Just"),
"language": MessageLookupByLibrary.simpleMessage("Language"), "language": MessageLookupByLibrary.simpleMessage("Language"),
"light": MessageLookupByLibrary.simpleMessage("Light"), "light": MessageLookupByLibrary.simpleMessage("Light"),
"list": MessageLookupByLibrary.simpleMessage("List"),
"logLevel": MessageLookupByLibrary.simpleMessage("LogLevel"), "logLevel": MessageLookupByLibrary.simpleMessage("LogLevel"),
"logcat": MessageLookupByLibrary.simpleMessage("Logcat"), "logcat": MessageLookupByLibrary.simpleMessage("Logcat"),
"logcatDesc": MessageLookupByLibrary.simpleMessage( "logcatDesc": MessageLookupByLibrary.simpleMessage(
"Disabling will hide the log entry"), "Disabling will hide the log entry"),
"logs": MessageLookupByLibrary.simpleMessage("Logs"), "logs": MessageLookupByLibrary.simpleMessage("Logs"),
"logsDesc": MessageLookupByLibrary.simpleMessage("Log capture records"), "logsDesc": MessageLookupByLibrary.simpleMessage("Log capture records"),
"min": MessageLookupByLibrary.simpleMessage("Min"),
"minimizeOnExit": "minimizeOnExit":
MessageLookupByLibrary.simpleMessage("Minimize on exit"), MessageLookupByLibrary.simpleMessage("Minimize on exit"),
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage( "minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
@@ -186,13 +194,14 @@ class MessageLookup extends MessageLookupByLibrary {
"notSelectedTip": MessageLookupByLibrary.simpleMessage( "notSelectedTip": MessageLookupByLibrary.simpleMessage(
"The current proxy group cannot be selected."), "The current proxy group cannot be selected."),
"nullConnectionsDesc": "nullConnectionsDesc":
MessageLookupByLibrary.simpleMessage("No Connections"), MessageLookupByLibrary.simpleMessage("No connections"),
"nullCoreInfoDesc": "nullCoreInfoDesc":
MessageLookupByLibrary.simpleMessage("Unable to obtain core info"), MessageLookupByLibrary.simpleMessage("Unable to obtain core info"),
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("No logs"), "nullLogsDesc": MessageLookupByLibrary.simpleMessage("No logs"),
"nullProfileDesc": MessageLookupByLibrary.simpleMessage( "nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"No profile, Please add a profile"), "No profile, Please add a profile"),
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"), "nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"),
"oneColumn": MessageLookupByLibrary.simpleMessage("One column"),
"other": MessageLookupByLibrary.simpleMessage("Other"), "other": MessageLookupByLibrary.simpleMessage("Other"),
"outboundMode": MessageLookupByLibrary.simpleMessage("Outbound mode"), "outboundMode": MessageLookupByLibrary.simpleMessage("Outbound mode"),
"override": MessageLookupByLibrary.simpleMessage("Override"), "override": MessageLookupByLibrary.simpleMessage("Override"),
@@ -228,9 +237,12 @@ class MessageLookup extends MessageLookupByLibrary {
"profiles": MessageLookupByLibrary.simpleMessage("Profiles"), "profiles": MessageLookupByLibrary.simpleMessage("Profiles"),
"project": MessageLookupByLibrary.simpleMessage("Project"), "project": MessageLookupByLibrary.simpleMessage("Project"),
"proxies": MessageLookupByLibrary.simpleMessage("Proxies"), "proxies": MessageLookupByLibrary.simpleMessage("Proxies"),
"proxiesSetting":
MessageLookupByLibrary.simpleMessage("Proxies setting"),
"proxyGroup": MessageLookupByLibrary.simpleMessage("Proxy group"),
"proxyPort": MessageLookupByLibrary.simpleMessage("ProxyPort"), "proxyPort": MessageLookupByLibrary.simpleMessage("ProxyPort"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage( "proxyPortDesc": MessageLookupByLibrary.simpleMessage(
"Set the clash listening port"), "Set the Clash listening port"),
"qrcode": MessageLookupByLibrary.simpleMessage("QR code"), "qrcode": MessageLookupByLibrary.simpleMessage("QR code"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage( "qrcodeDesc": MessageLookupByLibrary.simpleMessage(
"Scan QR code to obtain profile"), "Scan QR code to obtain profile"),
@@ -256,32 +268,41 @@ class MessageLookup extends MessageLookupByLibrary {
"selected": MessageLookupByLibrary.simpleMessage("Selected"), "selected": MessageLookupByLibrary.simpleMessage("Selected"),
"settings": MessageLookupByLibrary.simpleMessage("Settings"), "settings": MessageLookupByLibrary.simpleMessage("Settings"),
"show": MessageLookupByLibrary.simpleMessage("Show"), "show": MessageLookupByLibrary.simpleMessage("Show"),
"shrink": MessageLookupByLibrary.simpleMessage("Shrink"),
"silentLaunch": MessageLookupByLibrary.simpleMessage("SilentLaunch"), "silentLaunch": MessageLookupByLibrary.simpleMessage("SilentLaunch"),
"silentLaunchDesc": "silentLaunchDesc":
MessageLookupByLibrary.simpleMessage("Start in the background"), MessageLookupByLibrary.simpleMessage("Start in the background"),
"size": MessageLookupByLibrary.simpleMessage("Size"),
"sort": MessageLookupByLibrary.simpleMessage("Sort"),
"startVpn": MessageLookupByLibrary.simpleMessage("Staring VPN..."), "startVpn": MessageLookupByLibrary.simpleMessage("Staring VPN..."),
"stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."), "stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."),
"style": MessageLookupByLibrary.simpleMessage("Style"),
"submit": MessageLookupByLibrary.simpleMessage("Submit"), "submit": MessageLookupByLibrary.simpleMessage("Submit"),
"sync": MessageLookupByLibrary.simpleMessage("Sync"),
"systemProxy": MessageLookupByLibrary.simpleMessage("SystemProxy"), "systemProxy": MessageLookupByLibrary.simpleMessage("SystemProxy"),
"systemProxyDesc": MessageLookupByLibrary.simpleMessage( "systemProxyDesc": MessageLookupByLibrary.simpleMessage(
"Attach HTTP proxy to VpnService"), "Attach HTTP proxy to VpnService"),
"tab": MessageLookupByLibrary.simpleMessage("Tab"),
"tabAnimation": MessageLookupByLibrary.simpleMessage("Tab animation"), "tabAnimation": MessageLookupByLibrary.simpleMessage("Tab animation"),
"tabAnimationDesc": MessageLookupByLibrary.simpleMessage( "tabAnimationDesc": MessageLookupByLibrary.simpleMessage(
"When enabled, the home tab will add a toggle animation"), "When enabled, the home tab will add a toggle animation"),
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("Tcp concurrent"), "tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP concurrent"),
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage( "tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage(
"Enabling it will allow tcp concurrency"), "Enabling it will allow TCP concurrency"),
"testUrl": MessageLookupByLibrary.simpleMessage("Test url"),
"theme": MessageLookupByLibrary.simpleMessage("Theme"), "theme": MessageLookupByLibrary.simpleMessage("Theme"),
"themeColor": MessageLookupByLibrary.simpleMessage("Theme color"), "themeColor": MessageLookupByLibrary.simpleMessage("Theme color"),
"themeDesc": MessageLookupByLibrary.simpleMessage( "themeDesc": MessageLookupByLibrary.simpleMessage(
"Set dark mode,adjust the color"), "Set dark mode,adjust the color"),
"themeMode": MessageLookupByLibrary.simpleMessage("Theme mode"), "themeMode": MessageLookupByLibrary.simpleMessage("Theme mode"),
"threeColumns": MessageLookupByLibrary.simpleMessage("Three columns"),
"tip": MessageLookupByLibrary.simpleMessage("tip"), "tip": MessageLookupByLibrary.simpleMessage("tip"),
"tools": MessageLookupByLibrary.simpleMessage("Tools"), "tools": MessageLookupByLibrary.simpleMessage("Tools"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("Traffic usage"), "trafficUsage": MessageLookupByLibrary.simpleMessage("Traffic usage"),
"tun": MessageLookupByLibrary.simpleMessage("Tun mode"), "tun": MessageLookupByLibrary.simpleMessage("TUN mode"),
"tunDesc": MessageLookupByLibrary.simpleMessage( "tunDesc": MessageLookupByLibrary.simpleMessage(
"only effective in administrator mode"), "only effective in administrator mode"),
"twoColumns": MessageLookupByLibrary.simpleMessage("Two columns"),
"unableToUpdateCurrentProfileDesc": "unableToUpdateCurrentProfileDesc":
MessageLookupByLibrary.simpleMessage( MessageLookupByLibrary.simpleMessage(
"unable to update current profile"), "unable to update current profile"),

View File

@@ -36,7 +36,7 @@ class MessageLookup extends MessageLookupByLibrary {
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"), "addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"), "addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
"ago": MessageLookupByLibrary.simpleMessage(""), "ago": MessageLookupByLibrary.simpleMessage(""),
"allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过vpn"), "allowBypass": MessageLookupByLibrary.simpleMessage("允许应用绕过VPN"),
"allowBypassDesc": "allowBypassDesc":
MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"), MessageLookupByLibrary.simpleMessage("开启后部分应用可绕过VPN"),
"allowLan": MessageLookupByLibrary.simpleMessage("局域网代理"), "allowLan": MessageLookupByLibrary.simpleMessage("局域网代理"),
@@ -71,6 +71,7 @@ class MessageLookup extends MessageLookupByLibrary {
"checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"), "checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"),
"checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"), "checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"),
"checking": MessageLookupByLibrary.simpleMessage("检测中..."), "checking": MessageLookupByLibrary.simpleMessage("检测中..."),
"columns": MessageLookupByLibrary.simpleMessage("列数"),
"compatible": MessageLookupByLibrary.simpleMessage("兼容模式"), "compatible": MessageLookupByLibrary.simpleMessage("兼容模式"),
"compatibleDesc": "compatibleDesc":
MessageLookupByLibrary.simpleMessage("开启将失去部分应用能力获得全量的Clash的支持"), MessageLookupByLibrary.simpleMessage("开启将失去部分应用能力获得全量的Clash的支持"),
@@ -89,6 +90,7 @@ class MessageLookup extends MessageLookupByLibrary {
"days": MessageLookupByLibrary.simpleMessage(""), "days": MessageLookupByLibrary.simpleMessage(""),
"defaultSort": MessageLookupByLibrary.simpleMessage("按默认排序"), "defaultSort": MessageLookupByLibrary.simpleMessage("按默认排序"),
"defaultText": MessageLookupByLibrary.simpleMessage("默认"), "defaultText": MessageLookupByLibrary.simpleMessage("默认"),
"delay": MessageLookupByLibrary.simpleMessage("延迟"),
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"), "delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
"delete": MessageLookupByLibrary.simpleMessage("删除"), "delete": MessageLookupByLibrary.simpleMessage("删除"),
"desc": MessageLookupByLibrary.simpleMessage( "desc": MessageLookupByLibrary.simpleMessage(
@@ -100,11 +102,15 @@ class MessageLookup extends MessageLookupByLibrary {
"download": MessageLookupByLibrary.simpleMessage("下载"), "download": MessageLookupByLibrary.simpleMessage("下载"),
"edit": MessageLookupByLibrary.simpleMessage("编辑"), "edit": MessageLookupByLibrary.simpleMessage("编辑"),
"en": MessageLookupByLibrary.simpleMessage("英语"), "en": MessageLookupByLibrary.simpleMessage("英语"),
"exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"),
"excludeDesc":
MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"),
"exit": MessageLookupByLibrary.simpleMessage("退出"), "exit": MessageLookupByLibrary.simpleMessage("退出"),
"expand": MessageLookupByLibrary.simpleMessage("标准"),
"expirationTime": MessageLookupByLibrary.simpleMessage("到期时间"), "expirationTime": MessageLookupByLibrary.simpleMessage("到期时间"),
"externalController": MessageLookupByLibrary.simpleMessage("外部控制器"), "externalController": MessageLookupByLibrary.simpleMessage("外部控制器"),
"externalControllerDesc": "externalControllerDesc":
MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制clash内核"), MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制Clash内核"),
"externalResources": MessageLookupByLibrary.simpleMessage("外部资源"), "externalResources": MessageLookupByLibrary.simpleMessage("外部资源"),
"file": MessageLookupByLibrary.simpleMessage("文件"), "file": MessageLookupByLibrary.simpleMessage("文件"),
"fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"), "fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"),
@@ -112,6 +118,7 @@ class MessageLookup extends MessageLookupByLibrary {
"findProcessMode": MessageLookupByLibrary.simpleMessage("查找进程"), "findProcessMode": MessageLookupByLibrary.simpleMessage("查找进程"),
"findProcessModeDesc": "findProcessModeDesc":
MessageLookupByLibrary.simpleMessage("开启后存在闪退风险"), MessageLookupByLibrary.simpleMessage("开启后存在闪退风险"),
"fourColumns": MessageLookupByLibrary.simpleMessage("四列"),
"general": MessageLookupByLibrary.simpleMessage("基础"), "general": MessageLookupByLibrary.simpleMessage("基础"),
"geoData": MessageLookupByLibrary.simpleMessage("地理数据"), "geoData": MessageLookupByLibrary.simpleMessage("地理数据"),
"geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低内存模式"), "geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低内存模式"),
@@ -123,17 +130,18 @@ class MessageLookup extends MessageLookupByLibrary {
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"), "importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"), "infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
"init": MessageLookupByLibrary.simpleMessage("初始化"), "init": MessageLookupByLibrary.simpleMessage("初始化"),
"intranetIp": MessageLookupByLibrary.simpleMessage("内网 IP"), "intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
"ipCheckTimeout": MessageLookupByLibrary.simpleMessage("Ip检测超时"), "ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收ipv6流量"),
"just": MessageLookupByLibrary.simpleMessage("刚刚"), "just": MessageLookupByLibrary.simpleMessage("刚刚"),
"language": MessageLookupByLibrary.simpleMessage("语言"), "language": MessageLookupByLibrary.simpleMessage("语言"),
"light": MessageLookupByLibrary.simpleMessage("浅色"), "light": MessageLookupByLibrary.simpleMessage("浅色"),
"list": MessageLookupByLibrary.simpleMessage("列表"),
"logLevel": MessageLookupByLibrary.simpleMessage("日志等级"), "logLevel": MessageLookupByLibrary.simpleMessage("日志等级"),
"logcat": MessageLookupByLibrary.simpleMessage("日志捕获"), "logcat": MessageLookupByLibrary.simpleMessage("日志捕获"),
"logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"), "logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"),
"logs": MessageLookupByLibrary.simpleMessage("日志"), "logs": MessageLookupByLibrary.simpleMessage("日志"),
"logsDesc": MessageLookupByLibrary.simpleMessage("日志捕获记录"), "logsDesc": MessageLookupByLibrary.simpleMessage("日志捕获记录"),
"min": MessageLookupByLibrary.simpleMessage("最小"),
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"), "minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
"minimizeOnExitDesc": "minimizeOnExitDesc":
MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"), MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
@@ -156,6 +164,7 @@ class MessageLookup extends MessageLookupByLibrary {
"nullProfileDesc": "nullProfileDesc":
MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"), MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"), "nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
"other": MessageLookupByLibrary.simpleMessage("其他"), "other": MessageLookupByLibrary.simpleMessage("其他"),
"outboundMode": MessageLookupByLibrary.simpleMessage("出站模式"), "outboundMode": MessageLookupByLibrary.simpleMessage("出站模式"),
"override": MessageLookupByLibrary.simpleMessage("覆写"), "override": MessageLookupByLibrary.simpleMessage("覆写"),
@@ -185,8 +194,10 @@ class MessageLookup extends MessageLookupByLibrary {
"profiles": MessageLookupByLibrary.simpleMessage("配置"), "profiles": MessageLookupByLibrary.simpleMessage("配置"),
"project": MessageLookupByLibrary.simpleMessage("项目"), "project": MessageLookupByLibrary.simpleMessage("项目"),
"proxies": MessageLookupByLibrary.simpleMessage("代理"), "proxies": MessageLookupByLibrary.simpleMessage("代理"),
"proxiesSetting": MessageLookupByLibrary.simpleMessage("代理设置"),
"proxyGroup": MessageLookupByLibrary.simpleMessage("代理组"),
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"), "proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置clash监听端口"), "proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"), "qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"), "qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
"recovery": MessageLookupByLibrary.simpleMessage("恢复"), "recovery": MessageLookupByLibrary.simpleMessage("恢复"),
@@ -205,28 +216,37 @@ class MessageLookup extends MessageLookupByLibrary {
"selected": MessageLookupByLibrary.simpleMessage("已选择"), "selected": MessageLookupByLibrary.simpleMessage("已选择"),
"settings": MessageLookupByLibrary.simpleMessage("设置"), "settings": MessageLookupByLibrary.simpleMessage("设置"),
"show": MessageLookupByLibrary.simpleMessage("显示"), "show": MessageLookupByLibrary.simpleMessage("显示"),
"shrink": MessageLookupByLibrary.simpleMessage("紧凑"),
"silentLaunch": MessageLookupByLibrary.simpleMessage("静默启动"), "silentLaunch": MessageLookupByLibrary.simpleMessage("静默启动"),
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("后台启动"), "silentLaunchDesc": MessageLookupByLibrary.simpleMessage("后台启动"),
"size": MessageLookupByLibrary.simpleMessage("尺寸"),
"sort": MessageLookupByLibrary.simpleMessage("排序"),
"startVpn": MessageLookupByLibrary.simpleMessage("正在启动VPN..."), "startVpn": MessageLookupByLibrary.simpleMessage("正在启动VPN..."),
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."), "stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
"style": MessageLookupByLibrary.simpleMessage("风格"),
"submit": MessageLookupByLibrary.simpleMessage("提交"), "submit": MessageLookupByLibrary.simpleMessage("提交"),
"sync": MessageLookupByLibrary.simpleMessage("同步"),
"systemProxy": MessageLookupByLibrary.simpleMessage("系统代理"), "systemProxy": MessageLookupByLibrary.simpleMessage("系统代理"),
"systemProxyDesc": "systemProxyDesc":
MessageLookupByLibrary.simpleMessage("为VpnService附加HTTP代理"), MessageLookupByLibrary.simpleMessage("为VpnService附加HTTP代理"),
"tab": MessageLookupByLibrary.simpleMessage("标签页"),
"tabAnimation": MessageLookupByLibrary.simpleMessage("选项卡动画"), "tabAnimation": MessageLookupByLibrary.simpleMessage("选项卡动画"),
"tabAnimationDesc": "tabAnimationDesc":
MessageLookupByLibrary.simpleMessage("开启后,主页选项卡将添加切换动画"), MessageLookupByLibrary.simpleMessage("开启后,主页选项卡将添加切换动画"),
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"), "tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"),
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许tcp并发"), "tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许TCP并发"),
"testUrl": MessageLookupByLibrary.simpleMessage("测速链接"),
"theme": MessageLookupByLibrary.simpleMessage("主题"), "theme": MessageLookupByLibrary.simpleMessage("主题"),
"themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"), "themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"),
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"), "themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),
"themeMode": MessageLookupByLibrary.simpleMessage("主题模式"), "themeMode": MessageLookupByLibrary.simpleMessage("主题模式"),
"threeColumns": MessageLookupByLibrary.simpleMessage("三列"),
"tip": MessageLookupByLibrary.simpleMessage("提示"), "tip": MessageLookupByLibrary.simpleMessage("提示"),
"tools": MessageLookupByLibrary.simpleMessage("工具"), "tools": MessageLookupByLibrary.simpleMessage("工具"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"), "trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"),
"tun": MessageLookupByLibrary.simpleMessage("Tun模式"), "tun": MessageLookupByLibrary.simpleMessage("TUN模式"),
"tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"), "tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"),
"twoColumns": MessageLookupByLibrary.simpleMessage("两列"),
"unableToUpdateCurrentProfileDesc": "unableToUpdateCurrentProfileDesc":
MessageLookupByLibrary.simpleMessage("无法更新当前配置文件"), MessageLookupByLibrary.simpleMessage("无法更新当前配置文件"),
"unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"), "unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"),

View File

@@ -430,10 +430,10 @@ class AppLocalizations {
); );
} }
/// `Tun mode` /// `TUN mode`
String get tun { String get tun {
return Intl.message( return Intl.message(
'Tun mode', 'TUN mode',
name: 'tun', name: 'tun',
desc: '', desc: '',
args: [], args: [],
@@ -1170,10 +1170,10 @@ class AppLocalizations {
); );
} }
/// `Set the clash listening port` /// `Set the Clash listening port`
String get proxyPortDesc { String get proxyPortDesc {
return Intl.message( return Intl.message(
'Set the clash listening port', 'Set the Clash listening port',
name: 'proxyPortDesc', name: 'proxyPortDesc',
desc: '', desc: '',
args: [], args: [],
@@ -1670,16 +1670,6 @@ class AppLocalizations {
); );
} }
/// `Ip check timeout`
String get ipCheckTimeout {
return Intl.message(
'Ip check timeout',
name: 'ipCheckTimeout',
desc: '',
args: [],
);
}
/// `Search` /// `Search`
String get search { String get search {
return Intl.message( return Intl.message(
@@ -1720,20 +1710,20 @@ class AppLocalizations {
); );
} }
/// `Once enabled, the clash kernel can be controlled on port 9090` /// `Once enabled, the Clash kernel can be controlled on port 9090`
String get externalControllerDesc { String get externalControllerDesc {
return Intl.message( return Intl.message(
'Once enabled, the clash kernel can be controlled on port 9090', 'Once enabled, the Clash kernel can be controlled on port 9090',
name: 'externalControllerDesc', name: 'externalControllerDesc',
desc: '', desc: '',
args: [], args: [],
); );
} }
/// `When turned on it will be able to receive ipv6 traffic` /// `When turned on it will be able to receive IPv6 traffic`
String get ipv6Desc { String get ipv6Desc {
return Intl.message( return Intl.message(
'When turned on it will be able to receive ipv6 traffic', 'When turned on it will be able to receive IPv6 traffic',
name: 'ipv6Desc', name: 'ipv6Desc',
desc: '', desc: '',
args: [], args: [],
@@ -1790,20 +1780,20 @@ class AppLocalizations {
); );
} }
/// `Tcp concurrent` /// `TCP concurrent`
String get tcpConcurrent { String get tcpConcurrent {
return Intl.message( return Intl.message(
'Tcp concurrent', 'TCP concurrent',
name: 'tcpConcurrent', name: 'tcpConcurrent',
desc: '', desc: '',
args: [], args: [],
); );
} }
/// `Enabling it will allow tcp concurrency` /// `Enabling it will allow TCP concurrency`
String get tcpConcurrentDesc { String get tcpConcurrentDesc {
return Intl.message( return Intl.message(
'Enabling it will allow tcp concurrency', 'Enabling it will allow TCP concurrency',
name: 'tcpConcurrentDesc', name: 'tcpConcurrentDesc',
desc: '', desc: '',
args: [], args: [],
@@ -1930,10 +1920,10 @@ class AppLocalizations {
); );
} }
/// `No Connections` /// `No connections`
String get nullConnectionsDesc { String get nullConnectionsDesc {
return Intl.message( return Intl.message(
'No Connections', 'No connections',
name: 'nullConnectionsDesc', name: 'nullConnectionsDesc',
desc: '', desc: '',
args: [], args: [],
@@ -1941,10 +1931,10 @@ class AppLocalizations {
} }
/// `Intranet IP` /// `Intranet IP`
String get intranetIp { String get intranetIP {
return Intl.message( return Intl.message(
'Intranet IP', 'Intranet IP',
name: 'intranetIp', name: 'intranetIP',
desc: '', desc: '',
args: [], args: [],
); );
@@ -1989,6 +1979,206 @@ class AppLocalizations {
args: [], args: [],
); );
} }
/// `Test url`
String get testUrl {
return Intl.message(
'Test url',
name: 'testUrl',
desc: '',
args: [],
);
}
/// `Sync`
String get sync {
return Intl.message(
'Sync',
name: 'sync',
desc: '',
args: [],
);
}
/// `Hidden from recent tasks`
String get exclude {
return Intl.message(
'Hidden from recent tasks',
name: 'exclude',
desc: '',
args: [],
);
}
/// `When the app is in the background, the app is hidden from the recent task`
String get excludeDesc {
return Intl.message(
'When the app is in the background, the app is hidden from the recent task',
name: 'excludeDesc',
desc: '',
args: [],
);
}
/// `One column`
String get oneColumn {
return Intl.message(
'One column',
name: 'oneColumn',
desc: '',
args: [],
);
}
/// `Two columns`
String get twoColumns {
return Intl.message(
'Two columns',
name: 'twoColumns',
desc: '',
args: [],
);
}
/// `Three columns`
String get threeColumns {
return Intl.message(
'Three columns',
name: 'threeColumns',
desc: '',
args: [],
);
}
/// `Four columns`
String get fourColumns {
return Intl.message(
'Four columns',
name: 'fourColumns',
desc: '',
args: [],
);
}
/// `Standard`
String get expand {
return Intl.message(
'Standard',
name: 'expand',
desc: '',
args: [],
);
}
/// `Shrink`
String get shrink {
return Intl.message(
'Shrink',
name: 'shrink',
desc: '',
args: [],
);
}
/// `Min`
String get min {
return Intl.message(
'Min',
name: 'min',
desc: '',
args: [],
);
}
/// `Tab`
String get tab {
return Intl.message(
'Tab',
name: 'tab',
desc: '',
args: [],
);
}
/// `List`
String get list {
return Intl.message(
'List',
name: 'list',
desc: '',
args: [],
);
}
/// `Delay`
String get delay {
return Intl.message(
'Delay',
name: 'delay',
desc: '',
args: [],
);
}
/// `Style`
String get style {
return Intl.message(
'Style',
name: 'style',
desc: '',
args: [],
);
}
/// `Size`
String get size {
return Intl.message(
'Size',
name: 'size',
desc: '',
args: [],
);
}
/// `Sort`
String get sort {
return Intl.message(
'Sort',
name: 'sort',
desc: '',
args: [],
);
}
/// `Columns`
String get columns {
return Intl.message(
'Columns',
name: 'columns',
desc: '',
args: [],
);
}
/// `Proxies setting`
String get proxiesSetting {
return Intl.message(
'Proxies setting',
name: 'proxiesSetting',
desc: '',
args: [],
);
}
/// `Proxy group`
String get proxyGroup {
return Intl.message(
'Proxy group',
name: 'proxyGroup',
desc: '',
args: [],
);
}
} }
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> { class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -3,9 +3,11 @@ import 'dart:io';
import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/plugins/proxy.dart';
import 'package:fl_clash/plugins/tile.dart'; import 'package:fl_clash/plugins/tile.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'application.dart'; import 'application.dart';
import 'l10n/l10n.dart'; import 'l10n/l10n.dart';
import 'models/models.dart'; import 'models/models.dart';
@@ -13,10 +15,12 @@ import 'common/common.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await android?.init(); clashCore.initMessage();
await window?.init(); globalState.packageInfo = await PackageInfo.fromPlatform();
final config = await preferences.getConfig() ?? Config(); final config = await preferences.getConfig() ?? Config();
final clashConfig = await preferences.getClashConfig() ?? ClashConfig(); final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
await android?.init();
await window?.init(config.windowProps);
final appState = AppState( final appState = AppState(
mode: clashConfig.mode, mode: clashConfig.mode,
isCompatible: config.isCompatible, isCompatible: config.isCompatible,
@@ -42,6 +46,7 @@ Future<void> main() async {
@pragma('vm:entry-point') @pragma('vm:entry-point')
Future<void> vpnService() async { Future<void> vpnService() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
globalState.isVpnService = true;
final config = await preferences.getConfig() ?? Config(); final config = await preferences.getConfig() ?? Config();
final clashConfig = await preferences.getClashConfig() ?? ClashConfig(); final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
final appState = AppState( final appState = AppState(
@@ -49,45 +54,57 @@ Future<void> vpnService() async {
isCompatible: config.isCompatible, isCompatible: config.isCompatible,
selectedMap: config.currentSelectedMap, selectedMap: config.currentSelectedMap,
); );
clashMessage.addListener(ClashMessageListenerWithVpn(onTun: (String fd) {
proxyManager.setProtect(
int.parse(fd),
);
}));
await globalState.init( await globalState.init(
appState: appState, appState: appState,
config: config, config: config,
clashConfig: clashConfig, clashConfig: clashConfig,
); );
globalState.applyProfile( proxy?.setServiceMessageHandler(
appState: appState, ServiceMessageHandler(
config: config, onProtect: (Fd fd) async {
clashConfig: clashConfig, await proxy?.setProtect(fd.value);
clashCore.setFdMap(fd.id);
},
onProcess: (Process process) async {
var packageName = await app?.resolverProcess(process);
clashCore.setProcessMap(
ProcessMapItem(
id: process.id,
value: packageName ?? "",
),
);
},
onStarted: (String runTime) {
globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
},
onLoaded: (String groupName) {
final currentSelectedMap = config.currentSelectedMap;
final proxyName = currentSelectedMap[groupName];
if (proxyName == null) return;
clashCore.changeProxy(
ChangeProxyParams(
groupName: groupName,
proxyName: proxyName,
),
);
},
),
); );
final appLocalizations = await AppLocalizations.load( final appLocalizations = await AppLocalizations.load(
other.getLocaleForString(config.locale) ?? other.getLocaleForString(config.locale) ??
WidgetsBinding.instance.platformDispatcher.locale, WidgetsBinding.instance.platformDispatcher.locale,
); );
await app?.tip(appLocalizations.startVpn);
handleStart() async { await globalState.startSystemProxy(
await app?.tip(appLocalizations.startVpn); appState: appState,
await globalState.startSystemProxy( config: config,
appState: appState, clashConfig: clashConfig,
config: config, );
clashConfig: clashConfig,
);
globalState.updateTraffic(config: config);
globalState.updateFunctionLists = [
() {
globalState.updateTraffic(config: config);
}
];
}
handleStart();
tile?.addListener( tile?.addListener(
TileListenerWithVpn( TileListenerWithVpn(
@@ -99,25 +116,59 @@ Future<void> vpnService() async {
}, },
), ),
); );
globalState.updateTraffic();
globalState.updateFunctionLists = [
() {
globalState.updateTraffic();
}
];
} }
class ClashMessageListenerWithVpn with ClashMessageListener { @immutable
final Function(String fd) _onTun; class ServiceMessageHandler with ServiceMessageListener {
final Function(Fd fd) _onProtect;
final Function(Process process) _onProcess;
final Function(String runTime) _onStarted;
final Function(String groupName) _onLoaded;
ClashMessageListenerWithVpn({ const ServiceMessageHandler({
required Function(String fd) onTun, required Function(Fd fd) onProtect,
}) : _onTun = onTun; required Function(Process process) onProcess,
required Function(String runTime) onStarted,
required Function(String groupName) onLoaded,
})
: _onProtect = onProtect,
_onProcess = onProcess,
_onStarted = onStarted,
_onLoaded = onLoaded;
@override @override
void onTun(String fd) { onProtect(Fd fd) {
_onTun(fd); _onProtect(fd);
}
@override
onProcess(Process process) {
_onProcess(process);
}
@override
onStarted(String runTime) {
_onStarted(runTime);
}
@override
onLoaded(String groupName) {
_onLoaded(groupName);
} }
} }
@immutable
class TileListenerWithVpn with TileListener { class TileListenerWithVpn with TileListener {
final Function() _onStop; final Function() _onStop;
TileListenerWithVpn({ const TileListenerWithVpn({
required Function() onStop, required Function() onStop,
}) : _onStop = onStop; }) : _onStop = onStop;

View File

@@ -34,6 +34,7 @@ class AppState with ChangeNotifier {
List<Group> _groups; List<Group> _groups;
double _viewWidth; double _viewWidth;
List<Connection> _requests; List<Connection> _requests;
num _checkIpNum;
AppState({ AppState({
required Mode mode, required Mode mode,
@@ -47,6 +48,7 @@ class AppState with ChangeNotifier {
_viewWidth = 0, _viewWidth = 0,
_selectedMap = selectedMap, _selectedMap = selectedMap,
_sortNum = 0, _sortNum = 0,
_checkIpNum = 0,
_requests = [], _requests = [],
_mode = mode, _mode = mode,
_totalTraffic = Traffic(), _totalTraffic = Traffic(),
@@ -123,9 +125,10 @@ class AppState with ChangeNotifier {
final index = groups.indexWhere((element) => element.name == proxyName); final index = groups.indexWhere((element) => element.name == proxyName);
if (index == -1) return proxyName; if (index == -1) return proxyName;
final group = groups[index]; final group = groups[index];
return getRealProxyName(selectedMap.containsKey(proxyName) return getRealProxyName((selectedMap.containsKey(proxyName)
? selectedMap[proxyName] ? selectedMap[proxyName]
: group.now); : group.now)) ??
proxyName;
} }
String? get showProxyName { String? get showProxyName {
@@ -240,6 +243,15 @@ class AppState with ChangeNotifier {
} }
} }
num get checkIpNum => _checkIpNum;
set checkIpNum(num value) {
if (_checkIpNum != value) {
_checkIpNum = value;
notifyListeners();
}
}
Mode get mode => _mode; Mode get mode => _mode;
set mode(Mode value) { set mode(Mode value) {

View File

@@ -1,7 +1,10 @@
// ignore_for_file: invalid_annotation_target // ignore_for_file: invalid_annotation_target
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/common/constant.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@@ -104,6 +107,8 @@ class Dns {
} }
} }
typedef GeoXMap = Map<String, String>;
@JsonSerializable() @JsonSerializable()
class ClashConfig extends ChangeNotifier { class ClashConfig extends ChangeNotifier {
int _mixedPort; int _mixedPort;
@@ -118,7 +123,9 @@ class ClashConfig extends ChangeNotifier {
bool _tcpConcurrent; bool _tcpConcurrent;
Tun _tun; Tun _tun;
Dns _dns; Dns _dns;
GeoXMap _geoXUrl;
List<String> _rules; List<String> _rules;
String? _globalRealUa;
ClashConfig() ClashConfig()
: _mixedPort = 7890, : _mixedPort = 7890,
@@ -133,6 +140,7 @@ class ClashConfig extends ChangeNotifier {
_geodataLoader = geodataLoaderMemconservative, _geodataLoader = geodataLoaderMemconservative,
_externalController = '', _externalController = '',
_dns = Dns(), _dns = Dns(),
_geoXUrl = defaultGeoXMap,
_rules = []; _rules = [];
@JsonKey(name: "mixed-port", defaultValue: 7890) @JsonKey(name: "mixed-port", defaultValue: 7890)
@@ -235,7 +243,12 @@ class ClashConfig extends ChangeNotifier {
} }
} }
Tun get tun => _tun; Tun get tun {
if (Platform.isAndroid) {
return _tun.copyWith(enable: false);
}
return _tun;
}
set tun(Tun value) { set tun(Tun value) {
if (_tun != value) { if (_tun != value) {
@@ -262,6 +275,35 @@ class ClashConfig extends ChangeNotifier {
} }
} }
@JsonKey(name: "global-ua", defaultValue: null)
String get globalUa {
if (_globalRealUa == null) {
return globalState.packageInfo.ua;
} else {
return _globalRealUa!;
}
}
@JsonKey(name: "global-real-ua", defaultValue: null)
String? get globalRealUa => _globalRealUa;
set globalRealUa(String? value) {
if (_globalRealUa != value) {
_globalRealUa = value;
notifyListeners();
}
}
@JsonKey(name: "geox-url", defaultValue: defaultGeoXMap)
GeoXMap get geoXUrl => _geoXUrl;
set geoXUrl(GeoXMap value) {
if (!const MapEquality<String, String>().equals(value, _geoXUrl)) {
_geoXUrl = value;
notifyListeners();
}
}
update([ClashConfig? clashConfig]) { update([ClashConfig? clashConfig]) {
if (clashConfig != null) { if (clashConfig != null) {
_mixedPort = clashConfig._mixedPort; _mixedPort = clashConfig._mixedPort;
@@ -269,8 +311,16 @@ class ClashConfig extends ChangeNotifier {
_mode = clashConfig._mode; _mode = clashConfig._mode;
_logLevel = clashConfig._logLevel; _logLevel = clashConfig._logLevel;
_tun = clashConfig._tun; _tun = clashConfig._tun;
_findProcessMode = clashConfig._findProcessMode;
_geoXUrl = clashConfig._geoXUrl;
_unifiedDelay = clashConfig._unifiedDelay;
_globalRealUa = clashConfig._globalRealUa;
_tcpConcurrent = clashConfig._tcpConcurrent;
_externalController = clashConfig._externalController;
_geodataLoader = clashConfig._geodataLoader;
_dns = clashConfig._dns; _dns = clashConfig._dns;
_rules = clashConfig._rules; _rules = clashConfig._rules;
_globalRealUa = clashConfig.globalRealUa;
} }
notifyListeners(); notifyListeners();
} }
@@ -282,9 +332,4 @@ class ClashConfig extends ChangeNotifier {
factory ClashConfig.fromJson(Map<String, dynamic> json) { factory ClashConfig.fromJson(Map<String, dynamic> json) {
return _$ClashConfigFromJson(json); return _$ClashConfigFromJson(json);
} }
@override
String toString() {
return 'ClashConfig{_mixedPort: $_mixedPort, _allowLan: $_allowLan, _mode: $_mode, _logLevel: $_logLevel, _tun: $_tun, _dns: $_dns, _rules: $_rules}';
}
} }

View File

@@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@@ -28,13 +29,28 @@ class AccessControl with _$AccessControl {
class Props with _$Props { class Props with _$Props {
const factory Props({ const factory Props({
AccessControl? accessControl, AccessControl? accessControl,
bool? allowBypass, required bool allowBypass,
bool? systemProxy, required bool systemProxy,
}) = _Props; }) = _Props;
factory Props.fromJson(Map<String, Object?> json) => _$PropsFromJson(json); factory Props.fromJson(Map<String, Object?> json) => _$PropsFromJson(json);
} }
@freezed
class WindowProps with _$WindowProps {
const factory WindowProps({
@Default(1000) double width,
@Default(600) double height,
double? top,
double? left,
}) = _WindowProps;
factory WindowProps.fromJson(Map<String, Object?>? json) =>
json == null ? defaultWindowProps : _$WindowPropsFromJson(json);
}
const defaultWindowProps = WindowProps();
@JsonSerializable() @JsonSerializable()
class Config extends ChangeNotifier { class Config extends ChangeNotifier {
List<Profile> _profiles; List<Profile> _profiles;
@@ -55,7 +71,13 @@ class Config extends ChangeNotifier {
bool _autoCheckUpdate; bool _autoCheckUpdate;
bool _allowBypass; bool _allowBypass;
bool _systemProxy; bool _systemProxy;
bool _isExclude;
DAV? _dav; DAV? _dav;
ProxiesType _proxiesType;
ProxyCardType _proxyCardType;
int _proxiesColumns;
String _testUrl;
WindowProps _windowProps;
Config() Config()
: _profiles = [], : _profiles = [],
@@ -71,9 +93,15 @@ class Config extends ChangeNotifier {
_isAccessControl = false, _isAccessControl = false,
_autoCheckUpdate = true, _autoCheckUpdate = true,
_systemProxy = true, _systemProxy = true,
_testUrl = defaultTestUrl,
_accessControl = const AccessControl(), _accessControl = const AccessControl(),
_isAnimateToPage = true, _isAnimateToPage = true,
_allowBypass = true; _allowBypass = true,
_isExclude = false,
_proxyCardType = ProxyCardType.expand,
_windowProps = defaultWindowProps,
_proxiesType = ProxiesType.tab,
_proxiesColumns = 2;
deleteProfileById(String id) { deleteProfileById(String id) {
_profiles = profiles.where((element) => element.id != id).toList(); _profiles = profiles.where((element) => element.id != id).toList();
@@ -150,6 +178,19 @@ class Config extends ChangeNotifier {
String? get currentGroupName => currentProfile?.currentGroupName; String? get currentGroupName => currentProfile?.currentGroupName;
Set<String> get currentUnfoldSet => currentProfile?.unfoldSet ?? {};
updateCurrentUnfoldSet(Set<String> value) {
if (!const SetEquality<String>().equals(currentUnfoldSet, value)) {
_setProfile(
currentProfile!.copyWith(
unfoldSet: value,
),
);
notifyListeners();
}
}
updateCurrentGroupName(String groupName) { updateCurrentGroupName(String groupName) {
if (currentProfile != null && if (currentProfile != null &&
currentProfile!.currentGroupName != groupName) { currentProfile!.currentGroupName != groupName) {
@@ -364,6 +405,68 @@ class Config extends ChangeNotifier {
} }
} }
@JsonKey(
defaultValue: ProxiesType.tab,
unknownEnumValue: ProxiesType.tab,
)
ProxiesType get proxiesType => _proxiesType;
set proxiesType(ProxiesType value) {
if (_proxiesType != value) {
_proxiesType = value;
notifyListeners();
}
}
@JsonKey(defaultValue: ProxyCardType.expand)
ProxyCardType get proxyCardType => _proxyCardType;
set proxyCardType(ProxyCardType value) {
if (_proxyCardType != value) {
_proxyCardType = value;
notifyListeners();
}
}
@JsonKey(defaultValue: 2)
int get proxiesColumns => _proxiesColumns;
set proxiesColumns(int value) {
if (_proxiesColumns != value) {
_proxiesColumns = value;
notifyListeners();
}
}
@JsonKey(name: "test-url", defaultValue: defaultTestUrl)
String get testUrl => _testUrl;
set testUrl(String value) {
if (_testUrl != value) {
_testUrl = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get isExclude => _isExclude;
set isExclude(bool value) {
if (_isExclude != value) {
_isExclude = value;
notifyListeners();
}
}
WindowProps get windowProps => _windowProps;
set windowProps(WindowProps value) {
if (_windowProps != value) {
_windowProps = value;
notifyListeners();
}
}
update([ update([
Config? config, Config? config,
RecoveryOption recoveryOptions = RecoveryOption.all, RecoveryOption recoveryOptions = RecoveryOption.all,
@@ -383,6 +486,7 @@ class Config extends ChangeNotifier {
_autoLaunch = config._autoLaunch; _autoLaunch = config._autoLaunch;
_silentLaunch = config._silentLaunch; _silentLaunch = config._silentLaunch;
_autoRun = config._autoRun; _autoRun = config._autoRun;
_proxiesType = config._proxiesType;
_openLog = config._openLog; _openLog = config._openLog;
_themeMode = config._themeMode; _themeMode = config._themeMode;
_locale = config._locale; _locale = config._locale;
@@ -395,6 +499,9 @@ class Config extends ChangeNotifier {
_isAnimateToPage = config._isAnimateToPage; _isAnimateToPage = config._isAnimateToPage;
_autoCheckUpdate = config._autoCheckUpdate; _autoCheckUpdate = config._autoCheckUpdate;
_dav = config._dav; _dav = config._dav;
_testUrl = config._testUrl;
_isExclude = config._isExclude;
_windowProps = config._windowProps;
} }
notifyListeners(); notifyListeners();
} }
@@ -406,9 +513,4 @@ class Config extends ChangeNotifier {
factory Config.fromJson(Map<String, dynamic> json) { factory Config.fromJson(Map<String, dynamic> json) {
return _$ConfigFromJson(json); return _$ConfigFromJson(json);
} }
@override
String toString() {
return 'Config{_profiles: $_profiles, _isCompatible: $_isCompatible, _currentProfileId: $_currentProfileId, _autoLaunch: $_autoLaunch, _silentLaunch: $_silentLaunch, _autoRun: $_autoRun, _openLog: $_openLog, _themeMode: $_themeMode, _locale: $_locale, _primaryColor: $_primaryColor, _proxiesSortType: $_proxiesSortType, _isMinimizeOnExit: $_isMinimizeOnExit, _isAccessControl: $_isAccessControl, _accessControl: $_accessControl, _isAnimateToPage: $_isAnimateToPage, _dav: $_dav}';
}
} }

View File

@@ -48,7 +48,11 @@ class ConnectionsAndKeywords with _$ConnectionsAndKeywords {
_$ConnectionsAndKeywordsFromJson(json); _$ConnectionsAndKeywordsFromJson(json);
} }
extension ConnectionsAndKeywordsExt on ConnectionsAndKeywords {
extension ConnectionsAndKeywordsExt on ConnectionsAndKeywords{ List<Connection> get filteredConnections => connections
List<Connection> get filteredConnections => connections.where((connection)=> Set.from(connection.chains).containsAll(keywords)).toList(); .where((connection) => {
} ...connection.chains,
connection.metadata.process,
}.containsAll(keywords))
.toList();
}

View File

@@ -3,19 +3,32 @@
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/clash_config.dart'; import 'package:fl_clash/models/clash_config.dart';
import 'package:fl_clash/models/connection.dart'; import 'package:fl_clash/models/connection.dart';
import 'package:fl_clash/models/models.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
part 'generated/ffi.g.dart'; part 'generated/ffi.g.dart';
part 'generated/ffi.freezed.dart'; part 'generated/ffi.freezed.dart';
@freezed
class ConfigExtendedParams with _$ConfigExtendedParams {
const factory ConfigExtendedParams({
@JsonKey(name: "is-patch") required bool isPatch,
@JsonKey(name: "is-compatible") required bool isCompatible,
@JsonKey(name: "selected-map") required SelectedMap selectedMap,
@JsonKey(name: "test-url") required String testUrl,
}) = _ConfigExtendedParams;
factory ConfigExtendedParams.fromJson(Map<String, Object?> json) =>
_$ConfigExtendedParamsFromJson(json);
}
@freezed @freezed
class UpdateConfigParams with _$UpdateConfigParams { class UpdateConfigParams with _$UpdateConfigParams {
const factory UpdateConfigParams({ const factory UpdateConfigParams({
@JsonKey(name: "profile-path") String? profilePath, @JsonKey(name: "profile-path") String? profilePath,
required ClashConfig config, required ClashConfig config,
@JsonKey(name: "is-patch") required bool isPatch, required ConfigExtendedParams params,
@JsonKey(name: "is-compatible") required bool isCompatible,
}) = _UpdateConfigParams; }) = _UpdateConfigParams;
factory UpdateConfigParams.fromJson(Map<String, Object?> json) => factory UpdateConfigParams.fromJson(Map<String, Object?> json) =>
@@ -34,14 +47,25 @@ class ChangeProxyParams with _$ChangeProxyParams {
} }
@freezed @freezed
class Message with _$Message { class AppMessage with _$AppMessage {
const factory Message({ const factory AppMessage({
required MessageType type, required AppMessageType type,
dynamic data, dynamic data,
}) = _Message; }) = _AppMessage;
factory Message.fromJson(Map<String, Object?> json) => factory AppMessage.fromJson(Map<String, Object?> json) =>
_$MessageFromJson(json); _$AppMessageFromJson(json);
}
@freezed
class ServiceMessage with _$ServiceMessage {
const factory ServiceMessage({
required ServiceMessageType type,
dynamic data,
}) = _ServiceMessage;
factory ServiceMessage.fromJson(Map<String, Object?> json) =>
_$ServiceMessageFromJson(json);
} }
@freezed @freezed
@@ -75,6 +99,16 @@ class Process with _$Process {
_$ProcessFromJson(json); _$ProcessFromJson(json);
} }
@freezed
class Fd with _$Fd {
const factory Fd({
required int id,
required int value,
}) = _Fd;
factory Fd.fromJson(Map<String, Object?> json) => _$FdFromJson(json);
}
@freezed @freezed
class ProcessMapItem with _$ProcessMapItem { class ProcessMapItem with _$ProcessMapItem {
const factory ProcessMapItem({ const factory ProcessMapItem({
@@ -98,3 +132,27 @@ class ExternalProvider with _$ExternalProvider {
factory ExternalProvider.fromJson(Map<String, Object?> json) => factory ExternalProvider.fromJson(Map<String, Object?> json) =>
_$ExternalProviderFromJson(json); _$ExternalProviderFromJson(json);
} }
abstract mixin class AppMessageListener {
void onLog(Log log) {}
void onDelay(Delay delay) {}
void onRequest(Connection connection) {}
void onStarted(String runTime) {}
void onLoaded(String groupName) {}
}
abstract mixin class ServiceMessageListener {
onProtect(Fd fd) {}
onProcess(Process process) {}
onStarted(String runTime) {}
onLoaded(String groupName) {}
}

View File

@@ -51,7 +51,21 @@ ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => ClashConfig()
..tcpConcurrent = json['tcp-concurrent'] as bool? ?? false ..tcpConcurrent = json['tcp-concurrent'] as bool? ?? false
..tun = Tun.fromJson(json['tun'] as Map<String, dynamic>) ..tun = Tun.fromJson(json['tun'] as Map<String, dynamic>)
..dns = Dns.fromJson(json['dns'] as Map<String, dynamic>) ..dns = Dns.fromJson(json['dns'] as Map<String, dynamic>)
..rules = (json['rules'] as List<dynamic>).map((e) => e as String).toList(); ..rules = (json['rules'] as List<dynamic>).map((e) => e as String).toList()
..globalRealUa = json['global-real-ua'] as String?
..geoXUrl = (json['geox-url'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
) ??
{
'mmdb':
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb',
'asn':
'https://github.com/xishang0128/geoip/releases/download/latest/GeoLite2-ASN.mmdb',
'geoip':
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoIP.dat',
'geosite':
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat'
};
Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) => Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) =>
<String, dynamic>{ <String, dynamic>{
@@ -68,6 +82,8 @@ Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) =>
'tun': instance.tun, 'tun': instance.tun,
'dns': instance.dns, 'dns': instance.dns,
'rules': instance.rules, 'rules': instance.rules,
'global-real-ua': instance.globalRealUa,
'geox-url': instance.geoXUrl,
}; };
const _$ModeEnumMap = { const _$ModeEnumMap = {

View File

@@ -247,8 +247,8 @@ Props _$PropsFromJson(Map<String, dynamic> json) {
/// @nodoc /// @nodoc
mixin _$Props { mixin _$Props {
AccessControl? get accessControl => throw _privateConstructorUsedError; AccessControl? get accessControl => throw _privateConstructorUsedError;
bool? get allowBypass => throw _privateConstructorUsedError; bool get allowBypass => throw _privateConstructorUsedError;
bool? get systemProxy => throw _privateConstructorUsedError; bool get systemProxy => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
@@ -260,8 +260,7 @@ abstract class $PropsCopyWith<$Res> {
factory $PropsCopyWith(Props value, $Res Function(Props) then) = factory $PropsCopyWith(Props value, $Res Function(Props) then) =
_$PropsCopyWithImpl<$Res, Props>; _$PropsCopyWithImpl<$Res, Props>;
@useResult @useResult
$Res call( $Res call({AccessControl? accessControl, bool allowBypass, bool systemProxy});
{AccessControl? accessControl, bool? allowBypass, bool? systemProxy});
$AccessControlCopyWith<$Res>? get accessControl; $AccessControlCopyWith<$Res>? get accessControl;
} }
@@ -280,22 +279,22 @@ class _$PropsCopyWithImpl<$Res, $Val extends Props>
@override @override
$Res call({ $Res call({
Object? accessControl = freezed, Object? accessControl = freezed,
Object? allowBypass = freezed, Object? allowBypass = null,
Object? systemProxy = freezed, Object? systemProxy = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
accessControl: freezed == accessControl accessControl: freezed == accessControl
? _value.accessControl ? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable : accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?, as AccessControl?,
allowBypass: freezed == allowBypass allowBypass: null == allowBypass
? _value.allowBypass ? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable : allowBypass // ignore: cast_nullable_to_non_nullable
as bool?, as bool,
systemProxy: freezed == systemProxy systemProxy: null == systemProxy
? _value.systemProxy ? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable : systemProxy // ignore: cast_nullable_to_non_nullable
as bool?, as bool,
) as $Val); ) as $Val);
} }
@@ -319,8 +318,7 @@ abstract class _$$PropsImplCopyWith<$Res> implements $PropsCopyWith<$Res> {
__$$PropsImplCopyWithImpl<$Res>; __$$PropsImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call( $Res call({AccessControl? accessControl, bool allowBypass, bool systemProxy});
{AccessControl? accessControl, bool? allowBypass, bool? systemProxy});
@override @override
$AccessControlCopyWith<$Res>? get accessControl; $AccessControlCopyWith<$Res>? get accessControl;
@@ -338,22 +336,22 @@ class __$$PropsImplCopyWithImpl<$Res>
@override @override
$Res call({ $Res call({
Object? accessControl = freezed, Object? accessControl = freezed,
Object? allowBypass = freezed, Object? allowBypass = null,
Object? systemProxy = freezed, Object? systemProxy = null,
}) { }) {
return _then(_$PropsImpl( return _then(_$PropsImpl(
accessControl: freezed == accessControl accessControl: freezed == accessControl
? _value.accessControl ? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable : accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?, as AccessControl?,
allowBypass: freezed == allowBypass allowBypass: null == allowBypass
? _value.allowBypass ? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable : allowBypass // ignore: cast_nullable_to_non_nullable
as bool?, as bool,
systemProxy: freezed == systemProxy systemProxy: null == systemProxy
? _value.systemProxy ? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable : systemProxy // ignore: cast_nullable_to_non_nullable
as bool?, as bool,
)); ));
} }
} }
@@ -361,7 +359,10 @@ class __$$PropsImplCopyWithImpl<$Res>
/// @nodoc /// @nodoc
@JsonSerializable() @JsonSerializable()
class _$PropsImpl implements _Props { class _$PropsImpl implements _Props {
const _$PropsImpl({this.accessControl, this.allowBypass, this.systemProxy}); const _$PropsImpl(
{this.accessControl,
required this.allowBypass,
required this.systemProxy});
factory _$PropsImpl.fromJson(Map<String, dynamic> json) => factory _$PropsImpl.fromJson(Map<String, dynamic> json) =>
_$$PropsImplFromJson(json); _$$PropsImplFromJson(json);
@@ -369,9 +370,9 @@ class _$PropsImpl implements _Props {
@override @override
final AccessControl? accessControl; final AccessControl? accessControl;
@override @override
final bool? allowBypass; final bool allowBypass;
@override @override
final bool? systemProxy; final bool systemProxy;
@override @override
String toString() { String toString() {
@@ -413,19 +414,210 @@ class _$PropsImpl implements _Props {
abstract class _Props implements Props { abstract class _Props implements Props {
const factory _Props( const factory _Props(
{final AccessControl? accessControl, {final AccessControl? accessControl,
final bool? allowBypass, required final bool allowBypass,
final bool? systemProxy}) = _$PropsImpl; required final bool systemProxy}) = _$PropsImpl;
factory _Props.fromJson(Map<String, dynamic> json) = _$PropsImpl.fromJson; factory _Props.fromJson(Map<String, dynamic> json) = _$PropsImpl.fromJson;
@override @override
AccessControl? get accessControl; AccessControl? get accessControl;
@override @override
bool? get allowBypass; bool get allowBypass;
@override @override
bool? get systemProxy; bool get systemProxy;
@override @override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$PropsImplCopyWith<_$PropsImpl> get copyWith => _$$PropsImplCopyWith<_$PropsImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
WindowProps _$WindowPropsFromJson(Map<String, dynamic> json) {
return _WindowProps.fromJson(json);
}
/// @nodoc
mixin _$WindowProps {
double get width => throw _privateConstructorUsedError;
double get height => throw _privateConstructorUsedError;
double? get top => throw _privateConstructorUsedError;
double? get left => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$WindowPropsCopyWith<WindowProps> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $WindowPropsCopyWith<$Res> {
factory $WindowPropsCopyWith(
WindowProps value, $Res Function(WindowProps) then) =
_$WindowPropsCopyWithImpl<$Res, WindowProps>;
@useResult
$Res call({double width, double height, double? top, double? left});
}
/// @nodoc
class _$WindowPropsCopyWithImpl<$Res, $Val extends WindowProps>
implements $WindowPropsCopyWith<$Res> {
_$WindowPropsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? width = null,
Object? height = null,
Object? top = freezed,
Object? left = freezed,
}) {
return _then(_value.copyWith(
width: null == width
? _value.width
: width // ignore: cast_nullable_to_non_nullable
as double,
height: null == height
? _value.height
: height // ignore: cast_nullable_to_non_nullable
as double,
top: freezed == top
? _value.top
: top // ignore: cast_nullable_to_non_nullable
as double?,
left: freezed == left
? _value.left
: left // ignore: cast_nullable_to_non_nullable
as double?,
) as $Val);
}
}
/// @nodoc
abstract class _$$WindowPropsImplCopyWith<$Res>
implements $WindowPropsCopyWith<$Res> {
factory _$$WindowPropsImplCopyWith(
_$WindowPropsImpl value, $Res Function(_$WindowPropsImpl) then) =
__$$WindowPropsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({double width, double height, double? top, double? left});
}
/// @nodoc
class __$$WindowPropsImplCopyWithImpl<$Res>
extends _$WindowPropsCopyWithImpl<$Res, _$WindowPropsImpl>
implements _$$WindowPropsImplCopyWith<$Res> {
__$$WindowPropsImplCopyWithImpl(
_$WindowPropsImpl _value, $Res Function(_$WindowPropsImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? width = null,
Object? height = null,
Object? top = freezed,
Object? left = freezed,
}) {
return _then(_$WindowPropsImpl(
width: null == width
? _value.width
: width // ignore: cast_nullable_to_non_nullable
as double,
height: null == height
? _value.height
: height // ignore: cast_nullable_to_non_nullable
as double,
top: freezed == top
? _value.top
: top // ignore: cast_nullable_to_non_nullable
as double?,
left: freezed == left
? _value.left
: left // ignore: cast_nullable_to_non_nullable
as double?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$WindowPropsImpl implements _WindowProps {
const _$WindowPropsImpl(
{this.width = 1000, this.height = 600, this.top, this.left});
factory _$WindowPropsImpl.fromJson(Map<String, dynamic> json) =>
_$$WindowPropsImplFromJson(json);
@override
@JsonKey()
final double width;
@override
@JsonKey()
final double height;
@override
final double? top;
@override
final double? left;
@override
String toString() {
return 'WindowProps(width: $width, height: $height, top: $top, left: $left)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$WindowPropsImpl &&
(identical(other.width, width) || other.width == width) &&
(identical(other.height, height) || other.height == height) &&
(identical(other.top, top) || other.top == top) &&
(identical(other.left, left) || other.left == left));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, width, height, top, left);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$WindowPropsImplCopyWith<_$WindowPropsImpl> get copyWith =>
__$$WindowPropsImplCopyWithImpl<_$WindowPropsImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$WindowPropsImplToJson(
this,
);
}
}
abstract class _WindowProps implements WindowProps {
const factory _WindowProps(
{final double width,
final double height,
final double? top,
final double? left}) = _$WindowPropsImpl;
factory _WindowProps.fromJson(Map<String, dynamic> json) =
_$WindowPropsImpl.fromJson;
@override
double get width;
@override
double get height;
@override
double? get top;
@override
double? get left;
@override
@JsonKey(ignore: true)
_$$WindowPropsImplCopyWith<_$WindowPropsImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -34,7 +34,19 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
..isCompatible = json['isCompatible'] as bool? ?? true ..isCompatible = json['isCompatible'] as bool? ?? true
..autoCheckUpdate = json['autoCheckUpdate'] 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; ..systemProxy = json['systemProxy'] as bool? ?? true
..proxiesType = $enumDecodeNullable(_$ProxiesTypeEnumMap, json['proxiesType'],
unknownValue: ProxiesType.tab) ??
ProxiesType.tab
..proxyCardType =
$enumDecodeNullable(_$ProxyCardTypeEnumMap, json['proxyCardType']) ??
ProxyCardType.expand
..proxiesColumns = (json['proxiesColumns'] as num?)?.toInt() ?? 2
..testUrl =
json['test-url'] as String? ?? 'https://www.gstatic.com/generate_204'
..isExclude = json['isExclude'] as bool? ?? false
..windowProps =
WindowProps.fromJson(json['windowProps'] as Map<String, dynamic>?);
Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'profiles': instance.profiles, 'profiles': instance.profiles,
@@ -56,6 +68,12 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'autoCheckUpdate': instance.autoCheckUpdate, 'autoCheckUpdate': instance.autoCheckUpdate,
'allowBypass': instance.allowBypass, 'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy, 'systemProxy': instance.systemProxy,
'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!,
'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!,
'proxiesColumns': instance.proxiesColumns,
'test-url': instance.testUrl,
'isExclude': instance.isExclude,
'windowProps': instance.windowProps,
}; };
const _$ThemeModeEnumMap = { const _$ThemeModeEnumMap = {
@@ -70,6 +88,17 @@ const _$ProxiesSortTypeEnumMap = {
ProxiesSortType.name: 'name', ProxiesSortType.name: 'name',
}; };
const _$ProxiesTypeEnumMap = {
ProxiesType.tab: 'tab',
ProxiesType.list: 'list',
};
const _$ProxyCardTypeEnumMap = {
ProxyCardType.expand: 'expand',
ProxyCardType.shrink: 'shrink',
ProxyCardType.min: 'min',
};
_$AccessControlImpl _$$AccessControlImplFromJson(Map<String, dynamic> json) => _$AccessControlImpl _$$AccessControlImplFromJson(Map<String, dynamic> json) =>
_$AccessControlImpl( _$AccessControlImpl(
mode: $enumDecodeNullable(_$AccessControlModeEnumMap, json['mode']) ?? mode: $enumDecodeNullable(_$AccessControlModeEnumMap, json['mode']) ??
@@ -103,8 +132,8 @@ _$PropsImpl _$$PropsImplFromJson(Map<String, dynamic> json) => _$PropsImpl(
? null ? null
: AccessControl.fromJson( : AccessControl.fromJson(
json['accessControl'] as Map<String, dynamic>), json['accessControl'] as Map<String, dynamic>),
allowBypass: json['allowBypass'] as bool?, allowBypass: json['allowBypass'] as bool,
systemProxy: json['systemProxy'] as bool?, systemProxy: json['systemProxy'] as bool,
); );
Map<String, dynamic> _$$PropsImplToJson(_$PropsImpl instance) => Map<String, dynamic> _$$PropsImplToJson(_$PropsImpl instance) =>
@@ -113,3 +142,19 @@ Map<String, dynamic> _$$PropsImplToJson(_$PropsImpl instance) =>
'allowBypass': instance.allowBypass, 'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy, 'systemProxy': instance.systemProxy,
}; };
_$WindowPropsImpl _$$WindowPropsImplFromJson(Map<String, dynamic> json) =>
_$WindowPropsImpl(
width: (json['width'] as num?)?.toDouble() ?? 1000,
height: (json['height'] as num?)?.toDouble() ?? 600,
top: (json['top'] as num?)?.toDouble(),
left: (json['left'] as num?)?.toDouble(),
);
Map<String, dynamic> _$$WindowPropsImplToJson(_$WindowPropsImpl instance) =>
<String, dynamic>{
'width': instance.width,
'height': instance.height,
'top': instance.top,
'left': instance.left,
};

View File

@@ -14,6 +14,234 @@ T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError( final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
ConfigExtendedParams _$ConfigExtendedParamsFromJson(Map<String, dynamic> json) {
return _ConfigExtendedParams.fromJson(json);
}
/// @nodoc
mixin _$ConfigExtendedParams {
@JsonKey(name: "is-patch")
bool get isPatch => throw _privateConstructorUsedError;
@JsonKey(name: "is-compatible")
bool get isCompatible => throw _privateConstructorUsedError;
@JsonKey(name: "selected-map")
Map<String, String> get selectedMap => throw _privateConstructorUsedError;
@JsonKey(name: "test-url")
String get testUrl => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ConfigExtendedParamsCopyWith<ConfigExtendedParams> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ConfigExtendedParamsCopyWith<$Res> {
factory $ConfigExtendedParamsCopyWith(ConfigExtendedParams value,
$Res Function(ConfigExtendedParams) then) =
_$ConfigExtendedParamsCopyWithImpl<$Res, ConfigExtendedParams>;
@useResult
$Res call(
{@JsonKey(name: "is-patch") bool isPatch,
@JsonKey(name: "is-compatible") bool isCompatible,
@JsonKey(name: "selected-map") Map<String, String> selectedMap,
@JsonKey(name: "test-url") String testUrl});
}
/// @nodoc
class _$ConfigExtendedParamsCopyWithImpl<$Res,
$Val extends ConfigExtendedParams>
implements $ConfigExtendedParamsCopyWith<$Res> {
_$ConfigExtendedParamsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isPatch = null,
Object? isCompatible = null,
Object? selectedMap = null,
Object? testUrl = null,
}) {
return _then(_value.copyWith(
isPatch: null == isPatch
? _value.isPatch
: isPatch // ignore: cast_nullable_to_non_nullable
as bool,
isCompatible: null == isCompatible
? _value.isCompatible
: isCompatible // ignore: cast_nullable_to_non_nullable
as bool,
selectedMap: null == selectedMap
? _value.selectedMap
: selectedMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>,
testUrl: null == testUrl
? _value.testUrl
: testUrl // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$ConfigExtendedParamsImplCopyWith<$Res>
implements $ConfigExtendedParamsCopyWith<$Res> {
factory _$$ConfigExtendedParamsImplCopyWith(_$ConfigExtendedParamsImpl value,
$Res Function(_$ConfigExtendedParamsImpl) then) =
__$$ConfigExtendedParamsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{@JsonKey(name: "is-patch") bool isPatch,
@JsonKey(name: "is-compatible") bool isCompatible,
@JsonKey(name: "selected-map") Map<String, String> selectedMap,
@JsonKey(name: "test-url") String testUrl});
}
/// @nodoc
class __$$ConfigExtendedParamsImplCopyWithImpl<$Res>
extends _$ConfigExtendedParamsCopyWithImpl<$Res, _$ConfigExtendedParamsImpl>
implements _$$ConfigExtendedParamsImplCopyWith<$Res> {
__$$ConfigExtendedParamsImplCopyWithImpl(_$ConfigExtendedParamsImpl _value,
$Res Function(_$ConfigExtendedParamsImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isPatch = null,
Object? isCompatible = null,
Object? selectedMap = null,
Object? testUrl = null,
}) {
return _then(_$ConfigExtendedParamsImpl(
isPatch: null == isPatch
? _value.isPatch
: isPatch // ignore: cast_nullable_to_non_nullable
as bool,
isCompatible: null == isCompatible
? _value.isCompatible
: isCompatible // ignore: cast_nullable_to_non_nullable
as bool,
selectedMap: null == selectedMap
? _value._selectedMap
: selectedMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>,
testUrl: null == testUrl
? _value.testUrl
: testUrl // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$ConfigExtendedParamsImpl implements _ConfigExtendedParams {
const _$ConfigExtendedParamsImpl(
{@JsonKey(name: "is-patch") required this.isPatch,
@JsonKey(name: "is-compatible") required this.isCompatible,
@JsonKey(name: "selected-map")
required final Map<String, String> selectedMap,
@JsonKey(name: "test-url") required this.testUrl})
: _selectedMap = selectedMap;
factory _$ConfigExtendedParamsImpl.fromJson(Map<String, dynamic> json) =>
_$$ConfigExtendedParamsImplFromJson(json);
@override
@JsonKey(name: "is-patch")
final bool isPatch;
@override
@JsonKey(name: "is-compatible")
final bool isCompatible;
final Map<String, String> _selectedMap;
@override
@JsonKey(name: "selected-map")
Map<String, String> get selectedMap {
if (_selectedMap is EqualUnmodifiableMapView) return _selectedMap;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_selectedMap);
}
@override
@JsonKey(name: "test-url")
final String testUrl;
@override
String toString() {
return 'ConfigExtendedParams(isPatch: $isPatch, isCompatible: $isCompatible, selectedMap: $selectedMap, testUrl: $testUrl)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ConfigExtendedParamsImpl &&
(identical(other.isPatch, isPatch) || other.isPatch == isPatch) &&
(identical(other.isCompatible, isCompatible) ||
other.isCompatible == isCompatible) &&
const DeepCollectionEquality()
.equals(other._selectedMap, _selectedMap) &&
(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, isPatch, isCompatible,
const DeepCollectionEquality().hash(_selectedMap), testUrl);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ConfigExtendedParamsImplCopyWith<_$ConfigExtendedParamsImpl>
get copyWith =>
__$$ConfigExtendedParamsImplCopyWithImpl<_$ConfigExtendedParamsImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$ConfigExtendedParamsImplToJson(
this,
);
}
}
abstract class _ConfigExtendedParams implements ConfigExtendedParams {
const factory _ConfigExtendedParams(
{@JsonKey(name: "is-patch") required final bool isPatch,
@JsonKey(name: "is-compatible") required final bool isCompatible,
@JsonKey(name: "selected-map")
required final Map<String, String> selectedMap,
@JsonKey(name: "test-url") required final String testUrl}) =
_$ConfigExtendedParamsImpl;
factory _ConfigExtendedParams.fromJson(Map<String, dynamic> json) =
_$ConfigExtendedParamsImpl.fromJson;
@override
@JsonKey(name: "is-patch")
bool get isPatch;
@override
@JsonKey(name: "is-compatible")
bool get isCompatible;
@override
@JsonKey(name: "selected-map")
Map<String, String> get selectedMap;
@override
@JsonKey(name: "test-url")
String get testUrl;
@override
@JsonKey(ignore: true)
_$$ConfigExtendedParamsImplCopyWith<_$ConfigExtendedParamsImpl>
get copyWith => throw _privateConstructorUsedError;
}
UpdateConfigParams _$UpdateConfigParamsFromJson(Map<String, dynamic> json) { UpdateConfigParams _$UpdateConfigParamsFromJson(Map<String, dynamic> json) {
return _UpdateConfigParams.fromJson(json); return _UpdateConfigParams.fromJson(json);
} }
@@ -23,10 +251,7 @@ mixin _$UpdateConfigParams {
@JsonKey(name: "profile-path") @JsonKey(name: "profile-path")
String? get profilePath => throw _privateConstructorUsedError; String? get profilePath => throw _privateConstructorUsedError;
ClashConfig get config => throw _privateConstructorUsedError; ClashConfig get config => throw _privateConstructorUsedError;
@JsonKey(name: "is-patch") ConfigExtendedParams get params => throw _privateConstructorUsedError;
bool get isPatch => throw _privateConstructorUsedError;
@JsonKey(name: "is-compatible")
bool get isCompatible => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
@@ -43,8 +268,9 @@ abstract class $UpdateConfigParamsCopyWith<$Res> {
$Res call( $Res call(
{@JsonKey(name: "profile-path") String? profilePath, {@JsonKey(name: "profile-path") String? profilePath,
ClashConfig config, ClashConfig config,
@JsonKey(name: "is-patch") bool isPatch, ConfigExtendedParams params});
@JsonKey(name: "is-compatible") bool isCompatible});
$ConfigExtendedParamsCopyWith<$Res> get params;
} }
/// @nodoc /// @nodoc
@@ -62,8 +288,7 @@ class _$UpdateConfigParamsCopyWithImpl<$Res, $Val extends UpdateConfigParams>
$Res call({ $Res call({
Object? profilePath = freezed, Object? profilePath = freezed,
Object? config = null, Object? config = null,
Object? isPatch = null, Object? params = null,
Object? isCompatible = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
profilePath: freezed == profilePath profilePath: freezed == profilePath
@@ -74,16 +299,20 @@ class _$UpdateConfigParamsCopyWithImpl<$Res, $Val extends UpdateConfigParams>
? _value.config ? _value.config
: config // ignore: cast_nullable_to_non_nullable : config // ignore: cast_nullable_to_non_nullable
as ClashConfig, as ClashConfig,
isPatch: null == isPatch params: null == params
? _value.isPatch ? _value.params
: isPatch // ignore: cast_nullable_to_non_nullable : params // ignore: cast_nullable_to_non_nullable
as bool, as ConfigExtendedParams,
isCompatible: null == isCompatible
? _value.isCompatible
: isCompatible // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val); ) as $Val);
} }
@override
@pragma('vm:prefer-inline')
$ConfigExtendedParamsCopyWith<$Res> get params {
return $ConfigExtendedParamsCopyWith<$Res>(_value.params, (value) {
return _then(_value.copyWith(params: value) as $Val);
});
}
} }
/// @nodoc /// @nodoc
@@ -97,8 +326,10 @@ abstract class _$$UpdateConfigParamsImplCopyWith<$Res>
$Res call( $Res call(
{@JsonKey(name: "profile-path") String? profilePath, {@JsonKey(name: "profile-path") String? profilePath,
ClashConfig config, ClashConfig config,
@JsonKey(name: "is-patch") bool isPatch, ConfigExtendedParams params});
@JsonKey(name: "is-compatible") bool isCompatible});
@override
$ConfigExtendedParamsCopyWith<$Res> get params;
} }
/// @nodoc /// @nodoc
@@ -114,8 +345,7 @@ class __$$UpdateConfigParamsImplCopyWithImpl<$Res>
$Res call({ $Res call({
Object? profilePath = freezed, Object? profilePath = freezed,
Object? config = null, Object? config = null,
Object? isPatch = null, Object? params = null,
Object? isCompatible = null,
}) { }) {
return _then(_$UpdateConfigParamsImpl( return _then(_$UpdateConfigParamsImpl(
profilePath: freezed == profilePath profilePath: freezed == profilePath
@@ -126,14 +356,10 @@ class __$$UpdateConfigParamsImplCopyWithImpl<$Res>
? _value.config ? _value.config
: config // ignore: cast_nullable_to_non_nullable : config // ignore: cast_nullable_to_non_nullable
as ClashConfig, as ClashConfig,
isPatch: null == isPatch params: null == params
? _value.isPatch ? _value.params
: isPatch // ignore: cast_nullable_to_non_nullable : params // ignore: cast_nullable_to_non_nullable
as bool, as ConfigExtendedParams,
isCompatible: null == isCompatible
? _value.isCompatible
: isCompatible // ignore: cast_nullable_to_non_nullable
as bool,
)); ));
} }
} }
@@ -144,8 +370,7 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
const _$UpdateConfigParamsImpl( const _$UpdateConfigParamsImpl(
{@JsonKey(name: "profile-path") this.profilePath, {@JsonKey(name: "profile-path") this.profilePath,
required this.config, required this.config,
@JsonKey(name: "is-patch") required this.isPatch, required this.params});
@JsonKey(name: "is-compatible") required this.isCompatible});
factory _$UpdateConfigParamsImpl.fromJson(Map<String, dynamic> json) => factory _$UpdateConfigParamsImpl.fromJson(Map<String, dynamic> json) =>
_$$UpdateConfigParamsImplFromJson(json); _$$UpdateConfigParamsImplFromJson(json);
@@ -156,15 +381,11 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
@override @override
final ClashConfig config; final ClashConfig config;
@override @override
@JsonKey(name: "is-patch") final ConfigExtendedParams params;
final bool isPatch;
@override
@JsonKey(name: "is-compatible")
final bool isCompatible;
@override @override
String toString() { String toString() {
return 'UpdateConfigParams(profilePath: $profilePath, config: $config, isPatch: $isPatch, isCompatible: $isCompatible)'; return 'UpdateConfigParams(profilePath: $profilePath, config: $config, params: $params)';
} }
@override @override
@@ -175,15 +396,12 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
(identical(other.profilePath, profilePath) || (identical(other.profilePath, profilePath) ||
other.profilePath == profilePath) && other.profilePath == profilePath) &&
(identical(other.config, config) || other.config == config) && (identical(other.config, config) || other.config == config) &&
(identical(other.isPatch, isPatch) || other.isPatch == isPatch) && (identical(other.params, params) || other.params == params));
(identical(other.isCompatible, isCompatible) ||
other.isCompatible == isCompatible));
} }
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
int get hashCode => int get hashCode => Object.hash(runtimeType, profilePath, config, params);
Object.hash(runtimeType, profilePath, config, isPatch, isCompatible);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -202,11 +420,9 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams {
abstract class _UpdateConfigParams implements UpdateConfigParams { abstract class _UpdateConfigParams implements UpdateConfigParams {
const factory _UpdateConfigParams( const factory _UpdateConfigParams(
{@JsonKey(name: "profile-path") final String? profilePath, {@JsonKey(name: "profile-path") final String? profilePath,
required final ClashConfig config, required final ClashConfig config,
@JsonKey(name: "is-patch") required final bool isPatch, required final ConfigExtendedParams params}) = _$UpdateConfigParamsImpl;
@JsonKey(name: "is-compatible") required final bool isCompatible}) =
_$UpdateConfigParamsImpl;
factory _UpdateConfigParams.fromJson(Map<String, dynamic> json) = factory _UpdateConfigParams.fromJson(Map<String, dynamic> json) =
_$UpdateConfigParamsImpl.fromJson; _$UpdateConfigParamsImpl.fromJson;
@@ -217,11 +433,7 @@ abstract class _UpdateConfigParams implements UpdateConfigParams {
@override @override
ClashConfig get config; ClashConfig get config;
@override @override
@JsonKey(name: "is-patch") ConfigExtendedParams get params;
bool get isPatch;
@override
@JsonKey(name: "is-compatible")
bool get isCompatible;
@override @override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$UpdateConfigParamsImplCopyWith<_$UpdateConfigParamsImpl> get copyWith => _$$UpdateConfigParamsImplCopyWith<_$UpdateConfigParamsImpl> get copyWith =>
@@ -398,32 +610,34 @@ abstract class _ChangeProxyParams implements ChangeProxyParams {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
Message _$MessageFromJson(Map<String, dynamic> json) { AppMessage _$AppMessageFromJson(Map<String, dynamic> json) {
return _Message.fromJson(json); return _AppMessage.fromJson(json);
} }
/// @nodoc /// @nodoc
mixin _$Message { mixin _$AppMessage {
MessageType get type => throw _privateConstructorUsedError; AppMessageType get type => throw _privateConstructorUsedError;
dynamic get data => throw _privateConstructorUsedError; dynamic get data => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
$MessageCopyWith<Message> get copyWith => throw _privateConstructorUsedError; $AppMessageCopyWith<AppMessage> get copyWith =>
throw _privateConstructorUsedError;
} }
/// @nodoc /// @nodoc
abstract class $MessageCopyWith<$Res> { abstract class $AppMessageCopyWith<$Res> {
factory $MessageCopyWith(Message value, $Res Function(Message) then) = factory $AppMessageCopyWith(
_$MessageCopyWithImpl<$Res, Message>; AppMessage value, $Res Function(AppMessage) then) =
_$AppMessageCopyWithImpl<$Res, AppMessage>;
@useResult @useResult
$Res call({MessageType type, dynamic data}); $Res call({AppMessageType type, dynamic data});
} }
/// @nodoc /// @nodoc
class _$MessageCopyWithImpl<$Res, $Val extends Message> class _$AppMessageCopyWithImpl<$Res, $Val extends AppMessage>
implements $MessageCopyWith<$Res> { implements $AppMessageCopyWith<$Res> {
_$MessageCopyWithImpl(this._value, this._then); _$AppMessageCopyWithImpl(this._value, this._then);
// ignore: unused_field // ignore: unused_field
final $Val _value; final $Val _value;
@@ -440,7 +654,7 @@ class _$MessageCopyWithImpl<$Res, $Val extends Message>
type: null == type type: null == type
? _value.type ? _value.type
: type // ignore: cast_nullable_to_non_nullable : type // ignore: cast_nullable_to_non_nullable
as MessageType, as AppMessageType,
data: freezed == data data: freezed == data
? _value.data ? _value.data
: data // ignore: cast_nullable_to_non_nullable : data // ignore: cast_nullable_to_non_nullable
@@ -450,21 +664,22 @@ class _$MessageCopyWithImpl<$Res, $Val extends Message>
} }
/// @nodoc /// @nodoc
abstract class _$$MessageImplCopyWith<$Res> implements $MessageCopyWith<$Res> { abstract class _$$AppMessageImplCopyWith<$Res>
factory _$$MessageImplCopyWith( implements $AppMessageCopyWith<$Res> {
_$MessageImpl value, $Res Function(_$MessageImpl) then) = factory _$$AppMessageImplCopyWith(
__$$MessageImplCopyWithImpl<$Res>; _$AppMessageImpl value, $Res Function(_$AppMessageImpl) then) =
__$$AppMessageImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call({MessageType type, dynamic data}); $Res call({AppMessageType type, dynamic data});
} }
/// @nodoc /// @nodoc
class __$$MessageImplCopyWithImpl<$Res> class __$$AppMessageImplCopyWithImpl<$Res>
extends _$MessageCopyWithImpl<$Res, _$MessageImpl> extends _$AppMessageCopyWithImpl<$Res, _$AppMessageImpl>
implements _$$MessageImplCopyWith<$Res> { implements _$$AppMessageImplCopyWith<$Res> {
__$$MessageImplCopyWithImpl( __$$AppMessageImplCopyWithImpl(
_$MessageImpl _value, $Res Function(_$MessageImpl) _then) _$AppMessageImpl _value, $Res Function(_$AppMessageImpl) _then)
: super(_value, _then); : super(_value, _then);
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@@ -473,11 +688,11 @@ class __$$MessageImplCopyWithImpl<$Res>
Object? type = null, Object? type = null,
Object? data = freezed, Object? data = freezed,
}) { }) {
return _then(_$MessageImpl( return _then(_$AppMessageImpl(
type: null == type type: null == type
? _value.type ? _value.type
: type // ignore: cast_nullable_to_non_nullable : type // ignore: cast_nullable_to_non_nullable
as MessageType, as AppMessageType,
data: freezed == data data: freezed == data
? _value.data ? _value.data
: data // ignore: cast_nullable_to_non_nullable : data // ignore: cast_nullable_to_non_nullable
@@ -488,27 +703,27 @@ class __$$MessageImplCopyWithImpl<$Res>
/// @nodoc /// @nodoc
@JsonSerializable() @JsonSerializable()
class _$MessageImpl implements _Message { class _$AppMessageImpl implements _AppMessage {
const _$MessageImpl({required this.type, this.data}); const _$AppMessageImpl({required this.type, this.data});
factory _$MessageImpl.fromJson(Map<String, dynamic> json) => factory _$AppMessageImpl.fromJson(Map<String, dynamic> json) =>
_$$MessageImplFromJson(json); _$$AppMessageImplFromJson(json);
@override @override
final MessageType type; final AppMessageType type;
@override @override
final dynamic data; final dynamic data;
@override @override
String toString() { String toString() {
return 'Message(type: $type, data: $data)'; return 'AppMessage(type: $type, data: $data)';
} }
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$MessageImpl && other is _$AppMessageImpl &&
(identical(other.type, type) || other.type == type) && (identical(other.type, type) || other.type == type) &&
const DeepCollectionEquality().equals(other.data, data)); const DeepCollectionEquality().equals(other.data, data));
} }
@@ -521,30 +736,188 @@ class _$MessageImpl implements _Message {
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$MessageImplCopyWith<_$MessageImpl> get copyWith => _$$AppMessageImplCopyWith<_$AppMessageImpl> get copyWith =>
__$$MessageImplCopyWithImpl<_$MessageImpl>(this, _$identity); __$$AppMessageImplCopyWithImpl<_$AppMessageImpl>(this, _$identity);
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return _$$MessageImplToJson( return _$$AppMessageImplToJson(
this, this,
); );
} }
} }
abstract class _Message implements Message { abstract class _AppMessage implements AppMessage {
const factory _Message( const factory _AppMessage(
{required final MessageType type, final dynamic data}) = _$MessageImpl; {required final AppMessageType type,
final dynamic data}) = _$AppMessageImpl;
factory _Message.fromJson(Map<String, dynamic> json) = _$MessageImpl.fromJson; factory _AppMessage.fromJson(Map<String, dynamic> json) =
_$AppMessageImpl.fromJson;
@override @override
MessageType get type; AppMessageType get type;
@override @override
dynamic get data; dynamic get data;
@override @override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$MessageImplCopyWith<_$MessageImpl> get copyWith => _$$AppMessageImplCopyWith<_$AppMessageImpl> get copyWith =>
throw _privateConstructorUsedError;
}
ServiceMessage _$ServiceMessageFromJson(Map<String, dynamic> json) {
return _ServiceMessage.fromJson(json);
}
/// @nodoc
mixin _$ServiceMessage {
ServiceMessageType get type => throw _privateConstructorUsedError;
dynamic get data => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ServiceMessageCopyWith<ServiceMessage> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ServiceMessageCopyWith<$Res> {
factory $ServiceMessageCopyWith(
ServiceMessage value, $Res Function(ServiceMessage) then) =
_$ServiceMessageCopyWithImpl<$Res, ServiceMessage>;
@useResult
$Res call({ServiceMessageType type, dynamic data});
}
/// @nodoc
class _$ServiceMessageCopyWithImpl<$Res, $Val extends ServiceMessage>
implements $ServiceMessageCopyWith<$Res> {
_$ServiceMessageCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? type = null,
Object? data = freezed,
}) {
return _then(_value.copyWith(
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as ServiceMessageType,
data: freezed == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as dynamic,
) as $Val);
}
}
/// @nodoc
abstract class _$$ServiceMessageImplCopyWith<$Res>
implements $ServiceMessageCopyWith<$Res> {
factory _$$ServiceMessageImplCopyWith(_$ServiceMessageImpl value,
$Res Function(_$ServiceMessageImpl) then) =
__$$ServiceMessageImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({ServiceMessageType type, dynamic data});
}
/// @nodoc
class __$$ServiceMessageImplCopyWithImpl<$Res>
extends _$ServiceMessageCopyWithImpl<$Res, _$ServiceMessageImpl>
implements _$$ServiceMessageImplCopyWith<$Res> {
__$$ServiceMessageImplCopyWithImpl(
_$ServiceMessageImpl _value, $Res Function(_$ServiceMessageImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? type = null,
Object? data = freezed,
}) {
return _then(_$ServiceMessageImpl(
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as ServiceMessageType,
data: freezed == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as dynamic,
));
}
}
/// @nodoc
@JsonSerializable()
class _$ServiceMessageImpl implements _ServiceMessage {
const _$ServiceMessageImpl({required this.type, this.data});
factory _$ServiceMessageImpl.fromJson(Map<String, dynamic> json) =>
_$$ServiceMessageImplFromJson(json);
@override
final ServiceMessageType type;
@override
final dynamic data;
@override
String toString() {
return 'ServiceMessage(type: $type, data: $data)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ServiceMessageImpl &&
(identical(other.type, type) || other.type == type) &&
const DeepCollectionEquality().equals(other.data, data));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, type, const DeepCollectionEquality().hash(data));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ServiceMessageImplCopyWith<_$ServiceMessageImpl> get copyWith =>
__$$ServiceMessageImplCopyWithImpl<_$ServiceMessageImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$ServiceMessageImplToJson(
this,
);
}
}
abstract class _ServiceMessage implements ServiceMessage {
const factory _ServiceMessage(
{required final ServiceMessageType type,
final dynamic data}) = _$ServiceMessageImpl;
factory _ServiceMessage.fromJson(Map<String, dynamic> json) =
_$ServiceMessageImpl.fromJson;
@override
ServiceMessageType get type;
@override
dynamic get data;
@override
@JsonKey(ignore: true)
_$$ServiceMessageImplCopyWith<_$ServiceMessageImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -1006,6 +1379,151 @@ abstract class _Process implements Process {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
Fd _$FdFromJson(Map<String, dynamic> json) {
return _Fd.fromJson(json);
}
/// @nodoc
mixin _$Fd {
int get id => throw _privateConstructorUsedError;
int get value => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$FdCopyWith<Fd> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $FdCopyWith<$Res> {
factory $FdCopyWith(Fd value, $Res Function(Fd) then) =
_$FdCopyWithImpl<$Res, Fd>;
@useResult
$Res call({int id, int value});
}
/// @nodoc
class _$FdCopyWithImpl<$Res, $Val extends Fd> implements $FdCopyWith<$Res> {
_$FdCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? value = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
value: null == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$FdImplCopyWith<$Res> implements $FdCopyWith<$Res> {
factory _$$FdImplCopyWith(_$FdImpl value, $Res Function(_$FdImpl) then) =
__$$FdImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({int id, int value});
}
/// @nodoc
class __$$FdImplCopyWithImpl<$Res> extends _$FdCopyWithImpl<$Res, _$FdImpl>
implements _$$FdImplCopyWith<$Res> {
__$$FdImplCopyWithImpl(_$FdImpl _value, $Res Function(_$FdImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? value = null,
}) {
return _then(_$FdImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
value: null == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$FdImpl implements _Fd {
const _$FdImpl({required this.id, required this.value});
factory _$FdImpl.fromJson(Map<String, dynamic> json) =>
_$$FdImplFromJson(json);
@override
final int id;
@override
final int value;
@override
String toString() {
return 'Fd(id: $id, value: $value)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$FdImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.value, value) || other.value == value));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, id, value);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$FdImplCopyWith<_$FdImpl> get copyWith =>
__$$FdImplCopyWithImpl<_$FdImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$FdImplToJson(
this,
);
}
}
abstract class _Fd implements Fd {
const factory _Fd({required final int id, required final int value}) =
_$FdImpl;
factory _Fd.fromJson(Map<String, dynamic> json) = _$FdImpl.fromJson;
@override
int get id;
@override
int get value;
@override
@JsonKey(ignore: true)
_$$FdImplCopyWith<_$FdImpl> get copyWith =>
throw _privateConstructorUsedError;
}
ProcessMapItem _$ProcessMapItemFromJson(Map<String, dynamic> json) { ProcessMapItem _$ProcessMapItemFromJson(Map<String, dynamic> json) {
return _ProcessMapItem.fromJson(json); return _ProcessMapItem.fromJson(json);
} }

View File

@@ -6,13 +6,31 @@ part of '../ffi.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$ConfigExtendedParamsImpl _$$ConfigExtendedParamsImplFromJson(
Map<String, dynamic> json) =>
_$ConfigExtendedParamsImpl(
isPatch: json['is-patch'] as bool,
isCompatible: json['is-compatible'] as bool,
selectedMap: Map<String, String>.from(json['selected-map'] as Map),
testUrl: json['test-url'] as String,
);
Map<String, dynamic> _$$ConfigExtendedParamsImplToJson(
_$ConfigExtendedParamsImpl instance) =>
<String, dynamic>{
'is-patch': instance.isPatch,
'is-compatible': instance.isCompatible,
'selected-map': instance.selectedMap,
'test-url': instance.testUrl,
};
_$UpdateConfigParamsImpl _$$UpdateConfigParamsImplFromJson( _$UpdateConfigParamsImpl _$$UpdateConfigParamsImplFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
_$UpdateConfigParamsImpl( _$UpdateConfigParamsImpl(
profilePath: json['profile-path'] as String?, profilePath: json['profile-path'] as String?,
config: ClashConfig.fromJson(json['config'] as Map<String, dynamic>), config: ClashConfig.fromJson(json['config'] as Map<String, dynamic>),
isPatch: json['is-patch'] as bool, params:
isCompatible: json['is-compatible'] as bool, ConfigExtendedParams.fromJson(json['params'] as Map<String, dynamic>),
); );
Map<String, dynamic> _$$UpdateConfigParamsImplToJson( Map<String, dynamic> _$$UpdateConfigParamsImplToJson(
@@ -20,8 +38,7 @@ Map<String, dynamic> _$$UpdateConfigParamsImplToJson(
<String, dynamic>{ <String, dynamic>{
'profile-path': instance.profilePath, 'profile-path': instance.profilePath,
'config': instance.config, 'config': instance.config,
'is-patch': instance.isPatch, 'params': instance.params,
'is-compatible': instance.isCompatible,
}; };
_$ChangeProxyParamsImpl _$$ChangeProxyParamsImplFromJson( _$ChangeProxyParamsImpl _$$ChangeProxyParamsImplFromJson(
@@ -38,26 +55,44 @@ Map<String, dynamic> _$$ChangeProxyParamsImplToJson(
'proxy-name': instance.proxyName, 'proxy-name': instance.proxyName,
}; };
_$MessageImpl _$$MessageImplFromJson(Map<String, dynamic> json) => _$AppMessageImpl _$$AppMessageImplFromJson(Map<String, dynamic> json) =>
_$MessageImpl( _$AppMessageImpl(
type: $enumDecode(_$MessageTypeEnumMap, json['type']), type: $enumDecode(_$AppMessageTypeEnumMap, json['type']),
data: json['data'], data: json['data'],
); );
Map<String, dynamic> _$$MessageImplToJson(_$MessageImpl instance) => Map<String, dynamic> _$$AppMessageImplToJson(_$AppMessageImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'type': _$MessageTypeEnumMap[instance.type]!, 'type': _$AppMessageTypeEnumMap[instance.type]!,
'data': instance.data, 'data': instance.data,
}; };
const _$MessageTypeEnumMap = { const _$AppMessageTypeEnumMap = {
MessageType.log: 'log', AppMessageType.log: 'log',
MessageType.tun: 'tun', AppMessageType.delay: 'delay',
MessageType.delay: 'delay', AppMessageType.request: 'request',
MessageType.process: 'process', AppMessageType.started: 'started',
MessageType.now: 'now', AppMessageType.loaded: 'loaded',
MessageType.request: 'request', };
MessageType.run: 'run',
_$ServiceMessageImpl _$$ServiceMessageImplFromJson(Map<String, dynamic> json) =>
_$ServiceMessageImpl(
type: $enumDecode(_$ServiceMessageTypeEnumMap, json['type']),
data: json['data'],
);
Map<String, dynamic> _$$ServiceMessageImplToJson(
_$ServiceMessageImpl instance) =>
<String, dynamic>{
'type': _$ServiceMessageTypeEnumMap[instance.type]!,
'data': instance.data,
};
const _$ServiceMessageTypeEnumMap = {
ServiceMessageType.protect: 'protect',
ServiceMessageType.process: 'process',
ServiceMessageType.started: 'started',
ServiceMessageType.loaded: 'loaded',
}; };
_$DelayImpl _$$DelayImplFromJson(Map<String, dynamic> json) => _$DelayImpl( _$DelayImpl _$$DelayImplFromJson(Map<String, dynamic> json) => _$DelayImpl(
@@ -93,6 +128,16 @@ Map<String, dynamic> _$$ProcessImplToJson(_$ProcessImpl instance) =>
'metadata': instance.metadata, 'metadata': instance.metadata,
}; };
_$FdImpl _$$FdImplFromJson(Map<String, dynamic> json) => _$FdImpl(
id: (json['id'] as num).toInt(),
value: (json['value'] as num).toInt(),
);
Map<String, dynamic> _$$FdImplToJson(_$FdImpl instance) => <String, dynamic>{
'id': instance.id,
'value': instance.value,
};
_$ProcessMapItemImpl _$$ProcessMapItemImplFromJson(Map<String, dynamic> json) => _$ProcessMapItemImpl _$$ProcessMapItemImplFromJson(Map<String, dynamic> json) =>
_$ProcessMapItemImpl( _$ProcessMapItemImpl(
id: (json['id'] as num).toInt(), id: (json['id'] as num).toInt(),

View File

@@ -222,6 +222,7 @@ mixin _$Profile {
UserInfo? get userInfo => throw _privateConstructorUsedError; UserInfo? get userInfo => throw _privateConstructorUsedError;
bool get autoUpdate => throw _privateConstructorUsedError; bool get autoUpdate => throw _privateConstructorUsedError;
Map<String, String> get selectedMap => throw _privateConstructorUsedError; Map<String, String> get selectedMap => throw _privateConstructorUsedError;
Set<String> get unfoldSet => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
@@ -242,7 +243,8 @@ abstract class $ProfileCopyWith<$Res> {
Duration autoUpdateDuration, Duration autoUpdateDuration,
UserInfo? userInfo, UserInfo? userInfo,
bool autoUpdate, bool autoUpdate,
Map<String, String> selectedMap}); Map<String, String> selectedMap,
Set<String> unfoldSet});
$UserInfoCopyWith<$Res>? get userInfo; $UserInfoCopyWith<$Res>? get userInfo;
} }
@@ -269,6 +271,7 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
Object? userInfo = freezed, Object? userInfo = freezed,
Object? autoUpdate = null, Object? autoUpdate = null,
Object? selectedMap = null, Object? selectedMap = null,
Object? unfoldSet = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
id: null == id id: null == id
@@ -307,6 +310,10 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile>
? _value.selectedMap ? _value.selectedMap
: selectedMap // ignore: cast_nullable_to_non_nullable : selectedMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>, as Map<String, String>,
unfoldSet: null == unfoldSet
? _value.unfoldSet
: unfoldSet // ignore: cast_nullable_to_non_nullable
as Set<String>,
) as $Val); ) as $Val);
} }
@@ -339,7 +346,8 @@ abstract class _$$ProfileImplCopyWith<$Res> implements $ProfileCopyWith<$Res> {
Duration autoUpdateDuration, Duration autoUpdateDuration,
UserInfo? userInfo, UserInfo? userInfo,
bool autoUpdate, bool autoUpdate,
Map<String, String> selectedMap}); Map<String, String> selectedMap,
Set<String> unfoldSet});
@override @override
$UserInfoCopyWith<$Res>? get userInfo; $UserInfoCopyWith<$Res>? get userInfo;
@@ -365,6 +373,7 @@ class __$$ProfileImplCopyWithImpl<$Res>
Object? userInfo = freezed, Object? userInfo = freezed,
Object? autoUpdate = null, Object? autoUpdate = null,
Object? selectedMap = null, Object? selectedMap = null,
Object? unfoldSet = null,
}) { }) {
return _then(_$ProfileImpl( return _then(_$ProfileImpl(
id: null == id id: null == id
@@ -403,6 +412,10 @@ class __$$ProfileImplCopyWithImpl<$Res>
? _value._selectedMap ? _value._selectedMap
: selectedMap // ignore: cast_nullable_to_non_nullable : selectedMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>, as Map<String, String>,
unfoldSet: null == unfoldSet
? _value._unfoldSet
: unfoldSet // ignore: cast_nullable_to_non_nullable
as Set<String>,
)); ));
} }
} }
@@ -419,8 +432,10 @@ class _$ProfileImpl implements _Profile {
required this.autoUpdateDuration, required this.autoUpdateDuration,
this.userInfo, this.userInfo,
this.autoUpdate = true, this.autoUpdate = true,
final Map<String, String> selectedMap = const {}}) final Map<String, String> selectedMap = const {},
: _selectedMap = selectedMap; final Set<String> unfoldSet = const {}})
: _selectedMap = selectedMap,
_unfoldSet = unfoldSet;
factory _$ProfileImpl.fromJson(Map<String, dynamic> json) => factory _$ProfileImpl.fromJson(Map<String, dynamic> json) =>
_$$ProfileImplFromJson(json); _$$ProfileImplFromJson(json);
@@ -452,9 +467,18 @@ class _$ProfileImpl implements _Profile {
return EqualUnmodifiableMapView(_selectedMap); return EqualUnmodifiableMapView(_selectedMap);
} }
final Set<String> _unfoldSet;
@override
@JsonKey()
Set<String> get unfoldSet {
if (_unfoldSet is EqualUnmodifiableSetView) return _unfoldSet;
// ignore: implicit_dynamic_type
return EqualUnmodifiableSetView(_unfoldSet);
}
@override @override
String toString() { String toString() {
return 'Profile(id: $id, label: $label, currentGroupName: $currentGroupName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate, selectedMap: $selectedMap)'; return 'Profile(id: $id, label: $label, currentGroupName: $currentGroupName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate, selectedMap: $selectedMap, unfoldSet: $unfoldSet)';
} }
@override @override
@@ -476,7 +500,9 @@ class _$ProfileImpl implements _Profile {
(identical(other.autoUpdate, autoUpdate) || (identical(other.autoUpdate, autoUpdate) ||
other.autoUpdate == autoUpdate) && other.autoUpdate == autoUpdate) &&
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other._selectedMap, _selectedMap)); .equals(other._selectedMap, _selectedMap) &&
const DeepCollectionEquality()
.equals(other._unfoldSet, _unfoldSet));
} }
@JsonKey(ignore: true) @JsonKey(ignore: true)
@@ -491,7 +517,8 @@ class _$ProfileImpl implements _Profile {
autoUpdateDuration, autoUpdateDuration,
userInfo, userInfo,
autoUpdate, autoUpdate,
const DeepCollectionEquality().hash(_selectedMap)); const DeepCollectionEquality().hash(_selectedMap),
const DeepCollectionEquality().hash(_unfoldSet));
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -517,7 +544,8 @@ abstract class _Profile implements Profile {
required final Duration autoUpdateDuration, required final Duration autoUpdateDuration,
final UserInfo? userInfo, final UserInfo? userInfo,
final bool autoUpdate, final bool autoUpdate,
final Map<String, String> selectedMap}) = _$ProfileImpl; final Map<String, String> selectedMap,
final Set<String> unfoldSet}) = _$ProfileImpl;
factory _Profile.fromJson(Map<String, dynamic> json) = _$ProfileImpl.fromJson; factory _Profile.fromJson(Map<String, dynamic> json) = _$ProfileImpl.fromJson;
@@ -540,6 +568,8 @@ abstract class _Profile implements Profile {
@override @override
Map<String, String> get selectedMap; Map<String, String> get selectedMap;
@override @override
Set<String> get unfoldSet;
@override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$ProfileImplCopyWith<_$ProfileImpl> get copyWith => _$$ProfileImplCopyWith<_$ProfileImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;

View File

@@ -41,6 +41,10 @@ _$ProfileImpl _$$ProfileImplFromJson(Map<String, dynamic> json) =>
(k, e) => MapEntry(k, e as String), (k, e) => MapEntry(k, e as String),
) ?? ) ??
const {}, const {},
unfoldSet: (json['unfoldSet'] as List<dynamic>?)
?.map((e) => e as String)
.toSet() ??
const {},
); );
Map<String, dynamic> _$$ProfileImplToJson(_$ProfileImpl instance) => Map<String, dynamic> _$$ProfileImplToJson(_$ProfileImpl instance) =>
@@ -54,4 +58,5 @@ Map<String, dynamic> _$$ProfileImplToJson(_$ProfileImpl instance) =>
'userInfo': instance.userInfo, 'userInfo': instance.userInfo,
'autoUpdate': instance.autoUpdate, 'autoUpdate': instance.autoUpdate,
'selectedMap': instance.selectedMap, 'selectedMap': instance.selectedMap,
'unfoldSet': instance.unfoldSet.toList(),
}; };

View File

@@ -161,6 +161,7 @@ mixin _$CheckIpSelectorState {
bool get isInit => throw _privateConstructorUsedError; bool get isInit => throw _privateConstructorUsedError;
bool get isStart => throw _privateConstructorUsedError; bool get isStart => throw _privateConstructorUsedError;
Map<String, String> get selectedMap => throw _privateConstructorUsedError; Map<String, String> get selectedMap => throw _privateConstructorUsedError;
num get checkIpNum => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
$CheckIpSelectorStateCopyWith<CheckIpSelectorState> get copyWith => $CheckIpSelectorStateCopyWith<CheckIpSelectorState> get copyWith =>
@@ -173,7 +174,11 @@ abstract class $CheckIpSelectorStateCopyWith<$Res> {
$Res Function(CheckIpSelectorState) then) = $Res Function(CheckIpSelectorState) then) =
_$CheckIpSelectorStateCopyWithImpl<$Res, CheckIpSelectorState>; _$CheckIpSelectorStateCopyWithImpl<$Res, CheckIpSelectorState>;
@useResult @useResult
$Res call({bool isInit, bool isStart, Map<String, String> selectedMap}); $Res call(
{bool isInit,
bool isStart,
Map<String, String> selectedMap,
num checkIpNum});
} }
/// @nodoc /// @nodoc
@@ -193,6 +198,7 @@ class _$CheckIpSelectorStateCopyWithImpl<$Res,
Object? isInit = null, Object? isInit = null,
Object? isStart = null, Object? isStart = null,
Object? selectedMap = null, Object? selectedMap = null,
Object? checkIpNum = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
isInit: null == isInit isInit: null == isInit
@@ -207,6 +213,10 @@ class _$CheckIpSelectorStateCopyWithImpl<$Res,
? _value.selectedMap ? _value.selectedMap
: selectedMap // ignore: cast_nullable_to_non_nullable : selectedMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>, as Map<String, String>,
checkIpNum: null == checkIpNum
? _value.checkIpNum
: checkIpNum // ignore: cast_nullable_to_non_nullable
as num,
) as $Val); ) as $Val);
} }
} }
@@ -219,7 +229,11 @@ abstract class _$$CheckIpSelectorStateImplCopyWith<$Res>
__$$CheckIpSelectorStateImplCopyWithImpl<$Res>; __$$CheckIpSelectorStateImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call({bool isInit, bool isStart, Map<String, String> selectedMap}); $Res call(
{bool isInit,
bool isStart,
Map<String, String> selectedMap,
num checkIpNum});
} }
/// @nodoc /// @nodoc
@@ -236,6 +250,7 @@ class __$$CheckIpSelectorStateImplCopyWithImpl<$Res>
Object? isInit = null, Object? isInit = null,
Object? isStart = null, Object? isStart = null,
Object? selectedMap = null, Object? selectedMap = null,
Object? checkIpNum = null,
}) { }) {
return _then(_$CheckIpSelectorStateImpl( return _then(_$CheckIpSelectorStateImpl(
isInit: null == isInit isInit: null == isInit
@@ -250,6 +265,10 @@ class __$$CheckIpSelectorStateImplCopyWithImpl<$Res>
? _value._selectedMap ? _value._selectedMap
: selectedMap // ignore: cast_nullable_to_non_nullable : selectedMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>, as Map<String, String>,
checkIpNum: null == checkIpNum
? _value.checkIpNum
: checkIpNum // ignore: cast_nullable_to_non_nullable
as num,
)); ));
} }
} }
@@ -260,7 +279,8 @@ class _$CheckIpSelectorStateImpl implements _CheckIpSelectorState {
const _$CheckIpSelectorStateImpl( const _$CheckIpSelectorStateImpl(
{required this.isInit, {required this.isInit,
required this.isStart, required this.isStart,
required final Map<String, String> selectedMap}) required final Map<String, String> selectedMap,
required this.checkIpNum})
: _selectedMap = selectedMap; : _selectedMap = selectedMap;
@override @override
@@ -275,9 +295,12 @@ class _$CheckIpSelectorStateImpl implements _CheckIpSelectorState {
return EqualUnmodifiableMapView(_selectedMap); return EqualUnmodifiableMapView(_selectedMap);
} }
@override
final num checkIpNum;
@override @override
String toString() { String toString() {
return 'CheckIpSelectorState(isInit: $isInit, isStart: $isStart, selectedMap: $selectedMap)'; return 'CheckIpSelectorState(isInit: $isInit, isStart: $isStart, selectedMap: $selectedMap, checkIpNum: $checkIpNum)';
} }
@override @override
@@ -288,12 +311,14 @@ class _$CheckIpSelectorStateImpl implements _CheckIpSelectorState {
(identical(other.isInit, isInit) || other.isInit == isInit) && (identical(other.isInit, isInit) || other.isInit == isInit) &&
(identical(other.isStart, isStart) || other.isStart == isStart) && (identical(other.isStart, isStart) || other.isStart == isStart) &&
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other._selectedMap, _selectedMap)); .equals(other._selectedMap, _selectedMap) &&
(identical(other.checkIpNum, checkIpNum) ||
other.checkIpNum == checkIpNum));
} }
@override @override
int get hashCode => Object.hash(runtimeType, isInit, isStart, int get hashCode => Object.hash(runtimeType, isInit, isStart,
const DeepCollectionEquality().hash(_selectedMap)); const DeepCollectionEquality().hash(_selectedMap), checkIpNum);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@@ -306,10 +331,10 @@ class _$CheckIpSelectorStateImpl implements _CheckIpSelectorState {
abstract class _CheckIpSelectorState implements CheckIpSelectorState { abstract class _CheckIpSelectorState implements CheckIpSelectorState {
const factory _CheckIpSelectorState( const factory _CheckIpSelectorState(
{required final bool isInit, {required final bool isInit,
required final bool isStart, required final bool isStart,
required final Map<String, String> selectedMap}) = required final Map<String, String> selectedMap,
_$CheckIpSelectorStateImpl; required final num checkIpNum}) = _$CheckIpSelectorStateImpl;
@override @override
bool get isInit; bool get isInit;
@@ -318,6 +343,8 @@ abstract class _CheckIpSelectorState implements CheckIpSelectorState {
@override @override
Map<String, String> get selectedMap; Map<String, String> get selectedMap;
@override @override
num get checkIpNum;
@override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$CheckIpSelectorStateImplCopyWith<_$CheckIpSelectorStateImpl> _$$CheckIpSelectorStateImplCopyWith<_$CheckIpSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;
@@ -1730,39 +1757,37 @@ abstract class _ProxiesSelectorState implements ProxiesSelectorState {
} }
/// @nodoc /// @nodoc
mixin _$ProxiesTabViewSelectorState { mixin _$ProxyGroupSelectorState {
ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError; ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError;
ProxyCardType get proxyCardType => throw _privateConstructorUsedError;
num get sortNum => throw _privateConstructorUsedError; num get sortNum => throw _privateConstructorUsedError;
Group get group => throw _privateConstructorUsedError; List<Proxy> get proxies => throw _privateConstructorUsedError;
ViewMode get viewMode => throw _privateConstructorUsedError; int get columns => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
$ProxiesTabViewSelectorStateCopyWith<ProxiesTabViewSelectorState> $ProxyGroupSelectorStateCopyWith<ProxyGroupSelectorState> get copyWith =>
get copyWith => throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
/// @nodoc /// @nodoc
abstract class $ProxiesTabViewSelectorStateCopyWith<$Res> { abstract class $ProxyGroupSelectorStateCopyWith<$Res> {
factory $ProxiesTabViewSelectorStateCopyWith( factory $ProxyGroupSelectorStateCopyWith(ProxyGroupSelectorState value,
ProxiesTabViewSelectorState value, $Res Function(ProxyGroupSelectorState) then) =
$Res Function(ProxiesTabViewSelectorState) then) = _$ProxyGroupSelectorStateCopyWithImpl<$Res, ProxyGroupSelectorState>;
_$ProxiesTabViewSelectorStateCopyWithImpl<$Res,
ProxiesTabViewSelectorState>;
@useResult @useResult
$Res call( $Res call(
{ProxiesSortType proxiesSortType, {ProxiesSortType proxiesSortType,
ProxyCardType proxyCardType,
num sortNum, num sortNum,
Group group, List<Proxy> proxies,
ViewMode viewMode}); int columns});
$GroupCopyWith<$Res> get group;
} }
/// @nodoc /// @nodoc
class _$ProxiesTabViewSelectorStateCopyWithImpl<$Res, class _$ProxyGroupSelectorStateCopyWithImpl<$Res,
$Val extends ProxiesTabViewSelectorState> $Val extends ProxyGroupSelectorState>
implements $ProxiesTabViewSelectorStateCopyWith<$Res> { implements $ProxyGroupSelectorStateCopyWith<$Res> {
_$ProxiesTabViewSelectorStateCopyWithImpl(this._value, this._then); _$ProxyGroupSelectorStateCopyWithImpl(this._value, this._then);
// ignore: unused_field // ignore: unused_field
final $Val _value; final $Val _value;
@@ -1773,165 +1798,177 @@ class _$ProxiesTabViewSelectorStateCopyWithImpl<$Res,
@override @override
$Res call({ $Res call({
Object? proxiesSortType = null, Object? proxiesSortType = null,
Object? proxyCardType = null,
Object? sortNum = null, Object? sortNum = null,
Object? group = null, Object? proxies = null,
Object? viewMode = null, Object? columns = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
proxiesSortType: null == proxiesSortType proxiesSortType: null == proxiesSortType
? _value.proxiesSortType ? _value.proxiesSortType
: proxiesSortType // ignore: cast_nullable_to_non_nullable : proxiesSortType // ignore: cast_nullable_to_non_nullable
as ProxiesSortType, as ProxiesSortType,
proxyCardType: null == proxyCardType
? _value.proxyCardType
: proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,
sortNum: null == sortNum sortNum: null == sortNum
? _value.sortNum ? _value.sortNum
: sortNum // ignore: cast_nullable_to_non_nullable : sortNum // ignore: cast_nullable_to_non_nullable
as num, as num,
group: null == group proxies: null == proxies
? _value.group ? _value.proxies
: group // ignore: cast_nullable_to_non_nullable : proxies // ignore: cast_nullable_to_non_nullable
as Group, as List<Proxy>,
viewMode: null == viewMode columns: null == columns
? _value.viewMode ? _value.columns
: viewMode // ignore: cast_nullable_to_non_nullable : columns // ignore: cast_nullable_to_non_nullable
as ViewMode, as int,
) as $Val); ) as $Val);
} }
@override
@pragma('vm:prefer-inline')
$GroupCopyWith<$Res> get group {
return $GroupCopyWith<$Res>(_value.group, (value) {
return _then(_value.copyWith(group: value) as $Val);
});
}
} }
/// @nodoc /// @nodoc
abstract class _$$ProxiesTabViewSelectorStateImplCopyWith<$Res> abstract class _$$ProxyGroupSelectorStateImplCopyWith<$Res>
implements $ProxiesTabViewSelectorStateCopyWith<$Res> { implements $ProxyGroupSelectorStateCopyWith<$Res> {
factory _$$ProxiesTabViewSelectorStateImplCopyWith( factory _$$ProxyGroupSelectorStateImplCopyWith(
_$ProxiesTabViewSelectorStateImpl value, _$ProxyGroupSelectorStateImpl value,
$Res Function(_$ProxiesTabViewSelectorStateImpl) then) = $Res Function(_$ProxyGroupSelectorStateImpl) then) =
__$$ProxiesTabViewSelectorStateImplCopyWithImpl<$Res>; __$$ProxyGroupSelectorStateImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call( $Res call(
{ProxiesSortType proxiesSortType, {ProxiesSortType proxiesSortType,
ProxyCardType proxyCardType,
num sortNum, num sortNum,
Group group, List<Proxy> proxies,
ViewMode viewMode}); int columns});
@override
$GroupCopyWith<$Res> get group;
} }
/// @nodoc /// @nodoc
class __$$ProxiesTabViewSelectorStateImplCopyWithImpl<$Res> class __$$ProxyGroupSelectorStateImplCopyWithImpl<$Res>
extends _$ProxiesTabViewSelectorStateCopyWithImpl<$Res, extends _$ProxyGroupSelectorStateCopyWithImpl<$Res,
_$ProxiesTabViewSelectorStateImpl> _$ProxyGroupSelectorStateImpl>
implements _$$ProxiesTabViewSelectorStateImplCopyWith<$Res> { implements _$$ProxyGroupSelectorStateImplCopyWith<$Res> {
__$$ProxiesTabViewSelectorStateImplCopyWithImpl( __$$ProxyGroupSelectorStateImplCopyWithImpl(
_$ProxiesTabViewSelectorStateImpl _value, _$ProxyGroupSelectorStateImpl _value,
$Res Function(_$ProxiesTabViewSelectorStateImpl) _then) $Res Function(_$ProxyGroupSelectorStateImpl) _then)
: super(_value, _then); : super(_value, _then);
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? proxiesSortType = null, Object? proxiesSortType = null,
Object? proxyCardType = null,
Object? sortNum = null, Object? sortNum = null,
Object? group = null, Object? proxies = null,
Object? viewMode = null, Object? columns = null,
}) { }) {
return _then(_$ProxiesTabViewSelectorStateImpl( return _then(_$ProxyGroupSelectorStateImpl(
proxiesSortType: null == proxiesSortType proxiesSortType: null == proxiesSortType
? _value.proxiesSortType ? _value.proxiesSortType
: proxiesSortType // ignore: cast_nullable_to_non_nullable : proxiesSortType // ignore: cast_nullable_to_non_nullable
as ProxiesSortType, as ProxiesSortType,
proxyCardType: null == proxyCardType
? _value.proxyCardType
: proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,
sortNum: null == sortNum sortNum: null == sortNum
? _value.sortNum ? _value.sortNum
: sortNum // ignore: cast_nullable_to_non_nullable : sortNum // ignore: cast_nullable_to_non_nullable
as num, as num,
group: null == group proxies: null == proxies
? _value.group ? _value._proxies
: group // ignore: cast_nullable_to_non_nullable : proxies // ignore: cast_nullable_to_non_nullable
as Group, as List<Proxy>,
viewMode: null == viewMode columns: null == columns
? _value.viewMode ? _value.columns
: viewMode // ignore: cast_nullable_to_non_nullable : columns // ignore: cast_nullable_to_non_nullable
as ViewMode, as int,
)); ));
} }
} }
/// @nodoc /// @nodoc
class _$ProxiesTabViewSelectorStateImpl class _$ProxyGroupSelectorStateImpl implements _ProxyGroupSelectorState {
implements _ProxiesTabViewSelectorState { const _$ProxyGroupSelectorStateImpl(
const _$ProxiesTabViewSelectorStateImpl(
{required this.proxiesSortType, {required this.proxiesSortType,
required this.proxyCardType,
required this.sortNum, required this.sortNum,
required this.group, required final List<Proxy> proxies,
required this.viewMode}); required this.columns})
: _proxies = proxies;
@override @override
final ProxiesSortType proxiesSortType; final ProxiesSortType proxiesSortType;
@override @override
final ProxyCardType proxyCardType;
@override
final num sortNum; final num sortNum;
final List<Proxy> _proxies;
@override @override
final Group group; List<Proxy> get proxies {
if (_proxies is EqualUnmodifiableListView) return _proxies;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_proxies);
}
@override @override
final ViewMode viewMode; final int columns;
@override @override
String toString() { String toString() {
return 'ProxiesTabViewSelectorState(proxiesSortType: $proxiesSortType, sortNum: $sortNum, group: $group, viewMode: $viewMode)'; return 'ProxyGroupSelectorState(proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, proxies: $proxies, columns: $columns)';
} }
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$ProxiesTabViewSelectorStateImpl && other is _$ProxyGroupSelectorStateImpl &&
(identical(other.proxiesSortType, proxiesSortType) || (identical(other.proxiesSortType, proxiesSortType) ||
other.proxiesSortType == proxiesSortType) && other.proxiesSortType == proxiesSortType) &&
(identical(other.proxyCardType, proxyCardType) ||
other.proxyCardType == proxyCardType) &&
(identical(other.sortNum, sortNum) || other.sortNum == sortNum) && (identical(other.sortNum, sortNum) || other.sortNum == sortNum) &&
(identical(other.group, group) || other.group == group) && const DeepCollectionEquality().equals(other._proxies, _proxies) &&
(identical(other.viewMode, viewMode) || (identical(other.columns, columns) || other.columns == columns));
other.viewMode == viewMode));
} }
@override @override
int get hashCode => int get hashCode => Object.hash(runtimeType, proxiesSortType, proxyCardType,
Object.hash(runtimeType, proxiesSortType, sortNum, group, viewMode); sortNum, const DeepCollectionEquality().hash(_proxies), columns);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$ProxiesTabViewSelectorStateImplCopyWith<_$ProxiesTabViewSelectorStateImpl> _$$ProxyGroupSelectorStateImplCopyWith<_$ProxyGroupSelectorStateImpl>
get copyWith => __$$ProxiesTabViewSelectorStateImplCopyWithImpl< get copyWith => __$$ProxyGroupSelectorStateImplCopyWithImpl<
_$ProxiesTabViewSelectorStateImpl>(this, _$identity); _$ProxyGroupSelectorStateImpl>(this, _$identity);
} }
abstract class _ProxiesTabViewSelectorState abstract class _ProxyGroupSelectorState implements ProxyGroupSelectorState {
implements ProxiesTabViewSelectorState { const factory _ProxyGroupSelectorState(
const factory _ProxiesTabViewSelectorState(
{required final ProxiesSortType proxiesSortType, {required final ProxiesSortType proxiesSortType,
required final ProxyCardType proxyCardType,
required final num sortNum, required final num sortNum,
required final Group group, required final List<Proxy> proxies,
required final ViewMode viewMode}) = _$ProxiesTabViewSelectorStateImpl; required final int columns}) = _$ProxyGroupSelectorStateImpl;
@override @override
ProxiesSortType get proxiesSortType; ProxiesSortType get proxiesSortType;
@override @override
ProxyCardType get proxyCardType;
@override
num get sortNum; num get sortNum;
@override @override
Group get group; List<Proxy> get proxies;
@override @override
ViewMode get viewMode; int get columns;
@override @override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$ProxiesTabViewSelectorStateImplCopyWith<_$ProxiesTabViewSelectorStateImpl> _$$ProxyGroupSelectorStateImplCopyWith<_$ProxyGroupSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;
} }
@@ -2224,3 +2261,143 @@ abstract class _PackageListSelectorState implements PackageListSelectorState {
_$$PackageListSelectorStateImplCopyWith<_$PackageListSelectorStateImpl> _$$PackageListSelectorStateImplCopyWith<_$PackageListSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;
} }
/// @nodoc
mixin _$ColumnsSelectorState {
int get columns => throw _privateConstructorUsedError;
ViewMode get viewMode => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ColumnsSelectorStateCopyWith<ColumnsSelectorState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ColumnsSelectorStateCopyWith<$Res> {
factory $ColumnsSelectorStateCopyWith(ColumnsSelectorState value,
$Res Function(ColumnsSelectorState) then) =
_$ColumnsSelectorStateCopyWithImpl<$Res, ColumnsSelectorState>;
@useResult
$Res call({int columns, ViewMode viewMode});
}
/// @nodoc
class _$ColumnsSelectorStateCopyWithImpl<$Res,
$Val extends ColumnsSelectorState>
implements $ColumnsSelectorStateCopyWith<$Res> {
_$ColumnsSelectorStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? columns = null,
Object? viewMode = null,
}) {
return _then(_value.copyWith(
columns: null == columns
? _value.columns
: columns // ignore: cast_nullable_to_non_nullable
as int,
viewMode: null == viewMode
? _value.viewMode
: viewMode // ignore: cast_nullable_to_non_nullable
as ViewMode,
) as $Val);
}
}
/// @nodoc
abstract class _$$ColumnsSelectorStateImplCopyWith<$Res>
implements $ColumnsSelectorStateCopyWith<$Res> {
factory _$$ColumnsSelectorStateImplCopyWith(_$ColumnsSelectorStateImpl value,
$Res Function(_$ColumnsSelectorStateImpl) then) =
__$$ColumnsSelectorStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({int columns, ViewMode viewMode});
}
/// @nodoc
class __$$ColumnsSelectorStateImplCopyWithImpl<$Res>
extends _$ColumnsSelectorStateCopyWithImpl<$Res, _$ColumnsSelectorStateImpl>
implements _$$ColumnsSelectorStateImplCopyWith<$Res> {
__$$ColumnsSelectorStateImplCopyWithImpl(_$ColumnsSelectorStateImpl _value,
$Res Function(_$ColumnsSelectorStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? columns = null,
Object? viewMode = null,
}) {
return _then(_$ColumnsSelectorStateImpl(
columns: null == columns
? _value.columns
: columns // ignore: cast_nullable_to_non_nullable
as int,
viewMode: null == viewMode
? _value.viewMode
: viewMode // ignore: cast_nullable_to_non_nullable
as ViewMode,
));
}
}
/// @nodoc
class _$ColumnsSelectorStateImpl implements _ColumnsSelectorState {
const _$ColumnsSelectorStateImpl(
{required this.columns, required this.viewMode});
@override
final int columns;
@override
final ViewMode viewMode;
@override
String toString() {
return 'ColumnsSelectorState(columns: $columns, viewMode: $viewMode)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ColumnsSelectorStateImpl &&
(identical(other.columns, columns) || other.columns == columns) &&
(identical(other.viewMode, viewMode) ||
other.viewMode == viewMode));
}
@override
int get hashCode => Object.hash(runtimeType, columns, viewMode);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ColumnsSelectorStateImplCopyWith<_$ColumnsSelectorStateImpl>
get copyWith =>
__$$ColumnsSelectorStateImplCopyWithImpl<_$ColumnsSelectorStateImpl>(
this, _$identity);
}
abstract class _ColumnsSelectorState implements ColumnsSelectorState {
const factory _ColumnsSelectorState(
{required final int columns,
required final ViewMode viewMode}) = _$ColumnsSelectorStateImpl;
@override
int get columns;
@override
ViewMode get viewMode;
@override
@JsonKey(ignore: true)
_$$ColumnsSelectorStateImplCopyWith<_$ColumnsSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@@ -54,6 +54,7 @@ class Profile with _$Profile {
UserInfo? userInfo, UserInfo? userInfo,
@Default(true) bool autoUpdate, @Default(true) bool autoUpdate,
@Default({}) SelectedMap selectedMap, @Default({}) SelectedMap selectedMap,
@Default({}) Set<String> unfoldSet,
}) = _Profile; }) = _Profile;
factory Profile.fromJson(Map<String, Object?> json) => factory Profile.fromJson(Map<String, Object?> json) =>

View File

@@ -19,6 +19,7 @@ class CheckIpSelectorState with _$CheckIpSelectorState {
required bool isInit, required bool isInit,
required bool isStart, required bool isStart,
required SelectedMap selectedMap, required SelectedMap selectedMap,
required num checkIpNum
}) = _CheckIpSelectorState; }) = _CheckIpSelectorState;
} }
@@ -99,13 +100,14 @@ class ProxiesSelectorState with _$ProxiesSelectorState {
} }
@freezed @freezed
class ProxiesTabViewSelectorState with _$ProxiesTabViewSelectorState { class ProxyGroupSelectorState with _$ProxyGroupSelectorState {
const factory ProxiesTabViewSelectorState({ const factory ProxyGroupSelectorState({
required ProxiesSortType proxiesSortType, required ProxiesSortType proxiesSortType,
required ProxyCardType proxyCardType,
required num sortNum, required num sortNum,
required Group group, required List<Proxy> proxies,
required ViewMode viewMode, required int columns,
}) = _ProxiesTabViewSelectorState; }) = _ProxyGroupSelectorState;
} }
@freezed @freezed
@@ -122,3 +124,12 @@ class PackageListSelectorState with _$PackageListSelectorState {
required bool isAccessControl, required bool isAccessControl,
}) = _PackageListSelectorState; }) = _PackageListSelectorState;
} }
@freezed
class ColumnsSelectorState with _$ColumnsSelectorState {
const factory ColumnsSelectorState({
required int columns,
required ViewMode viewMode,
}) = _ColumnsSelectorState;
}

View File

@@ -1,24 +1,30 @@
import 'package:fl_clash/common/constant.dart'; import 'package:fl_clash/common/constant.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@immutable
class SystemColorSchemes { class SystemColorSchemes {
SystemColorSchemes({ final ColorScheme? lightColorScheme;
ColorScheme? lightColorScheme, final ColorScheme? darkColorScheme;
ColorScheme? darkColorScheme,
}) : lightColorScheme = lightColorScheme ?? const SystemColorSchemes({
ColorScheme.fromSeed(seedColor: defaultPrimaryColor), this.lightColorScheme,
darkColorScheme = darkColorScheme ?? this.darkColorScheme,
ColorScheme.fromSeed( });
seedColor: defaultPrimaryColor,
brightness: Brightness.dark,
);
ColorScheme lightColorScheme;
ColorScheme darkColorScheme;
getSystemColorSchemeForBrightness(Brightness? brightness) { getSystemColorSchemeForBrightness(Brightness? brightness) {
if (brightness != null && brightness == Brightness.dark) { if (brightness != null && brightness == Brightness.dark) {
return darkColorScheme; return darkColorScheme != null
? ColorScheme.fromSeed(
seedColor: darkColorScheme!.primary,
brightness: brightness,
)
: ColorScheme.fromSeed(
seedColor: defaultPrimaryColor,
brightness: brightness,
);
} }
return lightColorScheme; return lightColorScheme != null
? ColorScheme.fromSeed(seedColor: darkColorScheme!.primary)
: ColorScheme.fromSeed(seedColor: defaultPrimaryColor);
} }
} }

View File

@@ -14,6 +14,7 @@ class HomePage extends StatelessWidget {
const HomePage({super.key}); const HomePage({super.key});
_getNavigationBar({ _getNavigationBar({
required BuildContext context,
required ViewMode viewMode, required ViewMode viewMode,
required List<NavigationItem> navigationItems, required List<NavigationItem> navigationItems,
required int currentIndex, required int currentIndex,
@@ -34,6 +35,8 @@ class HomePage extends StatelessWidget {
} }
final extended = viewMode == ViewMode.desktop; final extended = viewMode == ViewMode.desktop;
return NavigationRail( return NavigationRail(
backgroundColor: context.colorScheme.surfaceContainer,
groupAlignment: -0.8,
destinations: navigationItems destinations: navigationItems
.map( .map(
(e) => NavigationRailDestination( (e) => NavigationRailDestination(
@@ -82,32 +85,37 @@ class HomePage extends StatelessWidget {
); );
final currentIndex = index == -1 ? 0 : index; final currentIndex = index == -1 ? 0 : index;
final navigationBar = _getNavigationBar( final navigationBar = _getNavigationBar(
context: context,
viewMode: viewMode, viewMode: viewMode,
navigationItems: navigationItems, navigationItems: navigationItems,
currentIndex: currentIndex, currentIndex: currentIndex,
); );
final bottomNavigationBar = final bottomNavigationBar =
viewMode == ViewMode.mobile ? navigationBar : null; viewMode == ViewMode.mobile ? navigationBar : null;
Widget body;
if (viewMode != ViewMode.mobile) { if (viewMode != ViewMode.mobile) {
body = Row( return Row(
children: [ children: [
navigationBar, navigationBar,
Expanded( Expanded(
flex: 1, flex: 1,
child: child!, child: CommonScaffold(
key: globalState.homeScaffoldKey,
title: Intl.message(
currentLabel,
),
body: child!,
bottomNavigationBar: bottomNavigationBar,
),
) )
], ],
); );
} else {
body = child!;
} }
return CommonScaffold( return CommonScaffold(
key: globalState.homeScaffoldKey, key: globalState.homeScaffoldKey,
title: Intl.message( title: Intl.message(
currentLabel, currentLabel,
), ),
body: body, body: child!,
bottomNavigationBar: bottomNavigationBar, bottomNavigationBar: bottomNavigationBar,
); );
}, },

Some files were not shown because too many files have changed in this diff Show More