Add sqlite store

Optimize android quick action

Optimize backup and restore

Optimize more details
This commit is contained in:
chen08209
2025-12-16 11:23:09 +08:00
parent 243b3037d9
commit 2fbb96f5c1
214 changed files with 15659 additions and 10751 deletions

11
.gitignore vendored
View File

@@ -21,7 +21,7 @@ migrate_working_dir/
# The .vscode folder contains launch configuration and tasks you configure in # The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line # VS Code which you may wish to be included in version control, so this line
# is commented out by default. # is commented out by default.
#.vscode/ .vscode/
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
**/doc/api/ **/doc/api/
@@ -41,6 +41,11 @@ app.*.symbols
# Obfuscation related # Obfuscation related
app.*.map.json app.*.map.json
#AI generated
CLAUDE.md
/.claude
# Android Studio will place build artifacts here # Android Studio will place build artifacts here
/android/app/debug /android/app/debug
/android/app/profile /android/app/profile
@@ -53,7 +58,6 @@ app.*.map.json
/android/core/**/cmake-build-*/ /android/core/**/cmake-build-*/
/android/core/**/jniLibs/ /android/core/**/jniLibs/
#FlClash #FlClash
/libclash/ /libclash/
/android/app/src/main/jniLibs/ /android/app/src/main/jniLibs/
@@ -61,3 +65,6 @@ app.*.map.json
/macos/**/Package.resolved /macos/**/Package.resolved
devtools_options.yaml devtools_options.yaml
# FVM Version Cache
.fvm/
.fvmrc

View File

@@ -1,11 +1,11 @@
# This file tracks properties of this Flutter project. # This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc. # Used by Flutter tool to assess capabilities and perform upgrades etc.
# #
# This file should be version controlled. # This file should be version controlled and should not be manually edited.
version: version:
revision: 796c8ef79279f9c774545b3771238c3098dbefab revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2"
channel: stable channel: "stable"
project_type: app project_type: app
@@ -13,26 +13,11 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
- platform: android
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: ios
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: linux
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: macos
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: web
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- platform: windows - platform: windows
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
# User provided section # User provided section

7
.run/main.dart.run.xml Normal file
View File

@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="additionalArgs" value="--dart-define-from-file env.json" />
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
<method v="2" />
</configuration>
</component>

View File

@@ -1,10 +0,0 @@
android_arm64:
dart ./setup.dart android --arch arm64
macos_arm64:
dart ./setup.dart macos --arch arm64
android_app:
dart ./setup.dart android
android_arm64_core:
dart ./setup.dart android --arch arm64 --out core
macos_arm64_core:
dart ./setup.dart macos --arch arm64 --out core

View File

@@ -64,16 +64,17 @@ android {
buildTypes { buildTypes {
debug { debug {
isMinifyEnabled = false isMinifyEnabled = false
applicationIdSuffix = ".debug" applicationIdSuffix = ".dev"
} }
release { release {
isMinifyEnabled = true isMinifyEnabled = true
isShrinkResources = true isShrinkResources = true
signingConfig = if (isRelease) { if (isRelease) {
signingConfigs.getByName("release") signingConfig = signingConfigs.getByName("release")
} else { } else {
signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
applicationIdSuffix = ".dev"
} }
proguardFiles( proguardFiles(

View File

@@ -41,6 +41,25 @@
"other_platform_oauth_client": [] "other_platform_oauth_client": []
} }
} }
},
{
"client_info": {
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
"android_client_info": {
"package_name": "com.follow.clash.dev"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "0"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
} }
] ]
} }

View File

@@ -1,13 +1,18 @@
package com.follow.clash package com.follow.clash
import android.app.Application
import android.content.Context.MODE_PRIVATE
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.widget.Toast
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import com.follow.clash.common.GlobalState import com.follow.clash.common.GlobalState
import com.follow.clash.models.SharedState
import com.google.gson.Gson
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
@@ -21,6 +26,30 @@ import kotlin.coroutines.resume
private const val ICON_TTL_DAYS = 1L private const val ICON_TTL_DAYS = 1L
val Application.sharedState: SharedState
get() {
try {
val sp = getSharedPreferences("FlutterSharedPreferences", MODE_PRIVATE)
val res = sp.getString("flutter.sharedState", "")
return Gson().fromJson(res, SharedState::class.java)
} catch (_: Exception) {
return SharedState()
}
}
private var lastToast: Toast? = null
fun Application.showToast(text: String?) {
Handler(Looper.getMainLooper()).post {
lastToast?.cancel()
lastToast = Toast.makeText(this, text, Toast.LENGTH_LONG).apply {
show()
}
}
}
suspend fun PackageManager.getPackageIconPath(packageName: String): String = suspend fun PackageManager.getPackageIconPath(packageName: String): String =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val cacheDir = GlobalState.application.cacheDir val cacheDir = GlobalState.application.cacheDir

View File

@@ -1,7 +1,6 @@
package com.follow.clash package com.follow.clash
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import com.follow.clash.common.GlobalState import com.follow.clash.common.GlobalState
import com.follow.clash.plugins.AppPlugin import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ServicePlugin import com.follow.clash.plugins.ServicePlugin
@@ -18,9 +17,6 @@ class MainActivity : FlutterActivity(),
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
lifecycleScope.launch {
State.destroyServiceEngine()
}
} }
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {

View File

@@ -1,5 +1,6 @@
package com.follow.clash package com.follow.clash
import com.follow.clash.common.GlobalState
import com.follow.clash.common.ServiceDelegate import com.follow.clash.common.ServiceDelegate
import com.follow.clash.common.formatString import com.follow.clash.common.formatString
import com.follow.clash.common.intent import com.follow.clash.common.intent
@@ -8,6 +9,7 @@ import com.follow.clash.service.ICallbackInterface
import com.follow.clash.service.IEventInterface import com.follow.clash.service.IEventInterface
import com.follow.clash.service.IRemoteInterface import com.follow.clash.service.IRemoteInterface
import com.follow.clash.service.IResultInterface import com.follow.clash.service.IResultInterface
import com.follow.clash.service.IVoidInterface
import com.follow.clash.service.RemoteService import com.follow.clash.service.RemoteService
import com.follow.clash.service.models.NotificationParams import com.follow.clash.service.models.NotificationParams
import com.follow.clash.service.models.VpnOptions import com.follow.clash.service.models.VpnOptions
@@ -40,7 +42,7 @@ object Service {
delegate.unbind() delegate.unbind()
} }
suspend fun invokeAction(data: String, cb: (result: String) -> Unit): Result<Unit> { suspend fun invokeAction(data: String, cb: ((result: String) -> Unit)?): Result<Unit> {
val res = mutableListOf<ByteArray>() val res = mutableListOf<ByteArray>()
return delegate.useService { return delegate.useService {
it.invokeAction( it.invokeAction(
@@ -51,13 +53,50 @@ object Service {
res.add(result ?: byteArrayOf()) res.add(result ?: byteArrayOf())
ack?.onAck() ack?.onAck()
if (isSuccess) { if (isSuccess) {
cb(res.formatString()) cb?.let { cb ->
cb(res.formatString())
}
} }
} }
}) })
} }
} }
suspend fun quickSetup(
initParamsString: String,
setupParamsString: String,
onStarted: (() -> Unit)?,
onResult: ((result: String) -> Unit)?,
): Result<Unit> {
val res = mutableListOf<ByteArray>()
return delegate.useService {
it.quickSetup(
initParamsString,
setupParamsString,
object : ICallbackInterface.Stub() {
override fun onResult(
result: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
) {
res.add(result ?: byteArrayOf())
ack?.onAck()
if (isSuccess) {
onResult?.let { cb ->
cb(res.formatString())
}
}
}
},
object : IVoidInterface.Stub() {
override fun invoke() {
onStarted?.let { onStarted ->
onStarted()
}
}
}
)
}
}
suspend fun setEventListener( suspend fun setEventListener(
cb: ((result: String?) -> Unit)? cb: ((result: String?) -> Unit)?
): Result<Unit> { ): Result<Unit> {
@@ -65,24 +104,24 @@ object Service {
return delegate.useService { return delegate.useService {
it.setEventListener( it.setEventListener(
when (cb != null) { when (cb != null) {
true -> object : IEventInterface.Stub() { true -> object : IEventInterface.Stub() {
override fun onEvent( override fun onEvent(
id: String, data: ByteArray?, isSuccess: Boolean, ack: IAckInterface? id: String, data: ByteArray?, isSuccess: Boolean, ack: IAckInterface?
) { ) {
if (results[id] == null) { if (results[id] == null) {
results[id] = mutableListOf() results[id] = mutableListOf()
} }
results[id]?.add(data ?: byteArrayOf()) results[id]?.add(data ?: byteArrayOf())
ack?.onAck() ack?.onAck()
if (isSuccess) { if (isSuccess) {
cb(results[id]?.formatString()) cb(results[id]?.formatString())
results.remove(id) results.remove(id)
}
} }
} }
}
false -> null false -> null
}) })
} }
} }
@@ -116,6 +155,7 @@ object Service {
try { try {
block(callback) block(callback)
} catch (e: Exception) { } catch (e: Exception) {
GlobalState.log("awaitIResultInterface $e")
if (continuation.isActive) { if (continuation.isActive) {
continuation.resumeWithException(e) continuation.resumeWithException(e)
} }

View File

@@ -1,18 +1,17 @@
package com.follow.clash package com.follow.clash
import android.net.VpnService
import com.follow.clash.common.GlobalState import com.follow.clash.common.GlobalState
import com.follow.clash.models.SharedState
import com.follow.clash.plugins.AppPlugin import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ServicePlugin
import com.follow.clash.plugins.TilePlugin import com.follow.clash.plugins.TilePlugin
import io.flutter.FlutterInjector import com.follow.clash.service.models.NotificationParams
import com.google.gson.Gson
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
enum class RunState { enum class RunState {
START, PENDING, STOP START, PENDING, STOP
@@ -25,20 +24,17 @@ object State {
var runTime: Long = 0 var runTime: Long = 0
var sharedState: SharedState = SharedState()
val runStateFlow: MutableStateFlow<RunState> = MutableStateFlow(RunState.STOP) val runStateFlow: MutableStateFlow<RunState> = MutableStateFlow(RunState.STOP)
var flutterEngine: FlutterEngine? = null var flutterEngine: FlutterEngine? = null
var serviceFlutterEngine: FlutterEngine? = null
val appPlugin: AppPlugin? val appPlugin: AppPlugin?
get() = flutterEngine?.plugin<AppPlugin>() ?: serviceFlutterEngine?.plugin<AppPlugin>() get() = flutterEngine?.plugin<AppPlugin>()
val servicePlugin: ServicePlugin?
get() = flutterEngine?.plugin<ServicePlugin>()
?: serviceFlutterEngine?.plugin<ServicePlugin>()
val tilePlugin: TilePlugin? val tilePlugin: TilePlugin?
get() = flutterEngine?.plugin<TilePlugin>() ?: serviceFlutterEngine?.plugin<TilePlugin>() get() = flutterEngine?.plugin<TilePlugin>()
suspend fun handleToggleAction() { suspend fun handleToggleAction() {
var action: (suspend () -> Unit)? var action: (suspend () -> Unit)?
@@ -77,7 +73,7 @@ object State {
if (flutterEngine != null) { if (flutterEngine != null) {
return return
} }
startServiceWithEngine() startServiceWithPref()
} }
} }
@@ -88,9 +84,10 @@ object State {
return return
} }
tilePlugin?.handleStop() tilePlugin?.handleStop()
if (flutterEngine != null || serviceFlutterEngine != null) { if (flutterEngine != null) {
return return
} }
GlobalState.application.showToast(sharedState.stopTip)
handleStopService() handleStopService()
} }
} }
@@ -106,72 +103,101 @@ object State {
startService() startService()
} }
fun handleStopService() { private fun startServiceWithPref() {
GlobalState.launch {
runLock.withLock {
if (runStateFlow.value != RunState.START) {
return@launch
}
runStateFlow.tryEmit(RunState.PENDING)
runTime = Service.stopService()
runStateFlow.tryEmit(RunState.STOP)
}
destroyServiceEngine()
}
}
suspend fun destroyServiceEngine() {
runLock.withLock {
GlobalState.log("Destroy service engine")
withContext(Dispatchers.Main) {
runCatching {
serviceFlutterEngine?.destroy()
serviceFlutterEngine = null
}
}
}
}
private fun startServiceWithEngine() {
GlobalState.launch { GlobalState.launch {
runLock.withLock { runLock.withLock {
if (runStateFlow.value != RunState.STOP) { if (runStateFlow.value != RunState.STOP) {
return@launch return@launch
} }
GlobalState.log("Create service engine") sharedState = GlobalState.application.sharedState
withContext(Dispatchers.Main) { setupAndStart()
serviceFlutterEngine?.destroy()
serviceFlutterEngine = FlutterEngine(GlobalState.application)
serviceFlutterEngine?.plugins?.add(ServicePlugin())
serviceFlutterEngine?.plugins?.add(AppPlugin())
serviceFlutterEngine?.plugins?.add(TilePlugin())
val dartEntrypoint = DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(), "_service"
)
serviceFlutterEngine?.dartExecutor?.executeDartEntrypoint(dartEntrypoint)
}
} }
} }
} }
suspend fun syncState() {
GlobalState.setCrashlytics(sharedState.crashlytics)
Service.updateNotificationParams(
NotificationParams(
title = sharedState.currentProfileName,
stopText = sharedState.stopText,
onlyStatisticsProxy = sharedState.onlyStatisticsProxy
)
)
Service.setCrashlytics(sharedState.crashlytics)
}
private suspend fun setupAndStart() {
Service.bind()
syncState()
GlobalState.application.showToast(sharedState.startTip)
val initParams = mutableMapOf<String, Any>()
initParams["home-dir"] = GlobalState.application.filesDir.path
initParams["version"] = android.os.Build.VERSION.SDK_INT
val initParamsString = Gson().toJson(initParams)
val setupParamsString = Gson().toJson(sharedState.setupParams)
Service.quickSetup(
initParamsString,
setupParamsString,
onStarted = {
startService()
},
onResult = {
if (it.isNotEmpty()) {
GlobalState.application.showToast(it)
}
},
)
}
private fun startService() { private fun startService() {
GlobalState.launch { GlobalState.launch {
runLock.withLock { runLock.withLock {
if (runStateFlow.value != RunState.STOP) { if (runStateFlow.value != RunState.STOP) {
return@launch return@launch
} }
runStateFlow.tryEmit(RunState.PENDING) try {
if (servicePlugin == null) { runStateFlow.tryEmit(RunState.PENDING)
return@launch val options = sharedState.vpnOptions ?: return@launch
} appPlugin?.let {
val options = servicePlugin?.handleGetVpnOptions() ?: return@launch it.prepare(options.enable) {
appPlugin?.prepare(options.enable) { runTime = Service.startService(options, runTime)
runTime = Service.startService(options, runTime) runStateFlow.tryEmit(RunState.START)
runStateFlow.tryEmit(RunState.START) }
} ?: run {
val intent = VpnService.prepare(GlobalState.application)
if (intent != null) {
return@launch
}
runTime = Service.startService(options, runTime)
runStateFlow.tryEmit(RunState.START)
}
} finally {
if (runStateFlow.value == RunState.PENDING) {
runStateFlow.tryEmit(RunState.STOP)
}
} }
} }
} }
}
fun handleStopService() {
GlobalState.launch {
runLock.withLock {
if (runStateFlow.value != RunState.START) {
return@launch
}
try {
runStateFlow.tryEmit(RunState.PENDING)
runTime = Service.stopService()
runStateFlow.tryEmit(RunState.STOP)
} finally {
if (runStateFlow.value == RunState.PENDING) {
runStateFlow.tryEmit(RunState.START)
}
}
}
}
} }
} }

View File

@@ -1,9 +1,22 @@
package com.follow.clash.models package com.follow.clash.models
import com.follow.clash.service.models.VpnOptions
import com.google.gson.annotations.SerializedName
data class AppState( data class SharedState(
val startTip: String = "Starting VPN...",
val stopTip: String = "Stopping VPN...",
val crashlytics: Boolean = true, val crashlytics: Boolean = true,
val currentProfileName: String = "FlClash", val currentProfileName: String = "FlClash",
val stopText: String = "Stop", val stopText: String = "Stop",
val onlyStatisticsProxy: Boolean = false, val onlyStatisticsProxy: Boolean = false,
val vpnOptions: VpnOptions? = null,
val setupParams: SetupParams? = null,
)
data class SetupParams(
@SerializedName("test-url")
val testUrl: String,
@SerializedName("selected-map")
val selectedMap: Map<String, String>,
) )

View File

@@ -9,7 +9,6 @@ import android.content.pm.ComponentInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.VpnService import android.net.VpnService
import android.os.Build import android.os.Build
import android.widget.Toast
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
@@ -24,6 +23,7 @@ import com.follow.clash.common.QuickAction
import com.follow.clash.common.quickIntent import com.follow.clash.common.quickIntent
import com.follow.clash.getPackageIconPath import com.follow.clash.getPackageIconPath
import com.follow.clash.models.Package import com.follow.clash.models.Package
import com.follow.clash.showToast
import com.google.gson.Gson import com.google.gson.Gson
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
@@ -193,7 +193,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
} }
private fun tip(message: String?) { private fun tip(message: String?) {
Toast.makeText(GlobalState.application, message, Toast.LENGTH_LONG).show() GlobalState.application.showToast(message)
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")

View File

@@ -3,13 +3,9 @@ package com.follow.clash.plugins
import com.follow.clash.RunState import com.follow.clash.RunState
import com.follow.clash.Service import com.follow.clash.Service
import com.follow.clash.State import com.follow.clash.State
import com.follow.clash.awaitResult
import com.follow.clash.common.Components import com.follow.clash.common.Components
import com.follow.clash.common.GlobalState
import com.follow.clash.invokeMethodOnMainThread import com.follow.clash.invokeMethodOnMainThread
import com.follow.clash.models.AppState import com.follow.clash.models.SharedState
import com.follow.clash.service.models.NotificationParams
import com.follow.clash.service.models.VpnOptions
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.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
@@ -38,7 +34,7 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) {
"init" -> { "init" -> {
handleInit(call, result) handleInit(result)
} }
"shutdown" -> { "shutdown" -> {
@@ -94,11 +90,6 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
result.success(true) result.success(true)
} }
suspend fun handleGetVpnOptions(): VpnOptions? {
val res = flutterMethodChannel.awaitResult<String>("getVpnOptions", null)
return Gson().fromJson(res, VpnOptions::class.java)
}
val semaphore = Semaphore(10) val semaphore = Semaphore(10)
fun handleSendEvent(value: String?) { fun handleSendEvent(value: String?) {
@@ -116,31 +107,19 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
private fun handleSyncState(call: MethodCall, result: MethodChannel.Result) { private fun handleSyncState(call: MethodCall, result: MethodChannel.Result) {
val data = call.arguments<String>()!! val data = call.arguments<String>()!!
val params = Gson().fromJson(data, AppState::class.java) State.sharedState = Gson().fromJson(data, SharedState::class.java)
GlobalState.setCrashlytics(params.crashlytics)
launch { launch {
Service.updateNotificationParams( State.syncState()
NotificationParams(
title = params.currentProfileName,
stopText = params.stopText,
onlyStatisticsProxy = params.onlyStatisticsProxy
)
)
Service.setCrashlytics(params.crashlytics)
result.success("") result.success("")
} }
} }
fun handleInit(call: MethodCall, result: MethodChannel.Result) {
fun handleInit(result: MethodChannel.Result) {
Service.bind() Service.bind()
launch { launch {
val needSetEventListener = call.arguments<Boolean>() ?: false Service.setEventListener {
when (needSetEventListener) { handleSendEvent(it)
true -> Service.setEventListener {
handleSendEvent(it)
}
false -> Service.setEventListener(null)
}.onSuccess { }.onSuccess {
result.success("") result.success("")
}.onFailure { }.onFailure {

View File

@@ -1,19 +1,10 @@
buildscript {
dependencies {
classpath(libs.build.kotlin)
}
}
plugins {
id("com.android.library") apply false
}
allprojects { allprojects {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
} }
val newBuildDir: Directory = val newBuildDir: Directory =
rootProject.layout.buildDirectory rootProject.layout.buildDirectory
.dir("../../build") .dir("../../build")
@@ -31,4 +22,3 @@ subprojects {
tasks.register<Delete>("clean") { tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory) delete(rootProject.layout.buildDirectory)
} }

View File

@@ -70,6 +70,14 @@ Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean su
suspend(suspended); suspend(suspended);
} }
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_quickSetup(JNIEnv *env, jobject thiz, jstring init_params_string,
jstring setup_params_string, jobject cb) {
const auto interface = new_global(cb);
quickSetup(interface, get_string(init_params_string), get_string(setup_params_string));
}
static jmethodID m_tun_interface_protect; static jmethodID m_tun_interface_protect;
static jmethodID m_tun_interface_resolve_process; static jmethodID m_tun_interface_resolve_process;
@@ -99,12 +107,12 @@ call_tun_interface_resolve_process_impl(void *tun_interface, const int protocol,
const int uid) { const int uid) {
ATTACH_JNI(); ATTACH_JNI();
const auto packageName = reinterpret_cast<jstring>(env->CallObjectMethod( const auto packageName = reinterpret_cast<jstring>(env->CallObjectMethod(
static_cast<jobject>(tun_interface), static_cast<jobject>(tun_interface),
m_tun_interface_resolve_process, m_tun_interface_resolve_process,
protocol, protocol,
new_string(source), new_string(source),
new_string(target), new_string(target),
uid)); uid));
return get_string(packageName); return get_string(packageName);
} }
@@ -191,4 +199,10 @@ extern "C"
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean suspended) { Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean suspended) {
} }
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_quickSetup(JNIEnv *env, jobject thiz, jstring init_params_string,
jstring setup_params_string, jobject cb) {
}
#endif #endif

View File

@@ -102,6 +102,28 @@ data object Core {
} }
} }
fun quickSetup(
initParamsString: String,
setupParamsString: String,
cb: (result: String?) -> Unit,
) {
quickSetup(
initParamsString,
setupParamsString,
object : InvokeInterface {
override fun onResult(result: String?) {
cb(result)
}
},
)
}
private external fun quickSetup(
initParamsString: String,
setupParamsString: String,
cb: InvokeInterface
)
external fun stopTun() external fun stopTun()
external fun getTraffic(onlyStatisticsProxy: Boolean): String external fun getTraffic(onlyStatisticsProxy: Boolean): String

View File

@@ -4,11 +4,13 @@ package com.follow.clash.service;
import com.follow.clash.service.ICallbackInterface; import com.follow.clash.service.ICallbackInterface;
import com.follow.clash.service.IEventInterface; import com.follow.clash.service.IEventInterface;
import com.follow.clash.service.IResultInterface; import com.follow.clash.service.IResultInterface;
import com.follow.clash.service.IVoidInterface;
import com.follow.clash.service.models.VpnOptions; import com.follow.clash.service.models.VpnOptions;
import com.follow.clash.service.models.NotificationParams; import com.follow.clash.service.models.NotificationParams;
interface IRemoteInterface { interface IRemoteInterface {
void invokeAction(in String data, in ICallbackInterface callback); void invokeAction(in String data, in ICallbackInterface callback);
void quickSetup(in String initParamsString, in String setupParamsString, in ICallbackInterface callback, in IVoidInterface onStarted);
void updateNotificationParams(in NotificationParams params); void updateNotificationParams(in NotificationParams params);
void startService(in VpnOptions options, in long runTime, in IResultInterface result); void startService(in VpnOptions options, in long runTime, in IResultInterface result);
void stopService(in IResultInterface result); void stopService(in IResultInterface result);

View File

@@ -0,0 +1,6 @@
// IVoidInterface.aidl
package com.follow.clash.service;
interface IVoidInterface {
oneway void invoke();
}

View File

@@ -98,6 +98,35 @@ class RemoteService : Service(),
} }
} }
override fun quickSetup(
initParamsString: String,
setupParamsString: String,
callback: ICallbackInterface,
onStarted: IVoidInterface
) {
Core.quickSetup(initParamsString, setupParamsString) {
launch {
runCatching {
val chunks = it?.chunkedForAidl() ?: listOf()
for ((index, chunk) in chunks.withIndex()) {
suspendCancellableCoroutine { cont ->
callback.onResult(
chunk,
index == chunks.lastIndex,
object : IAckInterface.Stub() {
override fun onAck() {
cont.resume(Unit)
}
},
)
}
}
}
}
}
onStarted()
}
override fun updateNotificationParams(params: NotificationParams?) { override fun updateNotificationParams(params: NotificationParams?) {
State.notificationParamsFlow.tryEmit(params) State.notificationParamsFlow.tryEmit(params)
} }
@@ -108,6 +137,7 @@ class RemoteService : Service(),
runtime: Long, runtime: Long,
result: IResultInterface, result: IResultInterface,
) { ) {
GlobalState.log("remote startService")
State.options = options State.options = options
handleStartService(runtime, result) handleStartService(runtime, result)
} }

View File

@@ -187,7 +187,7 @@ class VpnService : SystemVpnService(), IBaseService,
addDnsServer(DNS6) addDnsServer(DNS6)
} }
setMtu(9000) setMtu(9000)
options.accessControl.let { accessControl -> options.accessControlProps.let { accessControl ->
if (accessControl.enable) { if (accessControl.enable) {
when (accessControl.mode) { when (accessControl.mode) {
AccessControlMode.ACCEPT_SELECTED -> { AccessControlMode.ACCEPT_SELECTED -> {

View File

@@ -6,7 +6,7 @@ import kotlinx.parcelize.Parcelize
import java.net.InetAddress import java.net.InetAddress
@Parcelize @Parcelize
data class AccessControl( data class AccessControlProps(
val enable: Boolean, val enable: Boolean,
val mode: AccessControlMode, val mode: AccessControlMode,
val acceptList: List<String>, val acceptList: List<String>,
@@ -19,7 +19,7 @@ data class VpnOptions(
val port: Int, val port: Int,
val ipv6: Boolean, val ipv6: Boolean,
val dnsHijacking: Boolean, val dnsHijacking: Boolean,
val accessControl: AccessControl, val accessControlProps: AccessControlProps,
val allowBypass: Boolean, val allowBypass: Boolean,
val systemProxy: Boolean, val systemProxy: Boolean,
val bypassDomain: List<String>, val bypassDomain: List<String>,

View File

@@ -1,11 +1,12 @@
pluginManagement { pluginManagement {
val flutterSdkPath = run { val flutterSdkPath =
val properties = java.util.Properties() run {
file("local.properties").inputStream().use { properties.load(it) } val properties = java.util.Properties()
val flutterSdkPath = properties.getProperty("flutter.sdk") file("local.properties").inputStream().use { properties.load(it) }
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } val flutterSdkPath = properties.getProperty("flutter.sdk")
flutterSdkPath require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
} flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")

View File

@@ -129,14 +129,8 @@
"compatibleDesc": "Opening it will lose part of its application ability and gain the support of full amount of Clash.", "compatibleDesc": "Opening it will lose part of its application ability and gain the support of full amount of Clash.",
"notSelectedTip": "The current proxy group cannot be selected.", "notSelectedTip": "The current proxy group cannot be selected.",
"tip": "tip", "tip": "tip",
"backupAndRecovery": "Backup and Recovery",
"backupAndRecoveryDesc": "Sync data via WebDAV or file",
"account": "Account", "account": "Account",
"backup": "Backup", "backup": "Backup",
"recovery": "Recovery",
"recoveryProfiles": "Only recovery profiles",
"recoveryAll": "Recovery all data",
"recoverySuccess": "Recovery success",
"backupSuccess": "Backup success", "backupSuccess": "Backup success",
"noInfo": "No info", "noInfo": "No info",
"pleaseBindWebDAV": "Please bind WebDAV", "pleaseBindWebDAV": "Please bind WebDAV",
@@ -219,9 +213,7 @@
"local": "Local", "local": "Local",
"remote": "Remote", "remote": "Remote",
"remoteBackupDesc": "Backup local data to WebDAV", "remoteBackupDesc": "Backup local data to WebDAV",
"remoteRecoveryDesc": "Recovery data from WebDAV",
"localBackupDesc": "Backup local data to local", "localBackupDesc": "Backup local data to local",
"localRecoveryDesc": "Recovery data from file",
"mode": "Mode", "mode": "Mode",
"time": "Time", "time": "Time",
"source": "Source", "source": "Source",
@@ -381,9 +373,9 @@
"systemApp": "System APP", "systemApp": "System APP",
"noNetworkApp": "No network APP", "noNetworkApp": "No network APP",
"contactMe": "Contact me", "contactMe": "Contact me",
"recoveryStrategy": "Recovery strategy", "restoreStrategy": "Restore strategy",
"recoveryStrategy_override": "Override", "restoreStrategy_override": "Override",
"recoveryStrategy_compatible": "Compatible", "restoreStrategy_compatible": "Compatible",
"logsTest": "Logs test", "logsTest": "Logs test",
"emptyTip": "{label} cannot be empty", "emptyTip": "{label} cannot be empty",
"urlTip": "{label} must be a url", "urlTip": "{label} must be a url",
@@ -466,5 +458,23 @@
"vpnConfigChangeDetected": "VPN configuration change detected", "vpnConfigChangeDetected": "VPN configuration change detected",
"restart": "Restart", "restart": "Restart",
"speedStatistics": "Speed statistics", "speedStatistics": "Speed statistics",
"resetPageChangesTip": "The current page has changes. Are you sure you want to reset?" "resetPageChangesTip": "The current page has changes. Are you sure you want to reset?",
"overwriteTypeCustom": "Custom",
"overwriteTypeCustomDesc": "Custom mode, fully customize proxy groups and rules",
"unknownNetworkError": "Unknown network error",
"networkRequestException": "Network request exception, please try again later.",
"restoreException": "Recovery exception",
"networkException": "Network exception, please check your connection and try again",
"invalidBackupFile": "Invalid backup file",
"pruneCache": "Prune cache",
"backupAndRestore": "Backup and Restore",
"backupAndRestoreDesc": "Sync data via WebDAV or files",
"restore": "Restore",
"restoreSuccess": "Restore success",
"restoreFromWebDAVDesc": "Restore data via WebDAV",
"restoreFromFileDesc": "Restore data via file",
"restoreOnlyConfig": "Restore configuration files only",
"restoreAllData": "Restore all data",
"addProfile": "Add Profile",
"delayTest": "Delay Test"
} }

View File

@@ -129,14 +129,8 @@
"compatibleDesc": "有効化すると一部機能を失いますが、Clashの完全サポートを獲得", "compatibleDesc": "有効化すると一部機能を失いますが、Clashの完全サポートを獲得",
"notSelectedTip": "現在のプロキシグループは選択できません", "notSelectedTip": "現在のプロキシグループは選択できません",
"tip": "ヒント", "tip": "ヒント",
"backupAndRecovery": "バックアップと復元",
"backupAndRecoveryDesc": "WebDAVまたはファイルでデータを同期",
"account": "アカウント", "account": "アカウント",
"backup": "バックアップ", "backup": "バックアップ",
"recovery": "復元",
"recoveryProfiles": "プロファイルのみ復元",
"recoveryAll": "全データ復元",
"recoverySuccess": "復元成功",
"backupSuccess": "バックアップ成功", "backupSuccess": "バックアップ成功",
"noInfo": "情報なし", "noInfo": "情報なし",
"pleaseBindWebDAV": "WebDAVをバインドしてください", "pleaseBindWebDAV": "WebDAVをバインドしてください",
@@ -219,9 +213,7 @@
"local": "ローカル", "local": "ローカル",
"remote": "リモート", "remote": "リモート",
"remoteBackupDesc": "WebDAVにデータをバックアップ", "remoteBackupDesc": "WebDAVにデータをバックアップ",
"remoteRecoveryDesc": "WebDAVからデータを復元",
"localBackupDesc": "ローカルにデータをバックアップ", "localBackupDesc": "ローカルにデータをバックアップ",
"localRecoveryDesc": "ファイルからデータを復元",
"mode": "モード", "mode": "モード",
"time": "時間", "time": "時間",
"source": "ソース", "source": "ソース",
@@ -382,9 +374,9 @@
"systemApp": "システムアプリ", "systemApp": "システムアプリ",
"noNetworkApp": "ネットワークなしアプリ", "noNetworkApp": "ネットワークなしアプリ",
"contactMe": "連絡する", "contactMe": "連絡する",
"recoveryStrategy": "リカバリー戦略", "restoreStrategy": "復元ストラテジー",
"recoveryStrategy_override": "オーバーライド", "restoreStrategy_override": "上書き",
"recoveryStrategy_compatible": "互換", "restoreStrategy_compatible": "互換",
"logsTest": "ログテスト", "logsTest": "ログテスト",
"emptyTip": "{label}は空欄にできません", "emptyTip": "{label}は空欄にできません",
"urlTip": "{label}はURLである必要があります", "urlTip": "{label}はURLである必要があります",
@@ -467,5 +459,23 @@
"vpnConfigChangeDetected": "VPN設定の変更が検出されました", "vpnConfigChangeDetected": "VPN設定の変更が検出されました",
"restart": "再起動", "restart": "再起動",
"speedStatistics": "速度統計", "speedStatistics": "速度統計",
"resetPageChangesTip": "現在のページに変更があります。リセットしてもよろしいですか?" "resetPageChangesTip": "現在のページに変更があります。リセットしてもよろしいですか?",
"overwriteTypeCustom": "カスタム",
"overwriteTypeCustomDesc": "カスタムモード、プロキシグループとルールを完全にカスタマイズ可能",
"unknownNetworkError": "不明なネットワークエラー",
"networkRequestException": "ネットワーク要求例外、後でもう一度試してください。",
"restoreException": "復元例外",
"networkException": "ネットワーク例外、接続を確認してもう一度お試しください",
"invalidBackupFile": "無効なバックアップファイル",
"pruneCache": "キャッシュの削除",
"backupAndRestore": "バックアップと復元",
"backupAndRestoreDesc": "WebDAVまたはファイルを介してデータを同期する",
"restore": "復元",
"restoreSuccess": "復元に成功しました",
"restoreFromWebDAVDesc": "WebDAVを介してデータを復元する",
"restoreFromFileDesc": "ファイルを介してデータを復元する",
"restoreOnlyConfig": "設定ファイルのみを復元する",
"restoreAllData": "すべてのデータを復元する",
"addProfile": "プロファイルを追加",
"delayTest": "遅延テスト"
} }

View File

@@ -382,9 +382,9 @@
"systemApp": "Системное приложение", "systemApp": "Системное приложение",
"noNetworkApp": "Приложение без сети", "noNetworkApp": "Приложение без сети",
"contactMe": "Свяжитесь со мной", "contactMe": "Свяжитесь со мной",
"recoveryStrategy": "Стратегия восстановления", "restoreStrategy": "Стратегия восстановления",
"recoveryStrategy_override": "Переопределение", "restoreStrategy_override": "Перезаписать",
"recoveryStrategy_compatible": "Совместимый", "restoreStrategy_compatible": "Совместимый",
"logsTest": "Тест журналов", "logsTest": "Тест журналов",
"emptyTip": "{label} не может быть пустым", "emptyTip": "{label} не может быть пустым",
"urlTip": "{label} должен быть URL", "urlTip": "{label} должен быть URL",
@@ -467,5 +467,23 @@
"vpnConfigChangeDetected": "Обнаружено изменение конфигурации VPN", "vpnConfigChangeDetected": "Обнаружено изменение конфигурации VPN",
"restart": "Перезапустить", "restart": "Перезапустить",
"speedStatistics": "Статистика скорости", "speedStatistics": "Статистика скорости",
"resetPageChangesTip": "На текущей странице есть изменения. Вы уверены, что хотите сбросить?" "resetPageChangesTip": "На текущей странице есть изменения. Вы уверены, что хотите сбросить?",
"overwriteTypeCustom": "Пользовательский",
"overwriteTypeCustomDesc": "Пользовательский режим, полная настройка групп прокси и правил",
"unknownNetworkError": "Неизвестная сетевая ошибка",
"networkRequestException": "Исключение сетевого запроса, пожалуйста, попробуйте позже.",
"restoreException": "Ошибка восстановления",
"networkException": "Ошибка сети, проверьте соединение и попробуйте еще раз",
"invalidBackupFile": "Неверный файл резервной копии",
"pruneCache": "Очистить кэш",
"backupAndRestore": "Резервное копирование и восстановление",
"backupAndRestoreDesc": "Синхронизация данных через WebDAV или файлы",
"restore": "Восстановить",
"restoreSuccess": "Восстановление успешно",
"restoreFromWebDAVDesc": "Восстановить данные через WebDAV",
"restoreFromFileDesc": "Восстановить данные из файла",
"restoreOnlyConfig": "Восстановить только файлы конфигурации",
"restoreAllData": "Восстановить все данные",
"addProfile": "Добавить профиль",
"delayTest": "Тест задержки"
} }

View File

@@ -129,14 +129,8 @@
"compatibleDesc": "开启将失去部分应用能力获得全量的Clash的支持", "compatibleDesc": "开启将失去部分应用能力获得全量的Clash的支持",
"notSelectedTip": "当前代理组无法选中", "notSelectedTip": "当前代理组无法选中",
"tip": "提示", "tip": "提示",
"backupAndRecovery": "备份与恢复",
"backupAndRecoveryDesc": "通过WebDAV或者文件同步数据",
"account": "账号", "account": "账号",
"backup": "备份", "backup": "备份",
"recovery": "恢复",
"recoveryProfiles": "仅恢复配置文件",
"recoveryAll": "恢复所有数据",
"recoverySuccess": "恢复成功",
"backupSuccess": "备份成功", "backupSuccess": "备份成功",
"noInfo": "暂无信息", "noInfo": "暂无信息",
"pleaseBindWebDAV": "请绑定WebDAV", "pleaseBindWebDAV": "请绑定WebDAV",
@@ -219,9 +213,7 @@
"local": "本地", "local": "本地",
"remote": "远程", "remote": "远程",
"remoteBackupDesc": "备份数据到WebDAV", "remoteBackupDesc": "备份数据到WebDAV",
"remoteRecoveryDesc": "通过WebDAV恢复数据",
"localBackupDesc": "备份数据到本地", "localBackupDesc": "备份数据到本地",
"localRecoveryDesc": "通过文件恢复数据",
"mode": "模式", "mode": "模式",
"time": "时间", "time": "时间",
"source": "来源", "source": "来源",
@@ -382,9 +374,9 @@
"systemApp": "系统应用", "systemApp": "系统应用",
"noNetworkApp": "无网络应用", "noNetworkApp": "无网络应用",
"contactMe": "联系我", "contactMe": "联系我",
"recoveryStrategy": "恢复策略", "restoreStrategy": "恢复策略",
"recoveryStrategy_override": "覆盖", "restoreStrategy_override": "覆盖",
"recoveryStrategy_compatible": "兼容", "restoreStrategy_compatible": "兼容",
"logsTest": "日志测试", "logsTest": "日志测试",
"emptyTip": "{label}不能为空", "emptyTip": "{label}不能为空",
"urlTip": "{label}必须为URL", "urlTip": "{label}必须为URL",
@@ -467,5 +459,23 @@
"vpnConfigChangeDetected": "检测到VPN相关配置改动", "vpnConfigChangeDetected": "检测到VPN相关配置改动",
"restart": "重启", "restart": "重启",
"speedStatistics": "网速统计", "speedStatistics": "网速统计",
"resetPageChangesTip": "当前页面存在更改,确定重置吗?" "resetPageChangesTip": "当前页面存在更改,确定重置吗?",
"overwriteTypeCustom": "自定义",
"overwriteTypeCustomDesc": "自定义模式,支持完全自定义修改代理组以及规则",
"unknownNetworkError": "未知网络错误",
"networkRequestException": "网络请求异常,请稍后再试。",
"restoreException": "恢复异常",
"networkException": "网络异常,请检查连接后重试",
"invalidBackupFile": "无效备份文件",
"pruneCache": "修剪缓存",
"backupAndRestore": "备份与恢复",
"backupAndRestoreDesc": "通过WebDAV或者文件同步数据",
"restore": "恢复",
"restoreSuccess": "恢复成功",
"restoreFromWebDAVDesc": "通过WebDAV恢复数据",
"restoreFromFileDesc": "通过文件恢复数据",
"restoreOnlyConfig": "仅恢复配置文件",
"restoreAllData": "恢复所有数据",
"addProfile": "添加配置",
"delayTest": "延迟测试"
} }

View File

@@ -6,6 +6,7 @@ targets:
build_extensions: build_extensions:
'^lib/models/{{}}.dart': 'lib/models/generated/{{}}.g.dart' '^lib/models/{{}}.dart': 'lib/models/generated/{{}}.g.dart'
'^lib/providers/{{}}.dart': 'lib/providers/generated/{{}}.g.dart' '^lib/providers/{{}}.dart': 'lib/providers/generated/{{}}.g.dart'
'^lib/database/{{}}.dart': 'lib/database/generated/{{}}.g.dart'
freezed: freezed:
options: options:
build_extensions: build_extensions:

View File

@@ -37,12 +37,6 @@ var (
mBatch, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50)) mBatch, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50))
) )
type ExternalProviders []ExternalProvider
func (a ExternalProviders) Len() int { return len(a) }
func (a ExternalProviders) Less(i, j int) bool { return a[i].Name < a[j].Name }
func (a ExternalProviders) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func getExternalProvidersRaw() map[string]cp.Provider { func getExternalProvidersRaw() map[string]cp.Provider {
eps := make(map[string]cp.Provider) eps := make(map[string]cp.Provider)
for n, p := range tunnel.Providers() { for n, p := range tunnel.Providers() {
@@ -241,25 +235,8 @@ func updateConfig(params *UpdateParams) {
updateListeners() updateListeners()
} }
func parseWithPath(path string) (*config.Config, error) { func applyConfig(params *SetupParams) error {
buf, err := readFile(path) runtime.GC()
if err != nil {
return nil, err
}
rawConfig := config.DefaultRawConfig()
err = UnmarshalJson(buf, rawConfig)
if err != nil {
return nil, err
}
parseRawConfig, err := config.ParseRawConfig(rawConfig)
if err != nil {
return nil, err
}
return parseRawConfig, nil
}
func setupConfig(params *SetupParams) error {
runLock.Lock() runLock.Lock()
defer runLock.Unlock() defer runLock.Unlock()
var err error var err error
@@ -271,7 +248,6 @@ func setupConfig(params *SetupParams) error {
hub.ApplyConfig(currentConfig) hub.ApplyConfig(currentConfig)
patchSelectGroup(params.SelectedMap) patchSelectGroup(params.SelectedMap)
updateListeners() updateListeners()
runtime.GC()
return err return err
} }

View File

@@ -66,6 +66,11 @@ type ExternalProvider struct {
SubscriptionInfo *provider.SubscriptionInfo `json:"subscription-info"` SubscriptionInfo *provider.SubscriptionInfo `json:"subscription-info"`
} }
type ProxiesData struct {
Proxies map[string]constant.Proxy `json:"proxies"`
All []string `json:"all"`
}
const ( const (
messageMethod Method = "message" messageMethod Method = "message"
initClashMethod Method = "initClash" initClashMethod Method = "initClash"

View File

@@ -10,6 +10,7 @@ require (
) )
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/RyuaNerin/go-krypto v1.3.0 // indirect github.com/RyuaNerin/go-krypto v1.3.0 // indirect
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/ajg/form v1.5.1 // indirect github.com/ajg/form v1.5.1 // indirect
@@ -18,18 +19,14 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect
github.com/coreos/go-iptables v0.8.0 // indirect github.com/coreos/go-iptables v0.8.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/ebitengine/purego v0.9.1 // indirect github.com/enfein/mieru/v3 v3.26.2 // indirect
github.com/enfein/mieru/v3 v3.22.1 // indirect
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
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.9.0 // indirect github.com/fsnotify/fsnotify v1.9.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.2.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
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect github.com/gobwas/ws v1.4.0 // indirect
@@ -37,7 +34,7 @@ require (
github.com/golang/snappy v1.0.0 // indirect github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
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-20240727154555-813a5fbdbec8 // indirect
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/compress v1.17.9 // indirect
@@ -52,37 +49,42 @@ require (
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b // indirect github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b // indirect
github.com/metacubex/blake3 v0.1.0 // indirect github.com/metacubex/blake3 v0.1.0 // indirect
github.com/metacubex/chacha v0.1.5 // indirect github.com/metacubex/chacha v0.1.5 // indirect
github.com/metacubex/chi v0.1.0 // indirect
github.com/metacubex/cpu v0.1.0 // indirect
github.com/metacubex/fswatch v0.1.1 // indirect github.com/metacubex/fswatch v0.1.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-20250919004547-6122b699a301 // indirect github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 // indirect
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be // indirect github.com/metacubex/hkdf v0.1.0 // indirect
github.com/metacubex/hpke v0.1.0 // indirect
github.com/metacubex/http v0.1.0 // indirect
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 // indirect
github.com/metacubex/mlkem v0.1.0 // indirect
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 // indirect github.com/metacubex/qpack v0.6.0 // indirect
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001 // indirect
github.com/metacubex/randv2 v0.2.0 // indirect github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/restls-client-go v0.1.7 // indirect github.com/metacubex/restls-client-go v0.1.7 // indirect
github.com/metacubex/sing v0.5.6 // indirect github.com/metacubex/sing v0.5.6 // indirect
github.com/metacubex/sing-mux v0.3.4 // indirect github.com/metacubex/sing-mux v0.3.4 // indirect
github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb // indirect github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e // indirect
github.com/metacubex/sing-shadowsocks v0.2.12 // indirect github.com/metacubex/sing-shadowsocks v0.2.12 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.7 // indirect github.com/metacubex/sing-shadowsocks2 v0.2.7 // indirect
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
github.com/metacubex/sing-tun v0.4.9 // indirect github.com/metacubex/sing-tun v0.4.11 // indirect
github.com/metacubex/sing-vmess v0.2.4 // indirect github.com/metacubex/sing-vmess v0.2.4 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 // indirect github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 // indirect
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 // indirect github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 // indirect
github.com/metacubex/utls v1.8.3 // indirect github.com/metacubex/tls v0.1.1 // indirect
github.com/metacubex/utls v1.8.4 // indirect
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
github.com/miekg/dns v1.1.63 // indirect github.com/miekg/dns v1.1.63 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect github.com/mroth/weightedrand/v2 v2.1.0 // indirect
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/openacid/low v0.1.21 // indirect github.com/openacid/low v0.1.21 // indirect
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/quic-go/qpack v0.4.0 // indirect
github.com/sagernet/cors v1.2.1 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/samber/lo v1.52.0 // indirect github.com/samber/lo v1.52.0 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
@@ -103,7 +105,7 @@ require (
golang.org/x/net v0.35.0 // indirect golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.30.0 // indirect golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.10.0 // indirect
golang.org/x/tools v0.24.0 // indirect golang.org/x/tools v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // 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

View File

@@ -1,3 +1,5 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg= github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg=
github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM= github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM=
github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss= github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss=
@@ -12,9 +14,6 @@ github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xW
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc= github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -22,10 +21,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= github.com/enfein/mieru/v3 v3.26.2 h1:U/2XJc+3vrJD9r815FoFdwToQFEcqSOzzzWIPPhjfEU=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/enfein/mieru/v3 v3.26.2/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/enfein/mieru/v3 v3.22.1 h1:/XGYYXpEhEJlxosmtbpEJkhtRLHB8IToG7LB8kU2ZDY=
github.com/enfein/mieru/v3 v3.22.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
@@ -39,15 +36,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
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.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
@@ -57,17 +47,15 @@ github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakr
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
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-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I= github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
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=
@@ -98,47 +86,62 @@ github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cq
github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk= github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk=
github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M= github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M=
github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
github.com/metacubex/chi v0.1.0 h1:rjNDyDj50nRpicG43CNkIw4ssiCbmDL8d7wJXKlUCsg=
github.com/metacubex/chi v0.1.0/go.mod h1:zM5u5oMQt8b2DjvDHvzadKrP6B2ztmasL1YHRMbVV+g=
github.com/metacubex/cpu v0.1.0 h1:8PeTdV9j6UKbN1K5Jvtbi/Jock7dknvzyYuLb8Conmk=
github.com/metacubex/cpu v0.1.0/go.mod h1:09VEt4dSRLR+bOA8l4w4NDuzGZ8n5dkMv7e8axgEeTU=
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU= github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI= github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
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-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ= github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 h1:hUL81H0Ic/XIDkvtn9M1pmfDdfid7JzYQToY4Ps1TvQ=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU= github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be h1:Y7SigZIqfv/+RIA/D7R6EbB9p+brPRoGOM6zobSmRIM= github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA=
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs= github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4=
github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ=
github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U=
github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I=
github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 h1:I1uvJl206/HbkzEAZpLgGkZgUveOZb+P+6oTUj7dN+o= github.com/metacubex/qpack v0.6.0 h1:YqClGIMOpiRYLjV1qOs483Od08MdPgRnHjt90FuaAKw=
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c= github.com/metacubex/qpack v0.6.0/go.mod h1:lKGSi7Xk94IMvHGOmxS9eIei3bvIqpOAImEBsaOwTkA=
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001 h1:RlT3bFCIDM/NR9GWaDbFCrweOwpHRfgaT9c0zuRlPhY=
github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001/go.mod h1:oNzMrmylS897M3zSMuapIdwSwfq6F2qW01Z3NhVRJhk=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k= github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g= github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g=
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.6 h1:mEPDCadsCj3DB8gn+t/EtposlYuALEkExa/LUguw6/c= github.com/metacubex/sing v0.5.6 h1:mEPDCadsCj3DB8gn+t/EtposlYuALEkExa/LUguw6/c=
github.com/metacubex/sing v0.5.6/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= github.com/metacubex/sing v0.5.6/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.4 h1:tf4r27CIkzaxq9kBlAXQkgMXq2HPp5Mta60Kb4RCZF0= github.com/metacubex/sing-mux v0.3.4 h1:tf4r27CIkzaxq9kBlAXQkgMXq2HPp5Mta60Kb4RCZF0=
github.com/metacubex/sing-mux v0.3.4/go.mod h1:SEJfAuykNj/ozbPqngEYqyggwSr81+L7Nu09NRD5mh4= github.com/metacubex/sing-mux v0.3.4/go.mod h1:SEJfAuykNj/ozbPqngEYqyggwSr81+L7Nu09NRD5mh4=
github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb h1:gxrJmnxuEAel+kh3V7ntqkHjURif0xKDu76nzr/BF5Y= github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e h1:MLxp42z9Jd6LtY2suyawnl24oNzIsFxWc15bNeDIGxA=
github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb/go.mod h1:JK4+PYUKps6pnlicKjsSUAjAcvIUjhorIjdNZGg930M= github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e/go.mod h1:+lgKTd52xAarGtqugALISShyw4KxnoEpYe2u0zJh26w=
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE= github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU= github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A= github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE= github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-tun v0.4.9 h1:jY0Yyt8nnN3yQRN/jTxgqNCmGi1dsFdxdIi7pQUlVVU= github.com/metacubex/sing-tun v0.4.11 h1:NG5zpvYPbBXf+9GSUmDaGCDwl3hZXV677tbRAw0QtCM=
github.com/metacubex/sing-tun v0.4.9/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= github.com/metacubex/sing-tun v0.4.11/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I= github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I=
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM= github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2BhiAPgbJygiWhesPlfGmF+9Vw6ARdk=
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE= github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg=
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU= github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4= github.com/metacubex/tls v0.1.1 h1:BEcZrsPTTfNf4sKZ02EbZodv4UIj7fgHWa1Eqo12Bc0=
github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= github.com/metacubex/tls v0.1.1/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk= github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4= github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E= github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E=
@@ -149,9 +152,6 @@ github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd
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=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0= github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo= github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0= github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
@@ -164,10 +164,6 @@ github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
@@ -181,16 +177,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/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
@@ -234,22 +223,20 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.10.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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=

View File

@@ -1,6 +1,7 @@
package main package main
import ( import (
"cmp"
"context" "context"
"encoding/json" "encoding/json"
"github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter"
@@ -19,11 +20,11 @@ import (
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel" "github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/mihomo/tunnel/statistic" "github.com/metacubex/mihomo/tunnel/statistic"
"golang.org/x/exp/slices"
"net" "net"
"os" "os"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"sort"
"strconv" "strconv"
"time" "time"
) )
@@ -43,10 +44,8 @@ func handleInitClash(paramsString string) bool {
return false return false
} }
version = params.Version version = params.Version
if !isInit { constant.SetHomeDir(params.HomeDir)
constant.SetHomeDir(params.HomeDir) isInit = true
isInit = true
}
return isInit return isInit
} }
@@ -97,10 +96,52 @@ func handleValidateConfig(path string) string {
return "" return ""
} }
func handleGetProxies() map[string]constant.Proxy { func handleGetProxies() ProxiesData {
runLock.Lock() runLock.Lock()
defer runLock.Unlock() defer runLock.Unlock()
return tunnel.ProxiesWithProviders()
nameList := config.GetProxyNameList()
proxies := make(map[string]constant.Proxy)
for name, proxy := range tunnel.Proxies() {
proxies[name] = proxy
}
for _, p := range tunnel.Providers() {
for _, proxy := range p.Proxies() {
proxies[proxy.Name()] = proxy
}
}
hasGlobal := false
allNames := make([]string, 0, len(nameList)+1)
for _, name := range nameList {
if name == "GLOBAL" {
hasGlobal = true
}
p, ok := proxies[name]
if !ok || p == nil {
continue
}
switch p.Type() {
case constant.Selector, constant.URLTest, constant.Fallback, constant.Relay, constant.LoadBalance:
allNames = append(allNames, name)
default:
}
}
if !hasGlobal {
if p, ok := proxies["GLOBAL"]; ok && p != nil {
allNames = append([]string{"GLOBAL"}, allNames...)
}
}
return ProxiesData{
All: allNames,
Proxies: proxies,
}
} }
func handleChangeProxy(data string, fn func(string string)) { func handleChangeProxy(data string, fn func(string string)) {
@@ -143,7 +184,7 @@ func handleChangeProxy(data string, fn func(string string)) {
} }
func handleGetTraffic(onlyStatisticsProxy bool) string { func handleGetTraffic(onlyStatisticsProxy bool) string {
up, down := statistic.DefaultManager.Current(onlyStatisticsProxy) up, down := statistic.DefaultManager.NowTraffic(onlyStatisticsProxy)
traffic := map[string]int64{ traffic := map[string]int64{
"up": up, "up": up,
"down": down, "down": down,
@@ -157,7 +198,7 @@ func handleGetTraffic(onlyStatisticsProxy bool) string {
} }
func handleGetTotalTraffic(onlyStatisticsProxy bool) string { func handleGetTotalTraffic(onlyStatisticsProxy bool) string {
up, down := statistic.DefaultManager.Total(onlyStatisticsProxy) up, down := statistic.DefaultManager.TotalTraffic(onlyStatisticsProxy)
traffic := map[string]int64{ traffic := map[string]int64{
"up": up, "up": up,
"down": down, "down": down,
@@ -287,7 +328,9 @@ func handleGetExternalProviders() string {
} }
eps = append(eps, *externalProvider) eps = append(eps, *externalProvider)
} }
sort.Sort(ExternalProviders(eps)) slices.SortFunc(eps, func(a, b ExternalProvider) int {
return cmp.Compare(a.Name, b.Name)
})
data, err := json.Marshal(eps) data, err := json.Marshal(eps)
if err != nil { if err != nil {
return "" return ""
@@ -489,14 +532,17 @@ func handleDelFile(path string, result ActionResult) {
} }
func handleSetupConfig(bytes []byte) string { func handleSetupConfig(bytes []byte) string {
if !isInit {
return "not initialized"
}
var params = defaultSetupParams() var params = defaultSetupParams()
err := UnmarshalJson(bytes, params) err := UnmarshalJson(bytes, params)
if err != nil { if err != nil {
log.Errorln("unmarshalRawConfig error %v", err) log.Errorln("unmarshalRawConfig error %v", err)
_ = setupConfig(defaultSetupParams()) _ = applyConfig(defaultSetupParams())
return err.Error() return err.Error()
} }
err = setupConfig(params) err = applyConfig(params)
if err != nil { if err != nil {
return err.Error() return err.Error()
} }

View File

@@ -201,9 +201,29 @@ func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
//export startTUN //export startTUN
func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar *C.char) bool { func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar *C.char) bool {
handleStartTun(callback, int(fd), takeCString(stackChar), takeCString(addressChar), takeCString(dnsChar)) handleStartTun(callback, int(fd), takeCString(stackChar), takeCString(addressChar), takeCString(dnsChar))
if !isRunning {
handleStartListener()
} else {
handleResetConnections()
}
return true return true
} }
//export quickSetup
func quickSetup(callback unsafe.Pointer, initParamsChar *C.char, setupParamsChar *C.char) {
go func() {
initParamsString := takeCString(initParamsChar)
setupParamsString := takeCString(setupParamsChar)
if !handleInitClash(initParamsString) {
invokeResult(callback, "init failed")
return
}
isRunning = true
message := handleSetupConfig([]byte(setupParamsString))
invokeResult(callback, message)
}()
}
//export setEventListener //export setEventListener
func setEventListener(listener unsafe.Pointer) { func setEventListener(listener unsafe.Pointer) {
if eventListener != nil || listener == nil { if eventListener != nil || listener == nil {
@@ -241,6 +261,9 @@ func sendMessage(message Message) {
//export stopTun //export stopTun
func stopTun() { func stopTun() {
handleStopTun() handleStopTun()
if isRunning {
handleStopListener()
}
} }
//export suspend //export suspend

View File

@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
@@ -25,6 +26,7 @@ class Application extends ConsumerStatefulWidget {
class ApplicationState extends ConsumerState<Application> { class ApplicationState extends ConsumerState<Application> {
Timer? _autoUpdateProfilesTaskTimer; Timer? _autoUpdateProfilesTaskTimer;
bool _preHasVpn = false;
final _pageTransitionsTheme = const PageTransitionsTheme( final _pageTransitionsTheme = const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{ builders: <TargetPlatform, PageTransitionsBuilder>{
@@ -45,22 +47,22 @@ class ApplicationState extends ConsumerState<Application> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_autoUpdateProfilesTask();
globalState.appController = AppController(context, ref);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final currentContext = globalState.navigatorKey.currentContext; final currentContext = globalState.navigatorKey.currentContext;
if (currentContext != null) { if (currentContext != null) {
globalState.appController = AppController(currentContext, ref); await appController.attach(currentContext, ref);
} else {
exit(0);
} }
await globalState.appController.init(); _autoUpdateProfilesTask();
globalState.appController.initLink(); appController.initLink();
app?.initShortcuts(); app?.initShortcuts();
}); });
} }
void _autoUpdateProfilesTask() { void _autoUpdateProfilesTask() {
_autoUpdateProfilesTaskTimer = Timer(const Duration(minutes: 20), () async { _autoUpdateProfilesTaskTimer = Timer(const Duration(minutes: 20), () async {
await globalState.appController.autoUpdateProfiles(); await appController.autoUpdateProfiles();
_autoUpdateProfilesTask(); _autoUpdateProfilesTask();
}); });
} }
@@ -81,11 +83,13 @@ class ApplicationState extends ConsumerState<Application> {
child: CoreManager( child: CoreManager(
child: ConnectivityManager( child: ConnectivityManager(
onConnectivityChanged: (results) async { onConnectivityChanged: (results) async {
if (!results.contains(ConnectivityResult.vpn)) { commonPrint.log('connectivityChanged ${results.toString()}');
coreController.closeConnections(); appController.updateLocalIp();
final hasVpn = results.contains(ConnectivityResult.vpn);
if (_preHasVpn == hasVpn) {
appController.addCheckIp();
} }
globalState.appController.updateLocalIp(); _preHasVpn = hasVpn;
globalState.appController.addCheckIpNumDebounce();
}, },
child: child, child: child,
), ),
@@ -163,8 +167,7 @@ class ApplicationState extends ConsumerState<Application> {
linkManager.destroy(); linkManager.destroy();
_autoUpdateProfilesTaskTimer?.cancel(); _autoUpdateProfilesTaskTimer?.cancel();
await coreController.destroy(); await coreController.destroy();
await globalState.appController.savePreferences(); await appController.handleExit();
await globalState.appController.handleExit();
super.dispose(); super.dispose();
} }
} }

View File

@@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:archive/archive_io.dart'; import 'package:archive/archive_io.dart';
@@ -18,14 +17,11 @@ extension ArchiveExt on Archive {
final archiveFile = ArchiveFile(relativePath, data.length, data); final archiveFile = ArchiveFile(relativePath, data.length, data);
addFile(archiveFile); addFile(archiveFile);
} }
// else if (entity is Directory) {
// addDirectoryToArchive(entity.path, parentPath);
// }
} }
} }
void addTextFile<T>(String name, T raw) { // void addTextFile<T>(String name, T raw) {
final data = json.encode(raw); // final data = json.encode(raw);
addFile(ArchiveFile.string(name, data)); // addFile(ArchiveFile.string(name, data));
} // }
} }

View File

@@ -6,17 +6,21 @@ export 'constant.dart';
export 'context.dart'; export 'context.dart';
export 'converter.dart'; export 'converter.dart';
export 'datetime.dart'; export 'datetime.dart';
export 'file.dart';
export 'fixed.dart'; export 'fixed.dart';
export 'function.dart'; export 'function.dart';
export 'future.dart'; export 'future.dart';
export 'hive.dart';
export 'http.dart'; export 'http.dart';
export 'icons.dart'; export 'icons.dart';
export 'indexing.dart';
export 'iterable.dart'; export 'iterable.dart';
export 'keyboard.dart'; export 'keyboard.dart';
export 'launch.dart'; export 'launch.dart';
export 'link.dart'; export 'link.dart';
export 'lock.dart'; export 'lock.dart';
export 'measure.dart'; export 'measure.dart';
export 'migration.dart';
export 'mixin.dart'; export 'mixin.dart';
export 'navigation.dart'; export 'navigation.dart';
export 'navigator.dart'; export 'navigator.dart';
@@ -32,8 +36,10 @@ export 'proxy.dart';
export 'render.dart'; export 'render.dart';
export 'request.dart'; export 'request.dart';
export 'scroll.dart'; export 'scroll.dart';
export 'snowflake.dart';
export 'string.dart'; export 'string.dart';
export 'system.dart'; export 'system.dart';
export 'task.dart';
export 'text.dart'; export 'text.dart';
export 'tray.dart'; export 'tray.dart';
export 'utils.dart'; export 'utils.dart';

View File

@@ -1,8 +1,7 @@
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 'string.dart';
List<Group> computeSort({ List<Group> computeSort({
required List<Group> groups, required List<Group> groups,
required ProxiesSortType sortType, required ProxiesSortType sortType,
@@ -10,53 +9,54 @@ List<Group> computeSort({
required Map<String, String> selectedMap, required Map<String, String> selectedMap,
required String defaultTestUrl, required String defaultTestUrl,
}) { }) {
List<Proxy> sortOfDelay({
required List<Group> groups,
required List<Proxy> proxies,
required DelayMap delayMap,
required Map<String, String> selectedMap,
required String testUrl,
}) {
return List.from(proxies)..sort((a, b) {
final aDelayState = computeProxyDelayState(
proxyName: a.name,
testUrl: testUrl,
groups: groups,
selectedMap: selectedMap,
delayMap: delayMap,
);
final bDelayState = computeProxyDelayState(
proxyName: b.name,
testUrl: testUrl,
groups: groups,
selectedMap: selectedMap,
delayMap: delayMap,
);
return aDelayState.compareTo(bDelayState);
});
}
List<Proxy> sortOfName(List<Proxy> proxies) {
return List.of(proxies)..sort((a, b) => a.name.compareTo(b.name));
}
return groups.map((group) { return groups.map((group) {
final proxies = group.all; final proxies = group.all;
final newProxies = switch (sortType) { final newProxies = switch (sortType) {
ProxiesSortType.none => proxies, ProxiesSortType.none => proxies,
ProxiesSortType.delay => _sortOfDelay( ProxiesSortType.delay => sortOfDelay(
groups: groups, groups: groups,
proxies: proxies, proxies: proxies,
delayMap: delayMap, delayMap: delayMap,
selectedMap: selectedMap, selectedMap: selectedMap,
testUrl: group.testUrl.getSafeValue(defaultTestUrl), testUrl: group.testUrl.takeFirstValid([defaultTestUrl]),
), ),
ProxiesSortType.name => _sortOfName(proxies), ProxiesSortType.name => sortOfName(proxies),
}; };
return group.copyWith(all: newProxies); return group.copyWith(all: newProxies);
}).toList(); }).toList();
} }
DelayState computeProxyDelayState({ SelectedProxyState getRealSelectedProxyState(
required String proxyName,
required String testUrl,
required List<Group> groups,
required Map<String, String> selectedMap,
required DelayMap delayMap,
}) {
final state = computeRealSelectedProxyState(
proxyName,
groups: groups,
selectedMap: selectedMap,
);
final currentDelayMap = delayMap[state.testUrl.getSafeValue(testUrl)] ?? {};
final delay = currentDelayMap[state.proxyName];
return DelayState(delay: delay ?? 0, group: state.group);
}
SelectedProxyState computeRealSelectedProxyState(
String proxyName, {
required List<Group> groups,
required Map<String, String> selectedMap,
}) {
return _getRealSelectedProxyState(
SelectedProxyState(proxyName: proxyName),
groups: groups,
selectedMap: selectedMap,
);
}
SelectedProxyState _getRealSelectedProxyState(
SelectedProxyState state, { SelectedProxyState state, {
required List<Group> groups, required List<Group> groups,
required Map<String, String> selectedMap, required Map<String, String> selectedMap,
@@ -72,39 +72,39 @@ SelectedProxyState _getRealSelectedProxyState(
if (currentSelectedName.isEmpty) { if (currentSelectedName.isEmpty) {
return newState; return newState;
} }
return _getRealSelectedProxyState( return getRealSelectedProxyState(
newState.copyWith(proxyName: currentSelectedName, testUrl: group.testUrl), newState.copyWith(proxyName: currentSelectedName, testUrl: group.testUrl),
groups: groups, groups: groups,
selectedMap: selectedMap, selectedMap: selectedMap,
); );
} }
List<Proxy> _sortOfDelay({ SelectedProxyState computeRealSelectedProxyState(
String proxyName, {
required List<Group> groups, required List<Group> groups,
required List<Proxy> proxies,
required DelayMap delayMap,
required Map<String, String> selectedMap, required Map<String, String> selectedMap,
required String testUrl,
}) { }) {
return List.from(proxies)..sort((a, b) { return getRealSelectedProxyState(
final aDelayState = computeProxyDelayState( SelectedProxyState(proxyName: proxyName),
proxyName: a.name, groups: groups,
testUrl: testUrl, selectedMap: selectedMap,
groups: groups, );
selectedMap: selectedMap,
delayMap: delayMap,
);
final bDelayState = computeProxyDelayState(
proxyName: b.name,
testUrl: testUrl,
groups: groups,
selectedMap: selectedMap,
delayMap: delayMap,
);
return aDelayState.compareTo(bDelayState);
});
} }
List<Proxy> _sortOfName(List<Proxy> proxies) { DelayState computeProxyDelayState({
return List.of(proxies)..sort((a, b) => a.name.compareTo(b.name)); required String proxyName,
required String testUrl,
required List<Group> groups,
required Map<String, String> selectedMap,
required DelayMap delayMap,
}) {
final state = computeRealSelectedProxyState(
proxyName,
groups: groups,
selectedMap: selectedMap,
);
final currentDelayMap =
delayMap[state.testUrl.takeFirstValid([testUrl])] ?? {};
final delay = currentDelayMap[state.proxyName];
return DelayState(delay: delay ?? 0, group: state.group);
} }

View File

@@ -20,16 +20,18 @@ const helperPort = 47890;
const maxTextScale = 1.4; const maxTextScale = 1.4;
const minTextScale = 0.8; const minTextScale = 0.8;
final baseInfoEdgeInsets = EdgeInsets.symmetric( final baseInfoEdgeInsets = EdgeInsets.symmetric(
vertical: 16.ap, vertical: 16.mAp,
horizontal: 16.ap, horizontal: 16.mAp,
); );
final listHeaderPadding = EdgeInsets.only( final listHeaderPadding = EdgeInsets.only(
left: 16.ap, left: 16.mAp,
right: 8.ap, right: 8.mAp,
top: 24.ap, top: 24.mAp,
bottom: 8.ap, bottom: 8.mAp,
); );
const watchExecution = true;
final defaultTextScaleFactor = final defaultTextScaleFactor =
WidgetsBinding.instance.platformDispatcher.textScaleFactor; WidgetsBinding.instance.platformDispatcher.textScaleFactor;
const httpTimeoutDuration = Duration(milliseconds: 5000); const httpTimeoutDuration = Duration(milliseconds: 5000);
@@ -63,6 +65,7 @@ final commonFilter = ImageFilter.blur(
tileMode: TileMode.mirror, tileMode: TileMode.mirror,
); );
const listEquality = ListEquality();
const navigationItemListEquality = ListEquality<NavigationItem>(); const navigationItemListEquality = ListEquality<NavigationItem>();
const trackerInfoListEquality = ListEquality<TrackerInfo>(); const trackerInfoListEquality = ListEquality<TrackerInfo>();
const stringListEquality = ListEquality<String>(); const stringListEquality = ListEquality<String>();
@@ -70,15 +73,18 @@ const intListEquality = ListEquality<int>();
const logListEquality = ListEquality<Log>(); const logListEquality = ListEquality<Log>();
const groupListEquality = ListEquality<Group>(); const groupListEquality = ListEquality<Group>();
const ruleListEquality = ListEquality<Rule>(); const ruleListEquality = ListEquality<Rule>();
const scriptEquality = ListEquality<Script>(); const scriptListEquality = ListEquality<Script>();
const externalProviderListEquality = ListEquality<ExternalProvider>(); const externalProviderListEquality = ListEquality<ExternalProvider>();
const packageListEquality = ListEquality<Package>(); const packageListEquality = ListEquality<Package>();
const profileListEquality = ListEquality<Profile>();
const hotKeyActionListEquality = ListEquality<HotKeyAction>(); const hotKeyActionListEquality = ListEquality<HotKeyAction>();
const stringAndStringMapEquality = MapEquality<String, String>(); const stringAndStringMapEquality = MapEquality<String, String>();
const stringAndStringMapEntryListEquality = const stringAndStringMapEntryListEquality =
ListEquality<MapEntry<String, String>>(); ListEquality<MapEntry<String, String>>();
const stringAndStringMapEntryIterableEquality = const stringAndStringMapEntryIterableEquality =
IterableEquality<MapEntry<String, String>>(); IterableEquality<MapEntry<String, String>>();
const stringAndObjectMapEntryIterableEquality =
IterableEquality<MapEntry<String, Object?>>();
const delayMapEquality = MapEquality<String, Map<String, int?>>(); const delayMapEquality = MapEquality<String, Map<String, int?>>();
const stringSetEquality = SetEquality<String>(); const stringSetEquality = SetEquality<String>();
const keyboardModifierListEquality = SetEquality<KeyboardModifier>(); const keyboardModifierListEquality = SetEquality<KeyboardModifier>();
@@ -96,7 +102,8 @@ const profilesStoreKey = PageStorageKey<String>('profiles');
const defaultPrimaryColor = 0XFFD8C0C3; const defaultPrimaryColor = 0XFFD8C0C3;
double getWidgetHeight(num lines) { double getWidgetHeight(num lines) {
return max(lines * 80 + (lines - 1) * 16, 0).ap; final space = 14.mAp;
return max(lines * (80.ap + space) - space, 0);
} }
const maxLength = 1000; const maxLength = 1000;
@@ -119,3 +126,6 @@ const scriptTemplate = '''
const main = (config) => { const main = (config) => {
return config; return config;
}'''; }''';
const backupDatabaseName = 'database.sqlite';
const configJsonName = 'config.json';

View File

@@ -1,6 +1,6 @@
import 'package:fl_clash/l10n/l10n.dart'; import 'package:fl_clash/l10n/l10n.dart';
import 'package:fl_clash/manager/manager.dart'; import 'package:fl_clash/manager/manager.dart';
import 'package:fl_clash/models/widget.dart'; import 'package:fl_clash/models/state.dart';
import 'package:fl_clash/widgets/scaffold.dart'; import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
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';
@@ -10,19 +9,10 @@ class DAVClient {
Completer<bool> pingCompleter = Completer(); Completer<bool> pingCompleter = Completer();
late String fileName; late String fileName;
DAVClient(DAV dav) { DAVClient(DAVProps dav) {
client = newClient( client = newClient(dav.uri, user: dav.user, password: dav.password);
dav.uri,
user: dav.user,
password: dav.password,
);
fileName = dav.fileName; fileName = dav.fileName;
client.setHeaders( client.setHeaders({'accept-charset': 'utf-8', 'Content-Type': 'text/xml'});
{
'accept-charset': 'utf-8',
'Content-Type': 'text/xml',
},
);
client.setConnectTimeout(8000); client.setConnectTimeout(8000);
client.setSendTimeout(60000); client.setSendTimeout(60000);
client.setReceiveTimeout(60000); client.setReceiveTimeout(60000);
@@ -42,15 +32,16 @@ class DAVClient {
String get backupFile => '$root/$fileName'; String get backupFile => '$root/$fileName';
Future<bool> backup(Uint8List data) async { Future<bool> backup(String localFilePath) async {
await client.mkdir(root); await client.mkdir(root);
await client.write(backupFile, data); await client.writeFromFile(localFilePath, backupFile);
return true; return true;
} }
Future<List<int>> recovery() async { Future<bool> restore() async {
await client.mkdir(root); await client.mkdir(root);
final data = await client.read(backupFile); final backupFilePath = await appPath.backupFilePath;
return data; await client.read2File(backupFile, backupFilePath);
return true;
} }
} }

38
lib/common/file.dart Normal file
View File

@@ -0,0 +1,38 @@
import 'dart:io';
extension FileExt on File {
Future<void> safeCopy(String newPath) async {
if (!await exists()) {
await create(recursive: true);
return;
}
final targetFile = File(newPath);
if (!await targetFile.exists()) {
await targetFile.create(recursive: true);
}
await copy(newPath);
}
Future<File> safeWriteAsString(String str) async {
if (!await exists()) {
await create(recursive: true);
}
return await writeAsString(str);
}
Future<File> safeWriteAsBytes(List<int> bytes) async {
if (!await exists()) {
await create(recursive: true);
}
return await writeAsBytes(bytes);
}
}
extension FileSystemEntityExt on FileSystemEntity {
Future<void> safeDelete({bool recursive = false}) async {
if (!await exists()) {
return;
}
await delete(recursive: recursive);
}
}

View File

@@ -1,15 +1,15 @@
import 'dart:io'; import 'dart:io';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/controller.dart';
class FlClashHttpOverrides extends HttpOverrides { class FlClashHttpOverrides extends HttpOverrides {
static String handleFindProxy(Uri url) { static String handleFindProxy(Uri url) {
if ([localhost].contains(url.host)) { if ([localhost].contains(url.host)) {
return 'DIRECT'; return 'DIRECT';
} }
final port = globalState.config.patchClashConfig.mixedPort; final port = appController.config.patchClashConfig.mixedPort;
final isStart = globalState.appState.runTime != null; final isStart = appController.isStart;
commonPrint.log('find $url proxy:$isStart'); commonPrint.log('find $url proxy:$isStart');
if (!isStart) return 'DIRECT'; if (!isStart) return 'DIRECT';
return 'PROXY localhost:$port'; return 'PROXY localhost:$port';

260
lib/common/indexing.dart Normal file
View File

@@ -0,0 +1,260 @@
import 'dart:math';
class Indexing {
static const String digits =
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
static const String integerZero = 'a0';
static const String smallestInteger = 'A00000000000000000000000000';
static Indexing? _instance;
Indexing._internal();
factory Indexing() {
_instance ??= Indexing._internal();
return _instance!;
}
int _getIntegerLength(String head) {
if (head.compareTo('a') >= 0 && head.compareTo('z') <= 0) {
return head.codeUnitAt(0) - 'a'.codeUnitAt(0) + 2;
} else if (head.compareTo('A') >= 0 && head.compareTo('Z') <= 0) {
return 'Z'.codeUnitAt(0) - head.codeUnitAt(0) + 2;
} else {
throw Exception('Invalid order key head: $head');
}
}
bool _validateInteger(String integer) {
if (integer.length != _getIntegerLength(integer[0])) {
throw Exception('Invalid integer part of order key: $integer');
}
return true;
}
String? _incrementInteger(String x) {
_validateInteger(x);
String head = x[0];
List<String> digs = x.substring(1).split('');
bool carry = true;
for (int i = digs.length - 1; carry && i >= 0; i--) {
int d = digits.indexOf(digs[i]) + 1;
if (d == digits.length) {
digs[i] = '0';
} else {
digs[i] = digits[d];
carry = false;
}
}
if (carry) {
if (head == 'Z') {
return 'a0';
}
if (head == 'z') {
return null;
}
String h = String.fromCharCode(head.codeUnitAt(0) + 1);
if (h.compareTo('a') > 0) {
digs.add('0');
} else {
digs.removeLast();
}
return h + digs.join('');
} else {
return head + digs.join('');
}
}
String? _decrementInteger(String x) {
_validateInteger(x);
String head = x[0];
List<String> digs = x.substring(1).split('');
bool borrow = true;
for (int i = digs.length - 1; borrow && i >= 0; i--) {
int d = digits.indexOf(digs[i]) - 1;
if (d == -1) {
digs[i] = digits[digits.length - 1];
} else {
digs[i] = digits[d];
borrow = false;
}
}
if (borrow) {
if (head == 'a') {
return 'Z${digits[digits.length - 1]}';
}
if (head == 'A') {
return null;
}
String h = String.fromCharCode(head.codeUnitAt(0) - 1);
if (h.compareTo('Z') < 0) {
digs.add(digits[digits.length - 1]);
} else {
digs.removeLast();
}
return h + digs.join('');
} else {
return head + digs.join('');
}
}
String _midpoint(String a, String? b) {
if (b != null && a.compareTo(b) >= 0) {
throw Exception(
'Second order key must be greater than the first: $a, $b',
);
}
if (a.isNotEmpty && a[a.length - 1] == '0' ||
(b != null && b.isNotEmpty && b[b.length - 1] == '0')) {
throw Exception('Trailing zeros are not allowed: $a, $b');
}
if (b != null) {
int n = 0;
while ((n < a.length ? a[n] : '0') == b[n]) {
n++;
}
if (n > 0) {
return b.substring(0, n) +
_midpoint(
a.substring(min(n, a.length)),
b.substring(min(n, b.length)),
);
}
}
int digitA = (a.isNotEmpty) ? digits.indexOf(a[0]) : 0;
int digitB = (b != null && b.isNotEmpty)
? digits.indexOf(b[0])
: digits.length;
if (digitB - digitA > 1) {
int midDigit = (digitA + digitB + 1) ~/ 2;
return digits[midDigit];
} else {
if (b != null && b.length > 1) {
return b.substring(0, 1);
} else {
return digits[digitA] +
_midpoint(a.isNotEmpty ? a.substring(1) : '', null);
}
}
}
String _getIntegerPart(String key) {
int integerPartLength = _getIntegerLength(key[0]);
if (integerPartLength > key.length) {
throw Exception('Invalid order key: $key');
}
return key.substring(0, integerPartLength);
}
bool _validateOrderKey(String key) {
if (key == smallestInteger) {
throw Exception('Invalid order key: $key');
}
String i = _getIntegerPart(key);
String f = key.substring(i.length);
if (f.isNotEmpty && f[f.length - 1] == '0') {
throw Exception('Invalid order key: $key');
}
return true;
}
String? generateKeyBetween(String? a, String? b) {
if (a != null) {
_validateOrderKey(a);
}
if (b != null) {
_validateOrderKey(b);
}
if (a != null && b != null && a.compareTo(b) >= 0) {
throw Exception(
'Second order key must be greater than the first: $a, $b',
);
}
if (a == null && b == null) {
return integerZero;
}
if (a == null) {
b = b!;
String ib = _getIntegerPart(b);
String fb = b.substring(ib.length);
if (ib == smallestInteger) {
return ib + _midpoint('', fb);
}
return ib.compareTo(b) < 0 ? ib : _decrementInteger(ib);
}
if (b == null) {
String ia = _getIntegerPart(a);
String fa = a.substring(ia.length);
String? i = _incrementInteger(ia);
return i ?? ia + _midpoint(fa, null);
}
String ia = _getIntegerPart(a);
String fa = a.substring(ia.length);
String ib = _getIntegerPart(b);
String fb = b.substring(ib.length);
if (ia == ib) {
return ia + _midpoint(fa, fb);
}
String? i = _incrementInteger(ia);
return (i == null || i.compareTo(b) < 0) ? i : ia + _midpoint(fa, null);
}
List<String?> generateNKeysBetween(String? a, String? b, int n) {
if (n <= 0) {
return [];
}
if (n == 1) {
return [generateKeyBetween(a, b)];
}
if (b == null) {
String? c = generateKeyBetween(a, b);
List<String?> result = [c];
for (int i = 1; i < n; i++) {
c = generateKeyBetween(c, b);
result.add(c);
}
return result;
}
if (a == null) {
String? c = generateKeyBetween(a, b);
List<String?> result = [c];
for (int i = 1; i < n; i++) {
c = generateKeyBetween(a, c);
result.add(c);
}
return result.reversed.toList();
}
int mid = n ~/ 2;
String? c = generateKeyBetween(a, b);
return generateNKeysBetween(a, c, mid)
.followedBy([c])
.followedBy(generateNKeysBetween(c, b, n - mid - 1))
.toList();
}
}
final indexing = Indexing();

View File

@@ -1,5 +1,5 @@
extension IterableExt<T> on Iterable<T> { extension IterableExt<E> on Iterable<E> {
Iterable<T> separated(T separator) sync* { Iterable<E> separated(E separator) sync* {
final iterator = this.iterator; final iterator = this.iterator;
if (!iterator.moveNext()) return; if (!iterator.moveNext()) return;
@@ -11,7 +11,7 @@ extension IterableExt<T> on Iterable<T> {
} }
} }
Iterable<List<T>> chunks(int size) sync* { Iterable<List<E>> chunks(int size) sync* {
if (length == 0) return; if (length == 0) return;
var iterator = this.iterator; var iterator = this.iterator;
while (iterator.moveNext()) { while (iterator.moveNext()) {
@@ -23,7 +23,7 @@ extension IterableExt<T> on Iterable<T> {
} }
} }
Iterable<T> fill(int length, {required T Function(int count) filler}) sync* { Iterable<E> fill(int length, {required E Function(int count) filler}) sync* {
int count = 0; int count = 0;
for (var item in this) { for (var item in this) {
yield item; yield item;
@@ -36,7 +36,7 @@ extension IterableExt<T> on Iterable<T> {
} }
} }
Iterable<T> takeLast({int count = 50}) { Iterable<E> takeLast({int count = 50}) {
if (count <= 0) return Iterable.empty(); if (count <= 0) return Iterable.empty();
return count >= length ? this : toList().skip(length - count); return count >= length ? this : toList().skip(length - count);
} }
@@ -78,16 +78,18 @@ extension ListExt<T> on List<T> {
return sublist(start); return sublist(start);
} }
T safeGet(int index) { T? safeGet(int index, {T? defaultValue}) {
if (length > index) return this[index]; if (index < 0 || index >= length) {
return last; return defaultValue;
}
return this[index];
} }
T safeLast(T value) { T safeLast(T defaultValue) {
if (isNotEmpty) { if (isNotEmpty) {
return last; return last;
} }
return value; return defaultValue;
} }
void addOrRemove(T value) { void addOrRemove(T value) {

53
lib/common/migration.dart Normal file
View File

@@ -0,0 +1,53 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
class Migration {
static Migration? _instance;
late int _oldVersion;
Migration._internal();
final currentVersion = 1;
factory Migration() {
_instance ??= Migration._internal();
return _instance!;
}
Future<Config> migrationIfNeeded(
Map<String, Object?>? configMap, {
required Future<Config> Function(MigrationData data) sync,
}) async {
_oldVersion = await preferences.getVersion();
if (_oldVersion == currentVersion) {
try {
return Config.realFromJson(configMap);
} catch (_) {
final isV0 = configMap?['proxiesStyle'] != null;
if (isV0) {
_oldVersion = 0;
} else {
throw 'Local data is damaged. A reset is required to fix this issue.';
}
}
}
MigrationData data = MigrationData(configMap: configMap);
if (_oldVersion == 0 && configMap != null) {
final clashConfigMap = await preferences.getClashConfigMap();
if (clashConfigMap != null) {
configMap['patchClashConfig'] = clashConfigMap;
await preferences.clearClashConfig();
}
data = await _oldToNow(configMap);
}
final res = await sync(data);
await preferences.setVersion(currentVersion);
return res;
}
Future<MigrationData> _oldToNow(Map<String, Object?> configMap) async {
return await oldToNowTask(configMap);
}
}
final migration = Migration();

View File

@@ -2,17 +2,21 @@ import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> { mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
T get value => state;
set value(T value) { set value(T value) {
if (ref.mounted) { state = value;
state = value; }
} else {
onUpdate(value); bool equals(T previous, T next) {
} return false;
} }
@override @override
bool updateShouldNotify(previous, next) { bool updateShouldNotify(previous, next) {
final res = super.updateShouldNotify(previous, next); final res = !equals(previous, next)
? super.updateShouldNotify(previous, next)
: true;
if (res) { if (res) {
onUpdate(next); onUpdate(next);
} }
@@ -21,31 +25,19 @@ mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
void onUpdate(T value) {} void onUpdate(T value) {}
void update(T Function(T) builder) { void update(T? Function(T) builder) {
final value = builder(state); final res = builder(value);
this.value = value; if (res == null) {
return;
}
value = res;
} }
} }
mixin AnyNotifierMixin<T> on AnyNotifier<T, T> { mixin AsyncNotifierMixin<T> on AnyNotifier<AsyncValue<T>, T> {
T get value; T get value;
set value(T value) { set value(T value) {
if (ref.mounted) { state = AsyncData(value);
state = value;
} else {
onUpdate(value);
}
} }
@override
bool updateShouldNotify(previous, next) {
final res = super.updateShouldNotify(previous, next);
if (res) {
onUpdate(next);
}
return res;
}
void onUpdate(T value) {}
} }

View File

@@ -1,13 +1,11 @@
import 'package:animations/animations.dart'; import 'package:animations/animations.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/controller.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class BaseNavigator { class BaseNavigator {
static Future<T?> push<T>(BuildContext context, Widget child) async { static Future<T?> push<T>(BuildContext context, Widget child) async {
if (globalState.appState.viewMode != ViewMode.mobile) { if (!appController.isMobile) {
return await Navigator.of( return await Navigator.of(
context, context,
).push<T>(CommonDesktopRoute(builder: (context) => child)); ).push<T>(CommonDesktopRoute(builder: (context) => child));

View File

@@ -1,3 +1,5 @@
import 'dart:math';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart'; import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
@@ -20,6 +22,10 @@ extension NumExt on num {
return this * (1 + (globalState.theme.textScaleFactor - 1) * 0.5); return this * (1 + (globalState.theme.textScaleFactor - 1) * 0.5);
} }
double get mAp {
return this * min((1 + (globalState.theme.textScaleFactor - 1) * 0.5), 1);
}
TrafficShow get traffic { TrafficShow get traffic {
final units = TrafficUnit.values; final units = TrafficUnit.values;
var size = toDouble(); var size = toDouble();
@@ -51,7 +57,7 @@ extension NumExt on num {
extension DoubleExt on double { extension DoubleExt on double {
bool moreOrEqual(double value) { bool moreOrEqual(double value) {
return this > value || (value - this).abs() < precisionErrorTolerance + 2; return this > value || (value - this).abs() < precisionErrorTolerance + 1;
} }
} }

View File

@@ -61,19 +61,39 @@ class AppPath {
return directory.path; return directory.path;
} }
Future<String> get databasePath async {
final mHomeDirPath = await homeDirPath;
return join(mHomeDirPath, 'database.sqlite');
}
Future<String> get backupFilePath async {
final mHomeDirPath = await homeDirPath;
return join(mHomeDirPath, 'backup.zip');
}
Future<String> get restoreDirPath async {
final mHomeDirPath = await homeDirPath;
return join(mHomeDirPath, 'restore');
}
Future<String> get tempFilePath async {
final mTempDir = await tempDir.future;
return join(mTempDir.path, 'temp${utils.id}');
}
Future<String> get lockFilePath async { Future<String> get lockFilePath async {
final homeDirPath = await appPath.homeDirPath; final homeDirPath = await appPath.homeDirPath;
return join(homeDirPath, 'FlClash.lock'); return join(homeDirPath, 'FlClash.lock');
} }
Future<String> get configFilePath async { Future<String> get configFilePath async {
final homeDirPath = await appPath.homeDirPath; final mHomeDirPath = await homeDirPath;
return join(homeDirPath, 'config.yaml'); return join(mHomeDirPath, 'config.yaml');
} }
Future<String> get validateFilePath async { Future<String> get sharedFilePath async {
final homeDirPath = await appPath.homeDirPath; final mHomeDirPath = await homeDirPath;
return join(homeDirPath, 'temp', 'validate${utils.id}.yaml'); return join(mHomeDirPath, 'shared.json');
} }
Future<String> get sharedPreferencesPath async { Future<String> get sharedPreferencesPath async {
@@ -86,9 +106,18 @@ class AppPath {
return join(directory.path, profilesDirectoryName); return join(directory.path, profilesDirectoryName);
} }
Future<String> getProfilePath(String id) async { Future<String> getProfilePath(String fileName) async {
final directory = await profilesPath; return join(await profilesPath, '$fileName.yaml');
return join(directory, '$id.yaml'); }
Future<String> get scriptsDirPath async {
final path = await homeDirPath;
return join(path, 'scripts');
}
Future<String> getScriptPath(String fileName) async {
final path = await scriptsDirPath;
return join(path, '$fileName.js');
} }
Future<String> getIconsCacheDir() async { Future<String> getIconsCacheDir() async {

View File

@@ -7,9 +7,9 @@ import 'package:image_picker/image_picker.dart';
import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:mobile_scanner/mobile_scanner.dart';
class Picker { class Picker {
Future<PlatformFile?> pickerFile() async { Future<PlatformFile?> pickerFile({bool withData = true}) async {
final filePickerResult = await FilePicker.platform.pickFiles( final filePickerResult = await FilePicker.platform.pickFiles(
withData: true, withData: withData,
allowMultiple: false, allowMultiple: false,
initialDirectory: await appPath.downloadDirPath, initialDirectory: await appPath.downloadDirPath,
); );
@@ -20,15 +20,33 @@ class Picker {
final path = await FilePicker.platform.saveFile( final path = await FilePicker.platform.saveFile(
fileName: fileName, fileName: fileName,
initialDirectory: await appPath.downloadDirPath, initialDirectory: await appPath.downloadDirPath,
bytes: system.isAndroid ? bytes : null, bytes: bytes,
); );
if (!system.isAndroid && path != null) { if (!system.isAndroid && path != null) {
final file = await File(path).create(recursive: true); final file = File(path);
await file.writeAsBytes(bytes); await file.safeWriteAsBytes(bytes);
} }
return path; return path;
} }
Future<String?> saveFileWithPath(String fileName, String localPath) async {
final localFile = File(localPath);
if (!await localFile.exists()) {
await localFile.create(recursive: true);
}
final bytes = Platform.isAndroid ? await localFile.readAsBytes() : null;
final path = await FilePicker.platform.saveFile(
fileName: fileName,
initialDirectory: await appPath.downloadDirPath,
bytes: bytes,
);
if (path != null && bytes == null) {
await localFile.copy(path);
}
await localFile.safeDelete();
return path;
}
Future<String?> pickerConfigQRCode() async { Future<String?> pickerConfigQRCode() async {
final xFile = await ImagePicker().pickImage(source: ImageSource.gallery); final xFile = await ImagePicker().pickImage(source: ImageSource.gallery);
if (xFile == null) { if (xFile == null) {

View File

@@ -24,20 +24,60 @@ class Preferences {
return _instance!; return _instance!;
} }
Future<ClashConfig?> getClashConfig() async { Future<int> getVersion() async {
final preferences = await sharedPreferencesCompleter.future; final preferences = await sharedPreferencesCompleter.future;
final clashConfigString = preferences?.getString(clashConfigKey); return preferences?.getInt('version') ?? 0;
if (clashConfigString == null) return null; }
final clashConfigMap = json.decode(clashConfigString);
return ClashConfig.fromJson(clashConfigMap); Future<void> setVersion(int version) async {
final preferences = await sharedPreferencesCompleter.future;
await preferences?.setInt('version', version);
}
Future<void> saveShareState(SharedState shareState) async {
final preferences = await sharedPreferencesCompleter.future;
await preferences?.setString('sharedState', json.encode(shareState));
}
Future<Map<String, Object?>?> getConfigMap() async {
try {
final preferences = await sharedPreferencesCompleter.future;
final configString = preferences?.getString(configKey);
if (configString == null) return null;
final Map<String, Object?>? configMap = json.decode(configString);
return configMap;
} catch (_) {
return null;
}
}
Future<Map<String, Object?>?> getClashConfigMap() async {
try {
final preferences = await sharedPreferencesCompleter.future;
final clashConfigString = preferences?.getString(clashConfigKey);
if (clashConfigString == null) return null;
return json.decode(clashConfigString);
} catch (_) {
return null;
}
}
Future<void> clearClashConfig() async {
try {
final preferences = await sharedPreferencesCompleter.future;
await preferences?.remove(clashConfigKey);
return;
} catch (_) {
return;
}
} }
Future<Config?> getConfig() async { Future<Config?> getConfig() async {
final preferences = await sharedPreferencesCompleter.future; final configMap = await getConfigMap();
final configString = preferences?.getString(configKey); if (configMap == null) {
if (configString == null) return null; return null;
final configMap = json.decode(configString); }
return Config.compatibleFromJson(configMap); return Config.fromJson(configMap);
} }
Future<bool> saveConfig(Config config) async { Future<bool> saveConfig(Config config) async {
@@ -45,14 +85,9 @@ class Preferences {
return preferences?.setString(configKey, json.encode(config)) ?? false; return preferences?.setString(configKey, json.encode(config)) ?? false;
} }
Future<void> clearClashConfig() async {
final preferences = await sharedPreferencesCompleter.future;
preferences?.remove(clashConfigKey);
}
Future<void> clearPreferences() async { Future<void> clearPreferences() async {
final sharedPreferencesIns = await sharedPreferencesCompleter.future; final sharedPreferencesIns = await sharedPreferencesCompleter.future;
sharedPreferencesIns?.clear(); await sharedPreferencesIns?.clear();
} }
} }

View File

@@ -1,7 +1,7 @@
import 'package:fl_clash/controller.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/state.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class CommonPrint { class CommonPrint {
static CommonPrint? _instance; static CommonPrint? _instance;
@@ -16,12 +16,10 @@ class CommonPrint {
void log(String? text, {LogLevel logLevel = LogLevel.info}) { void log(String? text, {LogLevel logLevel = LogLevel.info}) {
final payload = '[APP] $text'; final payload = '[APP] $text';
debugPrint(payload); debugPrint(payload);
if (!globalState.isInit) { if (!appController.isAttach) {
return; return;
} }
globalState.appController.addLog( appController.addLog(Log.app(payload).copyWith(logLevel: logLevel));
Log.app(payload).copyWith(logLevel: logLevel),
);
} }
} }

View File

@@ -6,6 +6,8 @@ import 'dart:typed_data';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio/io.dart'; import 'package:dio/io.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.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/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@@ -22,7 +24,7 @@ class Request {
createHttpClient: () { createHttpClient: () {
final client = HttpClient(); final client = HttpClient();
client.findProxy = (Uri uri) { client.findProxy = (Uri uri) {
client.userAgent = globalState.ua; client.userAgent = appController.ua;
return FlClashHttpOverrides.handleFindProxy(uri); return FlClashHttpOverrides.handleFindProxy(uri);
}; };
return client; return client;
@@ -31,10 +33,23 @@ class Request {
} }
Future<Response<Uint8List>> getFileResponseForUrl(String url) async { Future<Response<Uint8List>> getFileResponseForUrl(String url) async {
return await _clashDio.get<Uint8List>( try {
url, return await _clashDio.get<Uint8List>(
options: Options(responseType: ResponseType.bytes), url,
); options: Options(responseType: ResponseType.bytes),
);
} catch (e) {
commonPrint.log('getFileResponseForUrl error ${e.toString()}');
if (e is DioException) {
if (e.type == DioExceptionType.unknown) {
throw appLocalizations.unknownNetworkError;
} else if (e.type == DioExceptionType.badResponse) {
throw appLocalizations.networkException;
}
rethrow;
}
throw appLocalizations.unknownNetworkError;
}
} }
Future<Response<String>> getTextResponseForUrl(String url) async { Future<Response<String>> getTextResponseForUrl(String url) async {
@@ -57,18 +72,23 @@ class Request {
} }
Future<Map<String, dynamic>?> checkForUpdate() async { Future<Map<String, dynamic>?> checkForUpdate() async {
final response = await dio.get( try {
'https://api.github.com/repos/$repository/releases/latest', final response = await dio.get(
options: Options(responseType: ResponseType.json), 'https://api.github.com/repos/$repository/releases/latest',
); options: Options(responseType: ResponseType.json),
if (response.statusCode != 200) return null; );
final data = response.data as Map<String, dynamic>; if (response.statusCode != 200) return null;
final remoteVersion = data['tag_name']; final data = response.data as Map<String, dynamic>;
final version = globalState.packageInfo.version; final remoteVersion = data['tag_name'];
final hasUpdate = final version = globalState.packageInfo.version;
utils.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0; final hasUpdate =
if (!hasUpdate) return null; utils.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0;
return data; if (!hasUpdate) return null;
return data;
} catch (e) {
commonPrint.log('checkForUpdate failed', logLevel: LogLevel.warning);
return null;
}
} }
final Map<String, IpInfo Function(Map<String, dynamic>)> _ipInfoSources = { final Map<String, IpInfo Function(Map<String, dynamic>)> _ipInfoSources = {
@@ -83,6 +103,7 @@ class Request {
Future<Result<IpInfo?>> checkIp({CancelToken? cancelToken}) async { Future<Result<IpInfo?>> checkIp({CancelToken? cancelToken}) async {
var failureCount = 0; var failureCount = 0;
final token = cancelToken ?? CancelToken();
final futures = _ipInfoSources.entries.map((source) async { final futures = _ipInfoSources.entries.map((source) async {
final Completer<Result<IpInfo?>> completer = Completer(); final Completer<Result<IpInfo?>> completer = Completer();
handleFailRes() { handleFailRes() {
@@ -94,7 +115,7 @@ class Request {
final future = dio final future = dio
.get<Map<String, dynamic>>( .get<Map<String, dynamic>>(
source.key, source.key,
cancelToken: cancelToken, cancelToken: token,
options: Options(responseType: ResponseType.json), options: Options(responseType: ResponseType.json),
) )
.timeout(const Duration(seconds: 10)); .timeout(const Duration(seconds: 10));
@@ -117,7 +138,7 @@ class Request {
return completer.future; return completer.future;
}); });
final res = await Future.any(futures); final res = await Future.any(futures);
cancelToken?.cancel(); token.cancel();
return res; return res;
} }

58
lib/common/snowflake.dart Normal file
View File

@@ -0,0 +1,58 @@
class Snowflake {
static Snowflake? _instance;
Snowflake._internal();
factory Snowflake() {
_instance ??= Snowflake._internal();
return _instance!;
}
static const int twepoch = 1704067200000;
static const int workerIdBits = 10;
static const int sequenceBits = 12;
static const int maxWorkerId = -1 ^ (-1 << workerIdBits);
static const int sequenceMask = -1 ^ (-1 << sequenceBits);
static const int workerIdShift = sequenceBits;
static const int timestampLeftShift = sequenceBits + workerIdBits;
final int workerId = 1;
int _lastTimestamp = -1;
int _sequence = 0;
int get id {
int timestamp = DateTime.now().millisecondsSinceEpoch;
if (timestamp < _lastTimestamp) {
throw ArgumentError(
'Clock moved backwards. Refusing to generate id for ${_lastTimestamp - timestamp} milliseconds',
);
}
if (timestamp == _lastTimestamp) {
_sequence = (_sequence + 1) & sequenceMask;
if (_sequence == 0) {
timestamp = _getNextMillis(_lastTimestamp);
}
} else {
_sequence = 0;
}
_lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) |
(workerId << workerIdShift) |
_sequence;
}
int _getNextMillis(int lastTimestamp) {
int timestamp = DateTime.now().millisecondsSinceEpoch;
while (timestamp <= lastTimestamp) {
timestamp = DateTime.now().millisecondsSinceEpoch;
}
return timestamp;
}
}
final snowflake = Snowflake();

33
lib/common/store.dart Normal file
View File

@@ -0,0 +1,33 @@
import 'dart:async';
class Store<T> {
late T _data;
Store(Stream stream, T defaultValue) {
stream.listen((data) {
_add(data);
});
_data = defaultValue;
}
bool equals(T oldValue, T newValue) {
return oldValue == newValue;
}
void _add(T value) {
if (!equals(_data, value)) {
_streamController.add(value);
_data = value;
}
}
final StreamController<T> _streamController = StreamController<T>.broadcast();
Stream<T> get stream => _streamController.stream;
T get value => _data;
set value(T value) {
_add(value);
}
}

View File

@@ -2,8 +2,7 @@ import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:fl_clash/common/common.dart';
import 'print.dart';
extension StringExtension on String { extension StringExtension on String {
bool get isUrl { bool get isUrl {
@@ -78,13 +77,26 @@ extension StringExtension on String {
// bool containsToLower(String target) { // bool containsToLower(String target) {
// return toLowerCase().contains(target); // return toLowerCase().contains(target);
// } // }
}
extension StringExtensionSafe on String? { Future<T> commonToJSON<T>() async {
String getSafeValue(String defaultValue) { final thresholdLimit = 51200;
if (this == null || this!.isEmpty) { if (length < thresholdLimit) {
return defaultValue; return json.decode(this);
} else {
return await decodeJSONTask<T>(this);
} }
return this!; }
}
extension StringNullExt on String? {
String takeFirstValid(List<String?> others, {String defaultValue = ''}) {
if (this != null && this!.trim().isNotEmpty) return this!.trim();
for (final s in others) {
if (s != null && s.trim().isNotEmpty) {
return s.trim();
}
}
return defaultValue;
} }
} }

View File

@@ -260,8 +260,9 @@ class Windows {
await Future.delayed(Duration(milliseconds: 300)); await Future.delayed(Duration(milliseconds: 300));
final retryStatus = await retry( final retryStatus = await retry(
task: checkService, task: checkService,
maxAttempts: 5,
retryIf: (status) => status != WindowsHelperServiceStatus.running, retryIf: (status) => status != WindowsHelperServiceStatus.running,
delay: commonDuration, delay: Duration(seconds: 1),
); );
return res && retryStatus == WindowsHelperServiceStatus.running; return res && retryStatus == WindowsHelperServiceStatus.running;
} }

612
lib/common/task.dart Normal file
View File

@@ -0,0 +1,612 @@
import 'dart:convert';
import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/database/database.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:path/path.dart';
Future<T> decodeJSONTask<T>(String data) async {
return await compute<String, T>(_decodeJSON, data);
}
Future<T> _decodeJSON<T>(String content) async {
return json.decode(content);
}
Future<String> encodeJSONTask<T>(T data) async {
return await compute<T, String>(_encodeJSON, data);
}
Future<String> _encodeJSON<T>(T content) async {
return json.encode(content);
}
Future<String> encodeYamlTask<T>(T data) async {
return await compute<T, String>(_encodeYaml, data);
}
Future<String> _encodeYaml<T>(T content) async {
return yaml.encode(content);
}
Future<List<Group>> toGroupsTask(ComputeGroupsState data) async {
return await compute<ComputeGroupsState, List<Group>>(_toGroupsTask, data);
}
Future<List<Group>> _toGroupsTask(ComputeGroupsState state) async {
final proxiesData = state.proxiesData;
final all = proxiesData.all;
final sortType = state.sortType;
final delayMap = state.delayMap;
final selectedMap = state.selectedMap;
final defaultTestUrl = state.defaultTestUrl;
final proxies = proxiesData.proxies;
if (proxies.isEmpty) return [];
final groupsRaw = all
.where((name) {
final proxy = proxies[name] ?? {};
return GroupTypeExtension.valueList.contains(proxy['type']);
})
.map((groupName) {
final group = proxies[groupName];
group['all'] = ((group['all'] ?? []) as List)
.map((name) => proxies[name])
.where((proxy) => proxy != null)
.toList();
return group;
})
.toList();
final groups = groupsRaw.map((e) => Group.fromJson(e)).toList();
return computeSort(
groups: groups,
sortType: sortType,
delayMap: delayMap,
selectedMap: selectedMap,
defaultTestUrl: defaultTestUrl,
);
}
Future<Map<String, dynamic>> makeRealProfileTask(
MakeRealProfileState data,
) async {
return await compute<MakeRealProfileState, Map<String, dynamic>>(
_makeRealProfileTask,
data,
);
}
Future<Map<String, dynamic>> _makeRealProfileTask(
MakeRealProfileState data,
) async {
final rawConfig = Map.from(data.rawConfig);
final realPatchConfig = data.realPatchConfig;
final profilesPath = data.profilesPath;
final profileId = data.profileId;
final overrideDns = data.overrideDns;
final addedRules = data.addedRules;
final appendSystemDns = data.appendSystemDns;
final defaultUA = data.defaultUA;
String getProvidersFilePathInner(String type, String url) {
return join(
profilesPath,
'providers',
profileId.toString(),
type,
url.toMd5(),
);
}
rawConfig['external-controller'] = realPatchConfig.externalController.value;
rawConfig['external-ui'] = '';
rawConfig['interface-name'] = '';
rawConfig['external-ui-url'] = '';
rawConfig['tcp-concurrent'] = realPatchConfig.tcpConcurrent;
rawConfig['unified-delay'] = realPatchConfig.unifiedDelay;
rawConfig['ipv6'] = realPatchConfig.ipv6;
rawConfig['log-level'] = realPatchConfig.logLevel.name;
rawConfig['port'] = 0;
rawConfig['socks-port'] = 0;
rawConfig['keep-alive-interval'] = realPatchConfig.keepAliveInterval;
rawConfig['mixed-port'] = realPatchConfig.mixedPort;
rawConfig['port'] = realPatchConfig.port;
rawConfig['socks-port'] = realPatchConfig.socksPort;
rawConfig['redir-port'] = realPatchConfig.redirPort;
rawConfig['tproxy-port'] = realPatchConfig.tproxyPort;
rawConfig['find-process-mode'] = realPatchConfig.findProcessMode.name;
rawConfig['allow-lan'] = realPatchConfig.allowLan;
rawConfig['mode'] = realPatchConfig.mode.name;
if (rawConfig['tun'] == null) {
rawConfig['tun'] = {};
}
rawConfig['tun']['enable'] = realPatchConfig.tun.enable;
rawConfig['tun']['device'] = realPatchConfig.tun.device;
rawConfig['tun']['dns-hijack'] = realPatchConfig.tun.dnsHijack;
rawConfig['tun']['stack'] = realPatchConfig.tun.stack.name;
rawConfig['tun']['route-address'] = realPatchConfig.tun.routeAddress;
rawConfig['tun']['auto-route'] = realPatchConfig.tun.autoRoute;
rawConfig['geodata-loader'] = realPatchConfig.geodataLoader.name;
if (rawConfig['sniffer']?['sniff'] != null) {
for (final value in (rawConfig['sniffer']?['sniff'] as Map).values) {
if (value['ports'] != null && value['ports'] is List) {
value['ports'] =
value['ports']?.map((item) => item.toString()).toList() ?? [];
}
}
}
if (rawConfig['profile'] == null) {
rawConfig['profile'] = {};
}
if (rawConfig['proxy-providers'] != null) {
final proxyProviders = rawConfig['proxy-providers'] as Map;
for (final key in proxyProviders.keys) {
final proxyProvider = proxyProviders[key];
if (proxyProvider['type'] != 'http') {
continue;
}
if (proxyProvider['url'] != null) {
proxyProvider['path'] = getProvidersFilePathInner(
'proxies',
proxyProvider['url'],
);
}
}
}
if (rawConfig['rule-providers'] != null) {
final ruleProviders = rawConfig['rule-providers'] as Map;
for (final key in ruleProviders.keys) {
final ruleProvider = ruleProviders[key];
if (ruleProvider['type'] != 'http') {
continue;
}
if (ruleProvider['url'] != null) {
ruleProvider['path'] = getProvidersFilePathInner(
'rules',
ruleProvider['url'],
);
}
}
}
rawConfig['profile']['store-selected'] = false;
rawConfig['geox-url'] = realPatchConfig.geoXUrl.toJson();
rawConfig['global-ua'] = realPatchConfig.globalUa ?? defaultUA;
if (rawConfig['hosts'] == null) {
rawConfig['hosts'] = {};
}
for (final host in realPatchConfig.hosts.entries) {
rawConfig['hosts'][host.key] = host.value.splitByMultipleSeparators;
}
if (rawConfig['dns'] == null) {
rawConfig['dns'] = {};
}
final isEnableDns = rawConfig['dns']['enable'] == true;
final systemDns = 'system://';
if (overrideDns || !isEnableDns) {
final dns = switch (!isEnableDns) {
true => realPatchConfig.dns.copyWith(
nameserver: [...realPatchConfig.dns.nameserver, systemDns],
),
false => realPatchConfig.dns,
};
rawConfig['dns'] = dns.toJson();
rawConfig['dns']['nameserver-policy'] = {};
for (final entry in dns.nameserverPolicy.entries) {
rawConfig['dns']['nameserver-policy'][entry.key] =
entry.value.splitByMultipleSeparators;
}
}
if (appendSystemDns) {
final List<String> nameserver = List<String>.from(
rawConfig['dns']['nameserver'] ?? [],
);
if (!nameserver.contains(systemDns)) {
rawConfig['dns']['nameserver'] = [...nameserver, systemDns];
}
}
List<String> rules = [];
if (rawConfig['rules'] != null) {
rules = List<String>.from(rawConfig['rules']);
}
rawConfig.remove('rules');
if (addedRules.isNotEmpty) {
final parsedNewRules = addedRules
.map((item) => ParsedRule.parseString(item.value))
.toList();
final hasMatchPlaceholder = parsedNewRules.any(
(item) => item.ruleTarget?.toUpperCase() == 'MATCH',
);
String? replacementTarget;
if (hasMatchPlaceholder) {
for (int i = rules.length - 1; i >= 0; i--) {
final parsed = ParsedRule.parseString(rules[i]);
if (parsed.ruleAction == RuleAction.MATCH) {
final target = parsed.ruleTarget;
if (target != null && target.isNotEmpty) {
replacementTarget = target;
break;
}
}
}
}
final List<String> finalAddedRules;
if (replacementTarget?.isNotEmpty == true) {
finalAddedRules = [];
for (int i = 0; i < parsedNewRules.length; i++) {
final parsed = parsedNewRules[i];
if (parsed.ruleTarget?.toUpperCase() == 'MATCH') {
finalAddedRules.add(
parsed.copyWith(ruleTarget: replacementTarget).value,
);
} else {
finalAddedRules.add(addedRules[i].value);
}
}
} else {
finalAddedRules = addedRules.map((e) => e.value).toList();
}
rules = [...finalAddedRules, ...rules];
}
rawConfig['rules'] = rules;
return Map<String, dynamic>.from(rawConfig);
}
Future<List<String>> shakingProfileTask(
VM2<Iterable<int>, Iterable<int>> data,
) async {
return await compute<
VM3<Iterable<int>, Iterable<int>, RootIsolateToken>,
List<String>
>(_shakingProfileTask, VM3(data.a, data.b, RootIsolateToken.instance!));
}
Future<List<String>> _shakingProfileTask(
VM3<Iterable<int>, Iterable<int>, RootIsolateToken> data,
) async {
final profileIds = data.a;
final scriptIds = data.b;
final token = data.c;
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
final profilesDir = Directory(await appPath.profilesPath);
final scriptsDir = Directory(await appPath.scriptsDirPath);
final providersDir = Directory(await appPath.getProvidersRootPath());
final List<String> targets = [];
void scanDirectory(
Directory dir,
Iterable<int> baseNames, {
bool skipProvidersFolder = false,
}) {
if (!dir.existsSync()) return;
final entities = dir.listSync(recursive: false, followLinks: false);
for (final entity in entities) {
if (entity is File) {
final id = basenameWithoutExtension(entity.path);
if (!baseNames.contains(int.tryParse(id))) {
targets.add(entity.path);
}
} else if (skipProvidersFolder && entity is Directory) {
if (basename(entity.path) == 'providers') {
continue;
}
}
}
}
scanDirectory(profilesDir, profileIds, skipProvidersFolder: true);
scanDirectory(providersDir, profileIds);
scanDirectory(scriptsDir, scriptIds);
return targets;
}
Future<String> encodeLogsTask(List<Log> data) async {
return await compute<List<Log>, String>(_encodeLogsTask, data);
}
Future<String> _encodeLogsTask(List<Log> data) async {
final logsRaw = data.map((item) => item.toString());
final logsRawString = logsRaw.join('\n');
return logsRawString;
}
Future<MigrationData> oldToNowTask(Map<String, Object?> data) async {
final homeDir = await appPath.homeDirPath;
return await compute<
VM3<Map<String, Object?>, String, String>,
MigrationData
>(_oldToNowTask, VM3(data, homeDir, homeDir));
}
Future<MigrationData> _oldToNowTask(
VM3<Map<String, Object?>, String, String> data,
) async {
final configMap = data.a;
final sourcePath = data.b;
final targetPath = data.c;
final accessControlMap = configMap['accessControl'];
final isAccessControl = configMap['isAccessControl'];
if (accessControlMap != null) {
(accessControlMap as Map)['enable'] = isAccessControl;
if (configMap['vpnProps'] != null) {
final vpnPropsRaw = configMap['vpnProps'] as Map;
vpnPropsRaw['accessControl'] = accessControlMap;
}
}
if (configMap['vpnProps'] != null) {
final vpnPropsRaw = configMap['vpnProps'] as Map;
vpnPropsRaw['accessControlProps'] = vpnPropsRaw['accessControl'];
}
configMap['davProps'] = configMap['dav'];
final appSettingProps = configMap['appSetting'] as Map? ?? {};
appSettingProps['restoreStrategy'] = appSettingProps['recoveryStrategy'];
configMap['appSettingProps'] = appSettingProps;
configMap['proxiesStyleProps'] = configMap['proxiesStyle'];
configMap['proxiesStyleProps'] = configMap['proxiesStyle'];
// final overwriteMap = configMap['overwrite'] as Map? ?? {};
// configMap['overwriteType'] = overwriteMap['type'];
// configMap['scriptId'] = overwriteMap['scriptOverwrite'];
List rawScripts = configMap['scripts'] as List<dynamic>? ?? [];
if (rawScripts.isEmpty) {
final scriptPropsJson = configMap['scriptProps'] as Map<String, dynamic>?;
if (scriptPropsJson != null) {
rawScripts = scriptPropsJson['scripts'] as List<dynamic>? ?? [];
}
}
final Map<String, int> idMap = {};
final List<Script> scripts = [];
for (final rawScript in rawScripts) {
final id = rawScript['id'] as String?;
final content = rawScript['content'] as String?;
final label = rawScript['label'] as String?;
if (id == null || content == null || label == null) {
continue;
}
final newId = idMap.updateCacheValue(rawScript['id'], () => snowflake.id);
final path = _getScriptPath(targetPath, newId.toString());
final file = File(path);
await file.safeWriteAsString(content);
scripts.add(
Script(id: newId, label: label, lastUpdateTime: DateTime.now()),
);
}
List rawRules = configMap['rules'] as List<dynamic>? ?? [];
final List<Rule> rules = [];
final List<ProfileRuleLink> links = [];
for (final rawRule in rawRules) {
final id = idMap.updateCacheValue(rawRule['id'], () => snowflake.id);
rawRule['id'] = id;
rules.add(Rule.fromJson(rawRule));
links.add(ProfileRuleLink(ruleId: id));
}
List rawProfiles = configMap['profiles'] as List<dynamic>? ?? [];
final List<Profile> profiles = [];
for (final rawProfile in rawProfiles) {
final rawId = rawProfile['id'] as String?;
if (rawId == null) {
continue;
}
final profileId = idMap.updateCacheValue(rawId, () => snowflake.id);
rawProfile['id'] = profileId;
final overwrite = rawProfile['overwrite'] as Map?;
if (overwrite != null) {
final standardOverwrite = overwrite['standardOverwrite'] as Map?;
if (standardOverwrite != null) {
final addedRules = standardOverwrite['addedRules'] as List? ?? [];
for (final addRule in addedRules) {
final id = idMap.updateCacheValue(addRule['id'], () => snowflake.id);
addRule['id'] = id;
rules.add(Rule.fromJson(addRule));
links.add(
ProfileRuleLink(
profileId: profileId,
ruleId: id,
scene: RuleScene.added,
),
);
}
final disabledRuleIds = standardOverwrite['disabledRuleIds'] as List?;
if (disabledRuleIds != null) {
for (final disabledRuleId in disabledRuleIds) {
final newDisabledRuleId = idMap[disabledRuleId];
if (newDisabledRuleId != null) {
links.add(
ProfileRuleLink(
profileId: profileId,
ruleId: newDisabledRuleId,
scene: RuleScene.disabled,
),
);
}
}
}
}
final scriptOverwrite = overwrite['scriptOverwrite'] as Map?;
if (scriptOverwrite != null) {
final scriptId = scriptOverwrite['scriptId'] as String?;
rawProfile['scriptId'] = scriptId != null ? idMap[scriptId] : null;
}
rawProfile['overwriteType'] = overwrite['type'];
}
final sourceFile = File(_getProfilePath(sourcePath, rawId));
final targetFilePath = _getProfilePath(targetPath, profileId.toString());
await sourceFile.safeCopy(targetFilePath);
profiles.add(Profile.fromJson(rawProfile));
}
final currentProfileId = configMap['currentProfileId'];
configMap['currentProfileId'] = currentProfileId != null
? idMap[currentProfileId]
: null;
return MigrationData(
configMap: configMap,
profiles: profiles,
rules: rules,
scripts: scripts,
links: links,
);
}
Future<String> backupTask(
Map<String, dynamic> configMap,
Iterable<String> fileNames,
) async {
return await compute<
VM3<Map<String, dynamic>, Iterable<String>, RootIsolateToken>,
String
>(_backupTask, VM3(configMap, fileNames, RootIsolateToken.instance!));
}
Future<String> _backupTask<T>(
VM3<Map<String, dynamic>, Iterable<String>, RootIsolateToken> args,
) async {
final configMap = args.a;
final fileNames = args.b;
final token = args.c;
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
final dbPath = await appPath.databasePath;
final configStr = json.encode(configMap);
final profilesDir = Directory(await appPath.profilesPath);
final scriptsDir = Directory(await appPath.scriptsDirPath);
final tempZipFilePath = await appPath.tempFilePath;
final tempDBFile = File(await appPath.tempFilePath);
final tempConfigFile = File(await appPath.tempFilePath);
final dbFile = File(dbPath);
if (await dbFile.exists()) {
await dbFile.copy(tempDBFile.path);
}
final encoder = ZipFileEncoder();
encoder.create(tempZipFilePath);
await tempConfigFile.writeAsString(configStr);
await encoder.addFile(tempDBFile, backupDatabaseName);
await encoder.addFile(tempConfigFile, configJsonName);
if (await profilesDir.exists()) {
await encoder.addDirectory(
profilesDir,
filter: (file, _) {
if (!fileNames.contains(basename(file.path))) {
return ZipFileOperation.skip;
}
return ZipFileOperation.include;
},
);
}
if (await scriptsDir.exists()) {
await encoder.addDirectory(
scriptsDir,
filter: (file, _) {
if (!fileNames.contains(basename(file.path))) {
return ZipFileOperation.skip;
}
return ZipFileOperation.include;
},
);
}
encoder.close();
await tempConfigFile.safeDelete();
await tempDBFile.safeDelete();
return tempZipFilePath;
}
Future<MigrationData> restoreTask() async {
return await compute<RootIsolateToken, MigrationData>(
_restoreTask,
RootIsolateToken.instance!,
);
}
Future<MigrationData> _restoreTask(RootIsolateToken token) async {
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
final backupFilePath = await appPath.backupFilePath;
final restoreDirPath = await appPath.restoreDirPath;
final homeDirPath = await appPath.homeDirPath;
final zipDecoder = ZipDecoder();
final input = InputFileStream(backupFilePath);
final archive = zipDecoder.decodeStream(input);
final dir = Directory(restoreDirPath);
await dir.create(recursive: true);
for (final file in archive.files) {
final outPath = join(restoreDirPath, posix.normalize(file.name));
final outputStream = OutputFileStream(outPath);
file.writeContent(outputStream);
await outputStream.close();
}
await input.close();
final restoreConfigFile = File(join(restoreDirPath, configJsonName));
if (!await restoreConfigFile.exists()) {
throw appLocalizations.invalidBackupFile;
}
final restoreConfigMap =
json.decode(await restoreConfigFile.readAsString())
as Map<String, Object?>?;
final version = restoreConfigMap?['version'] ?? 0;
MigrationData migrationData = MigrationData(configMap: restoreConfigMap);
if (version == 0 && restoreConfigMap != null) {
migrationData = await _oldToNowTask(
VM3(restoreConfigMap, restoreDirPath, homeDirPath),
);
return migrationData;
}
final backupDatabaseFile = File(join(restoreDirPath, backupDatabaseName));
if (!await backupDatabaseFile.exists()) {
return migrationData;
}
final database = Database(
driftDatabase(
name: 'database',
native: DriftNativeOptions(
databaseDirectory: () async => Directory(restoreDirPath),
),
),
);
final results = await Future.wait([
database.profilesDao.all().get(),
database.scriptsDao.all().get(),
database.rules.all().map((item) => item.toRule()).get(),
database.profileRuleLinks.all().map((item) => item.toLink()).get(),
]);
final profiles = results[0].cast<Profile>();
final scripts = results[1].cast<Script>();
final profilesMigration = profiles.map(
(item) => VM2(
_getProfilePath(restoreDirPath, item.id.toString()),
_getProfilePath(homeDirPath, item.id.toString()),
),
);
final scriptsMigration = scripts.map(
(item) => VM2(
_getScriptPath(restoreDirPath, item.id.toString()),
_getScriptPath(homeDirPath, item.id.toString()),
),
);
await _copyWithMapList([...profilesMigration, ...scriptsMigration]);
migrationData = migrationData.copyWith(
profiles: profiles,
scripts: scripts,
rules: results[2].cast<Rule>(),
links: results[3].cast<ProfileRuleLink>(),
);
await database.close();
return migrationData;
}
Future<void> _copyWithMapList(List<VM2<String, String>> copyMapList) async {
await Future.wait(
copyMapList.map((item) => File(item.a).safeCopy(item.b)).toList(),
);
}
String _getScriptPath(String root, String fileName) {
return join(root, 'scripts', '$fileName.js');
}
String _getProfilePath(String root, String fileName) {
return join(root, 'profiles', '$fileName.yaml');
}

View File

@@ -1,9 +1,8 @@
import 'dart:io'; import 'dart:io';
import 'package:fl_clash/common/iterable.dart'; import 'package:fl_clash/controller.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/state.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:tray_manager/tray_manager.dart'; import 'package:tray_manager/tray_manager.dart';
@@ -14,10 +13,23 @@ import 'system.dart';
import 'window.dart'; import 'window.dart';
class Tray { class Tray {
static Tray? _instance;
Tray._internal();
factory Tray() {
_instance ??= Tray._internal();
return _instance!;
}
String get trayIconSuffix { String get trayIconSuffix {
return system.isWindows ? 'ico' : 'png'; return system.isWindows ? 'ico' : 'png';
} }
Future<void> destroy() async {
await trayManager.destroy();
}
String getTryIcon({required bool isStart, required bool tunEnable}) { String getTryIcon({required bool isStart, required bool tunEnable}) {
if (system.isMacOS || !isStart) { if (system.isMacOS || !isStart) {
return 'assets/images/icon/status_1.$trayIconSuffix'; return 'assets/images/icon/status_1.$trayIconSuffix';
@@ -29,11 +41,10 @@ class Tray {
} }
Future _updateSystemTray({ Future _updateSystemTray({
bool force = false,
required bool isStart, required bool isStart,
required bool tunEnable, required bool tunEnable,
}) async { }) async {
if (Platform.isLinux || force) { if (Platform.isLinux) {
await trayManager.destroy(); await trayManager.destroy();
} }
await trayManager.setIcon( await trayManager.setIcon(
@@ -47,7 +58,7 @@ class Tray {
Future<void> update({ Future<void> update({
required TrayState trayState, required TrayState trayState,
bool focus = false, required Traffic traffic,
}) async { }) async {
if (system.isAndroid) { if (system.isAndroid) {
return; return;
@@ -56,7 +67,6 @@ class Tray {
await _updateSystemTray( await _updateSystemTray(
isStart: trayState.isStart, isStart: trayState.isStart,
tunEnable: trayState.tunEnable, tunEnable: trayState.tunEnable,
force: focus,
); );
} }
List<MenuItem> menuItems = []; List<MenuItem> menuItems = [];
@@ -70,7 +80,7 @@ class Tray {
final startMenuItem = MenuItem.checkbox( final startMenuItem = MenuItem.checkbox(
label: trayState.isStart ? appLocalizations.stop : appLocalizations.start, label: trayState.isStart ? appLocalizations.stop : appLocalizations.start,
onClick: (_) async { onClick: (_) async {
globalState.appController.updateStart(); appController.updateStart();
}, },
checked: false, checked: false,
); );
@@ -79,7 +89,7 @@ class Tray {
final speedStatistics = MenuItem.checkbox( final speedStatistics = MenuItem.checkbox(
label: appLocalizations.speedStatistics, label: appLocalizations.speedStatistics,
onClick: (_) async { onClick: (_) async {
globalState.appController.updateSpeedStatistics(); appController.updateSpeedStatistics();
}, },
checked: trayState.showTrayTitle, checked: trayState.showTrayTitle,
); );
@@ -91,7 +101,7 @@ class Tray {
MenuItem.checkbox( MenuItem.checkbox(
label: Intl.message(mode.name), label: Intl.message(mode.name),
onClick: (_) { onClick: (_) {
globalState.appController.changeMode(mode); appController.changeMode(mode);
}, },
checked: mode == trayState.mode, checked: mode == trayState.mode,
), ),
@@ -106,9 +116,8 @@ class Tray {
MenuItem.checkbox( MenuItem.checkbox(
label: proxy.name, label: proxy.name,
checked: checked:
globalState.getSelectedProxyName(group.name) == proxy.name, appController.getSelectedProxyName(group.name) == proxy.name,
onClick: (_) { onClick: (_) {
final appController = globalState.appController;
appController.updateCurrentSelectedMap(group.name, proxy.name); appController.updateCurrentSelectedMap(group.name, proxy.name);
appController.changeProxy( appController.changeProxy(
groupName: group.name, groupName: group.name,
@@ -134,7 +143,7 @@ class Tray {
MenuItem.checkbox( MenuItem.checkbox(
label: appLocalizations.tun, label: appLocalizations.tun,
onClick: (_) { onClick: (_) {
globalState.appController.updateTun(); appController.updateTun();
}, },
checked: trayState.tunEnable, checked: trayState.tunEnable,
), ),
@@ -143,7 +152,7 @@ class Tray {
MenuItem.checkbox( MenuItem.checkbox(
label: appLocalizations.systemProxy, label: appLocalizations.systemProxy,
onClick: (_) { onClick: (_) {
globalState.appController.updateSystemProxy(); appController.updateSystemProxy();
}, },
checked: trayState.systemProxy, checked: trayState.systemProxy,
), ),
@@ -153,7 +162,7 @@ class Tray {
final autoStartMenuItem = MenuItem.checkbox( final autoStartMenuItem = MenuItem.checkbox(
label: appLocalizations.autoLaunch, label: appLocalizations.autoLaunch,
onClick: (_) async { onClick: (_) async {
globalState.appController.updateAutoLaunch(); appController.updateAutoLaunch();
}, },
checked: trayState.autoLaunch, checked: trayState.autoLaunch,
); );
@@ -169,7 +178,7 @@ class Tray {
final exitMenuItem = MenuItem( final exitMenuItem = MenuItem(
label: appLocalizations.exit, label: appLocalizations.exit,
onClick: (_) async { onClick: (_) async {
await globalState.appController.handleExit(); await appController.handleExit();
}, },
); );
menuItems.add(exitMenuItem); menuItems.add(exitMenuItem);
@@ -179,13 +188,9 @@ class Tray {
await _updateSystemTray( await _updateSystemTray(
isStart: trayState.isStart, isStart: trayState.isStart,
tunEnable: trayState.tunEnable, tunEnable: trayState.tunEnable,
force: focus,
); );
} }
updateTrayTitle( updateTrayTitle(showTrayTitle: trayState.showTrayTitle, traffic: traffic);
showTrayTitle: trayState.showTrayTitle,
traffic: globalState.appState.traffics.list.safeLast(Traffic()),
);
} }
Future<void> updateTrayTitle({ Future<void> updateTrayTitle({

View File

@@ -10,6 +10,15 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class Utils { class Utils {
static Utils? _instance;
Utils._internal();
factory Utils() {
_instance ??= Utils._internal();
return _instance!;
}
Color? getDelayColor(int? delay) { Color? getDelayColor(int? delay) {
if (delay == null) return null; if (delay == null) return null;
if (delay < 0) return Colors.red; if (delay < 0) return Colors.red;
@@ -319,7 +328,7 @@ class Utils {
required Function function, required Function function,
required void Function(T data, int elapsedMilliseconds) onWatch, required void Function(T data, int elapsedMilliseconds) onWatch,
}) async { }) async {
if (kDebugMode) { if (kDebugMode && watchExecution) {
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
final res = await function(); final res = await function();
stopwatch.stop(); stopwatch.stop();
@@ -328,6 +337,21 @@ class Utils {
} }
return await function(); return await function();
} }
int fastHash(String string) {
var hash = 0xcbf29ce484222325;
var i = 0;
while (i < string.length) {
final codeUnit = string.codeUnitAt(i++);
hash ^= codeUnit >> 8;
hash *= 0x100000001b3;
hash ^= codeUnit & 0xFF;
hash *= 0x100000001b3;
}
return hash;
}
} }
final utils = Utils(); final utils = Utils();

View File

@@ -2,14 +2,21 @@ import 'dart:io';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/config.dart'; import 'package:fl_clash/models/config.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:screen_retriever/screen_retriever.dart'; import 'package:screen_retriever/screen_retriever.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
class Window { class Window {
Future<void> init(int version) async { static Window? _instance;
final props = globalState.config.windowProps;
Window._internal();
factory Window() {
_instance ??= Window._internal();
return _instance!;
}
Future<void> init(int version, WindowProps props) async {
final acquire = await singleInstanceLock.acquire(); final acquire = await singleInstanceLock.acquire();
if (!acquire) { if (!acquire) {
exit(0); exit(0);
@@ -76,6 +83,7 @@ class Window {
} }
Future<void> close() async { Future<void> close() async {
await windowManager.close();
exit(0); exit(0);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:isolate';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/core/core.dart'; import 'package:fl_clash/core/core.dart';
import 'package:fl_clash/core/interface.dart'; import 'package:fl_clash/core/interface.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/state.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
@@ -67,25 +65,23 @@ class CoreController {
); );
} }
Future<void> shutdown() async { Future<void> shutdown(bool isUser) async {
await _interface.shutdown(); await _interface.shutdown(isUser);
} }
FutureOr<bool> get isInit => _interface.isInit; FutureOr<bool> get isInit => _interface.isInit;
Future<String> validateConfig(String data) async { Future<String> validateConfig(String path) async {
final path = await appPath.validateFilePath;
await globalState.genValidateFile(path, data);
final res = await _interface.validateConfig(path); final res = await _interface.validateConfig(path);
await File(path).delete();
return res; return res;
} }
Future<String> validateConfigFormBytes(Uint8List bytes) async { Future<String> validateConfigWithData(String data) async {
final path = await appPath.validateFilePath; final path = await appPath.tempFilePath;
await globalState.genValidateFileFormBytes(path, bytes); final file = File(path);
await file.safeWriteAsString(data);
final res = await _interface.validateConfig(path); final res = await _interface.validateConfig(path);
await File(path).delete(); await File(path).safeDelete();
return res; return res;
} }
@@ -111,33 +107,16 @@ class CoreController {
required Map<String, String> selectedMap, required Map<String, String> selectedMap,
required String defaultTestUrl, required String defaultTestUrl,
}) async { }) async {
final proxies = await _interface.getProxies(); final proxiesData = await _interface.getProxies();
return Isolate.run<List<Group>>(() { return toGroupsTask(
if (proxies.isEmpty) return []; ComputeGroupsState(
final groupNames = [ proxiesData: proxiesData,
UsedProxy.GLOBAL.name,
...(proxies[UsedProxy.GLOBAL.name]['all'] as List).where((e) {
final proxy = proxies[e] ?? {};
return GroupTypeExtension.valueList.contains(proxy['type']);
}),
];
final groupsRaw = groupNames.map((groupName) {
final group = proxies[groupName];
group['all'] = ((group['all'] ?? []) as List)
.map((name) => proxies[name])
.where((proxy) => proxy != null)
.toList();
return group;
}).toList();
final groups = groupsRaw.map((e) => Group.fromJson(e)).toList();
return computeSort(
groups: groups,
sortType: sortType, sortType: sortType,
delayMap: delayMap, delayMap: delayMap,
selectedMap: selectedMap, selectedMap: selectedMap,
defaultTestUrl: defaultTestUrl, defaultTestUrl: defaultTestUrl,
); ),
}); );
} }
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) async { FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) async {
@@ -168,13 +147,11 @@ class CoreController {
if (externalProvidersRawString.isEmpty) { if (externalProvidersRawString.isEmpty) {
return []; return [];
} }
return Isolate.run<List<ExternalProvider>>(() { final externalProviders =
final externalProviders = (await externalProvidersRawString.commonToJSON<List<dynamic>>())
(json.decode(externalProvidersRawString) as List<dynamic>) .map((item) => ExternalProvider.fromJson(item))
.map((item) => ExternalProvider.fromJson(item)) .toList();
.toList(); return externalProviders;
return externalProviders;
});
} }
Future<ExternalProvider?> getExternalProvider( Future<ExternalProvider?> getExternalProvider(
@@ -220,11 +197,14 @@ class CoreController {
return Delay.fromJson(json.decode(data)); return Delay.fromJson(json.decode(data));
} }
Future<Map<String, dynamic>> getConfig(String id) async { Future<Map<String, dynamic>> getConfig(int id) async {
final profilePath = await appPath.getProfilePath(id); final profilePath = await appPath.getProfilePath(id.toString());
final res = await _interface.getConfig(profilePath); final res = await _interface.getConfig(profilePath);
if (res.isSuccess) { if (res.isSuccess) {
return res.data; final data = Map<String, dynamic>.from(res.data);
data['rules'] = data['rule'];
data.remove('rule');
return data;
} else { } else {
throw res.message; throw res.message;
} }

View File

@@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:isolate';
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';
@@ -12,7 +11,7 @@ mixin CoreInterface {
Future<String> preload(); Future<String> preload();
Future<bool> shutdown(); Future<bool> shutdown(bool isUser);
Future<bool> get isInit; Future<bool> get isInit;
@@ -28,7 +27,7 @@ mixin CoreInterface {
Future<String> setupConfig(SetupParams setupParams); Future<String> setupConfig(SetupParams setupParams);
Future<Map> getProxies(); Future<ProxiesData> getProxies();
Future<String> changeProxy(ChangeProxyParams changeProxyParams); Future<String> changeProxy(ChangeProxyParams changeProxyParams);
@@ -95,13 +94,13 @@ abstract class CoreHandlerInterface with CoreInterface {
); );
return null; return null;
} }
if (kDebugMode) { if (kDebugMode && watchExecution) {
commonPrint.log('Invoke ${method.name} ${DateTime.now()} $data'); commonPrint.log('Invoke ${method.name} ${DateTime.now()} $data');
} }
return utils.handleWatch( return await utils.handleWatch(
function: () async { function: () async {
return await invoke(method: method, data: data, timeout: timeout); return await invoke<T>(method: method, data: data, timeout: timeout);
}, },
onWatch: (data, elapsedMilliseconds) { onWatch: (data, elapsedMilliseconds) {
commonPrint.log('Invoke ${method.name} ${elapsedMilliseconds}ms'); commonPrint.log('Invoke ${method.name} ${elapsedMilliseconds}ms');
@@ -132,7 +131,7 @@ abstract class CoreHandlerInterface with CoreInterface {
} }
@override @override
Future<bool> shutdown(); Future<bool> shutdown(bool isUser);
@override @override
Future<bool> get isInit async { Future<bool> get isInit async {
@@ -164,16 +163,15 @@ abstract class CoreHandlerInterface with CoreInterface {
@override @override
Future<Result> getConfig(String path) async { Future<Result> getConfig(String path) async {
return await _invoke<Result>(method: ActionMethod.getConfig, data: path) ?? final res = await _invoke(method: ActionMethod.getConfig, data: path);
Result.success({}); return res ?? Result.success({});
} }
@override @override
Future<String> setupConfig(SetupParams setupParams) async { Future<String> setupConfig(SetupParams setupParams) async {
final data = await Isolate.run(() => json.encode(setupParams));
return await _invoke<String>( return await _invoke<String>(
method: ActionMethod.setupConfig, method: ActionMethod.setupConfig,
data: data, data: json.encode(setupParams),
) ?? ) ??
''; '';
} }
@@ -184,9 +182,13 @@ abstract class CoreHandlerInterface with CoreInterface {
} }
@override @override
Future<Map> getProxies() async { Future<ProxiesData> getProxies() async {
final map = await _invoke<Map>(method: ActionMethod.getProxies); final data = await _invoke<Map<String, dynamic>>(
return map ?? {}; method: ActionMethod.getProxies,
);
return data != null
? ProxiesData.fromJson(data)
: ProxiesData(proxies: {}, all: []);
} }
@override @override

View File

@@ -1,10 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/core.dart'; import 'package:fl_clash/models/core.dart';
import 'package:fl_clash/plugins/service.dart'; import 'package:fl_clash/plugins/service.dart';
import 'package:fl_clash/state.dart';
import 'interface.dart'; import 'interface.dart';
@@ -22,9 +22,7 @@ class CoreLib extends CoreHandlerInterface {
return res ?? ''; return res ?? '';
} }
_connectedCompleter.complete(true); _connectedCompleter.complete(true);
final syncRes = await service?.syncAndroidState( final syncRes = await service?.syncState(appController.sharedState);
globalState.getAndroidState(),
);
return syncRes ?? ''; return syncRes ?? '';
} }
@@ -39,10 +37,12 @@ class CoreLib extends CoreHandlerInterface {
} }
@override @override
Future<bool> shutdown() async { Future<bool> shutdown(_) async {
await service?.shutdown(); if (!_connectedCompleter.isCompleted) {
return false;
}
_connectedCompleter = Completer(); _connectedCompleter = Completer();
return true; return service?.shutdown() ?? true;
} }
@override @override

View File

@@ -16,6 +16,8 @@ class CoreService extends CoreHandlerInterface {
Completer<Socket> _socketCompleter = Completer(); Completer<Socket> _socketCompleter = Completer();
Completer<bool> _shutdownCompleter = Completer();
final Map<String, Completer> _callbackCompleterMap = {}; final Map<String, Completer> _callbackCompleterMap = {};
Process? _process; Process? _process;
@@ -35,6 +37,9 @@ class CoreService extends CoreHandlerInterface {
if (result.id?.isEmpty == true) { if (result.id?.isEmpty == true) {
coreEventManager.sendEvent(CoreEvent.fromJson(result.data)); coreEventManager.sendEvent(CoreEvent.fromJson(result.data));
} }
if (completer?.isCompleted == true) {
return;
}
completer?.complete(data); completer?.complete(data);
} }
@@ -70,11 +75,15 @@ class CoreService extends CoreHandlerInterface {
.transform(uint8ListToListIntConverter) .transform(uint8ListToListIntConverter)
.transform(utf8.decoder) .transform(utf8.decoder)
.transform(LineSplitter()) .transform(LineSplitter())
.listen((data) { .listen((data) async {
handleResult(ActionResult.fromJson(json.decode(data.trim()))); final dataJson = await data.trim().commonToJSON<dynamic>();
handleResult(ActionResult.fromJson(dataJson));
}) })
.onDone(() { .onDone(() {
_handleInvokeCrashEvent(); _handleInvokeCrashEvent();
if (!_shutdownCompleter.isCompleted) {
_shutdownCompleter.complete(true);
}
}); });
} }
@@ -86,7 +95,7 @@ class CoreService extends CoreHandlerInterface {
Future<void> start() async { Future<void> start() async {
if (_process != null) { if (_process != null) {
await shutdown(); await shutdown(false);
} }
final serverSocket = await _serverCompleter.future; final serverSocket = await _serverCompleter.future;
final arg = system.isWindows final arg = system.isWindows
@@ -112,7 +121,7 @@ class CoreService extends CoreHandlerInterface {
@override @override
destroy() async { destroy() async {
final server = await _serverCompleter.future; final server = await _serverCompleter.future;
await shutdown(); await shutdown(false);
await server.close(); await server.close();
await _deleteSocketFile(); await _deleteSocketFile();
return true; return true;
@@ -126,9 +135,7 @@ class CoreService extends CoreHandlerInterface {
Future<void> _deleteSocketFile() async { Future<void> _deleteSocketFile() async {
if (!system.isWindows) { if (!system.isWindows) {
final file = File(unixSocketPath); final file = File(unixSocketPath);
if (await file.exists()) { await file.safeDelete();
await file.delete();
}
} }
} }
@@ -136,12 +143,16 @@ class CoreService extends CoreHandlerInterface {
if (_socketCompleter.isCompleted) { if (_socketCompleter.isCompleted) {
final socket = await _socketCompleter.future; final socket = await _socketCompleter.future;
_socketCompleter = Completer(); _socketCompleter = Completer();
socket.close(); await socket.close();
} }
} }
@override @override
shutdown() async { shutdown(bool isUser) async {
if (!_socketCompleter.isCompleted && _process == null) {
return false;
}
_shutdownCompleter = Completer();
await _destroySocket(); await _destroySocket();
_clearCompleter(); _clearCompleter();
if (system.isWindows) { if (system.isWindows) {
@@ -149,7 +160,11 @@ class CoreService extends CoreHandlerInterface {
} }
_process?.kill(); _process?.kill();
_process = null; _process = null;
return true; if (isUser) {
return _shutdownCompleter.future;
} else {
return true;
}
} }
void _clearCompleter() { void _clearCompleter() {

View File

@@ -0,0 +1,78 @@
import 'dart:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
part 'generated/database.g.dart';
part 'links.dart';
part 'profiles.dart';
part 'rules.dart';
part 'scripts.dart';
@DriftDatabase(
tables: [Profiles, Scripts, Rules, ProfileRuleLinks],
daos: [ProfilesDao, ScriptsDao, RulesDao],
)
class Database extends _$Database {
Database([QueryExecutor? executor]) : super(executor ?? _openConnection());
@override
int get schemaVersion => 1;
static LazyDatabase _openConnection() {
return LazyDatabase(() async {
final databaseFile = File(await appPath.databasePath);
return NativeDatabase.createInBackground(databaseFile);
});
}
Future<void> restore(
List<Profile> profiles,
List<Script> scripts,
List<Rule> rules,
List<ProfileRuleLink> links, {
bool isOverride = false,
}) async {
if (profiles.isNotEmpty ||
scripts.isNotEmpty ||
rules.isNotEmpty ||
links.isNotEmpty) {
await batch((b) {
isOverride
? profilesDao.setAllWithBatch(b, profiles)
: profilesDao.putAllWithBatch(
b,
profiles.map((item) => item.toCompanion()),
);
scriptsDao.setAllWithBatch(b, scripts);
rulesDao.restoreWithBatch(b, rules, links);
});
}
}
}
extension TableInfoExt<Tbl extends Table, Row> on TableInfo<Tbl, Row> {
void setAll(
Batch batch,
Iterable<Insertable<Row>> items, {
required Expression<bool> Function(Tbl tbl) deleteFilter,
}) async {
batch.insertAllOnConflictUpdate(this, items);
batch.deleteWhere(this, deleteFilter);
}
Future<int> remove(Expression<bool> Function(Tbl tbl) filter) async {
return await (delete()..where(filter)).go();
}
Future<int> put(Insertable<Row> item) async {
return await insertOnConflictUpdate(item);
}
}
final database = Database();

File diff suppressed because it is too large Load Diff

52
lib/database/links.dart Normal file
View File

@@ -0,0 +1,52 @@
part of 'database.dart';
@DataClassName('RawProfileRuleLink')
@TableIndex(
name: 'idx_profile_scene_order',
columns: {#profileId, #scene, #order},
)
class ProfileRuleLinks extends Table {
@override
String get tableName => 'profile_rule_mapping';
TextColumn get id => text()();
IntColumn get profileId => integer().nullable().references(
Profiles,
#id,
onDelete: KeyAction.cascade,
)();
IntColumn get ruleId =>
integer().references(Rules, #id, onDelete: KeyAction.cascade)();
TextColumn get scene => textEnum<RuleScene>().nullable()();
TextColumn get order => text().nullable()();
@override
Set<Column> get primaryKey => {id};
}
extension RawProfileRuleLinkExt on RawProfileRuleLink {
ProfileRuleLink toLink() {
return ProfileRuleLink(
profileId: profileId,
ruleId: ruleId,
scene: scene,
order: order,
);
}
}
extension ProfileRuleLinksCompanionExt on ProfileRuleLink {
ProfileRuleLinksCompanion toCompanion() {
return ProfileRuleLinksCompanion.insert(
id: key,
ruleId: ruleId,
scene: Value(scene),
profileId: Value(profileId),
order: Value(order),
);
}
}

168
lib/database/profiles.dart Normal file
View File

@@ -0,0 +1,168 @@
part of 'database.dart';
@DataClassName('RawProfile')
class Profiles extends Table {
@override
String get tableName => 'profiles';
IntColumn get id => integer()();
TextColumn get label => text()();
TextColumn get currentGroupName => text().nullable()();
TextColumn get url => text()();
DateTimeColumn get lastUpdateDate => dateTime().nullable()();
TextColumn get overwriteType => textEnum<OverwriteType>()();
IntColumn get scriptId => integer().nullable()();
IntColumn get autoUpdateDurationMillis => integer()();
TextColumn get subscriptionInfo =>
text().map(const SubscriptionInfoConverter()).nullable()();
BoolColumn get autoUpdate => boolean()();
TextColumn get selectedMap => text().map(const StringMapConverter())();
TextColumn get unfoldSet => text().map(const StringSetConverter())();
IntColumn get order => integer().nullable()();
@override
Set<Column> get primaryKey => {id};
}
class SubscriptionInfoConverter
extends TypeConverter<SubscriptionInfo?, String?> {
const SubscriptionInfoConverter();
@override
SubscriptionInfo? fromSql(String? fromDb) {
if (fromDb == null) return null;
return SubscriptionInfo.fromJson(json.decode(fromDb));
}
@override
String? toSql(SubscriptionInfo? value) {
if (value == null) return null;
return json.encode(value.toJson());
}
}
@DriftAccessor(tables: [Profiles])
class ProfilesDao extends DatabaseAccessor<Database> with _$ProfilesDaoMixin {
ProfilesDao(super.attachedDatabase);
Selectable<Profile> all() {
final stmt = profiles.select();
stmt.orderBy([
(t) => OrderingTerm(expression: t.order, nulls: NullsOrder.last),
(t) => OrderingTerm.asc(t.id),
]);
return stmt.map((item) => item.toProfile());
}
Future<void> setAll(Iterable<Profile> profiles) async {
await batch((b) async {
setAllWithBatch(b, profiles);
});
}
Future<void> putAll<T extends Table, D extends DataClass>(
Iterable<Insertable<D>> items,
) async {
await batch((b) async {
putAllWithBatch(b, items);
});
}
void putAllWithBatch<T extends Table, D extends DataClass>(
Batch batch,
Iterable<Insertable<D>> items,
) {
batch.insertAllOnConflictUpdate(profiles, items);
}
void setAllWithBatch(Batch batch, Iterable<Profile> profiles) {
final List<ProfilesCompanion> items = [];
final List<int> ids = [];
profiles.forEachIndexed((index, profile) {
ids.add(profile.id);
items.add(profile.toCompanion(index));
});
this.profiles.setAll(batch, items, deleteFilter: (t) => t.id.isNotIn(ids));
}
}
class StringMapConverter extends TypeConverter<Map<String, String>, String> {
const StringMapConverter();
@override
Map<String, String> fromSql(String fromDb) {
return Map<String, String>.from(json.decode(fromDb));
}
@override
String toSql(Map<String, String> value) {
return json.encode(value);
}
}
class StringSetConverter extends TypeConverter<Set<String>, String> {
const StringSetConverter();
@override
Set<String> fromSql(String fromDb) {
return Set<String>.from(json.decode(fromDb));
}
@override
String toSql(Set<String> value) {
return json.encode(value.toList());
}
}
extension RawProfilExt on RawProfile {
Profile toProfile() {
return Profile(
id: id,
label: label,
currentGroupName: currentGroupName,
url: url,
lastUpdateDate: lastUpdateDate,
autoUpdateDuration: Duration(milliseconds: autoUpdateDurationMillis),
subscriptionInfo: subscriptionInfo,
autoUpdate: autoUpdate,
selectedMap: selectedMap,
unfoldSet: unfoldSet,
overwriteType: overwriteType,
scriptId: scriptId,
order: order,
);
}
}
extension ProfilesCompanionExt on Profile {
ProfilesCompanion toCompanion([int? order]) {
return ProfilesCompanion.insert(
id: Value(id),
label: label,
currentGroupName: Value(currentGroupName),
url: url,
lastUpdateDate: Value(lastUpdateDate),
autoUpdateDurationMillis: autoUpdateDuration.inMilliseconds,
subscriptionInfo: Value(subscriptionInfo),
autoUpdate: autoUpdate,
selectedMap: selectedMap,
unfoldSet: unfoldSet,
overwriteType: overwriteType,
scriptId: Value(scriptId),
order: Value(order ?? this.order),
);
}
}

283
lib/database/rules.dart Normal file
View File

@@ -0,0 +1,283 @@
part of 'database.dart';
@DataClassName('RawRule')
class Rules extends Table {
@override
String get tableName => 'rules';
IntColumn get id => integer()();
TextColumn get value => text()();
@override
Set<Column> get primaryKey => {id};
}
@DriftAccessor(tables: [Rules, ProfileRuleLinks])
class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
RulesDao(super.attachedDatabase);
Selectable<Rule> allGlobalAddedRules() {
return _get();
}
Selectable<Rule> allProfileAddedRules(int profileId) {
return _get(profileId: profileId, scene: RuleScene.added);
}
Selectable<Rule> allProfileDisabledRules(int profileId) {
return _get(profileId: profileId, scene: RuleScene.disabled);
}
Selectable<Rule> allAddedRules(int profileId) {
final disabledIdsQuery = selectOnly(profileRuleLinks)
..addColumns([profileRuleLinks.ruleId])
..where(
profileRuleLinks.profileId.equals(profileId) &
profileRuleLinks.scene.equalsValue(RuleScene.disabled),
);
final query = select(rules).join([
innerJoin(profileRuleLinks, profileRuleLinks.ruleId.equalsExp(rules.id)),
]);
query.where(
(profileRuleLinks.profileId.isNull() |
(profileRuleLinks.profileId.equals(profileId) &
profileRuleLinks.scene.equalsValue(RuleScene.added))) &
profileRuleLinks.ruleId.isNotInQuery(disabledIdsQuery),
);
query.orderBy([
OrderingTerm.desc(
profileRuleLinks.profileId.isNull().caseMatch<int>(
when: {const Constant(true): const Constant(1)},
orElse: const Constant(0),
),
),
OrderingTerm.desc(profileRuleLinks.order),
]);
return query.map((row) {
final ruleData = row.readTable(rules);
final order = row.read(profileRuleLinks.order);
return ruleData.toRule(order);
});
}
void restoreWithBatch(
Batch batch,
Iterable<Rule> rules,
Iterable<ProfileRuleLink> links,
) {
batch.insertAllOnConflictUpdate(
this.rules,
rules.map((item) => item.toCompanion()),
);
final ruleIds = rules.map((item) => item.id);
batch.deleteWhere(this.rules, (t) => t.id.isNotIn(ruleIds));
batch.insertAllOnConflictUpdate(
profileRuleLinks,
links.map((item) => item.toCompanion()),
);
final linkKeys = links.map((item) => item.key);
batch.deleteWhere(profileRuleLinks, (t) => t.id.isNotIn(linkKeys));
}
Future<void> delRules(Iterable<int> ruleIds) {
return _delAll(ruleIds);
}
Future<void> putGlobalRule(Rule rule) {
return _put(rule);
}
Future<void> putProfileAddedRule(int profileId, Rule rule) {
return _put(rule, profileId: profileId, scene: RuleScene.added);
}
Future<void> putProfileDisabledRule(int profileId, Rule rule) {
return _put(rule, profileId: profileId, scene: RuleScene.added);
}
Future<void> putGlobalRules(Iterable<Rule> rules) {
return _putAll(rules);
}
Future<void> setGlobalRules(Iterable<Rule> rules) {
return _set(rules);
}
Future<int> putDisabledLink(int profileId, int ruleId) async {
return await profileRuleLinks.insertOnConflictUpdate(
ProfileRuleLink(
ruleId: ruleId,
profileId: profileId,
scene: RuleScene.disabled,
).toCompanion(),
);
}
Future<bool> delDisabledLink(int profileId, int ruleId) async {
return await profileRuleLinks.deleteOne(
ProfileRuleLink(
profileId: profileId,
ruleId: ruleId,
scene: RuleScene.disabled,
).toCompanion(),
);
}
Future<int> orderGlobalRule({
required int ruleId,
required String order,
}) async {
return await _order(ruleId: ruleId, order: order);
}
Future<int> orderProfileAddedRule(
int profileId, {
required int ruleId,
required String order,
}) async {
return await _order(
ruleId: ruleId,
order: order,
profileId: profileId,
scene: RuleScene.added,
);
}
Selectable<Rule> _get({int? profileId, RuleScene? scene}) {
final query = select(rules).join([
innerJoin(profileRuleLinks, profileRuleLinks.ruleId.equalsExp(rules.id)),
]);
query.where(
profileId == null
? profileRuleLinks.profileId.isNull()
: profileRuleLinks.profileId.equals(profileId) &
profileRuleLinks.scene.equalsValue(scene),
);
query.orderBy([
OrderingTerm.desc(profileRuleLinks.order),
OrderingTerm.desc(profileRuleLinks.id),
]);
return query.map((row) {
return row.readTable(rules).toRule(row.read(profileRuleLinks.order));
});
}
Future<int> _order({
required int ruleId,
required String order,
int? profileId,
RuleScene? scene,
}) async {
final stmt = profileRuleLinks.update();
stmt.where((t) {
return (profileId == null
? t.profileId.isNull()
: t.profileId.equals(profileId)) &
t.ruleId.equals(ruleId) &
t.scene.equalsValue(scene);
});
return await stmt.write(ProfileRuleLinksCompanion(order: Value(order)));
}
Future<int> _put(Rule rule, {int? profileId, RuleScene? scene}) async {
return transaction(() async {
final row = await rules.insertOnConflictUpdate(rule.toCompanion());
if (row == 0) {
return 0;
}
return await profileRuleLinks.insertOnConflictUpdate(
ProfileRuleLink(
ruleId: rule.id,
profileId: profileId,
scene: scene,
).toCompanion(),
);
});
}
Future<void> _delAll(Iterable<int> ruleIds) async {
await rules.deleteWhere((t) => t.id.isIn(ruleIds));
}
Future<void> _putAll(
Iterable<Rule> rules, {
int? profileId,
RuleScene? scene,
}) async {
await batch((b) {
b.insertAllOnConflictUpdate(
this.rules,
rules.map((item) => item.toCompanion()),
);
b.insertAllOnConflictUpdate(
profileRuleLinks,
rules.map(
(item) => ProfileRuleLink(
ruleId: item.id,
profileId: profileId,
scene: scene,
).toCompanion(),
),
);
});
}
Future<void> _set(
Iterable<Rule> rules, {
int? profileId,
RuleScene? scene,
}) async {
await batch((b) {
b.insertAllOnConflictUpdate(
this.rules,
rules.map((item) => item.toCompanion()),
);
b.deleteWhere(
profileRuleLinks,
(t) =>
(profileId == null
? t.profileId.isNull()
: t.profileId.equals(profileId)) &
(scene == null ? const Constant(true) : t.scene.equalsValue(scene)),
);
b.insertAllOnConflictUpdate(
profileRuleLinks,
rules.map(
(item) => ProfileRuleLink(
ruleId: item.id,
profileId: profileId,
scene: scene,
).toCompanion(),
),
);
b.deleteWhere(this.rules, (r) {
final linkedIds = selectOnly(profileRuleLinks);
linkedIds.addColumns([profileRuleLinks.ruleId]);
return r.id.isNotInQuery(linkedIds);
});
});
}
}
extension RawRuleExt on RawRule {
Rule toRule([String? order]) {
return Rule(id: id, value: value, order: order);
}
}
extension RulesCompanionExt on Rule {
RulesCompanion toCompanion() {
return RulesCompanion.insert(id: Value(id), value: value);
}
}

63
lib/database/scripts.dart Normal file
View File

@@ -0,0 +1,63 @@
part of 'database.dart';
@DataClassName('RawScript')
class Scripts extends Table {
@override
String get tableName => 'scripts';
IntColumn get id => integer()();
TextColumn get label => text()();
DateTimeColumn get lastUpdateTime => dateTime()();
@override
Set<Column> get primaryKey => {id};
}
@DriftAccessor(tables: [Scripts])
class ScriptsDao extends DatabaseAccessor<Database> with _$ScriptsDaoMixin {
ScriptsDao(super.attachedDatabase);
Selectable<Script> all() {
return scripts.select().map((item) => item.toScript());
}
Selectable<Script> get(int scriptId) {
final stmt = scripts.select();
stmt.where((t) => t.id.equals(scriptId));
return stmt.map((it) => it.toScript());
}
Future<void> setAll(Iterable<Script> scripts) async {
await batch((b) async {
await setAllWithBatch(b, scripts);
});
}
Future<void> setAllWithBatch(Batch batch, Iterable<Script> scripts) async {
final List<ScriptsCompanion> items = [];
final List<int> ids = [];
for (final script in scripts) {
ids.add(script.id);
items.add(script.toCompanion());
}
this.scripts.setAll(batch, items, deleteFilter: (t) => t.id.isNotIn(ids));
}
}
extension RawScriptExt on RawScript {
Script toScript() {
return Script(id: id, label: label, lastUpdateTime: lastUpdateTime);
}
}
extension ScriptsCompanionExt on Script {
ScriptsCompanion toCompanion() {
return ScriptsCompanion.insert(
id: Value(id),
label: label,
lastUpdateTime: lastUpdateTime,
);
}
}

View File

@@ -133,7 +133,7 @@ enum InvokeMessageType { protect, process }
enum FindProcessMode { always, off } enum FindProcessMode { always, off }
enum RecoveryOption { all, onlyProfiles } enum RestoreOption { all, onlyProfiles }
enum ChipType { action, delete } enum ChipType { action, delete }
@@ -260,8 +260,8 @@ enum AuthorizeCode { none, success, error }
enum WindowsHelperServiceStatus { none, presence, running } enum WindowsHelperServiceStatus { none, presence, running }
enum FunctionTag { enum FunctionTag {
updateClashConfig, updateConfig,
setupClashConfig, setupConfig,
updateStatus, updateStatus,
updateGroups, updateGroups,
addCheckIpNum, addCheckIpNum,
@@ -281,6 +281,7 @@ enum FunctionTag {
requests, requests,
autoScrollToEnd, autoScrollToEnd,
loadedProvider, loadedProvider,
saveSharedFile,
} }
enum DashboardWidget { enum DashboardWidget {
@@ -406,7 +407,7 @@ enum OverwriteType {
enum RuleTarget { DIRECT, REJECT, MATCH } enum RuleTarget { DIRECT, REJECT, MATCH }
enum RecoveryStrategy { compatible, override } enum RestoreStrategy { compatible, override }
enum CacheTag { logs, rules, requests, proxiesList } enum CacheTag { logs, rules, requests, proxiesList }
@@ -418,4 +419,8 @@ enum ScrollPositionCacheKey { tools, profiles, proxiesList, proxiesTabList }
enum QueryTag { proxies, access } enum QueryTag { proxies, access }
enum LoadingTag { profiles, backup_restore, access, proxies }
enum CoreStatus { connecting, connected, disconnected } enum CoreStatus { connecting, connected, disconnected }
enum RuleScene { added, disabled, custom }

View File

@@ -14,7 +14,7 @@ class RuleItem extends StatelessWidget {
final bool isSelected; final bool isSelected;
final bool isEditing; final bool isEditing;
final Rule rule; final Rule rule;
final void Function(String id) onSelected; final void Function() onSelected;
final void Function(Rule rule) onEdit; final void Function(Rule rule) onEdit;
const RuleItem({ const RuleItem({
@@ -31,7 +31,7 @@ class RuleItem extends StatelessWidget {
return CommonSelectedListItem( return CommonSelectedListItem(
isSelected: isSelected, isSelected: isSelected,
onSelected: () { onSelected: () {
onSelected(rule.id); onSelected();
}, },
title: Text( title: Text(
rule.value, rule.value,

View File

@@ -81,6 +81,7 @@ class MessageLookup extends MessageLookupByLibrary {
"action_tun": MessageLookupByLibrary.simpleMessage("TUN"), "action_tun": MessageLookupByLibrary.simpleMessage("TUN"),
"action_view": MessageLookupByLibrary.simpleMessage("Show/Hide"), "action_view": MessageLookupByLibrary.simpleMessage("Show/Hide"),
"add": MessageLookupByLibrary.simpleMessage("Add"), "add": MessageLookupByLibrary.simpleMessage("Add"),
"addProfile": MessageLookupByLibrary.simpleMessage("Add Profile"),
"addRule": MessageLookupByLibrary.simpleMessage("Add rule"), "addRule": MessageLookupByLibrary.simpleMessage("Add rule"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage( "addedOriginRules": MessageLookupByLibrary.simpleMessage(
"Attach on the original rules", "Attach on the original rules",
@@ -164,11 +165,11 @@ class MessageLookup extends MessageLookupByLibrary {
"Auto update interval (minutes)", "Auto update interval (minutes)",
), ),
"backup": MessageLookupByLibrary.simpleMessage("Backup"), "backup": MessageLookupByLibrary.simpleMessage("Backup"),
"backupAndRecovery": MessageLookupByLibrary.simpleMessage( "backupAndRestore": MessageLookupByLibrary.simpleMessage(
"Backup and Recovery", "Backup and Restore",
), ),
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage( "backupAndRestoreDesc": MessageLookupByLibrary.simpleMessage(
"Sync data via WebDAV or file", "Sync data via WebDAV or files",
), ),
"backupSuccess": MessageLookupByLibrary.simpleMessage("Backup success"), "backupSuccess": MessageLookupByLibrary.simpleMessage("Backup success"),
"basicConfig": MessageLookupByLibrary.simpleMessage("Basic configuration"), "basicConfig": MessageLookupByLibrary.simpleMessage("Basic configuration"),
@@ -269,6 +270,7 @@ class MessageLookup extends MessageLookupByLibrary {
"defaultText": MessageLookupByLibrary.simpleMessage("Default"), "defaultText": MessageLookupByLibrary.simpleMessage("Default"),
"delay": MessageLookupByLibrary.simpleMessage("Delay"), "delay": MessageLookupByLibrary.simpleMessage("Delay"),
"delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"), "delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"),
"delayTest": MessageLookupByLibrary.simpleMessage("Delay Test"),
"delete": MessageLookupByLibrary.simpleMessage("Delete"), "delete": MessageLookupByLibrary.simpleMessage("Delete"),
"deleteMultipTip": m1, "deleteMultipTip": m1,
"deleteTip": m2, "deleteTip": m2,
@@ -425,6 +427,9 @@ class MessageLookup extends MessageLookupByLibrary {
"internet": MessageLookupByLibrary.simpleMessage("Internet"), "internet": MessageLookupByLibrary.simpleMessage("Internet"),
"interval": MessageLookupByLibrary.simpleMessage("Interval"), "interval": MessageLookupByLibrary.simpleMessage("Interval"),
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"), "intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
"invalidBackupFile": MessageLookupByLibrary.simpleMessage(
"Invalid backup file",
),
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"), "ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
"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",
@@ -450,9 +455,6 @@ class MessageLookup extends MessageLookupByLibrary {
"localBackupDesc": MessageLookupByLibrary.simpleMessage( "localBackupDesc": MessageLookupByLibrary.simpleMessage(
"Backup local data to local", "Backup local data to local",
), ),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"Recovery data from file",
),
"log": MessageLookupByLibrary.simpleMessage("Log"), "log": MessageLookupByLibrary.simpleMessage("Log"),
"logLevel": MessageLookupByLibrary.simpleMessage("LogLevel"), "logLevel": MessageLookupByLibrary.simpleMessage("LogLevel"),
"logcat": MessageLookupByLibrary.simpleMessage("Logcat"), "logcat": MessageLookupByLibrary.simpleMessage("Logcat"),
@@ -504,6 +506,12 @@ class MessageLookup extends MessageLookupByLibrary {
"networkDetection": MessageLookupByLibrary.simpleMessage( "networkDetection": MessageLookupByLibrary.simpleMessage(
"Network detection", "Network detection",
), ),
"networkException": MessageLookupByLibrary.simpleMessage(
"Network exception, please check your connection and try again",
),
"networkRequestException": MessageLookupByLibrary.simpleMessage(
"Network request exception, please try again later.",
),
"networkSpeed": MessageLookupByLibrary.simpleMessage("Network speed"), "networkSpeed": MessageLookupByLibrary.simpleMessage("Network speed"),
"networkType": MessageLookupByLibrary.simpleMessage("Network type"), "networkType": MessageLookupByLibrary.simpleMessage("Network type"),
"neutralScheme": MessageLookupByLibrary.simpleMessage("Neutral"), "neutralScheme": MessageLookupByLibrary.simpleMessage("Neutral"),
@@ -564,6 +572,10 @@ class MessageLookup extends MessageLookupByLibrary {
"Override the original rule", "Override the original rule",
), ),
"overrideScript": MessageLookupByLibrary.simpleMessage("Override script"), "overrideScript": MessageLookupByLibrary.simpleMessage("Override script"),
"overwriteTypeCustom": MessageLookupByLibrary.simpleMessage("Custom"),
"overwriteTypeCustomDesc": MessageLookupByLibrary.simpleMessage(
"Custom mode, fully customize proxy groups and rules",
),
"palette": MessageLookupByLibrary.simpleMessage("Palette"), "palette": MessageLookupByLibrary.simpleMessage("Palette"),
"password": MessageLookupByLibrary.simpleMessage("Password"), "password": MessageLookupByLibrary.simpleMessage("Password"),
"paste": MessageLookupByLibrary.simpleMessage("Paste"), "paste": MessageLookupByLibrary.simpleMessage("Paste"),
@@ -636,27 +648,13 @@ class MessageLookup extends MessageLookupByLibrary {
"Set the Clash listening port", "Set the Clash listening port",
), ),
"proxyProviders": MessageLookupByLibrary.simpleMessage("Proxy providers"), "proxyProviders": MessageLookupByLibrary.simpleMessage("Proxy providers"),
"pruneCache": MessageLookupByLibrary.simpleMessage("Prune cache"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("Pure black mode"), "pureBlackMode": MessageLookupByLibrary.simpleMessage("Pure black mode"),
"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",
), ),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("Rainbow"), "rainbowScheme": MessageLookupByLibrary.simpleMessage("Rainbow"),
"recovery": MessageLookupByLibrary.simpleMessage("Recovery"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("Recovery all data"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
"Only recovery profiles",
),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage(
"Recovery strategy",
),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage(
"Compatible",
),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
"Override",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("Recovery success"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir Port"), "redirPort": MessageLookupByLibrary.simpleMessage("Redir Port"),
"redo": MessageLookupByLibrary.simpleMessage("redo"), "redo": MessageLookupByLibrary.simpleMessage("redo"),
"regExp": MessageLookupByLibrary.simpleMessage("RegExp"), "regExp": MessageLookupByLibrary.simpleMessage("RegExp"),
@@ -668,9 +666,6 @@ class MessageLookup extends MessageLookupByLibrary {
"remoteDestination": MessageLookupByLibrary.simpleMessage( "remoteDestination": MessageLookupByLibrary.simpleMessage(
"Remote destination", "Remote destination",
), ),
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"Recovery data from WebDAV",
),
"remove": MessageLookupByLibrary.simpleMessage("Remove"), "remove": MessageLookupByLibrary.simpleMessage("Remove"),
"rename": MessageLookupByLibrary.simpleMessage("Rename"), "rename": MessageLookupByLibrary.simpleMessage("Rename"),
"request": MessageLookupByLibrary.simpleMessage("Request"), "request": MessageLookupByLibrary.simpleMessage("Request"),
@@ -695,6 +690,28 @@ class MessageLookup extends MessageLookupByLibrary {
"restartCoreTip": MessageLookupByLibrary.simpleMessage( "restartCoreTip": MessageLookupByLibrary.simpleMessage(
"Are you sure you want to restart the core?", "Are you sure you want to restart the core?",
), ),
"restore": MessageLookupByLibrary.simpleMessage("Restore"),
"restoreAllData": MessageLookupByLibrary.simpleMessage("Restore all data"),
"restoreException": MessageLookupByLibrary.simpleMessage(
"Recovery exception",
),
"restoreFromFileDesc": MessageLookupByLibrary.simpleMessage(
"Restore data via file",
),
"restoreFromWebDAVDesc": MessageLookupByLibrary.simpleMessage(
"Restore data via WebDAV",
),
"restoreOnlyConfig": MessageLookupByLibrary.simpleMessage(
"Restore configuration files only",
),
"restoreStrategy": MessageLookupByLibrary.simpleMessage("Restore strategy"),
"restoreStrategy_compatible": MessageLookupByLibrary.simpleMessage(
"Compatible",
),
"restoreStrategy_override": MessageLookupByLibrary.simpleMessage(
"Override",
),
"restoreSuccess": MessageLookupByLibrary.simpleMessage("Restore success"),
"routeAddress": MessageLookupByLibrary.simpleMessage("Route address"), "routeAddress": MessageLookupByLibrary.simpleMessage("Route address"),
"routeAddressDesc": MessageLookupByLibrary.simpleMessage( "routeAddressDesc": MessageLookupByLibrary.simpleMessage(
"Config listen route address", "Config listen route address",
@@ -806,6 +823,9 @@ class MessageLookup extends MessageLookupByLibrary {
"Remove extra delays such as handshaking", "Remove extra delays such as handshaking",
), ),
"unknown": MessageLookupByLibrary.simpleMessage("Unknown"), "unknown": MessageLookupByLibrary.simpleMessage("Unknown"),
"unknownNetworkError": MessageLookupByLibrary.simpleMessage(
"Unknown network error",
),
"unnamed": MessageLookupByLibrary.simpleMessage("Unnamed"), "unnamed": MessageLookupByLibrary.simpleMessage("Unnamed"),
"update": MessageLookupByLibrary.simpleMessage("Update"), "update": MessageLookupByLibrary.simpleMessage("Update"),
"upload": MessageLookupByLibrary.simpleMessage("Upload"), "upload": MessageLookupByLibrary.simpleMessage("Upload"),

View File

@@ -72,6 +72,7 @@ class MessageLookup extends MessageLookupByLibrary {
"action_tun": MessageLookupByLibrary.simpleMessage("TUN"), "action_tun": MessageLookupByLibrary.simpleMessage("TUN"),
"action_view": MessageLookupByLibrary.simpleMessage("表示/非表示"), "action_view": MessageLookupByLibrary.simpleMessage("表示/非表示"),
"add": MessageLookupByLibrary.simpleMessage("追加"), "add": MessageLookupByLibrary.simpleMessage("追加"),
"addProfile": MessageLookupByLibrary.simpleMessage("プロファイルを追加"),
"addRule": MessageLookupByLibrary.simpleMessage("ルールを追加"), "addRule": MessageLookupByLibrary.simpleMessage("ルールを追加"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage("元のルールに追加"), "addedOriginRules": MessageLookupByLibrary.simpleMessage("元のルールに追加"),
"addedRules": MessageLookupByLibrary.simpleMessage("追加ルール"), "addedRules": MessageLookupByLibrary.simpleMessage("追加ルール"),
@@ -117,9 +118,9 @@ class MessageLookup extends MessageLookupByLibrary {
"autoUpdate": MessageLookupByLibrary.simpleMessage("自動更新"), "autoUpdate": MessageLookupByLibrary.simpleMessage("自動更新"),
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自動更新間隔(分)"), "autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自動更新間隔(分)"),
"backup": MessageLookupByLibrary.simpleMessage("バックアップ"), "backup": MessageLookupByLibrary.simpleMessage("バックアップ"),
"backupAndRecovery": MessageLookupByLibrary.simpleMessage("バックアップと復元"), "backupAndRestore": MessageLookupByLibrary.simpleMessage("バックアップと復元"),
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage( "backupAndRestoreDesc": MessageLookupByLibrary.simpleMessage(
"WebDAVまたはファイルデータを同期", "WebDAVまたはファイルを介してデータを同期する",
), ),
"backupSuccess": MessageLookupByLibrary.simpleMessage("バックアップ成功"), "backupSuccess": MessageLookupByLibrary.simpleMessage("バックアップ成功"),
"basicConfig": MessageLookupByLibrary.simpleMessage("基本設定"), "basicConfig": MessageLookupByLibrary.simpleMessage("基本設定"),
@@ -204,6 +205,7 @@ class MessageLookup extends MessageLookupByLibrary {
"defaultText": MessageLookupByLibrary.simpleMessage("デフォルト"), "defaultText": MessageLookupByLibrary.simpleMessage("デフォルト"),
"delay": MessageLookupByLibrary.simpleMessage("遅延"), "delay": MessageLookupByLibrary.simpleMessage("遅延"),
"delaySort": MessageLookupByLibrary.simpleMessage("遅延順"), "delaySort": MessageLookupByLibrary.simpleMessage("遅延順"),
"delayTest": MessageLookupByLibrary.simpleMessage("遅延テスト"),
"delete": MessageLookupByLibrary.simpleMessage("削除"), "delete": MessageLookupByLibrary.simpleMessage("削除"),
"deleteMultipTip": m1, "deleteMultipTip": m1,
"deleteTip": m2, "deleteTip": m2,
@@ -318,6 +320,7 @@ class MessageLookup extends MessageLookupByLibrary {
"internet": MessageLookupByLibrary.simpleMessage("インターネット"), "internet": MessageLookupByLibrary.simpleMessage("インターネット"),
"interval": MessageLookupByLibrary.simpleMessage("インターバル"), "interval": MessageLookupByLibrary.simpleMessage("インターバル"),
"intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"), "intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"),
"invalidBackupFile": MessageLookupByLibrary.simpleMessage("無効なバックアップファイル"),
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"), "ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"), "ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"),
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("IPv6インバウンドを許可"), "ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("IPv6インバウンドを許可"),
@@ -337,7 +340,6 @@ class MessageLookup extends MessageLookupByLibrary {
"loading": MessageLookupByLibrary.simpleMessage("読み込み中..."), "loading": MessageLookupByLibrary.simpleMessage("読み込み中..."),
"local": MessageLookupByLibrary.simpleMessage("ローカル"), "local": MessageLookupByLibrary.simpleMessage("ローカル"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage("ローカルにデータをバックアップ"), "localBackupDesc": MessageLookupByLibrary.simpleMessage("ローカルにデータをバックアップ"),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("ファイルからデータを復元"),
"log": MessageLookupByLibrary.simpleMessage("ログ"), "log": MessageLookupByLibrary.simpleMessage("ログ"),
"logLevel": MessageLookupByLibrary.simpleMessage("ログレベル"), "logLevel": MessageLookupByLibrary.simpleMessage("ログレベル"),
"logcat": MessageLookupByLibrary.simpleMessage("ログキャット"), "logcat": MessageLookupByLibrary.simpleMessage("ログキャット"),
@@ -375,6 +377,12 @@ class MessageLookup extends MessageLookupByLibrary {
"network": MessageLookupByLibrary.simpleMessage("ネットワーク"), "network": MessageLookupByLibrary.simpleMessage("ネットワーク"),
"networkDesc": MessageLookupByLibrary.simpleMessage("ネットワーク関連設定の変更"), "networkDesc": MessageLookupByLibrary.simpleMessage("ネットワーク関連設定の変更"),
"networkDetection": MessageLookupByLibrary.simpleMessage("ネットワーク検出"), "networkDetection": MessageLookupByLibrary.simpleMessage("ネットワーク検出"),
"networkException": MessageLookupByLibrary.simpleMessage(
"ネットワーク例外、接続を確認してもう一度お試しください",
),
"networkRequestException": MessageLookupByLibrary.simpleMessage(
"ネットワーク要求例外、後でもう一度試してください。",
),
"networkSpeed": MessageLookupByLibrary.simpleMessage("ネットワーク速度"), "networkSpeed": MessageLookupByLibrary.simpleMessage("ネットワーク速度"),
"networkType": MessageLookupByLibrary.simpleMessage("ネットワーク種別"), "networkType": MessageLookupByLibrary.simpleMessage("ネットワーク種別"),
"neutralScheme": MessageLookupByLibrary.simpleMessage("ニュートラル"), "neutralScheme": MessageLookupByLibrary.simpleMessage("ニュートラル"),
@@ -423,6 +431,10 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideMode": MessageLookupByLibrary.simpleMessage("上書きモード"), "overrideMode": MessageLookupByLibrary.simpleMessage("上書きモード"),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを上書き"), "overrideOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを上書き"),
"overrideScript": MessageLookupByLibrary.simpleMessage("上書きスクリプト"), "overrideScript": MessageLookupByLibrary.simpleMessage("上書きスクリプト"),
"overwriteTypeCustom": MessageLookupByLibrary.simpleMessage("カスタム"),
"overwriteTypeCustomDesc": MessageLookupByLibrary.simpleMessage(
"カスタムモード、プロキシグループとルールを完全にカスタマイズ可能",
),
"palette": MessageLookupByLibrary.simpleMessage("パレット"), "palette": MessageLookupByLibrary.simpleMessage("パレット"),
"password": MessageLookupByLibrary.simpleMessage("パスワード"), "password": MessageLookupByLibrary.simpleMessage("パスワード"),
"paste": MessageLookupByLibrary.simpleMessage("貼り付け"), "paste": MessageLookupByLibrary.simpleMessage("貼り付け"),
@@ -483,19 +495,11 @@ class MessageLookup extends MessageLookupByLibrary {
"proxyPort": MessageLookupByLibrary.simpleMessage("プロキシポート"), "proxyPort": MessageLookupByLibrary.simpleMessage("プロキシポート"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("Clashのリスニングポートを設定"), "proxyPortDesc": MessageLookupByLibrary.simpleMessage("Clashのリスニングポートを設定"),
"proxyProviders": MessageLookupByLibrary.simpleMessage("プロキシプロバイダー"), "proxyProviders": MessageLookupByLibrary.simpleMessage("プロキシプロバイダー"),
"pruneCache": MessageLookupByLibrary.simpleMessage("キャッシュの削除"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("純黒モード"), "pureBlackMode": MessageLookupByLibrary.simpleMessage("純黒モード"),
"qrcode": MessageLookupByLibrary.simpleMessage("QRコード"), "qrcode": MessageLookupByLibrary.simpleMessage("QRコード"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("QRコードをスキャンしてプロファイルを取得"), "qrcodeDesc": MessageLookupByLibrary.simpleMessage("QRコードをスキャンしてプロファイルを取得"),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("レインボー"), "rainbowScheme": MessageLookupByLibrary.simpleMessage("レインボー"),
"recovery": MessageLookupByLibrary.simpleMessage("復元"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("全データ復元"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("プロファイルのみ復元"),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage("リカバリー戦略"),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("互換性"),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
"オーバーライド",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("復元成功"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redirポート"), "redirPort": MessageLookupByLibrary.simpleMessage("Redirポート"),
"redo": MessageLookupByLibrary.simpleMessage("やり直す"), "redo": MessageLookupByLibrary.simpleMessage("やり直す"),
"regExp": MessageLookupByLibrary.simpleMessage("正規表現"), "regExp": MessageLookupByLibrary.simpleMessage("正規表現"),
@@ -505,9 +509,6 @@ class MessageLookup extends MessageLookupByLibrary {
"WebDAVにデータをバックアップ", "WebDAVにデータをバックアップ",
), ),
"remoteDestination": MessageLookupByLibrary.simpleMessage("リモート宛先"), "remoteDestination": MessageLookupByLibrary.simpleMessage("リモート宛先"),
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"WebDAVからデータを復元",
),
"remove": MessageLookupByLibrary.simpleMessage("削除"), "remove": MessageLookupByLibrary.simpleMessage("削除"),
"rename": MessageLookupByLibrary.simpleMessage("リネーム"), "rename": MessageLookupByLibrary.simpleMessage("リネーム"),
"request": MessageLookupByLibrary.simpleMessage("リクエスト"), "request": MessageLookupByLibrary.simpleMessage("リクエスト"),
@@ -526,6 +527,20 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"restart": MessageLookupByLibrary.simpleMessage("再起動"), "restart": MessageLookupByLibrary.simpleMessage("再起動"),
"restartCoreTip": MessageLookupByLibrary.simpleMessage("コアを再起動してもよろしいですか?"), "restartCoreTip": MessageLookupByLibrary.simpleMessage("コアを再起動してもよろしいですか?"),
"restore": MessageLookupByLibrary.simpleMessage("復元"),
"restoreAllData": MessageLookupByLibrary.simpleMessage("すべてのデータを復元する"),
"restoreException": MessageLookupByLibrary.simpleMessage("復元例外"),
"restoreFromFileDesc": MessageLookupByLibrary.simpleMessage(
"ファイルを介してデータを復元する",
),
"restoreFromWebDAVDesc": MessageLookupByLibrary.simpleMessage(
"WebDAVを介してデータを復元する",
),
"restoreOnlyConfig": MessageLookupByLibrary.simpleMessage("設定ファイルのみを復元する"),
"restoreStrategy": MessageLookupByLibrary.simpleMessage("復元ストラテジー"),
"restoreStrategy_compatible": MessageLookupByLibrary.simpleMessage("互換"),
"restoreStrategy_override": MessageLookupByLibrary.simpleMessage("上書き"),
"restoreSuccess": MessageLookupByLibrary.simpleMessage("復元に成功しました"),
"routeAddress": MessageLookupByLibrary.simpleMessage("ルートアドレス"), "routeAddress": MessageLookupByLibrary.simpleMessage("ルートアドレス"),
"routeAddressDesc": MessageLookupByLibrary.simpleMessage("ルートアドレスを設定"), "routeAddressDesc": MessageLookupByLibrary.simpleMessage("ルートアドレスを設定"),
"routeMode": MessageLookupByLibrary.simpleMessage("ルートモード"), "routeMode": MessageLookupByLibrary.simpleMessage("ルートモード"),
@@ -619,6 +634,7 @@ class MessageLookup extends MessageLookupByLibrary {
"ハンドシェイクなどの余分な遅延を削除", "ハンドシェイクなどの余分な遅延を削除",
), ),
"unknown": MessageLookupByLibrary.simpleMessage("不明"), "unknown": MessageLookupByLibrary.simpleMessage("不明"),
"unknownNetworkError": MessageLookupByLibrary.simpleMessage("不明なネットワークエラー"),
"unnamed": MessageLookupByLibrary.simpleMessage("無題"), "unnamed": MessageLookupByLibrary.simpleMessage("無題"),
"update": MessageLookupByLibrary.simpleMessage("更新"), "update": MessageLookupByLibrary.simpleMessage("更新"),
"upload": MessageLookupByLibrary.simpleMessage("アップロード"), "upload": MessageLookupByLibrary.simpleMessage("アップロード"),

View File

@@ -80,6 +80,7 @@ class MessageLookup extends MessageLookupByLibrary {
"action_tun": MessageLookupByLibrary.simpleMessage("TUN"), "action_tun": MessageLookupByLibrary.simpleMessage("TUN"),
"action_view": MessageLookupByLibrary.simpleMessage("Показать/Скрыть"), "action_view": MessageLookupByLibrary.simpleMessage("Показать/Скрыть"),
"add": MessageLookupByLibrary.simpleMessage("Добавить"), "add": MessageLookupByLibrary.simpleMessage("Добавить"),
"addProfile": MessageLookupByLibrary.simpleMessage("Добавить профиль"),
"addRule": MessageLookupByLibrary.simpleMessage("Добавить правило"), "addRule": MessageLookupByLibrary.simpleMessage("Добавить правило"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage( "addedOriginRules": MessageLookupByLibrary.simpleMessage(
"Добавить к оригинальным правилам", "Добавить к оригинальным правилам",
@@ -161,11 +162,11 @@ class MessageLookup extends MessageLookupByLibrary {
"Интервал автообновления (минуты)", "Интервал автообновления (минуты)",
), ),
"backup": MessageLookupByLibrary.simpleMessage("Резервное копирование"), "backup": MessageLookupByLibrary.simpleMessage("Резервное копирование"),
"backupAndRecovery": MessageLookupByLibrary.simpleMessage( "backupAndRestore": MessageLookupByLibrary.simpleMessage(
"Резервное копирование и восстановление", "Резервное копирование и восстановление",
), ),
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage( "backupAndRestoreDesc": MessageLookupByLibrary.simpleMessage(
"Синхронизация данных через WebDAV или файл", "Синхронизация данных через WebDAV или файлы",
), ),
"backupSuccess": MessageLookupByLibrary.simpleMessage( "backupSuccess": MessageLookupByLibrary.simpleMessage(
"Резервное копирование успешно", "Резервное копирование успешно",
@@ -276,6 +277,7 @@ class MessageLookup extends MessageLookupByLibrary {
"defaultText": MessageLookupByLibrary.simpleMessage("По умолчанию"), "defaultText": MessageLookupByLibrary.simpleMessage("По умолчанию"),
"delay": MessageLookupByLibrary.simpleMessage("Задержка"), "delay": MessageLookupByLibrary.simpleMessage("Задержка"),
"delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"), "delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"),
"delayTest": MessageLookupByLibrary.simpleMessage("Тест задержки"),
"delete": MessageLookupByLibrary.simpleMessage("Удалить"), "delete": MessageLookupByLibrary.simpleMessage("Удалить"),
"deleteMultipTip": m1, "deleteMultipTip": m1,
"deleteTip": m2, "deleteTip": m2,
@@ -444,6 +446,9 @@ class MessageLookup extends MessageLookupByLibrary {
"internet": MessageLookupByLibrary.simpleMessage("Интернет"), "internet": MessageLookupByLibrary.simpleMessage("Интернет"),
"interval": MessageLookupByLibrary.simpleMessage("Интервал"), "interval": MessageLookupByLibrary.simpleMessage("Интервал"),
"intranetIP": MessageLookupByLibrary.simpleMessage("Внутренний IP"), "intranetIP": MessageLookupByLibrary.simpleMessage("Внутренний IP"),
"invalidBackupFile": MessageLookupByLibrary.simpleMessage(
"Неверный файл резервной копии",
),
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"), "ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage( "ipv6Desc": MessageLookupByLibrary.simpleMessage(
"При включении будет возможно получать IPv6 трафик", "При включении будет возможно получать IPv6 трафик",
@@ -469,9 +474,6 @@ class MessageLookup extends MessageLookupByLibrary {
"localBackupDesc": MessageLookupByLibrary.simpleMessage( "localBackupDesc": MessageLookupByLibrary.simpleMessage(
"Резервное копирование локальных данных на локальный диск", "Резервное копирование локальных данных на локальный диск",
), ),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"Восстановление данных из файла",
),
"log": MessageLookupByLibrary.simpleMessage("Журнал"), "log": MessageLookupByLibrary.simpleMessage("Журнал"),
"logLevel": MessageLookupByLibrary.simpleMessage("Уровень логов"), "logLevel": MessageLookupByLibrary.simpleMessage("Уровень логов"),
"logcat": MessageLookupByLibrary.simpleMessage("Logcat"), "logcat": MessageLookupByLibrary.simpleMessage("Logcat"),
@@ -527,6 +529,12 @@ class MessageLookup extends MessageLookupByLibrary {
"networkDetection": MessageLookupByLibrary.simpleMessage( "networkDetection": MessageLookupByLibrary.simpleMessage(
"Обнаружение сети", "Обнаружение сети",
), ),
"networkException": MessageLookupByLibrary.simpleMessage(
"Ошибка сети, проверьте соединение и попробуйте еще раз",
),
"networkRequestException": MessageLookupByLibrary.simpleMessage(
"Исключение сетевого запроса, пожалуйста, попробуйте позже.",
),
"networkSpeed": MessageLookupByLibrary.simpleMessage("Скорость сети"), "networkSpeed": MessageLookupByLibrary.simpleMessage("Скорость сети"),
"networkType": MessageLookupByLibrary.simpleMessage("Тип сети"), "networkType": MessageLookupByLibrary.simpleMessage("Тип сети"),
"neutralScheme": MessageLookupByLibrary.simpleMessage("Нейтральные"), "neutralScheme": MessageLookupByLibrary.simpleMessage("Нейтральные"),
@@ -595,6 +603,12 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideScript": MessageLookupByLibrary.simpleMessage( "overrideScript": MessageLookupByLibrary.simpleMessage(
"Скрипт переопределения", "Скрипт переопределения",
), ),
"overwriteTypeCustom": MessageLookupByLibrary.simpleMessage(
"Пользовательский",
),
"overwriteTypeCustomDesc": MessageLookupByLibrary.simpleMessage(
"Пользовательский режим, полная настройка групп прокси и правил",
),
"palette": MessageLookupByLibrary.simpleMessage("Палитра"), "palette": MessageLookupByLibrary.simpleMessage("Палитра"),
"password": MessageLookupByLibrary.simpleMessage("Пароль"), "password": MessageLookupByLibrary.simpleMessage("Пароль"),
"paste": MessageLookupByLibrary.simpleMessage("Вставить"), "paste": MessageLookupByLibrary.simpleMessage("Вставить"),
@@ -669,31 +683,13 @@ class MessageLookup extends MessageLookupByLibrary {
"Установить порт прослушивания Clash", "Установить порт прослушивания Clash",
), ),
"proxyProviders": MessageLookupByLibrary.simpleMessage("Провайдеры прокси"), "proxyProviders": MessageLookupByLibrary.simpleMessage("Провайдеры прокси"),
"pruneCache": MessageLookupByLibrary.simpleMessage("Очистить кэш"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("Чисто черный режим"), "pureBlackMode": MessageLookupByLibrary.simpleMessage("Чисто черный режим"),
"qrcode": MessageLookupByLibrary.simpleMessage("QR-код"), "qrcode": MessageLookupByLibrary.simpleMessage("QR-код"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage( "qrcodeDesc": MessageLookupByLibrary.simpleMessage(
"Сканируйте QR-код для получения профиля", "Сканируйте QR-код для получения профиля",
), ),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("Радужные"), "rainbowScheme": MessageLookupByLibrary.simpleMessage("Радужные"),
"recovery": MessageLookupByLibrary.simpleMessage("Восстановление"),
"recoveryAll": MessageLookupByLibrary.simpleMessage(
"Восстановить все данные",
),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
"Только восстановление профилей",
),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage(
"Стратегия восстановления",
),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage(
"Совместимый",
),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
"Переопределение",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage(
"Восстановление успешно",
),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir-порт"), "redirPort": MessageLookupByLibrary.simpleMessage("Redir-порт"),
"redo": MessageLookupByLibrary.simpleMessage("Повторить"), "redo": MessageLookupByLibrary.simpleMessage("Повторить"),
"regExp": MessageLookupByLibrary.simpleMessage("Регулярное выражение"), "regExp": MessageLookupByLibrary.simpleMessage("Регулярное выражение"),
@@ -705,9 +701,6 @@ class MessageLookup extends MessageLookupByLibrary {
"remoteDestination": MessageLookupByLibrary.simpleMessage( "remoteDestination": MessageLookupByLibrary.simpleMessage(
"Удалённое назначение", "Удалённое назначение",
), ),
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"Восстановление данных с WebDAV",
),
"remove": MessageLookupByLibrary.simpleMessage("Удалить"), "remove": MessageLookupByLibrary.simpleMessage("Удалить"),
"rename": MessageLookupByLibrary.simpleMessage("Переименовать"), "rename": MessageLookupByLibrary.simpleMessage("Переименовать"),
"request": MessageLookupByLibrary.simpleMessage("Запрос"), "request": MessageLookupByLibrary.simpleMessage("Запрос"),
@@ -734,6 +727,34 @@ class MessageLookup extends MessageLookupByLibrary {
"restartCoreTip": MessageLookupByLibrary.simpleMessage( "restartCoreTip": MessageLookupByLibrary.simpleMessage(
"Вы уверены, что хотите перезапустить ядро?", "Вы уверены, что хотите перезапустить ядро?",
), ),
"restore": MessageLookupByLibrary.simpleMessage("Восстановить"),
"restoreAllData": MessageLookupByLibrary.simpleMessage(
"Восстановить все данные",
),
"restoreException": MessageLookupByLibrary.simpleMessage(
"Ошибка восстановления",
),
"restoreFromFileDesc": MessageLookupByLibrary.simpleMessage(
"Восстановить данные из файла",
),
"restoreFromWebDAVDesc": MessageLookupByLibrary.simpleMessage(
"Восстановить данные через WebDAV",
),
"restoreOnlyConfig": MessageLookupByLibrary.simpleMessage(
"Восстановить только файлы конфигурации",
),
"restoreStrategy": MessageLookupByLibrary.simpleMessage(
"Стратегия восстановления",
),
"restoreStrategy_compatible": MessageLookupByLibrary.simpleMessage(
"Совместимый",
),
"restoreStrategy_override": MessageLookupByLibrary.simpleMessage(
"Перезаписать",
),
"restoreSuccess": MessageLookupByLibrary.simpleMessage(
"Восстановление успешно",
),
"routeAddress": MessageLookupByLibrary.simpleMessage("Адрес маршрутизации"), "routeAddress": MessageLookupByLibrary.simpleMessage("Адрес маршрутизации"),
"routeAddressDesc": MessageLookupByLibrary.simpleMessage( "routeAddressDesc": MessageLookupByLibrary.simpleMessage(
"Настройка адреса прослушивания маршрутизации", "Настройка адреса прослушивания маршрутизации",
@@ -851,6 +872,9 @@ class MessageLookup extends MessageLookupByLibrary {
"Убрать дополнительные задержки, такие как рукопожатие", "Убрать дополнительные задержки, такие как рукопожатие",
), ),
"unknown": MessageLookupByLibrary.simpleMessage("Неизвестно"), "unknown": MessageLookupByLibrary.simpleMessage("Неизвестно"),
"unknownNetworkError": MessageLookupByLibrary.simpleMessage(
"Неизвестная сетевая ошибка",
),
"unnamed": MessageLookupByLibrary.simpleMessage("Без имени"), "unnamed": MessageLookupByLibrary.simpleMessage("Без имени"),
"update": MessageLookupByLibrary.simpleMessage("Обновить"), "update": MessageLookupByLibrary.simpleMessage("Обновить"),
"upload": MessageLookupByLibrary.simpleMessage("Загрузка"), "upload": MessageLookupByLibrary.simpleMessage("Загрузка"),

View File

@@ -70,6 +70,7 @@ class MessageLookup extends MessageLookupByLibrary {
"action_tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"), "action_tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
"action_view": MessageLookupByLibrary.simpleMessage("显示/隐藏"), "action_view": MessageLookupByLibrary.simpleMessage("显示/隐藏"),
"add": MessageLookupByLibrary.simpleMessage("添加"), "add": MessageLookupByLibrary.simpleMessage("添加"),
"addProfile": MessageLookupByLibrary.simpleMessage("添加配置"),
"addRule": MessageLookupByLibrary.simpleMessage("添加规则"), "addRule": MessageLookupByLibrary.simpleMessage("添加规则"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage("附加到原始规则"), "addedOriginRules": MessageLookupByLibrary.simpleMessage("附加到原始规则"),
"addedRules": MessageLookupByLibrary.simpleMessage("附加规则"), "addedRules": MessageLookupByLibrary.simpleMessage("附加规则"),
@@ -109,8 +110,8 @@ class MessageLookup extends MessageLookupByLibrary {
"autoUpdate": MessageLookupByLibrary.simpleMessage("自动更新"), "autoUpdate": MessageLookupByLibrary.simpleMessage("自动更新"),
"autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自动更新间隔(分钟)"), "autoUpdateInterval": MessageLookupByLibrary.simpleMessage("自动更新间隔(分钟)"),
"backup": MessageLookupByLibrary.simpleMessage("备份"), "backup": MessageLookupByLibrary.simpleMessage("备份"),
"backupAndRecovery": MessageLookupByLibrary.simpleMessage("备份与恢复"), "backupAndRestore": MessageLookupByLibrary.simpleMessage("备份与恢复"),
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage( "backupAndRestoreDesc": MessageLookupByLibrary.simpleMessage(
"通过WebDAV或者文件同步数据", "通过WebDAV或者文件同步数据",
), ),
"backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"), "backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"),
@@ -184,6 +185,7 @@ class MessageLookup extends MessageLookupByLibrary {
"defaultText": MessageLookupByLibrary.simpleMessage("默认"), "defaultText": MessageLookupByLibrary.simpleMessage("默认"),
"delay": MessageLookupByLibrary.simpleMessage("延迟"), "delay": MessageLookupByLibrary.simpleMessage("延迟"),
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"), "delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
"delayTest": MessageLookupByLibrary.simpleMessage("延迟测试"),
"delete": MessageLookupByLibrary.simpleMessage("删除"), "delete": MessageLookupByLibrary.simpleMessage("删除"),
"deleteMultipTip": m1, "deleteMultipTip": m1,
"deleteTip": m2, "deleteTip": m2,
@@ -284,6 +286,7 @@ class MessageLookup extends MessageLookupByLibrary {
"internet": MessageLookupByLibrary.simpleMessage("互联网"), "internet": MessageLookupByLibrary.simpleMessage("互联网"),
"interval": MessageLookupByLibrary.simpleMessage("间隔"), "interval": MessageLookupByLibrary.simpleMessage("间隔"),
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"), "intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
"invalidBackupFile": MessageLookupByLibrary.simpleMessage("无效备份文件"),
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"), "ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"), "ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("允许IPv6入站"), "ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("允许IPv6入站"),
@@ -301,7 +304,6 @@ class MessageLookup extends MessageLookupByLibrary {
"loading": MessageLookupByLibrary.simpleMessage("加载中..."), "loading": MessageLookupByLibrary.simpleMessage("加载中..."),
"local": MessageLookupByLibrary.simpleMessage("本地"), "local": MessageLookupByLibrary.simpleMessage("本地"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到本地"), "localBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到本地"),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过文件恢复数据"),
"log": MessageLookupByLibrary.simpleMessage("日志"), "log": MessageLookupByLibrary.simpleMessage("日志"),
"logLevel": MessageLookupByLibrary.simpleMessage("日志等级"), "logLevel": MessageLookupByLibrary.simpleMessage("日志等级"),
"logcat": MessageLookupByLibrary.simpleMessage("日志捕获"), "logcat": MessageLookupByLibrary.simpleMessage("日志捕获"),
@@ -335,6 +337,10 @@ class MessageLookup extends MessageLookupByLibrary {
"network": MessageLookupByLibrary.simpleMessage("网络"), "network": MessageLookupByLibrary.simpleMessage("网络"),
"networkDesc": MessageLookupByLibrary.simpleMessage("修改网络相关设置"), "networkDesc": MessageLookupByLibrary.simpleMessage("修改网络相关设置"),
"networkDetection": MessageLookupByLibrary.simpleMessage("网络检测"), "networkDetection": MessageLookupByLibrary.simpleMessage("网络检测"),
"networkException": MessageLookupByLibrary.simpleMessage("网络异常,请检查连接后重试"),
"networkRequestException": MessageLookupByLibrary.simpleMessage(
"网络请求异常,请稍后再试。",
),
"networkSpeed": MessageLookupByLibrary.simpleMessage("网络速度"), "networkSpeed": MessageLookupByLibrary.simpleMessage("网络速度"),
"networkType": MessageLookupByLibrary.simpleMessage("网络类型"), "networkType": MessageLookupByLibrary.simpleMessage("网络类型"),
"neutralScheme": MessageLookupByLibrary.simpleMessage("中性"), "neutralScheme": MessageLookupByLibrary.simpleMessage("中性"),
@@ -373,6 +379,10 @@ class MessageLookup extends MessageLookupByLibrary {
"overrideMode": MessageLookupByLibrary.simpleMessage("覆写模式"), "overrideMode": MessageLookupByLibrary.simpleMessage("覆写模式"),
"overrideOriginRules": MessageLookupByLibrary.simpleMessage("覆盖原始规则"), "overrideOriginRules": MessageLookupByLibrary.simpleMessage("覆盖原始规则"),
"overrideScript": MessageLookupByLibrary.simpleMessage("覆写脚本"), "overrideScript": MessageLookupByLibrary.simpleMessage("覆写脚本"),
"overwriteTypeCustom": MessageLookupByLibrary.simpleMessage("自定义"),
"overwriteTypeCustomDesc": MessageLookupByLibrary.simpleMessage(
"自定义模式,支持完全自定义修改代理组以及规则",
),
"palette": MessageLookupByLibrary.simpleMessage("调色板"), "palette": MessageLookupByLibrary.simpleMessage("调色板"),
"password": MessageLookupByLibrary.simpleMessage("密码"), "password": MessageLookupByLibrary.simpleMessage("密码"),
"paste": MessageLookupByLibrary.simpleMessage("粘贴"), "paste": MessageLookupByLibrary.simpleMessage("粘贴"),
@@ -423,17 +433,11 @@ class MessageLookup extends MessageLookupByLibrary {
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"), "proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"), "proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
"proxyProviders": MessageLookupByLibrary.simpleMessage("代理提供者"), "proxyProviders": MessageLookupByLibrary.simpleMessage("代理提供者"),
"pruneCache": MessageLookupByLibrary.simpleMessage("修剪缓存"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"), "pureBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"),
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"), "qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"), "qrcodeDesc": MessageLookupByLibrary.simpleMessage("扫描二维码获取配置文件"),
"rainbowScheme": MessageLookupByLibrary.simpleMessage("彩虹"), "rainbowScheme": MessageLookupByLibrary.simpleMessage("彩虹"),
"recovery": MessageLookupByLibrary.simpleMessage("恢复"),
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
"recoveryStrategy": MessageLookupByLibrary.simpleMessage("恢复策略"),
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("兼容"),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage("覆盖"),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir端口"), "redirPort": MessageLookupByLibrary.simpleMessage("Redir端口"),
"redo": MessageLookupByLibrary.simpleMessage("重做"), "redo": MessageLookupByLibrary.simpleMessage("重做"),
"regExp": MessageLookupByLibrary.simpleMessage("正则"), "regExp": MessageLookupByLibrary.simpleMessage("正则"),
@@ -441,7 +445,6 @@ class MessageLookup extends MessageLookupByLibrary {
"remote": MessageLookupByLibrary.simpleMessage("远程"), "remote": MessageLookupByLibrary.simpleMessage("远程"),
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"), "remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
"remoteDestination": MessageLookupByLibrary.simpleMessage("远程目标"), "remoteDestination": MessageLookupByLibrary.simpleMessage("远程目标"),
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过WebDAV恢复数据"),
"remove": MessageLookupByLibrary.simpleMessage("移除"), "remove": MessageLookupByLibrary.simpleMessage("移除"),
"rename": MessageLookupByLibrary.simpleMessage("重命名"), "rename": MessageLookupByLibrary.simpleMessage("重命名"),
"request": MessageLookupByLibrary.simpleMessage("请求"), "request": MessageLookupByLibrary.simpleMessage("请求"),
@@ -460,6 +463,18 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"restart": MessageLookupByLibrary.simpleMessage("重启"), "restart": MessageLookupByLibrary.simpleMessage("重启"),
"restartCoreTip": MessageLookupByLibrary.simpleMessage("您确定要重启核心吗?"), "restartCoreTip": MessageLookupByLibrary.simpleMessage("您确定要重启核心吗?"),
"restore": MessageLookupByLibrary.simpleMessage("恢复"),
"restoreAllData": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
"restoreException": MessageLookupByLibrary.simpleMessage("恢复异常"),
"restoreFromFileDesc": MessageLookupByLibrary.simpleMessage("通过文件恢复数据"),
"restoreFromWebDAVDesc": MessageLookupByLibrary.simpleMessage(
"通过WebDAV恢复数据",
),
"restoreOnlyConfig": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
"restoreStrategy": MessageLookupByLibrary.simpleMessage("恢复策略"),
"restoreStrategy_compatible": MessageLookupByLibrary.simpleMessage("兼容"),
"restoreStrategy_override": MessageLookupByLibrary.simpleMessage("覆盖"),
"restoreSuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
"routeAddress": MessageLookupByLibrary.simpleMessage("路由地址"), "routeAddress": MessageLookupByLibrary.simpleMessage("路由地址"),
"routeAddressDesc": MessageLookupByLibrary.simpleMessage("配置监听路由地址"), "routeAddressDesc": MessageLookupByLibrary.simpleMessage("配置监听路由地址"),
"routeMode": MessageLookupByLibrary.simpleMessage("路由模式"), "routeMode": MessageLookupByLibrary.simpleMessage("路由模式"),
@@ -547,6 +562,7 @@ class MessageLookup extends MessageLookupByLibrary {
"unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"), "unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"),
"unifiedDelayDesc": MessageLookupByLibrary.simpleMessage("去除握手等额外延迟"), "unifiedDelayDesc": MessageLookupByLibrary.simpleMessage("去除握手等额外延迟"),
"unknown": MessageLookupByLibrary.simpleMessage("未知"), "unknown": MessageLookupByLibrary.simpleMessage("未知"),
"unknownNetworkError": MessageLookupByLibrary.simpleMessage("未知网络错误"),
"unnamed": MessageLookupByLibrary.simpleMessage("未命名"), "unnamed": MessageLookupByLibrary.simpleMessage("未命名"),
"update": MessageLookupByLibrary.simpleMessage("更新"), "update": MessageLookupByLibrary.simpleMessage("更新"),
"upload": MessageLookupByLibrary.simpleMessage("上传"), "upload": MessageLookupByLibrary.simpleMessage("上传"),

View File

@@ -999,26 +999,6 @@ class AppLocalizations {
return Intl.message('tip', name: 'tip', desc: '', args: []); return Intl.message('tip', name: 'tip', desc: '', args: []);
} }
/// `Backup and Recovery`
String get backupAndRecovery {
return Intl.message(
'Backup and Recovery',
name: 'backupAndRecovery',
desc: '',
args: [],
);
}
/// `Sync data via WebDAV or file`
String get backupAndRecoveryDesc {
return Intl.message(
'Sync data via WebDAV or file',
name: 'backupAndRecoveryDesc',
desc: '',
args: [],
);
}
/// `Account` /// `Account`
String get account { String get account {
return Intl.message('Account', name: 'account', desc: '', args: []); return Intl.message('Account', name: 'account', desc: '', args: []);
@@ -1029,41 +1009,6 @@ class AppLocalizations {
return Intl.message('Backup', name: 'backup', desc: '', args: []); return Intl.message('Backup', name: 'backup', desc: '', args: []);
} }
/// `Recovery`
String get recovery {
return Intl.message('Recovery', name: 'recovery', desc: '', args: []);
}
/// `Only recovery profiles`
String get recoveryProfiles {
return Intl.message(
'Only recovery profiles',
name: 'recoveryProfiles',
desc: '',
args: [],
);
}
/// `Recovery all data`
String get recoveryAll {
return Intl.message(
'Recovery all data',
name: 'recoveryAll',
desc: '',
args: [],
);
}
/// `Recovery success`
String get recoverySuccess {
return Intl.message(
'Recovery success',
name: 'recoverySuccess',
desc: '',
args: [],
);
}
/// `Backup success` /// `Backup success`
String get backupSuccess { String get backupSuccess {
return Intl.message( return Intl.message(
@@ -1689,16 +1634,6 @@ class AppLocalizations {
); );
} }
/// `Recovery data from WebDAV`
String get remoteRecoveryDesc {
return Intl.message(
'Recovery data from WebDAV',
name: 'remoteRecoveryDesc',
desc: '',
args: [],
);
}
/// `Backup local data to local` /// `Backup local data to local`
String get localBackupDesc { String get localBackupDesc {
return Intl.message( return Intl.message(
@@ -1709,16 +1644,6 @@ class AppLocalizations {
); );
} }
/// `Recovery data from file`
String get localRecoveryDesc {
return Intl.message(
'Recovery data from file',
name: 'localRecoveryDesc',
desc: '',
args: [],
);
}
/// `Mode` /// `Mode`
String get mode { String get mode {
return Intl.message('Mode', name: 'mode', desc: '', args: []); return Intl.message('Mode', name: 'mode', desc: '', args: []);
@@ -2934,31 +2859,31 @@ class AppLocalizations {
return Intl.message('Contact me', name: 'contactMe', desc: '', args: []); return Intl.message('Contact me', name: 'contactMe', desc: '', args: []);
} }
/// `Recovery strategy` /// `Restore strategy`
String get recoveryStrategy { String get restoreStrategy {
return Intl.message( return Intl.message(
'Recovery strategy', 'Restore strategy',
name: 'recoveryStrategy', name: 'restoreStrategy',
desc: '', desc: '',
args: [], args: [],
); );
} }
/// `Override` /// `Override`
String get recoveryStrategy_override { String get restoreStrategy_override {
return Intl.message( return Intl.message(
'Override', 'Override',
name: 'recoveryStrategy_override', name: 'restoreStrategy_override',
desc: '', desc: '',
args: [], args: [],
); );
} }
/// `Compatible` /// `Compatible`
String get recoveryStrategy_compatible { String get restoreStrategy_compatible {
return Intl.message( return Intl.message(
'Compatible', 'Compatible',
name: 'recoveryStrategy_compatible', name: 'restoreStrategy_compatible',
desc: '', desc: '',
args: [], args: [],
); );
@@ -3663,6 +3588,166 @@ class AppLocalizations {
args: [], args: [],
); );
} }
/// `Custom`
String get overwriteTypeCustom {
return Intl.message(
'Custom',
name: 'overwriteTypeCustom',
desc: '',
args: [],
);
}
/// `Custom mode, fully customize proxy groups and rules`
String get overwriteTypeCustomDesc {
return Intl.message(
'Custom mode, fully customize proxy groups and rules',
name: 'overwriteTypeCustomDesc',
desc: '',
args: [],
);
}
/// `Unknown network error`
String get unknownNetworkError {
return Intl.message(
'Unknown network error',
name: 'unknownNetworkError',
desc: '',
args: [],
);
}
/// `Network request exception, please try again later.`
String get networkRequestException {
return Intl.message(
'Network request exception, please try again later.',
name: 'networkRequestException',
desc: '',
args: [],
);
}
/// `Recovery exception`
String get restoreException {
return Intl.message(
'Recovery exception',
name: 'restoreException',
desc: '',
args: [],
);
}
/// `Network exception, please check your connection and try again`
String get networkException {
return Intl.message(
'Network exception, please check your connection and try again',
name: 'networkException',
desc: '',
args: [],
);
}
/// `Invalid backup file`
String get invalidBackupFile {
return Intl.message(
'Invalid backup file',
name: 'invalidBackupFile',
desc: '',
args: [],
);
}
/// `Prune cache`
String get pruneCache {
return Intl.message('Prune cache', name: 'pruneCache', desc: '', args: []);
}
/// `Backup and Restore`
String get backupAndRestore {
return Intl.message(
'Backup and Restore',
name: 'backupAndRestore',
desc: '',
args: [],
);
}
/// `Sync data via WebDAV or files`
String get backupAndRestoreDesc {
return Intl.message(
'Sync data via WebDAV or files',
name: 'backupAndRestoreDesc',
desc: '',
args: [],
);
}
/// `Restore`
String get restore {
return Intl.message('Restore', name: 'restore', desc: '', args: []);
}
/// `Restore success`
String get restoreSuccess {
return Intl.message(
'Restore success',
name: 'restoreSuccess',
desc: '',
args: [],
);
}
/// `Restore data via WebDAV`
String get restoreFromWebDAVDesc {
return Intl.message(
'Restore data via WebDAV',
name: 'restoreFromWebDAVDesc',
desc: '',
args: [],
);
}
/// `Restore data via file`
String get restoreFromFileDesc {
return Intl.message(
'Restore data via file',
name: 'restoreFromFileDesc',
desc: '',
args: [],
);
}
/// `Restore configuration files only`
String get restoreOnlyConfig {
return Intl.message(
'Restore configuration files only',
name: 'restoreOnlyConfig',
desc: '',
args: [],
);
}
/// `Restore all data`
String get restoreAllData {
return Intl.message(
'Restore all data',
name: 'restoreAllData',
desc: '',
args: [],
);
}
/// `Add Profile`
String get addProfile {
return Intl.message('Add Profile', name: 'addProfile', desc: '', args: []);
}
/// `Delay Test`
String get delayTest {
return Intl.message('Delay Test', name: 'delayTest', desc: '', args: []);
}
} }
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> { class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -1,65 +1,31 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/pages/error.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:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'application.dart'; import 'application.dart';
import 'common/common.dart'; import 'common/common.dart';
import 'core/controller.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); try {
final version = await system.version; WidgetsFlutterBinding.ensureInitialized();
await globalState.initApp(version); final version = await system.version;
HttpOverrides.global = FlClashHttpOverrides(); final container = await globalState.init(version);
runApp(ProviderScope(child: const Application())); HttpOverrides.global = FlClashHttpOverrides();
} runApp(
UncontrolledProviderScope(
@pragma('vm:entry-point') container: container,
Future<void> _service(List<String> flags) async { child: const Application(),
WidgetsFlutterBinding.ensureInitialized(); ),
globalState.isService = true; );
await globalState.init(); } catch (e, s) {
await coreController.preload(); return runApp(
tile?.addListener( MaterialApp(
_TileListenerWithService( home: InitErrorScreen(error: e, stack: s),
onStop: () async { ),
await app?.tip(appLocalizations.stopVpn); );
await globalState.handleStop();
},
),
);
app?.tip(appLocalizations.startVpn);
final version = await system.version;
await coreController.init(version);
final clashConfig = globalState.config.patchClashConfig.copyWith.tun(
enable: false,
);
final setupState = globalState.getSetupState(
globalState.config.currentProfileId,
);
globalState.setupConfig(
setupState: setupState,
patchConfig: clashConfig,
preloadInvoke: () {
globalState.handleStart();
},
);
}
@immutable
class _TileListenerWithService with TileListener {
final Function() _onStop;
const _TileListenerWithService({required Function() onStop})
: _onStop = onStop;
@override
void onStop() {
_onStop();
} }
} }

View File

@@ -1,6 +1,7 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/core/core.dart'; import 'package:fl_clash/core/core.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/core.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/plugins/service.dart'; import 'package:fl_clash/plugins/service.dart';
import 'package:fl_clash/providers/providers.dart'; import 'package:fl_clash/providers/providers.dart';
@@ -27,9 +28,14 @@ class _AndroidContainerState extends ConsumerState<AndroidManager>
) { ) {
app?.updateExcludeFromRecents(next); app?.updateExcludeFromRecents(next);
}, fireImmediately: true); }, fireImmediately: true);
ref.listenManual(androidStateProvider, (prev, next) { ref.listenManual(sharedStateProvider, (prev, next) {
if (prev != next) { if (prev != next) {
service?.syncAndroidState(next); debouncer.call(FunctionTag.saveSharedFile, () async {
preferences.saveShareState(next);
}, duration: Duration(seconds: 1));
if (prev?.needSyncSharedState != next.needSyncSharedState) {
service?.syncState(next.needSyncSharedState);
}
} }
}); });
service?.addListener(this); service?.addListener(this);

View File

@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/manager/window_manager.dart'; import 'package:fl_clash/manager/window_manager.dart';
import 'package:fl_clash/providers/providers.dart'; import 'package:fl_clash/providers/providers.dart';
@@ -25,26 +26,19 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
ref.listenManual(layoutChangeProvider, (prev, next) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (prev != next) {
globalState.computeHeightMapCache = {};
}
});
});
ref.listenManual(checkIpProvider, (prev, next) { ref.listenManual(checkIpProvider, (prev, next) {
if (prev != next && next.b) { if (prev != next && next.a && next.c) {
detectionState.startCheck(); ref.read(networkDetectionProvider.notifier).startCheck();
} }
}, fireImmediately: true); });
ref.listenManual(configStateProvider, (prev, next) { ref.listenManual(configProvider, (prev, next) {
if (prev != next) { if (prev != next) {
globalState.appController.savePreferencesDebounce(); appController.savePreferencesDebounce();
} }
}); });
ref.listenManual(needUpdateGroupsProvider, (prev, next) { ref.listenManual(needUpdateGroupsProvider, (prev, next) {
if (prev != next) { if (prev != next) {
globalState.appController.updateGroupsDebounce(); appController.updateGroupsDebounce();
} }
}); });
if (window == null) { if (window == null) {
@@ -73,20 +67,18 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
commonPrint.log('$state'); commonPrint.log('$state');
if (state == AppLifecycleState.resumed) { if (state == AppLifecycleState.resumed) {
render?.resume(); render?.resume();
}
if (state == AppLifecycleState.resumed) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
detectionState.tryStartCheck(); appController.tryCheckIp();
if (system.isAndroid) {
appController.tryStartCore();
}
}); });
if (system.isAndroid) {
globalState.appController.tryStartCore();
}
} }
} }
@override @override
void didChangePlatformBrightness() { void didChangePlatformBrightness() {
globalState.appController.updateBrightness(); appController.updateBrightness();
} }
@override @override
@@ -132,20 +124,20 @@ class AppSidebarContainer extends ConsumerWidget {
const AppSidebarContainer({super.key, required this.child}); const AppSidebarContainer({super.key, required this.child});
Widget _buildLoading() { // Widget _buildLoading() {
return Consumer( // return Consumer(
builder: (_, ref, _) { // builder: (_, ref, _) {
final loading = ref.watch(loadingProvider); // final loading = ref.watch(loadingProvider);
final isMobileView = ref.watch(isMobileViewProvider); // final isMobileView = ref.watch(isMobileViewProvider);
return loading && !isMobileView // return loading && !isMobileView
? RotatedBox( // ? RotatedBox(
quarterTurns: 1, // quarterTurns: 1,
child: const LinearProgressIndicator(), // child: const LinearProgressIndicator(),
) // )
: Container(); // : Container();
}, // },
); // );
} // }
Widget _buildBackground({ Widget _buildBackground({
required BuildContext context, required BuildContext context,
@@ -187,84 +179,74 @@ class AppSidebarContainer extends ConsumerWidget {
_buildBackground( _buildBackground(
context: context, context: context,
child: SafeArea( child: SafeArea(
child: Stack( child: Column(
alignment: Alignment.topRight, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Column( if (system.isMacOS) SizedBox(height: 22),
crossAxisAlignment: CrossAxisAlignment.center, SizedBox(height: 10),
children: [ if (!system.isMacOS) ...[
if (system.isMacOS) SizedBox(height: 22), ClipRect(child: AppIcon()),
SizedBox(height: 10), SizedBox(height: 12),
if (!system.isMacOS) ...[ ],
ClipRect(child: AppIcon()), Expanded(
SizedBox(height: 12), child: ScrollConfiguration(
], behavior: HiddenBarScrollBehavior(),
Expanded( child: Column(
child: ScrollConfiguration( crossAxisAlignment: CrossAxisAlignment.start,
behavior: HiddenBarScrollBehavior(), children: [
child: Column( Expanded(
crossAxisAlignment: CrossAxisAlignment.start, child: NavigationRail(
children: [ scrollable: true,
Expanded( minExtendedWidth: 200,
child: NavigationRail( backgroundColor: Colors.transparent,
scrollable: true, selectedLabelTextStyle: context
minExtendedWidth: 200, .textTheme
backgroundColor: Colors.transparent, .labelLarge!
selectedLabelTextStyle: context .copyWith(color: context.colorScheme.onSurface),
.textTheme unselectedLabelTextStyle: context
.labelLarge! .textTheme
.copyWith( .labelLarge!
color: context.colorScheme.onSurface, .copyWith(color: context.colorScheme.onSurface),
), destinations: navigationItems
unselectedLabelTextStyle: context .map(
.textTheme (e) => NavigationRailDestination(
.labelLarge! icon: e.icon,
.copyWith( label: Text(Intl.message(e.label.name)),
color: context.colorScheme.onSurface, ),
), )
destinations: navigationItems .toList(),
.map( onDestinationSelected: (index) {
(e) => NavigationRailDestination( appController.toPage(
icon: e.icon, navigationItems[index].label,
label: Text(Intl.message(e.label.name)), );
), },
) extended: false,
.toList(), selectedIndex: currentIndex,
onDestinationSelected: (index) { labelType: showLabel
globalState.appController.toPage( ? NavigationRailLabelType.all
navigationItems[index].label, : NavigationRailLabelType.none,
); ),
},
extended: false,
selectedIndex: currentIndex,
labelType: showLabel
? NavigationRailLabelType.all
: NavigationRailLabelType.none,
),
),
],
), ),
), ],
), ),
const SizedBox(height: 16), ),
IconButton(
onPressed: () {
ref
.read(appSettingProvider.notifier)
.updateState(
(state) =>
state.copyWith(showLabel: !state.showLabel),
);
},
icon: Icon(
Icons.menu,
color: context.colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
],
), ),
_buildLoading(), const SizedBox(height: 16),
IconButton(
onPressed: () {
ref
.read(appSettingProvider.notifier)
.update(
(state) =>
state.copyWith(showLabel: !state.showLabel),
);
},
icon: Icon(
Icons.menu,
color: context.colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
], ],
), ),
), ),

View File

@@ -1,4 +1,5 @@
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/core/core.dart'; import 'package:fl_clash/core/core.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';
@@ -29,14 +30,17 @@ class _CoreContainerState extends ConsumerState<CoreManager>
void initState() { void initState() {
super.initState(); super.initState();
coreEventManager.addListener(this); coreEventManager.addListener(this);
ref.listenManual(needSetupProvider, (prev, next) { ref.listenManual(
if (prev != next) { currentSetupStateProvider.select((state) => state?.profileId),
globalState.appController.handleChangeProfile(); (prev, next) {
} if (prev != next) {
}); appController.fullSetup();
}
},
);
ref.listenManual(updateParamsProvider, (prev, next) { ref.listenManual(updateParamsProvider, (prev, next) {
if (prev != next) { if (prev != next) {
globalState.appController.updateClashConfigDebounce(); appController.updateConfigDebounce();
} }
}); });
ref.listenManual(appSettingProvider.select((state) => state.openLogs), ( ref.listenManual(appSettingProvider.select((state) => state.openLogs), (
@@ -60,7 +64,6 @@ class _CoreContainerState extends ConsumerState<CoreManager>
@override @override
Future<void> onDelay(Delay delay) async { Future<void> onDelay(Delay delay) async {
super.onDelay(delay); super.onDelay(delay);
final appController = globalState.appController;
appController.setDelay(delay); appController.setDelay(delay);
debouncer.call(FunctionTag.updateDelay, () async { debouncer.call(FunctionTag.updateDelay, () async {
appController.updateGroupsDebounce(); appController.updateGroupsDebounce();
@@ -88,23 +91,21 @@ class _CoreContainerState extends ConsumerState<CoreManager>
.read(providersProvider.notifier) .read(providersProvider.notifier)
.setProvider(await coreController.getExternalProvider(providerName)); .setProvider(await coreController.getExternalProvider(providerName));
debouncer.call(FunctionTag.loadedProvider, () async { debouncer.call(FunctionTag.loadedProvider, () async {
globalState.appController.updateGroupsDebounce(); appController.updateGroupsDebounce();
}, duration: const Duration(milliseconds: 5000)); }, duration: const Duration(milliseconds: 5000));
super.onLoaded(providerName); super.onLoaded(providerName);
} }
@override @override
Future<void> onCrash(String message) async { Future<void> onCrash(String message) async {
if (!globalState.isUserDisconnected &&
WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) {
context.showNotifier(message);
}
globalState.isUserDisconnected = false;
if (ref.read(coreStatusProvider) != CoreStatus.connected) { if (ref.read(coreStatusProvider) != CoreStatus.connected) {
return; return;
} }
ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected; ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
await coreController.shutdown(); if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) {
context.showNotifier(message);
}
await coreController.shutdown(false);
super.onCrash(message); super.onCrash(message);
} }
} }

View File

@@ -1,8 +1,8 @@
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart'; import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/providers/config.dart'; import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -11,10 +11,7 @@ import 'package:hotkey_manager/hotkey_manager.dart';
class HotKeyManager extends ConsumerStatefulWidget { class HotKeyManager extends ConsumerStatefulWidget {
final Widget child; final Widget child;
const HotKeyManager({ const HotKeyManager({super.key, required this.child});
super.key,
required this.child,
});
@override @override
ConsumerState<HotKeyManager> createState() => _HotKeyManagerState(); ConsumerState<HotKeyManager> createState() => _HotKeyManagerState();
@@ -24,29 +21,25 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
ref.listenManual( ref.listenManual(hotKeyActionsProvider, (prev, next) {
hotKeyActionsProvider, if (!hotKeyActionListEquality.equals(prev, next)) {
(prev, next) { _updateHotKeys(hotKeyActions: next);
if (!hotKeyActionListEquality.equals(prev, next)) { }
_updateHotKeys(hotKeyActions: next); }, fireImmediately: true);
}
},
fireImmediately: true,
);
} }
Future<void> _handleHotKeyAction(HotAction action) async { Future<void> _handleHotKeyAction(HotAction action) async {
switch (action) { switch (action) {
case HotAction.mode: case HotAction.mode:
globalState.appController.updateMode(); appController.updateMode();
case HotAction.start: case HotAction.start:
globalState.appController.updateStart(); appController.updateStart();
case HotAction.view: case HotAction.view:
globalState.appController.updateVisible(); appController.updateVisible();
case HotAction.proxy: case HotAction.proxy:
globalState.appController.updateSystemProxy(); appController.updateSystemProxy();
case HotAction.tun: case HotAction.tun:
globalState.appController.updateTun(); appController.updateTun();
} }
} }
@@ -54,27 +47,25 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
required List<HotKeyAction> hotKeyActions, required List<HotKeyAction> hotKeyActions,
}) async { }) async {
await hotKeyManager.unregisterAll(); await hotKeyManager.unregisterAll();
final hotkeyActionHandles = hotKeyActions.where( final hotkeyActionHandles = hotKeyActions
(hotKeyAction) { .where((hotKeyAction) {
return hotKeyAction.key != null && hotKeyAction.modifiers.isNotEmpty; return hotKeyAction.key != null && hotKeyAction.modifiers.isNotEmpty;
}, })
).map<Future>( .map<Future>((hotKeyAction) async {
(hotKeyAction) async { final modifiers = hotKeyAction.modifiers
final modifiers = hotKeyAction.modifiers .map((item) => item.toHotKeyModifier())
.map((item) => item.toHotKeyModifier()) .toList();
.toList(); final hotKey = HotKey(
final hotKey = HotKey( key: PhysicalKeyboardKey(hotKeyAction.key!),
key: PhysicalKeyboardKey(hotKeyAction.key!), modifiers: modifiers,
modifiers: modifiers, );
); return await hotKeyManager.register(
return await hotKeyManager.register( hotKey,
hotKey, keyDownHandler: (_) {
keyDownHandler: (_) { _handleHotKeyAction(hotKeyAction.action);
_handleHotKeyAction(hotKeyAction.action); },
}, );
); });
},
);
await Future.wait(hotkeyActionHandles); await Future.wait(hotkeyActionHandles);
} }
@@ -87,7 +78,7 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
child: Actions( child: Actions(
actions: { actions: {
CloseWindowIntent: CallbackAction<CloseWindowIntent>( CloseWindowIntent: CallbackAction<CloseWindowIntent>(
onInvoke: (_) => globalState.appController.handleBackOrExit(), onInvoke: (_) => appController.handleBackOrExit(),
), ),
DoNothingIntent: CallbackAction<DoNothingIntent>( DoNothingIntent: CallbackAction<DoNothingIntent>(
onInvoke: (_) => null, onInvoke: (_) => null,
@@ -100,8 +91,6 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return _buildShortcuts( return _buildShortcuts(widget.child);
widget.child,
);
} }
} }

View File

@@ -6,7 +6,6 @@ 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/providers/providers.dart'; import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/widgets/fade_box.dart'; import 'package:fl_clash/widgets/fade_box.dart';
import 'package:fl_clash/widgets/loading.dart';
import 'package:fl_clash/widgets/theme.dart'; import 'package:fl_clash/widgets/theme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -191,7 +190,7 @@ class StatusManagerState extends State<StatusManager> {
), ),
), ),
), ),
LoadingIndicator(), // LoadingIndicator(),
], ],
), ),
), ),
@@ -200,63 +199,63 @@ class StatusManagerState extends State<StatusManager> {
} }
} }
class LoadingIndicator extends ConsumerWidget { // class LoadingIndicator extends ConsumerWidget {
const LoadingIndicator({super.key}); // const LoadingIndicator({super.key});
//
@override // @override
Widget build(BuildContext context, ref) { // Widget build(BuildContext context, ref) {
final loading = ref.watch(loadingProvider); // final loading = ref.watch(loadingProvider);
final isMobileView = ref.watch(isMobileViewProvider); // final isMobileView = ref.watch(isMobileViewProvider);
return AnimatedSwitcher( // return AnimatedSwitcher(
switchInCurve: Curves.easeIn, // switchInCurve: Curves.easeIn,
switchOutCurve: Curves.easeOut, // switchOutCurve: Curves.easeOut,
duration: midDuration, // duration: midDuration,
transitionBuilder: (Widget child, Animation<double> animation) { // transitionBuilder: (Widget child, Animation<double> animation) {
return SlideTransition( // return SlideTransition(
position: Tween<Offset>( // position: Tween<Offset>(
begin: const Offset(1, 0), // begin: const Offset(1, 0),
end: Offset.zero, // end: Offset.zero,
).animate(animation), // ).animate(animation),
child: child, // child: child,
); // );
}, // },
child: loading && isMobileView // child: loading && isMobileView
? Container( // ? Container(
height: 54, // height: 54,
margin: EdgeInsets.only(top: 8, left: 14, right: 14), // margin: EdgeInsets.only(top: 8, left: 14, right: 14),
child: Material( // child: Material(
elevation: 3, // elevation: 3,
color: context.colorScheme.surfaceContainer, // color: context.colorScheme.surfaceContainer,
surfaceTintColor: context.colorScheme.surfaceTint, // surfaceTintColor: context.colorScheme.surfaceTint,
shape: const RoundedSuperellipseBorder( // shape: const RoundedSuperellipseBorder(
borderRadius: BorderRadius.all(Radius.circular(14)), // borderRadius: BorderRadius.all(Radius.circular(14)),
), // ),
child: Padding( // child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16), // padding: EdgeInsets.symmetric(horizontal: 16),
child: Row( // child: Row(
mainAxisSize: MainAxisSize.min, // mainAxisSize: MainAxisSize.min,
spacing: 12, // spacing: 12,
mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ // children: [
Flexible( // Flexible(
child: Text( // child: Text(
context.appLocalizations.loading, // context.appLocalizations.loading,
style: context.textTheme.labelLarge?.copyWith( // style: context.textTheme.labelLarge?.copyWith(
color: context.colorScheme.onSurfaceVariant, // color: context.colorScheme.onSurfaceVariant,
), // ),
), // ),
), // ),
SizedBox( // SizedBox(
height: 32, // height: 32,
width: 32, // width: 32,
child: CommonCircleLoading(), // child: CommonCircleLoading(),
), // ),
], // ],
), // ),
), // ),
), // ),
) // )
: SizedBox(), // : SizedBox(),
); // );
} // }
} // }

View File

@@ -2,6 +2,8 @@ import 'dart:math';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/common/theme.dart'; import 'package:fl_clash/common/theme.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/providers/app.dart';
import 'package:fl_clash/providers/config.dart'; import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -28,17 +30,27 @@ class ThemeManager extends ConsumerWidget {
final iconBrightness = brightness == Brightness.light final iconBrightness = brightness == Brightness.light
? Brightness.dark ? Brightness.dark
: Brightness.light; : Brightness.light;
globalState.appState = globalState.appState.copyWith( WidgetsBinding.instance.addPostFrameCallback((_) {
systemUiOverlayStyle: SystemUiOverlayStyle( ref
.read(systemUiOverlayStyleStateProvider.notifier)
.update(
(state) => state.copyWith(
statusBarColor: Colors.transparent,
statusBarIconBrightness: iconBrightness,
systemNavigationBarIconBrightness: iconBrightness,
systemNavigationBarColor: context.colorScheme.surface,
systemNavigationBarDividerColor: Colors.transparent,
),
);
});
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor: Colors.transparent, statusBarColor: Colors.transparent,
statusBarIconBrightness: iconBrightness, statusBarIconBrightness: iconBrightness,
systemNavigationBarIconBrightness: iconBrightness, systemNavigationBarIconBrightness: iconBrightness,
systemNavigationBarColor: context.colorScheme.surface, systemNavigationBarColor: context.colorScheme.surface,
systemNavigationBarDividerColor: Colors.transparent, systemNavigationBarDividerColor: Colors.transparent,
), ),
);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: globalState.appState.systemUiOverlayStyle,
sized: false, sized: false,
child: child, child: child,
); );
@@ -98,7 +110,7 @@ class ThemeManager extends ConsumerWidget {
), ),
child: LayoutBuilder( child: LayoutBuilder(
builder: (_, container) { builder: (_, container) {
globalState.appController.updateViewSize( appController.updateViewSize(
Size(container.maxWidth, container.maxHeight), Size(container.maxWidth, container.maxHeight),
); );
return _buildSystemUi(child); return _buildSystemUi(child);

View File

@@ -1,38 +1,46 @@
import 'package:fl_clash/models/app.dart'; import 'package:fl_clash/common/app_localizations.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/core/controller.dart';
import 'package:fl_clash/plugins/app.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/providers/providers.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class TileManager extends StatefulWidget { class TileManager extends ConsumerStatefulWidget {
final Widget child; final Widget child;
const TileManager({super.key, required this.child}); const TileManager({super.key, required this.child});
@override @override
State<TileManager> createState() => _TileContainerState(); ConsumerState<TileManager> createState() => _TileContainerState();
} }
class _TileContainerState extends State<TileManager> with TileListener { class _TileContainerState extends ConsumerState<TileManager> with TileListener {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return widget.child; return widget.child;
} }
bool get isStart => ref.read(isStartProvider);
@override @override
Future<void> onStart() async { Future<void> onStart() async {
if (globalState.appState.isStart) { if (isStart && coreController.isCompleted) {
return; return;
} }
globalState.appController.updateStatus(true); appController.updateStatus(true);
app?.tip(appLocalizations.startVpn);
super.onStart(); super.onStart();
} }
@override @override
Future<void> onStop() async { Future<void> onStop() async {
if (!globalState.appState.isStart) { if (!isStart) {
return; return;
} }
globalState.appController.updateStatus(false); appController.updateStatus(false);
app?.tip(appLocalizations.stopVpn);
super.onStop(); super.onStop();
} }

View File

@@ -1,6 +1,6 @@
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/providers/state.dart'; import 'package:fl_clash/providers/state.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:tray_manager/tray_manager.dart'; import 'package:tray_manager/tray_manager.dart';
@@ -21,7 +21,7 @@ class _TrayContainerState extends ConsumerState<TrayManager> with TrayListener {
trayManager.addListener(this); trayManager.addListener(this);
ref.listenManual(trayStateProvider, (prev, next) { ref.listenManual(trayStateProvider, (prev, next) {
if (prev != next) { if (prev != next) {
globalState.appController.updateTray(); appController.updateTray();
} }
}); });
if (system.isMacOS) { if (system.isMacOS) {

View File

@@ -1,4 +1,5 @@
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.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/providers/state.dart'; import 'package:fl_clash/providers/state.dart';
@@ -39,7 +40,7 @@ class _VpnContainerState extends ConsumerState<VpnManager> {
actionText: appLocalizations.restart, actionText: appLocalizations.restart,
action: () async { action: () async {
await globalState.handleStop(); await globalState.handleStop();
await globalState.appController.updateStatus(true); await appController.updateStatus(true);
}, },
), ),
); );

View File

@@ -1,9 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/providers/providers.dart'; import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:window_ext/window_ext.dart'; import 'package:window_ext/window_ext.dart';
@@ -44,7 +44,7 @@ class _WindowContainerState extends ConsumerState<WindowManager>
@override @override
void onWindowClose() async { void onWindowClose() async {
await globalState.appController.handleBackOrExit(); await appController.handleBackOrExit();
super.onWindowClose(); super.onWindowClose();
} }
@@ -57,19 +57,17 @@ class _WindowContainerState extends ConsumerState<WindowManager>
@override @override
Future<void> onShouldTerminate() async { Future<void> onShouldTerminate() async {
await globalState.appController.handleExit(); await appController.handleExit();
super.onShouldTerminate(); super.onShouldTerminate();
} }
@override @override
Future<void> onWindowMoved() async { void onWindowMoved() {
super.onWindowMoved(); super.onWindowMoved();
final offset = await windowManager.getPosition(); windowManager.getPosition().then((offset) {
ref ref.read(windowSettingProvider.notifier);
.read(windowSettingProvider.notifier) // .update((state) => state.copyWith(top: offset.dy, left: offset.dx));
.updateState( });
(state) => state.copyWith(top: offset.dy, left: offset.dx),
);
} }
@override @override
@@ -78,14 +76,14 @@ class _WindowContainerState extends ConsumerState<WindowManager>
final size = await windowManager.getSize(); final size = await windowManager.getSize();
ref ref
.read(windowSettingProvider.notifier) .read(windowSettingProvider.notifier)
.updateState( .update(
(state) => state.copyWith(width: size.width, height: size.height), (state) => state.copyWith(width: size.width, height: size.height),
); );
} }
@override @override
void onWindowMinimize() async { void onWindowMinimize() async {
globalState.appController.savePreferencesDebounce(); appController.savePreferencesDebounce();
commonPrint.log('minimize'); commonPrint.log('minimize');
render?.pause(); render?.pause();
super.onWindowMinimize(); super.onWindowMinimize();
@@ -222,7 +220,7 @@ class _WindowHeaderState extends State<WindowHeader> {
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {
globalState.appController.handleBackOrExit(); appController.handleBackOrExit();
}, },
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
), ),

View File

@@ -1,6 +1,5 @@
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/selector.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@@ -36,10 +35,6 @@ abstract class AppState with _$AppState {
@Default(false) bool realTunEnable, @Default(false) bool realTunEnable,
@Default(false) bool loading, @Default(false) bool loading,
required SystemUiOverlayStyle systemUiOverlayStyle, required SystemUiOverlayStyle systemUiOverlayStyle,
ProfileOverrideModel? profileOverrideModel,
@Default({}) Map<QueryTag, String> queryMap,
@Default({}) Map<String, String> selectedItemMap,
@Default({}) Map<String, Set<String>> selectedItemsMap,
@Default(CoreStatus.connecting) CoreStatus coreStatus, @Default(CoreStatus.connecting) CoreStatus coreStatus,
}) = _AppState; }) = _AppState;
} }

View File

@@ -375,17 +375,18 @@ extension ParsedRuleExt on ParsedRule {
@freezed @freezed
abstract class Rule with _$Rule { abstract class Rule with _$Rule {
const factory Rule({required String id, required String value}) = _Rule; const factory Rule({required int id, required String value, String? order}) =
_Rule;
factory Rule.value(String value) { factory Rule.value(String value) {
return Rule(value: value, id: utils.uuidV4); return Rule(value: value, id: snowflake.id);
} }
factory Rule.fromJson(Map<String, Object?> json) => _$RuleFromJson(json); factory Rule.fromJson(Map<String, Object?> json) => _$RuleFromJson(json);
} }
extension RulesExt on List<Rule> { extension RulesExt on List<Rule> {
List<Rule> updateWith(Rule rule) { List<Rule> copyAndPut(Rule rule) {
var newList = List<Rule>.from(this); var newList = List<Rule>.from(this);
final index = newList.indexWhere((item) => item.id == rule.id); final index = newList.indexWhere((item) => item.id == rule.id);
if (index != -1) { if (index != -1) {

View File

@@ -1,3 +1,5 @@
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';
@@ -215,15 +217,16 @@ extension TrackerInfosStateExt on TrackerInfosState {
const defaultDavFileName = 'backup.zip'; const defaultDavFileName = 'backup.zip';
@freezed @freezed
abstract class DAV with _$DAV { abstract class DAVProps with _$DAVProps {
const factory DAV({ const factory DAVProps({
required String uri, required String uri,
required String user, required String user,
required String password, required String password,
@Default(defaultDavFileName) String fileName, @Default(defaultDavFileName) String fileName,
}) = _DAV; }) = _DAVProps;
factory DAV.fromJson(Map<String, Object?> json) => _$DAVFromJson(json); factory DAVProps.fromJson(Map<String, Object?> json) =>
_$DAVPropsFromJson(json);
} }
@freezed @freezed
@@ -471,19 +474,6 @@ class PopupMenuItemData {
final List<PopupMenuItemData> subItems; final List<PopupMenuItemData> subItems;
} }
@freezed
abstract class AndroidState with _$AndroidState {
const factory AndroidState({
required String currentProfileName,
required String stopText,
required bool onlyStatisticsProxy,
required bool crashlytics,
}) = _AndroidState;
factory AndroidState.fromJson(Map<String, Object?> json) =>
_$AndroidStateFromJson(json);
}
class CloseWindowIntent extends Intent { class CloseWindowIntent extends Intent {
const CloseWindowIntent(); const CloseWindowIntent();
} }
@@ -512,20 +502,24 @@ extension ResultExt on Result {
@freezed @freezed
abstract class Script with _$Script { abstract class Script with _$Script {
const factory Script({ const factory Script({
required String id, required int id,
required String label, required String label,
required String content, required DateTime lastUpdateTime,
}) = _Script; }) = _Script;
factory Script.create({required String label, required String content}) {
return Script(id: utils.uuidV4, label: label, content: content);
}
factory Script.fromJson(Map<String, Object?> json) => _$ScriptFromJson(json); factory Script.fromJson(Map<String, Object?> json) => _$ScriptFromJson(json);
factory Script.create({required String label}) {
return Script(
id: snowflake.id,
label: label,
lastUpdateTime: DateTime.now(),
);
}
} }
extension ScriptsExt on List<Script> { extension ScriptsExt on List<Script> {
Script? get(String? id) { Script? get(int? id) {
if (id == null) { if (id == null) {
return null; return null;
} }
@@ -537,6 +531,38 @@ extension ScriptsExt on List<Script> {
} }
} }
extension ScriptExt on Script {
String get fileName => '$id.js';
Future<String> get path async => await appPath.getScriptPath(id.toString());
Future<String?> get content async {
final file = File(await path);
if (await file.exists()) {
return file.readAsString();
}
return null;
}
Future<Script> save(String content) async {
final file = File(await path);
if (!await file.exists()) {
await file.create(recursive: true);
}
await file.writeAsString(content);
return copyWith(lastUpdateTime: DateTime.now());
}
Future<Script> saveWithPath(String copyPath) async {
final file = File(await path);
if (!await file.exists()) {
await file.create(recursive: true);
}
await File(copyPath).copy(copyPath);
return copyWith(lastUpdateTime: DateTime.now());
}
}
@freezed @freezed
abstract class DelayState with _$DelayState { abstract class DelayState with _$DelayState {
const factory DelayState({required int delay, required bool group}) = const factory DelayState({required int delay, required bool group}) =
@@ -562,3 +588,11 @@ extension DelayStateExt on DelayState {
return 0; return 0;
} }
} }
@freezed
abstract class UpdatingMessage with _$UpdatingMessage {
const factory UpdatingMessage({
required String label,
required String message,
}) = _UpdatingMessage;
}

View File

@@ -31,9 +31,9 @@ const defaultBypassDomain = [
const defaultAppSettingProps = AppSettingProps(); const defaultAppSettingProps = AppSettingProps();
const defaultVpnProps = VpnProps(); const defaultVpnProps = VpnProps();
const defaultNetworkProps = NetworkProps(); const defaultNetworkProps = NetworkProps();
const defaultProxiesStyle = ProxiesStyle(); const defaultProxiesStyleProps = ProxiesStyleProps();
const defaultWindowProps = WindowProps(); const defaultWindowProps = WindowProps();
const defaultAccessControl = AccessControl(); const defaultAccessControlProps = AccessControlProps();
final defaultThemeProps = ThemeProps(primaryColor: defaultPrimaryColor); final defaultThemeProps = ThemeProps(primaryColor: defaultPrimaryColor);
const List<DashboardWidget> defaultDashboardWidgets = [ const List<DashboardWidget> defaultDashboardWidgets = [
@@ -82,7 +82,7 @@ abstract class AppSettingProps with _$AppSettingProps {
@Default(true) bool minimizeOnExit, @Default(true) bool minimizeOnExit,
@Default(false) bool hidden, @Default(false) bool hidden,
@Default(false) bool developerMode, @Default(false) bool developerMode,
@Default(RecoveryStrategy.compatible) RecoveryStrategy recoveryStrategy, @Default(RestoreStrategy.compatible) RestoreStrategy restoreStrategy,
@Default(true) bool showTrayTitle, @Default(true) bool showTrayTitle,
}) = _AppSettingProps; }) = _AppSettingProps;
@@ -97,8 +97,8 @@ abstract class AppSettingProps with _$AppSettingProps {
} }
@freezed @freezed
abstract class AccessControl with _$AccessControl { abstract class AccessControlProps with _$AccessControlProps {
const factory AccessControl({ const factory AccessControlProps({
@Default(false) bool enable, @Default(false) bool enable,
@Default(AccessControlMode.rejectSelected) AccessControlMode mode, @Default(AccessControlMode.rejectSelected) AccessControlMode mode,
@Default([]) List<String> acceptList, @Default([]) List<String> acceptList,
@@ -106,19 +106,19 @@ abstract class AccessControl with _$AccessControl {
@Default(AccessSortType.none) AccessSortType sort, @Default(AccessSortType.none) AccessSortType sort,
@Default(true) bool isFilterSystemApp, @Default(true) bool isFilterSystemApp,
@Default(true) bool isFilterNonInternetApp, @Default(true) bool isFilterNonInternetApp,
}) = _AccessControl; }) = _AccessControlProps;
factory AccessControl.fromJson(Map<String, Object?> json) => factory AccessControlProps.fromJson(Map<String, Object?> json) =>
_$AccessControlFromJson(json); _$AccessControlPropsFromJson(json);
} }
extension AccessControlExt on AccessControl { extension AccessControlPropsExt on AccessControlProps {
List<String> get currentList => switch (mode) { List<String> get currentList => switch (mode) {
AccessControlMode.acceptSelected => acceptList, AccessControlMode.acceptSelected => acceptList,
AccessControlMode.rejectSelected => rejectList, AccessControlMode.rejectSelected => rejectList,
}; };
AccessControl copyWithNewList(List<String> value) => switch (mode) { AccessControlProps copyWithNewList(List<String> value) => switch (mode) {
AccessControlMode.acceptSelected => copyWith(acceptList: value), AccessControlMode.acceptSelected => copyWith(acceptList: value),
AccessControlMode.rejectSelected => copyWith(rejectList: value), AccessControlMode.rejectSelected => copyWith(rejectList: value),
}; };
@@ -151,7 +151,7 @@ abstract class VpnProps with _$VpnProps {
@Default(false) bool ipv6, @Default(false) bool ipv6,
@Default(true) bool allowBypass, @Default(true) bool allowBypass,
@Default(false) bool dnsHijacking, @Default(false) bool dnsHijacking,
@Default(defaultAccessControl) AccessControl accessControl, @Default(defaultAccessControlProps) AccessControlProps accessControlProps,
}) = _VpnProps; }) = _VpnProps;
factory VpnProps.fromJson(Map<String, Object?>? json) => factory VpnProps.fromJson(Map<String, Object?>? json) =>
@@ -173,17 +173,18 @@ abstract class NetworkProps with _$NetworkProps {
} }
@freezed @freezed
abstract class ProxiesStyle with _$ProxiesStyle { abstract class ProxiesStyleProps with _$ProxiesStyleProps {
const factory ProxiesStyle({ const factory ProxiesStyleProps({
@Default(ProxiesType.tab) ProxiesType type, @Default(ProxiesType.tab) ProxiesType type,
@Default(ProxiesSortType.none) ProxiesSortType sortType, @Default(ProxiesSortType.none) ProxiesSortType sortType,
@Default(ProxiesLayout.standard) ProxiesLayout layout, @Default(ProxiesLayout.standard) ProxiesLayout layout,
@Default(ProxiesIconStyle.standard) ProxiesIconStyle iconStyle, @Default(ProxiesIconStyle.standard) ProxiesIconStyle iconStyle,
@Default(ProxyCardType.expand) ProxyCardType cardType, @Default(ProxyCardType.expand) ProxyCardType cardType,
}) = _ProxiesStyle; }) = _ProxiesStyleProps;
factory ProxiesStyle.fromJson(Map<String, Object?>? json) => factory ProxiesStyleProps.fromJson(Map<String, Object?>? json) => json == null
json == null ? defaultProxiesStyle : _$ProxiesStyleFromJson(json); ? defaultProxiesStyleProps
: _$ProxiesStylePropsFromJson(json);
} }
@freezed @freezed
@@ -223,63 +224,30 @@ abstract class ThemeProps with _$ThemeProps {
} }
} }
@freezed
abstract class ScriptProps with _$ScriptProps {
const factory ScriptProps({
String? currentId,
@Default([]) List<Script> scripts,
}) = _ScriptProps;
factory ScriptProps.fromJson(Map<String, Object?> json) =>
_$ScriptPropsFromJson(json);
}
@freezed @freezed
abstract class Config with _$Config { abstract class Config with _$Config {
const factory Config({ const factory Config({
int? currentProfileId,
@Default(false) bool overrideDns,
@Default([]) List<HotKeyAction> hotKeyActions,
@JsonKey(fromJson: AppSettingProps.safeFromJson) @JsonKey(fromJson: AppSettingProps.safeFromJson)
@Default(defaultAppSettingProps) @Default(defaultAppSettingProps)
AppSettingProps appSetting, AppSettingProps appSettingProps,
@Default([]) List<Profile> profiles, DAVProps? davProps,
@Default([]) List<HotKeyAction> hotKeyActions,
String? currentProfileId,
@Default(false) bool overrideDns,
DAV? dav,
@Default(defaultNetworkProps) NetworkProps networkProps, @Default(defaultNetworkProps) NetworkProps networkProps,
@Default(defaultVpnProps) VpnProps vpnProps, @Default(defaultVpnProps) VpnProps vpnProps,
@JsonKey(fromJson: ThemeProps.safeFromJson) required ThemeProps themeProps, @JsonKey(fromJson: ThemeProps.safeFromJson) required ThemeProps themeProps,
@Default(defaultProxiesStyle) ProxiesStyle proxiesStyle, @Default(defaultProxiesStyleProps) ProxiesStyleProps proxiesStyleProps,
@Default(defaultWindowProps) WindowProps windowProps, @Default(defaultWindowProps) WindowProps windowProps,
@Default(defaultClashConfig) ClashConfig patchClashConfig, @Default(defaultClashConfig) ClashConfig patchClashConfig,
@Default([]) List<Script> scripts,
@Default([]) List<Rule> rules,
}) = _Config; }) = _Config;
factory Config.fromJson(Map<String, Object?> json) => _$ConfigFromJson(json); factory Config.fromJson(Map<String, Object?> json) => _$ConfigFromJson(json);
factory Config.compatibleFromJson(Map<String, Object?> json) { factory Config.realFromJson(Map<String, Object?>? json) {
try { if (json == null) {
final accessControlMap = json['accessControl']; return Config(themeProps: defaultThemeProps);
final isAccessControl = json['isAccessControl']; }
if (accessControlMap != null) { return _$ConfigFromJson(json);
(accessControlMap as Map)['enable'] = isAccessControl;
if (json['vpnProps'] != null) {
(json['vpnProps'] as Map)['accessControl'] = accessControlMap;
}
}
if (json['scripts'] == null) {
final scriptPropsJson = json['scriptProps'] as Map<String, Object?>?;
if (scriptPropsJson != null) {
json['scripts'] = scriptPropsJson['scripts'];
}
}
} catch (_) {}
return Config.fromJson(json);
}
}
extension ConfigExt on Config {
Profile? get currentProfile {
return profiles.getProfile(currentProfileId);
} }
} }

View File

@@ -44,7 +44,7 @@ abstract class VpnOptions with _$VpnOptions {
required int port, required int port,
required bool ipv6, required bool ipv6,
required bool dnsHijacking, required bool dnsHijacking,
required AccessControl accessControl, required AccessControlProps accessControlProps,
required bool allowBypass, required bool allowBypass,
required bool systemProxy, required bool systemProxy,
required List<String> bypassDomain, required List<String> bypassDomain,
@@ -154,7 +154,6 @@ abstract class ExternalProvider with _$ExternalProvider {
required int count, required int count,
@JsonKey(name: 'subscription-info', fromJson: subscriptionInfoFormCore) @JsonKey(name: 'subscription-info', fromJson: subscriptionInfoFormCore)
SubscriptionInfo? subscriptionInfo, SubscriptionInfo? subscriptionInfo,
@Default(false) bool isUpdating,
@JsonKey(name: 'vehicle-type') required String vehicleType, @JsonKey(name: 'vehicle-type') required String vehicleType,
@JsonKey(name: 'update-at') required DateTime updateAt, @JsonKey(name: 'update-at') required DateTime updateAt,
}) = _ExternalProvider; }) = _ExternalProvider;
@@ -163,6 +162,10 @@ abstract class ExternalProvider with _$ExternalProvider {
_$ExternalProviderFromJson(json); _$ExternalProviderFromJson(json);
} }
extension ExternalProviderExt on ExternalProvider {
String get updatingKey => 'provider_$name';
}
@freezed @freezed
abstract class Action with _$Action { abstract class Action with _$Action {
const factory Action({ const factory Action({
@@ -174,6 +177,17 @@ abstract class Action with _$Action {
factory Action.fromJson(Map<String, Object?> json) => _$ActionFromJson(json); factory Action.fromJson(Map<String, Object?> json) => _$ActionFromJson(json);
} }
@freezed
abstract class ProxiesData with _$ProxiesData {
const factory ProxiesData({
required Map<String, dynamic> proxies,
required List<String> all,
}) = _ProxiesData;
factory ProxiesData.fromJson(Map<String, Object?> json) =>
_$ProxiesDataFromJson(json);
}
@freezed @freezed
abstract class ActionResult with _$ActionResult { abstract class ActionResult with _$ActionResult {
const factory ActionResult({ const factory ActionResult({

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc /// @nodoc
mixin _$AppState { mixin _$AppState {
bool get isInit; bool get backBlock; PageLabel get pageLabel; List<Package> get packages; int get sortNum; Size get viewSize; double get sideWidth; DelayMap get delayMap; List<Group> get groups; int get checkIpNum; Brightness get brightness; int? get runTime; List<ExternalProvider> get providers; String? get localIp; FixedList<TrackerInfo> get requests; int get version; FixedList<Log> get logs; FixedList<Traffic> get traffics; Traffic get totalTraffic; bool get realTunEnable; bool get loading; SystemUiOverlayStyle get systemUiOverlayStyle; ProfileOverrideModel? get profileOverrideModel; Map<QueryTag, String> get queryMap; Map<String, String> get selectedItemMap; Map<String, Set<String>> get selectedItemsMap; CoreStatus get coreStatus; bool get isInit; bool get backBlock; PageLabel get pageLabel; List<Package> get packages; int get sortNum; Size get viewSize; double get sideWidth; DelayMap get delayMap; List<Group> get groups; int get checkIpNum; Brightness get brightness; int? get runTime; List<ExternalProvider> get providers; String? get localIp; FixedList<TrackerInfo> get requests; int get version; FixedList<Log> get logs; FixedList<Traffic> get traffics; Traffic get totalTraffic; bool get realTunEnable; bool get loading; SystemUiOverlayStyle get systemUiOverlayStyle; CoreStatus get coreStatus;
/// Create a copy of AppState /// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $AppStateCopyWith<AppState> get copyWith => _$AppStateCopyWithImpl<AppState>(thi
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&(identical(other.isInit, isInit) || other.isInit == isInit)&&(identical(other.backBlock, backBlock) || other.backBlock == backBlock)&&(identical(other.pageLabel, pageLabel) || other.pageLabel == pageLabel)&&const DeepCollectionEquality().equals(other.packages, packages)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.viewSize, viewSize) || other.viewSize == viewSize)&&(identical(other.sideWidth, sideWidth) || other.sideWidth == sideWidth)&&const DeepCollectionEquality().equals(other.delayMap, delayMap)&&const DeepCollectionEquality().equals(other.groups, groups)&&(identical(other.checkIpNum, checkIpNum) || other.checkIpNum == checkIpNum)&&(identical(other.brightness, brightness) || other.brightness == brightness)&&(identical(other.runTime, runTime) || other.runTime == runTime)&&const DeepCollectionEquality().equals(other.providers, providers)&&(identical(other.localIp, localIp) || other.localIp == localIp)&&(identical(other.requests, requests) || other.requests == requests)&&(identical(other.version, version) || other.version == version)&&(identical(other.logs, logs) || other.logs == logs)&&(identical(other.traffics, traffics) || other.traffics == traffics)&&(identical(other.totalTraffic, totalTraffic) || other.totalTraffic == totalTraffic)&&(identical(other.realTunEnable, realTunEnable) || other.realTunEnable == realTunEnable)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.systemUiOverlayStyle, systemUiOverlayStyle) || other.systemUiOverlayStyle == systemUiOverlayStyle)&&(identical(other.profileOverrideModel, profileOverrideModel) || other.profileOverrideModel == profileOverrideModel)&&const DeepCollectionEquality().equals(other.queryMap, queryMap)&&const DeepCollectionEquality().equals(other.selectedItemMap, selectedItemMap)&&const DeepCollectionEquality().equals(other.selectedItemsMap, selectedItemsMap)&&(identical(other.coreStatus, coreStatus) || other.coreStatus == coreStatus)); return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&(identical(other.isInit, isInit) || other.isInit == isInit)&&(identical(other.backBlock, backBlock) || other.backBlock == backBlock)&&(identical(other.pageLabel, pageLabel) || other.pageLabel == pageLabel)&&const DeepCollectionEquality().equals(other.packages, packages)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.viewSize, viewSize) || other.viewSize == viewSize)&&(identical(other.sideWidth, sideWidth) || other.sideWidth == sideWidth)&&const DeepCollectionEquality().equals(other.delayMap, delayMap)&&const DeepCollectionEquality().equals(other.groups, groups)&&(identical(other.checkIpNum, checkIpNum) || other.checkIpNum == checkIpNum)&&(identical(other.brightness, brightness) || other.brightness == brightness)&&(identical(other.runTime, runTime) || other.runTime == runTime)&&const DeepCollectionEquality().equals(other.providers, providers)&&(identical(other.localIp, localIp) || other.localIp == localIp)&&(identical(other.requests, requests) || other.requests == requests)&&(identical(other.version, version) || other.version == version)&&(identical(other.logs, logs) || other.logs == logs)&&(identical(other.traffics, traffics) || other.traffics == traffics)&&(identical(other.totalTraffic, totalTraffic) || other.totalTraffic == totalTraffic)&&(identical(other.realTunEnable, realTunEnable) || other.realTunEnable == realTunEnable)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.systemUiOverlayStyle, systemUiOverlayStyle) || other.systemUiOverlayStyle == systemUiOverlayStyle)&&(identical(other.coreStatus, coreStatus) || other.coreStatus == coreStatus));
} }
@override @override
int get hashCode => Object.hashAll([runtimeType,isInit,backBlock,pageLabel,const DeepCollectionEquality().hash(packages),sortNum,viewSize,sideWidth,const DeepCollectionEquality().hash(delayMap),const DeepCollectionEquality().hash(groups),checkIpNum,brightness,runTime,const DeepCollectionEquality().hash(providers),localIp,requests,version,logs,traffics,totalTraffic,realTunEnable,loading,systemUiOverlayStyle,profileOverrideModel,const DeepCollectionEquality().hash(queryMap),const DeepCollectionEquality().hash(selectedItemMap),const DeepCollectionEquality().hash(selectedItemsMap),coreStatus]); int get hashCode => Object.hashAll([runtimeType,isInit,backBlock,pageLabel,const DeepCollectionEquality().hash(packages),sortNum,viewSize,sideWidth,const DeepCollectionEquality().hash(delayMap),const DeepCollectionEquality().hash(groups),checkIpNum,brightness,runTime,const DeepCollectionEquality().hash(providers),localIp,requests,version,logs,traffics,totalTraffic,realTunEnable,loading,systemUiOverlayStyle,coreStatus]);
@override @override
String toString() { String toString() {
return 'AppState(isInit: $isInit, backBlock: $backBlock, pageLabel: $pageLabel, packages: $packages, sortNum: $sortNum, viewSize: $viewSize, sideWidth: $sideWidth, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, realTunEnable: $realTunEnable, loading: $loading, systemUiOverlayStyle: $systemUiOverlayStyle, profileOverrideModel: $profileOverrideModel, queryMap: $queryMap, selectedItemMap: $selectedItemMap, selectedItemsMap: $selectedItemsMap, coreStatus: $coreStatus)'; return 'AppState(isInit: $isInit, backBlock: $backBlock, pageLabel: $pageLabel, packages: $packages, sortNum: $sortNum, viewSize: $viewSize, sideWidth: $sideWidth, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, realTunEnable: $realTunEnable, loading: $loading, systemUiOverlayStyle: $systemUiOverlayStyle, coreStatus: $coreStatus)';
} }
@@ -45,11 +45,11 @@ abstract mixin class $AppStateCopyWith<$Res> {
factory $AppStateCopyWith(AppState value, $Res Function(AppState) _then) = _$AppStateCopyWithImpl; factory $AppStateCopyWith(AppState value, $Res Function(AppState) _then) = _$AppStateCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, ProfileOverrideModel? profileOverrideModel, Map<QueryTag, String> queryMap, Map<String, String> selectedItemMap, Map<String, Set<String>> selectedItemsMap, CoreStatus coreStatus bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus
}); });
$TrafficCopyWith<$Res> get totalTraffic;$ProfileOverrideModelCopyWith<$Res>? get profileOverrideModel; $TrafficCopyWith<$Res> get totalTraffic;
} }
/// @nodoc /// @nodoc
@@ -62,7 +62,7 @@ class _$AppStateCopyWithImpl<$Res>
/// Create a copy of AppState /// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? isInit = null,Object? backBlock = null,Object? pageLabel = null,Object? packages = null,Object? sortNum = null,Object? viewSize = null,Object? sideWidth = null,Object? delayMap = null,Object? groups = null,Object? checkIpNum = null,Object? brightness = null,Object? runTime = freezed,Object? providers = null,Object? localIp = freezed,Object? requests = null,Object? version = null,Object? logs = null,Object? traffics = null,Object? totalTraffic = null,Object? realTunEnable = null,Object? loading = null,Object? systemUiOverlayStyle = null,Object? profileOverrideModel = freezed,Object? queryMap = null,Object? selectedItemMap = null,Object? selectedItemsMap = null,Object? coreStatus = null,}) { @pragma('vm:prefer-inline') @override $Res call({Object? isInit = null,Object? backBlock = null,Object? pageLabel = null,Object? packages = null,Object? sortNum = null,Object? viewSize = null,Object? sideWidth = null,Object? delayMap = null,Object? groups = null,Object? checkIpNum = null,Object? brightness = null,Object? runTime = freezed,Object? providers = null,Object? localIp = freezed,Object? requests = null,Object? version = null,Object? logs = null,Object? traffics = null,Object? totalTraffic = null,Object? realTunEnable = null,Object? loading = null,Object? systemUiOverlayStyle = null,Object? coreStatus = null,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
isInit: null == isInit ? _self.isInit : isInit // ignore: cast_nullable_to_non_nullable isInit: null == isInit ? _self.isInit : isInit // ignore: cast_nullable_to_non_nullable
as bool,backBlock: null == backBlock ? _self.backBlock : backBlock // ignore: cast_nullable_to_non_nullable as bool,backBlock: null == backBlock ? _self.backBlock : backBlock // ignore: cast_nullable_to_non_nullable
@@ -86,11 +86,7 @@ as FixedList<Traffic>,totalTraffic: null == totalTraffic ? _self.totalTraffic :
as Traffic,realTunEnable: null == realTunEnable ? _self.realTunEnable : realTunEnable // ignore: cast_nullable_to_non_nullable as Traffic,realTunEnable: null == realTunEnable ? _self.realTunEnable : realTunEnable // ignore: cast_nullable_to_non_nullable
as bool,loading: null == loading ? _self.loading : loading // ignore: cast_nullable_to_non_nullable as bool,loading: null == loading ? _self.loading : loading // ignore: cast_nullable_to_non_nullable
as bool,systemUiOverlayStyle: null == systemUiOverlayStyle ? _self.systemUiOverlayStyle : systemUiOverlayStyle // ignore: cast_nullable_to_non_nullable as bool,systemUiOverlayStyle: null == systemUiOverlayStyle ? _self.systemUiOverlayStyle : systemUiOverlayStyle // ignore: cast_nullable_to_non_nullable
as SystemUiOverlayStyle,profileOverrideModel: freezed == profileOverrideModel ? _self.profileOverrideModel : profileOverrideModel // ignore: cast_nullable_to_non_nullable as SystemUiOverlayStyle,coreStatus: null == coreStatus ? _self.coreStatus : coreStatus // ignore: cast_nullable_to_non_nullable
as ProfileOverrideModel?,queryMap: null == queryMap ? _self.queryMap : queryMap // ignore: cast_nullable_to_non_nullable
as Map<QueryTag, String>,selectedItemMap: null == selectedItemMap ? _self.selectedItemMap : selectedItemMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>,selectedItemsMap: null == selectedItemsMap ? _self.selectedItemsMap : selectedItemsMap // ignore: cast_nullable_to_non_nullable
as Map<String, Set<String>>,coreStatus: null == coreStatus ? _self.coreStatus : coreStatus // ignore: cast_nullable_to_non_nullable
as CoreStatus, as CoreStatus,
)); ));
} }
@@ -103,18 +99,6 @@ $TrafficCopyWith<$Res> get totalTraffic {
return $TrafficCopyWith<$Res>(_self.totalTraffic, (value) { return $TrafficCopyWith<$Res>(_self.totalTraffic, (value) {
return _then(_self.copyWith(totalTraffic: value)); return _then(_self.copyWith(totalTraffic: value));
}); });
}/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ProfileOverrideModelCopyWith<$Res>? get profileOverrideModel {
if (_self.profileOverrideModel == null) {
return null;
}
return $ProfileOverrideModelCopyWith<$Res>(_self.profileOverrideModel!, (value) {
return _then(_self.copyWith(profileOverrideModel: value));
});
} }
} }
@@ -197,10 +181,10 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, ProfileOverrideModel? profileOverrideModel, Map<QueryTag, String> queryMap, Map<String, String> selectedItemMap, Map<String, Set<String>> selectedItemsMap, CoreStatus coreStatus)? $default,{required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _AppState() when $default != null: case _AppState() when $default != null:
return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.realTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.profileOverrideModel,_that.queryMap,_that.selectedItemMap,_that.selectedItemsMap,_that.coreStatus);case _: return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.realTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.coreStatus);case _:
return orElse(); return orElse();
} }
@@ -218,10 +202,10 @@ return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_tha
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, ProfileOverrideModel? profileOverrideModel, Map<QueryTag, String> queryMap, Map<String, String> selectedItemMap, Map<String, Set<String>> selectedItemsMap, CoreStatus coreStatus) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _AppState(): case _AppState():
return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.realTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.profileOverrideModel,_that.queryMap,_that.selectedItemMap,_that.selectedItemsMap,_that.coreStatus);case _: return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.realTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.coreStatus);case _:
throw StateError('Unexpected subclass'); throw StateError('Unexpected subclass');
} }
@@ -238,10 +222,10 @@ return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_tha
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, ProfileOverrideModel? profileOverrideModel, Map<QueryTag, String> queryMap, Map<String, String> selectedItemMap, Map<String, Set<String>> selectedItemsMap, CoreStatus coreStatus)? $default,) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus)? $default,) {final _that = this;
switch (_that) { switch (_that) {
case _AppState() when $default != null: case _AppState() when $default != null:
return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.realTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.profileOverrideModel,_that.queryMap,_that.selectedItemMap,_that.selectedItemsMap,_that.coreStatus);case _: return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.realTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.coreStatus);case _:
return null; return null;
} }
@@ -253,7 +237,7 @@ return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_tha
class _AppState implements AppState { class _AppState implements AppState {
const _AppState({this.isInit = false, this.backBlock = false, this.pageLabel = PageLabel.dashboard, final List<Package> packages = const [], this.sortNum = 0, required this.viewSize, this.sideWidth = 0, final DelayMap delayMap = const {}, final List<Group> groups = const [], this.checkIpNum = 0, required this.brightness, this.runTime, final List<ExternalProvider> providers = const [], this.localIp, required this.requests, required this.version, required this.logs, required this.traffics, required this.totalTraffic, this.realTunEnable = false, this.loading = false, required this.systemUiOverlayStyle, this.profileOverrideModel, final Map<QueryTag, String> queryMap = const {}, final Map<String, String> selectedItemMap = const {}, final Map<String, Set<String>> selectedItemsMap = const {}, this.coreStatus = CoreStatus.connecting}): _packages = packages,_delayMap = delayMap,_groups = groups,_providers = providers,_queryMap = queryMap,_selectedItemMap = selectedItemMap,_selectedItemsMap = selectedItemsMap; const _AppState({this.isInit = false, this.backBlock = false, this.pageLabel = PageLabel.dashboard, final List<Package> packages = const [], this.sortNum = 0, required this.viewSize, this.sideWidth = 0, final DelayMap delayMap = const {}, final List<Group> groups = const [], this.checkIpNum = 0, required this.brightness, this.runTime, final List<ExternalProvider> providers = const [], this.localIp, required this.requests, required this.version, required this.logs, required this.traffics, required this.totalTraffic, this.realTunEnable = false, this.loading = false, required this.systemUiOverlayStyle, this.coreStatus = CoreStatus.connecting}): _packages = packages,_delayMap = delayMap,_groups = groups,_providers = providers;
@override@JsonKey() final bool isInit; @override@JsonKey() final bool isInit;
@@ -302,28 +286,6 @@ class _AppState implements AppState {
@override@JsonKey() final bool realTunEnable; @override@JsonKey() final bool realTunEnable;
@override@JsonKey() final bool loading; @override@JsonKey() final bool loading;
@override final SystemUiOverlayStyle systemUiOverlayStyle; @override final SystemUiOverlayStyle systemUiOverlayStyle;
@override final ProfileOverrideModel? profileOverrideModel;
final Map<QueryTag, String> _queryMap;
@override@JsonKey() Map<QueryTag, String> get queryMap {
if (_queryMap is EqualUnmodifiableMapView) return _queryMap;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_queryMap);
}
final Map<String, String> _selectedItemMap;
@override@JsonKey() Map<String, String> get selectedItemMap {
if (_selectedItemMap is EqualUnmodifiableMapView) return _selectedItemMap;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_selectedItemMap);
}
final Map<String, Set<String>> _selectedItemsMap;
@override@JsonKey() Map<String, Set<String>> get selectedItemsMap {
if (_selectedItemsMap is EqualUnmodifiableMapView) return _selectedItemsMap;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_selectedItemsMap);
}
@override@JsonKey() final CoreStatus coreStatus; @override@JsonKey() final CoreStatus coreStatus;
/// Create a copy of AppState /// Create a copy of AppState
@@ -336,16 +298,16 @@ _$AppStateCopyWith<_AppState> get copyWith => __$AppStateCopyWithImpl<_AppState>
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&(identical(other.isInit, isInit) || other.isInit == isInit)&&(identical(other.backBlock, backBlock) || other.backBlock == backBlock)&&(identical(other.pageLabel, pageLabel) || other.pageLabel == pageLabel)&&const DeepCollectionEquality().equals(other._packages, _packages)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.viewSize, viewSize) || other.viewSize == viewSize)&&(identical(other.sideWidth, sideWidth) || other.sideWidth == sideWidth)&&const DeepCollectionEquality().equals(other._delayMap, _delayMap)&&const DeepCollectionEquality().equals(other._groups, _groups)&&(identical(other.checkIpNum, checkIpNum) || other.checkIpNum == checkIpNum)&&(identical(other.brightness, brightness) || other.brightness == brightness)&&(identical(other.runTime, runTime) || other.runTime == runTime)&&const DeepCollectionEquality().equals(other._providers, _providers)&&(identical(other.localIp, localIp) || other.localIp == localIp)&&(identical(other.requests, requests) || other.requests == requests)&&(identical(other.version, version) || other.version == version)&&(identical(other.logs, logs) || other.logs == logs)&&(identical(other.traffics, traffics) || other.traffics == traffics)&&(identical(other.totalTraffic, totalTraffic) || other.totalTraffic == totalTraffic)&&(identical(other.realTunEnable, realTunEnable) || other.realTunEnable == realTunEnable)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.systemUiOverlayStyle, systemUiOverlayStyle) || other.systemUiOverlayStyle == systemUiOverlayStyle)&&(identical(other.profileOverrideModel, profileOverrideModel) || other.profileOverrideModel == profileOverrideModel)&&const DeepCollectionEquality().equals(other._queryMap, _queryMap)&&const DeepCollectionEquality().equals(other._selectedItemMap, _selectedItemMap)&&const DeepCollectionEquality().equals(other._selectedItemsMap, _selectedItemsMap)&&(identical(other.coreStatus, coreStatus) || other.coreStatus == coreStatus)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&(identical(other.isInit, isInit) || other.isInit == isInit)&&(identical(other.backBlock, backBlock) || other.backBlock == backBlock)&&(identical(other.pageLabel, pageLabel) || other.pageLabel == pageLabel)&&const DeepCollectionEquality().equals(other._packages, _packages)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.viewSize, viewSize) || other.viewSize == viewSize)&&(identical(other.sideWidth, sideWidth) || other.sideWidth == sideWidth)&&const DeepCollectionEquality().equals(other._delayMap, _delayMap)&&const DeepCollectionEquality().equals(other._groups, _groups)&&(identical(other.checkIpNum, checkIpNum) || other.checkIpNum == checkIpNum)&&(identical(other.brightness, brightness) || other.brightness == brightness)&&(identical(other.runTime, runTime) || other.runTime == runTime)&&const DeepCollectionEquality().equals(other._providers, _providers)&&(identical(other.localIp, localIp) || other.localIp == localIp)&&(identical(other.requests, requests) || other.requests == requests)&&(identical(other.version, version) || other.version == version)&&(identical(other.logs, logs) || other.logs == logs)&&(identical(other.traffics, traffics) || other.traffics == traffics)&&(identical(other.totalTraffic, totalTraffic) || other.totalTraffic == totalTraffic)&&(identical(other.realTunEnable, realTunEnable) || other.realTunEnable == realTunEnable)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.systemUiOverlayStyle, systemUiOverlayStyle) || other.systemUiOverlayStyle == systemUiOverlayStyle)&&(identical(other.coreStatus, coreStatus) || other.coreStatus == coreStatus));
} }
@override @override
int get hashCode => Object.hashAll([runtimeType,isInit,backBlock,pageLabel,const DeepCollectionEquality().hash(_packages),sortNum,viewSize,sideWidth,const DeepCollectionEquality().hash(_delayMap),const DeepCollectionEquality().hash(_groups),checkIpNum,brightness,runTime,const DeepCollectionEquality().hash(_providers),localIp,requests,version,logs,traffics,totalTraffic,realTunEnable,loading,systemUiOverlayStyle,profileOverrideModel,const DeepCollectionEquality().hash(_queryMap),const DeepCollectionEquality().hash(_selectedItemMap),const DeepCollectionEquality().hash(_selectedItemsMap),coreStatus]); int get hashCode => Object.hashAll([runtimeType,isInit,backBlock,pageLabel,const DeepCollectionEquality().hash(_packages),sortNum,viewSize,sideWidth,const DeepCollectionEquality().hash(_delayMap),const DeepCollectionEquality().hash(_groups),checkIpNum,brightness,runTime,const DeepCollectionEquality().hash(_providers),localIp,requests,version,logs,traffics,totalTraffic,realTunEnable,loading,systemUiOverlayStyle,coreStatus]);
@override @override
String toString() { String toString() {
return 'AppState(isInit: $isInit, backBlock: $backBlock, pageLabel: $pageLabel, packages: $packages, sortNum: $sortNum, viewSize: $viewSize, sideWidth: $sideWidth, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, realTunEnable: $realTunEnable, loading: $loading, systemUiOverlayStyle: $systemUiOverlayStyle, profileOverrideModel: $profileOverrideModel, queryMap: $queryMap, selectedItemMap: $selectedItemMap, selectedItemsMap: $selectedItemsMap, coreStatus: $coreStatus)'; return 'AppState(isInit: $isInit, backBlock: $backBlock, pageLabel: $pageLabel, packages: $packages, sortNum: $sortNum, viewSize: $viewSize, sideWidth: $sideWidth, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, realTunEnable: $realTunEnable, loading: $loading, systemUiOverlayStyle: $systemUiOverlayStyle, coreStatus: $coreStatus)';
} }
@@ -356,11 +318,11 @@ abstract mixin class _$AppStateCopyWith<$Res> implements $AppStateCopyWith<$Res>
factory _$AppStateCopyWith(_AppState value, $Res Function(_AppState) _then) = __$AppStateCopyWithImpl; factory _$AppStateCopyWith(_AppState value, $Res Function(_AppState) _then) = __$AppStateCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, ProfileOverrideModel? profileOverrideModel, Map<QueryTag, String> queryMap, Map<String, String> selectedItemMap, Map<String, Set<String>> selectedItemsMap, CoreStatus coreStatus bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, bool realTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus
}); });
@override $TrafficCopyWith<$Res> get totalTraffic;@override $ProfileOverrideModelCopyWith<$Res>? get profileOverrideModel; @override $TrafficCopyWith<$Res> get totalTraffic;
} }
/// @nodoc /// @nodoc
@@ -373,7 +335,7 @@ class __$AppStateCopyWithImpl<$Res>
/// Create a copy of AppState /// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? isInit = null,Object? backBlock = null,Object? pageLabel = null,Object? packages = null,Object? sortNum = null,Object? viewSize = null,Object? sideWidth = null,Object? delayMap = null,Object? groups = null,Object? checkIpNum = null,Object? brightness = null,Object? runTime = freezed,Object? providers = null,Object? localIp = freezed,Object? requests = null,Object? version = null,Object? logs = null,Object? traffics = null,Object? totalTraffic = null,Object? realTunEnable = null,Object? loading = null,Object? systemUiOverlayStyle = null,Object? profileOverrideModel = freezed,Object? queryMap = null,Object? selectedItemMap = null,Object? selectedItemsMap = null,Object? coreStatus = null,}) { @override @pragma('vm:prefer-inline') $Res call({Object? isInit = null,Object? backBlock = null,Object? pageLabel = null,Object? packages = null,Object? sortNum = null,Object? viewSize = null,Object? sideWidth = null,Object? delayMap = null,Object? groups = null,Object? checkIpNum = null,Object? brightness = null,Object? runTime = freezed,Object? providers = null,Object? localIp = freezed,Object? requests = null,Object? version = null,Object? logs = null,Object? traffics = null,Object? totalTraffic = null,Object? realTunEnable = null,Object? loading = null,Object? systemUiOverlayStyle = null,Object? coreStatus = null,}) {
return _then(_AppState( return _then(_AppState(
isInit: null == isInit ? _self.isInit : isInit // ignore: cast_nullable_to_non_nullable isInit: null == isInit ? _self.isInit : isInit // ignore: cast_nullable_to_non_nullable
as bool,backBlock: null == backBlock ? _self.backBlock : backBlock // ignore: cast_nullable_to_non_nullable as bool,backBlock: null == backBlock ? _self.backBlock : backBlock // ignore: cast_nullable_to_non_nullable
@@ -397,11 +359,7 @@ as FixedList<Traffic>,totalTraffic: null == totalTraffic ? _self.totalTraffic :
as Traffic,realTunEnable: null == realTunEnable ? _self.realTunEnable : realTunEnable // ignore: cast_nullable_to_non_nullable as Traffic,realTunEnable: null == realTunEnable ? _self.realTunEnable : realTunEnable // ignore: cast_nullable_to_non_nullable
as bool,loading: null == loading ? _self.loading : loading // ignore: cast_nullable_to_non_nullable as bool,loading: null == loading ? _self.loading : loading // ignore: cast_nullable_to_non_nullable
as bool,systemUiOverlayStyle: null == systemUiOverlayStyle ? _self.systemUiOverlayStyle : systemUiOverlayStyle // ignore: cast_nullable_to_non_nullable as bool,systemUiOverlayStyle: null == systemUiOverlayStyle ? _self.systemUiOverlayStyle : systemUiOverlayStyle // ignore: cast_nullable_to_non_nullable
as SystemUiOverlayStyle,profileOverrideModel: freezed == profileOverrideModel ? _self.profileOverrideModel : profileOverrideModel // ignore: cast_nullable_to_non_nullable as SystemUiOverlayStyle,coreStatus: null == coreStatus ? _self.coreStatus : coreStatus // ignore: cast_nullable_to_non_nullable
as ProfileOverrideModel?,queryMap: null == queryMap ? _self._queryMap : queryMap // ignore: cast_nullable_to_non_nullable
as Map<QueryTag, String>,selectedItemMap: null == selectedItemMap ? _self._selectedItemMap : selectedItemMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>,selectedItemsMap: null == selectedItemsMap ? _self._selectedItemsMap : selectedItemsMap // ignore: cast_nullable_to_non_nullable
as Map<String, Set<String>>,coreStatus: null == coreStatus ? _self.coreStatus : coreStatus // ignore: cast_nullable_to_non_nullable
as CoreStatus, as CoreStatus,
)); ));
} }
@@ -415,18 +373,6 @@ $TrafficCopyWith<$Res> get totalTraffic {
return $TrafficCopyWith<$Res>(_self.totalTraffic, (value) { return $TrafficCopyWith<$Res>(_self.totalTraffic, (value) {
return _then(_self.copyWith(totalTraffic: value)); return _then(_self.copyWith(totalTraffic: value));
}); });
}/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ProfileOverrideModelCopyWith<$Res>? get profileOverrideModel {
if (_self.profileOverrideModel == null) {
return null;
}
return $ProfileOverrideModelCopyWith<$Res>(_self.profileOverrideModel!, (value) {
return _then(_self.copyWith(profileOverrideModel: value));
});
} }
} }

View File

@@ -2698,7 +2698,7 @@ as bool,
/// @nodoc /// @nodoc
mixin _$Rule { mixin _$Rule {
String get id; String get value; int get id; String get value; String? get order;
/// Create a copy of Rule /// Create a copy of Rule
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -2711,16 +2711,16 @@ $RuleCopyWith<Rule> get copyWith => _$RuleCopyWithImpl<Rule>(this as Rule, _$ide
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Rule&&(identical(other.id, id) || other.id == id)&&(identical(other.value, value) || other.value == value)); return identical(this, other) || (other.runtimeType == runtimeType&&other is Rule&&(identical(other.id, id) || other.id == id)&&(identical(other.value, value) || other.value == value)&&(identical(other.order, order) || other.order == order));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,value); int get hashCode => Object.hash(runtimeType,id,value,order);
@override @override
String toString() { String toString() {
return 'Rule(id: $id, value: $value)'; return 'Rule(id: $id, value: $value, order: $order)';
} }
@@ -2731,7 +2731,7 @@ abstract mixin class $RuleCopyWith<$Res> {
factory $RuleCopyWith(Rule value, $Res Function(Rule) _then) = _$RuleCopyWithImpl; factory $RuleCopyWith(Rule value, $Res Function(Rule) _then) = _$RuleCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String id, String value int id, String value, String? order
}); });
@@ -2748,11 +2748,12 @@ class _$RuleCopyWithImpl<$Res>
/// Create a copy of Rule /// Create a copy of Rule
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? value = null,}) { @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? value = null,Object? order = freezed,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,value: null == value ? _self.value : value // ignore: cast_nullable_to_non_nullable as int,value: null == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
as String, as String,order: freezed == order ? _self.order : order // ignore: cast_nullable_to_non_nullable
as String?,
)); ));
} }
@@ -2837,10 +2838,10 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String value)? $default,{required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int id, String value, String? order)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _Rule() when $default != null: case _Rule() when $default != null:
return $default(_that.id,_that.value);case _: return $default(_that.id,_that.value,_that.order);case _:
return orElse(); return orElse();
} }
@@ -2858,10 +2859,10 @@ return $default(_that.id,_that.value);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String value) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int id, String value, String? order) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _Rule(): case _Rule():
return $default(_that.id,_that.value);case _: return $default(_that.id,_that.value,_that.order);case _:
throw StateError('Unexpected subclass'); throw StateError('Unexpected subclass');
} }
@@ -2878,10 +2879,10 @@ return $default(_that.id,_that.value);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String value)? $default,) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int id, String value, String? order)? $default,) {final _that = this;
switch (_that) { switch (_that) {
case _Rule() when $default != null: case _Rule() when $default != null:
return $default(_that.id,_that.value);case _: return $default(_that.id,_that.value,_that.order);case _:
return null; return null;
} }
@@ -2893,11 +2894,12 @@ return $default(_that.id,_that.value);case _:
@JsonSerializable() @JsonSerializable()
class _Rule implements Rule { class _Rule implements Rule {
const _Rule({required this.id, required this.value}); const _Rule({required this.id, required this.value, this.order});
factory _Rule.fromJson(Map<String, dynamic> json) => _$RuleFromJson(json); factory _Rule.fromJson(Map<String, dynamic> json) => _$RuleFromJson(json);
@override final String id; @override final int id;
@override final String value; @override final String value;
@override final String? order;
/// Create a copy of Rule /// Create a copy of Rule
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -2912,16 +2914,16 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Rule&&(identical(other.id, id) || other.id == id)&&(identical(other.value, value) || other.value == value)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _Rule&&(identical(other.id, id) || other.id == id)&&(identical(other.value, value) || other.value == value)&&(identical(other.order, order) || other.order == order));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,value); int get hashCode => Object.hash(runtimeType,id,value,order);
@override @override
String toString() { String toString() {
return 'Rule(id: $id, value: $value)'; return 'Rule(id: $id, value: $value, order: $order)';
} }
@@ -2932,7 +2934,7 @@ abstract mixin class _$RuleCopyWith<$Res> implements $RuleCopyWith<$Res> {
factory _$RuleCopyWith(_Rule value, $Res Function(_Rule) _then) = __$RuleCopyWithImpl; factory _$RuleCopyWith(_Rule value, $Res Function(_Rule) _then) = __$RuleCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String id, String value int id, String value, String? order
}); });
@@ -2949,11 +2951,12 @@ class __$RuleCopyWithImpl<$Res>
/// Create a copy of Rule /// Create a copy of Rule
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? value = null,}) { @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? value = null,Object? order = freezed,}) {
return _then(_Rule( return _then(_Rule(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,value: null == value ? _self.value : value // ignore: cast_nullable_to_non_nullable as int,value: null == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
as String, as String,order: freezed == order ? _self.order : order // ignore: cast_nullable_to_non_nullable
as String?,
)); ));
} }

View File

@@ -285,12 +285,16 @@ Map<String, dynamic> _$GeoXUrlToJson(_GeoXUrl instance) => <String, dynamic>{
'geosite': instance.geosite, 'geosite': instance.geosite,
}; };
_Rule _$RuleFromJson(Map<String, dynamic> json) => _Rule _$RuleFromJson(Map<String, dynamic> json) => _Rule(
_Rule(id: json['id'] as String, value: json['value'] as String); id: (json['id'] as num).toInt(),
value: json['value'] as String,
order: json['order'] as String?,
);
Map<String, dynamic> _$RuleToJson(_Rule instance) => <String, dynamic>{ Map<String, dynamic> _$RuleToJson(_Rule instance) => <String, dynamic>{
'id': instance.id, 'id': instance.id,
'value': instance.value, 'value': instance.value,
'order': instance.order,
}; };
_SubRule _$SubRuleFromJson(Map<String, dynamic> json) => _SubRule _$SubRuleFromJson(Map<String, dynamic> json) =>

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