Compare commits

...

2 Commits
dev ... v0.8.92

Author SHA1 Message Date
chen08209
2fbb96f5c1 Add sqlite store
Optimize android quick action

Optimize backup and restore

Optimize more details
2026-02-02 10:15:42 +08:00
chen08209
243b3037d9 Update changelog 2025-12-12 06:52:14 +00:00
215 changed files with 15669 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
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
@@ -41,6 +41,11 @@ app.*.symbols
# Obfuscation related
app.*.map.json
#AI generated
CLAUDE.md
/.claude
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
@@ -53,7 +58,6 @@ app.*.map.json
/android/core/**/cmake-build-*/
/android/core/**/jniLibs/
#FlClash
/libclash/
/android/app/src/main/jniLibs/
@@ -61,3 +65,6 @@ app.*.map.json
/macos/**/Package.resolved
devtools_options.yaml
# FVM Version Cache
.fvm/
.fvmrc

View File

@@ -1,11 +1,11 @@
# This file tracks properties of this Flutter project.
# 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:
revision: 796c8ef79279f9c774545b3771238c3098dbefab
channel: stable
revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2"
channel: "stable"
project_type: app
@@ -13,26 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
- 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
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
- platform: windows
create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
# 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,3 +1,13 @@
## v0.8.91
- Fix windows some issues
- Optimize overwrite handle
- Optimize access control page
- Optimize some details
## v0.8.90
- Fix android tile service

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 {
debug {
isMinifyEnabled = false
applicationIdSuffix = ".debug"
applicationIdSuffix = ".dev"
}
release {
isMinifyEnabled = true
isShrinkResources = true
signingConfig = if (isRelease) {
signingConfigs.getByName("release")
if (isRelease) {
signingConfig = signingConfigs.getByName("release")
} else {
signingConfigs.getByName("debug")
signingConfig = signingConfigs.getByName("debug")
applicationIdSuffix = ".dev"
}
proguardFiles(

View File

@@ -41,6 +41,25 @@
"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
import android.app.Application
import android.content.Context.MODE_PRIVATE
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import androidx.core.graphics.drawable.toBitmap
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.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel
@@ -21,6 +26,30 @@ import kotlin.coroutines.resume
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 =
withContext(Dispatchers.IO) {
val cacheDir = GlobalState.application.cacheDir
@@ -118,4 +147,4 @@ fun <T> MethodChannel.invokeMethodOnMainThread(
}
})
}
}
}

View File

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

View File

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

View File

@@ -1,18 +1,17 @@
package com.follow.clash
import android.net.VpnService
import com.follow.clash.common.GlobalState
import com.follow.clash.models.SharedState
import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ServicePlugin
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.dart.DartExecutor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
enum class RunState {
START, PENDING, STOP
@@ -25,20 +24,17 @@ object State {
var runTime: Long = 0
var sharedState: SharedState = SharedState()
val runStateFlow: MutableStateFlow<RunState> = MutableStateFlow(RunState.STOP)
var flutterEngine: FlutterEngine? = null
var serviceFlutterEngine: FlutterEngine? = null
val appPlugin: AppPlugin?
get() = flutterEngine?.plugin<AppPlugin>() ?: serviceFlutterEngine?.plugin<AppPlugin>()
val servicePlugin: ServicePlugin?
get() = flutterEngine?.plugin<ServicePlugin>()
?: serviceFlutterEngine?.plugin<ServicePlugin>()
get() = flutterEngine?.plugin<AppPlugin>()
val tilePlugin: TilePlugin?
get() = flutterEngine?.plugin<TilePlugin>() ?: serviceFlutterEngine?.plugin<TilePlugin>()
get() = flutterEngine?.plugin<TilePlugin>()
suspend fun handleToggleAction() {
var action: (suspend () -> Unit)?
@@ -77,7 +73,7 @@ object State {
if (flutterEngine != null) {
return
}
startServiceWithEngine()
startServiceWithPref()
}
}
@@ -88,9 +84,10 @@ object State {
return
}
tilePlugin?.handleStop()
if (flutterEngine != null || serviceFlutterEngine != null) {
if (flutterEngine != null) {
return
}
GlobalState.application.showToast(sharedState.stopTip)
handleStopService()
}
}
@@ -106,72 +103,101 @@ object State {
startService()
}
fun handleStopService() {
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() {
private fun startServiceWithPref() {
GlobalState.launch {
runLock.withLock {
if (runStateFlow.value != RunState.STOP) {
return@launch
}
GlobalState.log("Create service engine")
withContext(Dispatchers.Main) {
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)
}
sharedState = GlobalState.application.sharedState
setupAndStart()
}
}
}
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() {
GlobalState.launch {
runLock.withLock {
if (runStateFlow.value != RunState.STOP) {
return@launch
}
runStateFlow.tryEmit(RunState.PENDING)
if (servicePlugin == null) {
return@launch
}
val options = servicePlugin?.handleGetVpnOptions() ?: return@launch
appPlugin?.prepare(options.enable) {
runTime = Service.startService(options, runTime)
runStateFlow.tryEmit(RunState.START)
try {
runStateFlow.tryEmit(RunState.PENDING)
val options = sharedState.vpnOptions ?: return@launch
appPlugin?.let {
it.prepare(options.enable) {
runTime = Service.startService(options, runTime)
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
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 currentProfileName: String = "FlClash",
val stopText: String = "Stop",
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.net.VpnService
import android.os.Build
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
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.getPackageIconPath
import com.follow.clash.models.Package
import com.follow.clash.showToast
import com.google.gson.Gson
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.plugins.FlutterPlugin
@@ -193,7 +193,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
}
private fun tip(message: String?) {
Toast.makeText(GlobalState.application, message, Toast.LENGTH_LONG).show()
GlobalState.application.showToast(message)
}
@Suppress("DEPRECATION")

View File

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

View File

@@ -1,19 +1,10 @@
buildscript {
dependencies {
classpath(libs.build.kotlin)
}
}
plugins {
id("com.android.library") apply false
}
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
@@ -31,4 +22,3 @@ subprojects {
tasks.register<Delete>("clean") {
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);
}
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_resolve_process;
@@ -99,12 +107,12 @@ call_tun_interface_resolve_process_impl(void *tun_interface, const int protocol,
const int uid) {
ATTACH_JNI();
const auto packageName = reinterpret_cast<jstring>(env->CallObjectMethod(
static_cast<jobject>(tun_interface),
m_tun_interface_resolve_process,
protocol,
new_string(source),
new_string(target),
uid));
static_cast<jobject>(tun_interface),
m_tun_interface_resolve_process,
protocol,
new_string(source),
new_string(target),
uid));
return get_string(packageName);
}
@@ -191,4 +199,10 @@ extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean suspended) {
}
#endif
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

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 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.IEventInterface;
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.NotificationParams;
interface IRemoteInterface {
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 startService(in VpnOptions options, in long runTime, 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?) {
State.notificationParamsFlow.tryEmit(params)
}
@@ -108,6 +137,7 @@ class RemoteService : Service(),
runtime: Long,
result: IResultInterface,
) {
GlobalState.log("remote startService")
State.options = options
handleStartService(runtime, result)
}

View File

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

View File

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

View File

@@ -1,11 +1,12 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
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.",
"notSelectedTip": "The current proxy group cannot be selected.",
"tip": "tip",
"backupAndRecovery": "Backup and Recovery",
"backupAndRecoveryDesc": "Sync data via WebDAV or file",
"account": "Account",
"backup": "Backup",
"recovery": "Recovery",
"recoveryProfiles": "Only recovery profiles",
"recoveryAll": "Recovery all data",
"recoverySuccess": "Recovery success",
"backupSuccess": "Backup success",
"noInfo": "No info",
"pleaseBindWebDAV": "Please bind WebDAV",
@@ -219,9 +213,7 @@
"local": "Local",
"remote": "Remote",
"remoteBackupDesc": "Backup local data to WebDAV",
"remoteRecoveryDesc": "Recovery data from WebDAV",
"localBackupDesc": "Backup local data to local",
"localRecoveryDesc": "Recovery data from file",
"mode": "Mode",
"time": "Time",
"source": "Source",
@@ -381,9 +373,9 @@
"systemApp": "System APP",
"noNetworkApp": "No network APP",
"contactMe": "Contact me",
"recoveryStrategy": "Recovery strategy",
"recoveryStrategy_override": "Override",
"recoveryStrategy_compatible": "Compatible",
"restoreStrategy": "Restore strategy",
"restoreStrategy_override": "Override",
"restoreStrategy_compatible": "Compatible",
"logsTest": "Logs test",
"emptyTip": "{label} cannot be empty",
"urlTip": "{label} must be a url",
@@ -466,5 +458,23 @@
"vpnConfigChangeDetected": "VPN configuration change detected",
"restart": "Restart",
"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の完全サポートを獲得",
"notSelectedTip": "現在のプロキシグループは選択できません",
"tip": "ヒント",
"backupAndRecovery": "バックアップと復元",
"backupAndRecoveryDesc": "WebDAVまたはファイルでデータを同期",
"account": "アカウント",
"backup": "バックアップ",
"recovery": "復元",
"recoveryProfiles": "プロファイルのみ復元",
"recoveryAll": "全データ復元",
"recoverySuccess": "復元成功",
"backupSuccess": "バックアップ成功",
"noInfo": "情報なし",
"pleaseBindWebDAV": "WebDAVをバインドしてください",
@@ -219,9 +213,7 @@
"local": "ローカル",
"remote": "リモート",
"remoteBackupDesc": "WebDAVにデータをバックアップ",
"remoteRecoveryDesc": "WebDAVからデータを復元",
"localBackupDesc": "ローカルにデータをバックアップ",
"localRecoveryDesc": "ファイルからデータを復元",
"mode": "モード",
"time": "時間",
"source": "ソース",
@@ -382,9 +374,9 @@
"systemApp": "システムアプリ",
"noNetworkApp": "ネットワークなしアプリ",
"contactMe": "連絡する",
"recoveryStrategy": "リカバリー戦略",
"recoveryStrategy_override": "オーバーライド",
"recoveryStrategy_compatible": "互換",
"restoreStrategy": "復元ストラテジー",
"restoreStrategy_override": "上書き",
"restoreStrategy_compatible": "互換",
"logsTest": "ログテスト",
"emptyTip": "{label}は空欄にできません",
"urlTip": "{label}はURLである必要があります",
@@ -467,5 +459,23 @@
"vpnConfigChangeDetected": "VPN設定の変更が検出されました",
"restart": "再起動",
"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": "Системное приложение",
"noNetworkApp": "Приложение без сети",
"contactMe": "Свяжитесь со мной",
"recoveryStrategy": "Стратегия восстановления",
"recoveryStrategy_override": "Переопределение",
"recoveryStrategy_compatible": "Совместимый",
"restoreStrategy": "Стратегия восстановления",
"restoreStrategy_override": "Перезаписать",
"restoreStrategy_compatible": "Совместимый",
"logsTest": "Тест журналов",
"emptyTip": "{label} не может быть пустым",
"urlTip": "{label} должен быть URL",
@@ -467,5 +467,23 @@
"vpnConfigChangeDetected": "Обнаружено изменение конфигурации VPN",
"restart": "Перезапустить",
"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的支持",
"notSelectedTip": "当前代理组无法选中",
"tip": "提示",
"backupAndRecovery": "备份与恢复",
"backupAndRecoveryDesc": "通过WebDAV或者文件同步数据",
"account": "账号",
"backup": "备份",
"recovery": "恢复",
"recoveryProfiles": "仅恢复配置文件",
"recoveryAll": "恢复所有数据",
"recoverySuccess": "恢复成功",
"backupSuccess": "备份成功",
"noInfo": "暂无信息",
"pleaseBindWebDAV": "请绑定WebDAV",
@@ -219,9 +213,7 @@
"local": "本地",
"remote": "远程",
"remoteBackupDesc": "备份数据到WebDAV",
"remoteRecoveryDesc": "通过WebDAV恢复数据",
"localBackupDesc": "备份数据到本地",
"localRecoveryDesc": "通过文件恢复数据",
"mode": "模式",
"time": "时间",
"source": "来源",
@@ -382,9 +374,9 @@
"systemApp": "系统应用",
"noNetworkApp": "无网络应用",
"contactMe": "联系我",
"recoveryStrategy": "恢复策略",
"recoveryStrategy_override": "覆盖",
"recoveryStrategy_compatible": "兼容",
"restoreStrategy": "恢复策略",
"restoreStrategy_override": "覆盖",
"restoreStrategy_compatible": "兼容",
"logsTest": "日志测试",
"emptyTip": "{label}不能为空",
"urlTip": "{label}必须为URL",
@@ -467,5 +459,23 @@
"vpnConfigChangeDetected": "检测到VPN相关配置改动",
"restart": "重启",
"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:
'^lib/models/{{}}.dart': 'lib/models/generated/{{}}.g.dart'
'^lib/providers/{{}}.dart': 'lib/providers/generated/{{}}.g.dart'
'^lib/database/{{}}.dart': 'lib/database/generated/{{}}.g.dart'
freezed:
options:
build_extensions:

View File

@@ -37,12 +37,6 @@ var (
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 {
eps := make(map[string]cp.Provider)
for n, p := range tunnel.Providers() {
@@ -241,25 +235,8 @@ func updateConfig(params *UpdateParams) {
updateListeners()
}
func parseWithPath(path string) (*config.Config, error) {
buf, err := readFile(path)
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 {
func applyConfig(params *SetupParams) error {
runtime.GC()
runLock.Lock()
defer runLock.Unlock()
var err error
@@ -271,7 +248,6 @@ func setupConfig(params *SetupParams) error {
hub.ApplyConfig(currentConfig)
patchSelectGroup(params.SelectedMap)
updateListeners()
runtime.GC()
return err
}

View File

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

View File

@@ -10,6 +10,7 @@ require (
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/RyuaNerin/go-krypto v1.3.0 // indirect
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/ajg/form v1.5.1 // indirect
@@ -18,18 +19,14 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect
github.com/coreos/go-iptables v0.8.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/enfein/mieru/v3 v3.22.1 // indirect
github.com/enfein/mieru/v3 v3.26.2 // indirect
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
github.com/fsnotify/fsnotify v1.9.0 // 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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
@@ -37,7 +34,7 @@ require (
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // 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/josharian/native v1.1.0 // 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/blake3 v0.1.0 // 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/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 // indirect
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be // indirect
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 // 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/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/restls-client-go v0.1.7 // indirect
github.com/metacubex/sing v0.5.6 // 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-shadowsocks2 v0.2.7 // 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-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 // indirect
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 // indirect
github.com/metacubex/utls v1.8.3 // indirect
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 // indirect
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 // 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/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // 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/oschwald/maxminddb-golang v1.12.0 // 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/samber/lo v1.52.0 // 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/sys v0.30.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
google.golang.org/protobuf v1.34.2 // 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/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM=
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/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
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/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
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/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
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/enfein/mieru/v3 v3.26.2 h1:U/2XJc+3vrJD9r815FoFdwToQFEcqSOzzzWIPPhjfEU=
github.com/enfein/mieru/v3 v3.26.2/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/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
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/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.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/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/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
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/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
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/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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
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/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/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
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/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/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/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/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be h1:Y7SigZIqfv/+RIA/D7R6EbB9p+brPRoGOM6zobSmRIM=
github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs=
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 h1:hUL81H0Ic/XIDkvtn9M1pmfDdfid7JzYQToY4Ps1TvQ=
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA=
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/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/quic-go v0.55.1-0.20251024060151-bd465f127128/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c=
github.com/metacubex/qpack v0.6.0 h1:YqClGIMOpiRYLjV1qOs483Od08MdPgRnHjt90FuaAKw=
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/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
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/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/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
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-quic v0.0.0-20251004051927-c45ee18473bb h1:gxrJmnxuEAel+kh3V7ntqkHjURif0xKDu76nzr/BF5Y=
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 h1:MLxp42z9Jd6LtY2suyawnl24oNzIsFxWc15bNeDIGxA=
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/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
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-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-tun v0.4.9 h1:jY0Yyt8nnN3yQRN/jTxgqNCmGi1dsFdxdIi7pQUlVVU=
github.com/metacubex/sing-tun v0.4.9/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
github.com/metacubex/sing-tun v0.4.11 h1:NG5zpvYPbBXf+9GSUmDaGCDwl3hZXV677tbRAw0QtCM=
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/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/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-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU=
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4=
github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2BhiAPgbJygiWhesPlfGmF+9Vw6ARdk=
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg=
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/tls v0.1.1 h1:BEcZrsPTTfNf4sKZ02EbZodv4UIj7fgHWa1Eqo12Bc0=
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/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
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/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/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/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
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/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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.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.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/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=
@@ -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-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-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-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.1.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.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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
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.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=

View File

@@ -1,6 +1,7 @@
package main
import (
"cmp"
"context"
"encoding/json"
"github.com/metacubex/mihomo/adapter"
@@ -19,11 +20,11 @@ import (
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/mihomo/tunnel/statistic"
"golang.org/x/exp/slices"
"net"
"os"
"runtime"
"runtime/debug"
"sort"
"strconv"
"time"
)
@@ -43,10 +44,8 @@ func handleInitClash(paramsString string) bool {
return false
}
version = params.Version
if !isInit {
constant.SetHomeDir(params.HomeDir)
isInit = true
}
constant.SetHomeDir(params.HomeDir)
isInit = true
return isInit
}
@@ -97,10 +96,52 @@ func handleValidateConfig(path string) string {
return ""
}
func handleGetProxies() map[string]constant.Proxy {
func handleGetProxies() ProxiesData {
runLock.Lock()
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)) {
@@ -143,7 +184,7 @@ func handleChangeProxy(data string, fn func(string string)) {
}
func handleGetTraffic(onlyStatisticsProxy bool) string {
up, down := statistic.DefaultManager.Current(onlyStatisticsProxy)
up, down := statistic.DefaultManager.NowTraffic(onlyStatisticsProxy)
traffic := map[string]int64{
"up": up,
"down": down,
@@ -157,7 +198,7 @@ func handleGetTraffic(onlyStatisticsProxy bool) string {
}
func handleGetTotalTraffic(onlyStatisticsProxy bool) string {
up, down := statistic.DefaultManager.Total(onlyStatisticsProxy)
up, down := statistic.DefaultManager.TotalTraffic(onlyStatisticsProxy)
traffic := map[string]int64{
"up": up,
"down": down,
@@ -287,7 +328,9 @@ func handleGetExternalProviders() string {
}
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)
if err != nil {
return ""
@@ -489,14 +532,17 @@ func handleDelFile(path string, result ActionResult) {
}
func handleSetupConfig(bytes []byte) string {
if !isInit {
return "not initialized"
}
var params = defaultSetupParams()
err := UnmarshalJson(bytes, params)
if err != nil {
log.Errorln("unmarshalRawConfig error %v", err)
_ = setupConfig(defaultSetupParams())
_ = applyConfig(defaultSetupParams())
return err.Error()
}
err = setupConfig(params)
err = applyConfig(params)
if err != nil {
return err.Error()
}

View File

@@ -201,9 +201,29 @@ func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
//export startTUN
func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar *C.char) bool {
handleStartTun(callback, int(fd), takeCString(stackChar), takeCString(addressChar), takeCString(dnsChar))
if !isRunning {
handleStartListener()
} else {
handleResetConnections()
}
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
func setEventListener(listener unsafe.Pointer) {
if eventListener != nil || listener == nil {
@@ -241,6 +261,9 @@ func sendMessage(message Message) {
//export stopTun
func stopTun() {
handleStopTun()
if isRunning {
handleStopListener()
}
}
//export suspend

View File

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

View File

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

View File

@@ -6,17 +6,21 @@ export 'constant.dart';
export 'context.dart';
export 'converter.dart';
export 'datetime.dart';
export 'file.dart';
export 'fixed.dart';
export 'function.dart';
export 'future.dart';
export 'hive.dart';
export 'http.dart';
export 'icons.dart';
export 'indexing.dart';
export 'iterable.dart';
export 'keyboard.dart';
export 'launch.dart';
export 'link.dart';
export 'lock.dart';
export 'measure.dart';
export 'migration.dart';
export 'mixin.dart';
export 'navigation.dart';
export 'navigator.dart';
@@ -32,8 +36,10 @@ export 'proxy.dart';
export 'render.dart';
export 'request.dart';
export 'scroll.dart';
export 'snowflake.dart';
export 'string.dart';
export 'system.dart';
export 'task.dart';
export 'text.dart';
export 'tray.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/models/models.dart';
import 'string.dart';
List<Group> computeSort({
required List<Group> groups,
required ProxiesSortType sortType,
@@ -10,53 +9,54 @@ List<Group> computeSort({
required Map<String, String> selectedMap,
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) {
final proxies = group.all;
final newProxies = switch (sortType) {
ProxiesSortType.none => proxies,
ProxiesSortType.delay => _sortOfDelay(
ProxiesSortType.delay => sortOfDelay(
groups: groups,
proxies: proxies,
delayMap: delayMap,
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);
}).toList();
}
DelayState computeProxyDelayState({
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 getRealSelectedProxyState(
SelectedProxyState state, {
required List<Group> groups,
required Map<String, String> selectedMap,
@@ -72,39 +72,39 @@ SelectedProxyState _getRealSelectedProxyState(
if (currentSelectedName.isEmpty) {
return newState;
}
return _getRealSelectedProxyState(
return getRealSelectedProxyState(
newState.copyWith(proxyName: currentSelectedName, testUrl: group.testUrl),
groups: groups,
selectedMap: selectedMap,
);
}
List<Proxy> _sortOfDelay({
SelectedProxyState computeRealSelectedProxyState(
String proxyName, {
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);
});
return getRealSelectedProxyState(
SelectedProxyState(proxyName: proxyName),
groups: groups,
selectedMap: selectedMap,
);
}
List<Proxy> _sortOfName(List<Proxy> proxies) {
return List.of(proxies)..sort((a, b) => a.name.compareTo(b.name));
DelayState computeProxyDelayState({
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 minTextScale = 0.8;
final baseInfoEdgeInsets = EdgeInsets.symmetric(
vertical: 16.ap,
horizontal: 16.ap,
vertical: 16.mAp,
horizontal: 16.mAp,
);
final listHeaderPadding = EdgeInsets.only(
left: 16.ap,
right: 8.ap,
top: 24.ap,
bottom: 8.ap,
left: 16.mAp,
right: 8.mAp,
top: 24.mAp,
bottom: 8.mAp,
);
const watchExecution = true;
final defaultTextScaleFactor =
WidgetsBinding.instance.platformDispatcher.textScaleFactor;
const httpTimeoutDuration = Duration(milliseconds: 5000);
@@ -63,6 +65,7 @@ final commonFilter = ImageFilter.blur(
tileMode: TileMode.mirror,
);
const listEquality = ListEquality();
const navigationItemListEquality = ListEquality<NavigationItem>();
const trackerInfoListEquality = ListEquality<TrackerInfo>();
const stringListEquality = ListEquality<String>();
@@ -70,15 +73,18 @@ const intListEquality = ListEquality<int>();
const logListEquality = ListEquality<Log>();
const groupListEquality = ListEquality<Group>();
const ruleListEquality = ListEquality<Rule>();
const scriptEquality = ListEquality<Script>();
const scriptListEquality = ListEquality<Script>();
const externalProviderListEquality = ListEquality<ExternalProvider>();
const packageListEquality = ListEquality<Package>();
const profileListEquality = ListEquality<Profile>();
const hotKeyActionListEquality = ListEquality<HotKeyAction>();
const stringAndStringMapEquality = MapEquality<String, String>();
const stringAndStringMapEntryListEquality =
ListEquality<MapEntry<String, String>>();
const stringAndStringMapEntryIterableEquality =
IterableEquality<MapEntry<String, String>>();
const stringAndObjectMapEntryIterableEquality =
IterableEquality<MapEntry<String, Object?>>();
const delayMapEquality = MapEquality<String, Map<String, int?>>();
const stringSetEquality = SetEquality<String>();
const keyboardModifierListEquality = SetEquality<KeyboardModifier>();
@@ -96,7 +102,8 @@ const profilesStoreKey = PageStorageKey<String>('profiles');
const defaultPrimaryColor = 0XFFD8C0C3;
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;
@@ -119,3 +126,6 @@ const scriptTemplate = '''
const main = (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/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:flutter/material.dart';

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
@@ -10,19 +9,10 @@ class DAVClient {
Completer<bool> pingCompleter = Completer();
late String fileName;
DAVClient(DAV dav) {
client = newClient(
dav.uri,
user: dav.user,
password: dav.password,
);
DAVClient(DAVProps dav) {
client = newClient(dav.uri, user: dav.user, password: dav.password);
fileName = dav.fileName;
client.setHeaders(
{
'accept-charset': 'utf-8',
'Content-Type': 'text/xml',
},
);
client.setHeaders({'accept-charset': 'utf-8', 'Content-Type': 'text/xml'});
client.setConnectTimeout(8000);
client.setSendTimeout(60000);
client.setReceiveTimeout(60000);
@@ -42,15 +32,16 @@ class DAVClient {
String get backupFile => '$root/$fileName';
Future<bool> backup(Uint8List data) async {
Future<bool> backup(String localFilePath) async {
await client.mkdir(root);
await client.write(backupFile, data);
await client.writeFromFile(localFilePath, backupFile);
return true;
}
Future<List<int>> recovery() async {
Future<bool> restore() async {
await client.mkdir(root);
final data = await client.read(backupFile);
return data;
final backupFilePath = await appPath.backupFilePath;
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 'package:fl_clash/common/common.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/controller.dart';
class FlClashHttpOverrides extends HttpOverrides {
static String handleFindProxy(Uri url) {
if ([localhost].contains(url.host)) {
return 'DIRECT';
}
final port = globalState.config.patchClashConfig.mixedPort;
final isStart = globalState.appState.runTime != null;
final port = appController.config.patchClashConfig.mixedPort;
final isStart = appController.isStart;
commonPrint.log('find $url proxy:$isStart');
if (!isStart) return 'DIRECT';
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> {
Iterable<T> separated(T separator) sync* {
extension IterableExt<E> on Iterable<E> {
Iterable<E> separated(E separator) sync* {
final iterator = this.iterator;
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;
var iterator = this.iterator;
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;
for (var item in this) {
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();
return count >= length ? this : toList().skip(length - count);
}
@@ -78,16 +78,18 @@ extension ListExt<T> on List<T> {
return sublist(start);
}
T safeGet(int index) {
if (length > index) return this[index];
return last;
T? safeGet(int index, {T? defaultValue}) {
if (index < 0 || index >= length) {
return defaultValue;
}
return this[index];
}
T safeLast(T value) {
T safeLast(T defaultValue) {
if (isNotEmpty) {
return last;
}
return value;
return defaultValue;
}
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';
mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
T get value => state;
set value(T value) {
if (ref.mounted) {
state = value;
} else {
onUpdate(value);
}
state = value;
}
bool equals(T previous, T next) {
return false;
}
@override
bool updateShouldNotify(previous, next) {
final res = super.updateShouldNotify(previous, next);
final res = !equals(previous, next)
? super.updateShouldNotify(previous, next)
: true;
if (res) {
onUpdate(next);
}
@@ -21,31 +25,19 @@ mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
void onUpdate(T value) {}
void update(T Function(T) builder) {
final value = builder(state);
this.value = value;
void update(T? Function(T) builder) {
final res = builder(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;
set value(T value) {
if (ref.mounted) {
state = value;
} else {
onUpdate(value);
}
state = AsyncData(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:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/controller.dart';
import 'package:flutter/material.dart';
class BaseNavigator {
static Future<T?> push<T>(BuildContext context, Widget child) async {
if (globalState.appState.viewMode != ViewMode.mobile) {
if (!appController.isMobile) {
return await Navigator.of(
context,
).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/models/common.dart';
import 'package:fl_clash/state.dart';
@@ -20,6 +22,10 @@ extension NumExt on num {
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 {
final units = TrafficUnit.values;
var size = toDouble();
@@ -51,7 +57,7 @@ extension NumExt on num {
extension DoubleExt on double {
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;
}
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 {
final homeDirPath = await appPath.homeDirPath;
return join(homeDirPath, 'FlClash.lock');
}
Future<String> get configFilePath async {
final homeDirPath = await appPath.homeDirPath;
return join(homeDirPath, 'config.yaml');
final mHomeDirPath = await homeDirPath;
return join(mHomeDirPath, 'config.yaml');
}
Future<String> get validateFilePath async {
final homeDirPath = await appPath.homeDirPath;
return join(homeDirPath, 'temp', 'validate${utils.id}.yaml');
Future<String> get sharedFilePath async {
final mHomeDirPath = await homeDirPath;
return join(mHomeDirPath, 'shared.json');
}
Future<String> get sharedPreferencesPath async {
@@ -86,9 +106,18 @@ class AppPath {
return join(directory.path, profilesDirectoryName);
}
Future<String> getProfilePath(String id) async {
final directory = await profilesPath;
return join(directory, '$id.yaml');
Future<String> getProfilePath(String fileName) async {
return join(await profilesPath, '$fileName.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 {

View File

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

View File

@@ -24,20 +24,60 @@ class Preferences {
return _instance!;
}
Future<ClashConfig?> getClashConfig() async {
Future<int> getVersion() async {
final preferences = await sharedPreferencesCompleter.future;
final clashConfigString = preferences?.getString(clashConfigKey);
if (clashConfigString == null) return null;
final clashConfigMap = json.decode(clashConfigString);
return ClashConfig.fromJson(clashConfigMap);
return preferences?.getInt('version') ?? 0;
}
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 {
final preferences = await sharedPreferencesCompleter.future;
final configString = preferences?.getString(configKey);
if (configString == null) return null;
final configMap = json.decode(configString);
return Config.compatibleFromJson(configMap);
final configMap = await getConfigMap();
if (configMap == null) {
return null;
}
return Config.fromJson(configMap);
}
Future<bool> saveConfig(Config config) async {
@@ -45,14 +85,9 @@ class Preferences {
return preferences?.setString(configKey, json.encode(config)) ?? false;
}
Future<void> clearClashConfig() async {
final preferences = await sharedPreferencesCompleter.future;
preferences?.remove(clashConfigKey);
}
Future<void> clearPreferences() async {
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/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class CommonPrint {
static CommonPrint? _instance;
@@ -16,12 +16,10 @@ class CommonPrint {
void log(String? text, {LogLevel logLevel = LogLevel.info}) {
final payload = '[APP] $text';
debugPrint(payload);
if (!globalState.isInit) {
if (!appController.isAttach) {
return;
}
globalState.appController.addLog(
Log.app(payload).copyWith(logLevel: logLevel),
);
appController.addLog(Log.app(payload).copyWith(logLevel: logLevel));
}
}

View File

@@ -6,6 +6,8 @@ import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:dio/io.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/state.dart';
import 'package:flutter/cupertino.dart';
@@ -22,7 +24,7 @@ class Request {
createHttpClient: () {
final client = HttpClient();
client.findProxy = (Uri uri) {
client.userAgent = globalState.ua;
client.userAgent = appController.ua;
return FlClashHttpOverrides.handleFindProxy(uri);
};
return client;
@@ -31,10 +33,23 @@ class Request {
}
Future<Response<Uint8List>> getFileResponseForUrl(String url) async {
return await _clashDio.get<Uint8List>(
url,
options: Options(responseType: ResponseType.bytes),
);
try {
return await _clashDio.get<Uint8List>(
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 {
@@ -57,18 +72,23 @@ class Request {
}
Future<Map<String, dynamic>?> checkForUpdate() async {
final response = await dio.get(
'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>;
final remoteVersion = data['tag_name'];
final version = globalState.packageInfo.version;
final hasUpdate =
utils.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0;
if (!hasUpdate) return null;
return data;
try {
final response = await dio.get(
'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>;
final remoteVersion = data['tag_name'];
final version = globalState.packageInfo.version;
final hasUpdate =
utils.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0;
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 = {
@@ -83,6 +103,7 @@ class Request {
Future<Result<IpInfo?>> checkIp({CancelToken? cancelToken}) async {
var failureCount = 0;
final token = cancelToken ?? CancelToken();
final futures = _ipInfoSources.entries.map((source) async {
final Completer<Result<IpInfo?>> completer = Completer();
handleFailRes() {
@@ -94,7 +115,7 @@ class Request {
final future = dio
.get<Map<String, dynamic>>(
source.key,
cancelToken: cancelToken,
cancelToken: token,
options: Options(responseType: ResponseType.json),
)
.timeout(const Duration(seconds: 10));
@@ -117,7 +138,7 @@ class Request {
return completer.future;
});
final res = await Future.any(futures);
cancelToken?.cancel();
token.cancel();
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 'package:crypto/crypto.dart';
import 'print.dart';
import 'package:fl_clash/common/common.dart';
extension StringExtension on String {
bool get isUrl {
@@ -78,13 +77,26 @@ extension StringExtension on String {
// bool containsToLower(String target) {
// return toLowerCase().contains(target);
// }
}
extension StringExtensionSafe on String? {
String getSafeValue(String defaultValue) {
if (this == null || this!.isEmpty) {
return defaultValue;
Future<T> commonToJSON<T>() async {
final thresholdLimit = 51200;
if (length < thresholdLimit) {
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));
final retryStatus = await retry(
task: checkService,
maxAttempts: 5,
retryIf: (status) => status != WindowsHelperServiceStatus.running,
delay: commonDuration,
delay: Duration(seconds: 1),
);
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 'package:fl_clash/common/iterable.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/state.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:tray_manager/tray_manager.dart';
@@ -14,10 +13,23 @@ import 'system.dart';
import 'window.dart';
class Tray {
static Tray? _instance;
Tray._internal();
factory Tray() {
_instance ??= Tray._internal();
return _instance!;
}
String get trayIconSuffix {
return system.isWindows ? 'ico' : 'png';
}
Future<void> destroy() async {
await trayManager.destroy();
}
String getTryIcon({required bool isStart, required bool tunEnable}) {
if (system.isMacOS || !isStart) {
return 'assets/images/icon/status_1.$trayIconSuffix';
@@ -29,11 +41,10 @@ class Tray {
}
Future _updateSystemTray({
bool force = false,
required bool isStart,
required bool tunEnable,
}) async {
if (Platform.isLinux || force) {
if (Platform.isLinux) {
await trayManager.destroy();
}
await trayManager.setIcon(
@@ -47,7 +58,7 @@ class Tray {
Future<void> update({
required TrayState trayState,
bool focus = false,
required Traffic traffic,
}) async {
if (system.isAndroid) {
return;
@@ -56,7 +67,6 @@ class Tray {
await _updateSystemTray(
isStart: trayState.isStart,
tunEnable: trayState.tunEnable,
force: focus,
);
}
List<MenuItem> menuItems = [];
@@ -70,7 +80,7 @@ class Tray {
final startMenuItem = MenuItem.checkbox(
label: trayState.isStart ? appLocalizations.stop : appLocalizations.start,
onClick: (_) async {
globalState.appController.updateStart();
appController.updateStart();
},
checked: false,
);
@@ -79,7 +89,7 @@ class Tray {
final speedStatistics = MenuItem.checkbox(
label: appLocalizations.speedStatistics,
onClick: (_) async {
globalState.appController.updateSpeedStatistics();
appController.updateSpeedStatistics();
},
checked: trayState.showTrayTitle,
);
@@ -91,7 +101,7 @@ class Tray {
MenuItem.checkbox(
label: Intl.message(mode.name),
onClick: (_) {
globalState.appController.changeMode(mode);
appController.changeMode(mode);
},
checked: mode == trayState.mode,
),
@@ -106,9 +116,8 @@ class Tray {
MenuItem.checkbox(
label: proxy.name,
checked:
globalState.getSelectedProxyName(group.name) == proxy.name,
appController.getSelectedProxyName(group.name) == proxy.name,
onClick: (_) {
final appController = globalState.appController;
appController.updateCurrentSelectedMap(group.name, proxy.name);
appController.changeProxy(
groupName: group.name,
@@ -134,7 +143,7 @@ class Tray {
MenuItem.checkbox(
label: appLocalizations.tun,
onClick: (_) {
globalState.appController.updateTun();
appController.updateTun();
},
checked: trayState.tunEnable,
),
@@ -143,7 +152,7 @@ class Tray {
MenuItem.checkbox(
label: appLocalizations.systemProxy,
onClick: (_) {
globalState.appController.updateSystemProxy();
appController.updateSystemProxy();
},
checked: trayState.systemProxy,
),
@@ -153,7 +162,7 @@ class Tray {
final autoStartMenuItem = MenuItem.checkbox(
label: appLocalizations.autoLaunch,
onClick: (_) async {
globalState.appController.updateAutoLaunch();
appController.updateAutoLaunch();
},
checked: trayState.autoLaunch,
);
@@ -169,7 +178,7 @@ class Tray {
final exitMenuItem = MenuItem(
label: appLocalizations.exit,
onClick: (_) async {
await globalState.appController.handleExit();
await appController.handleExit();
},
);
menuItems.add(exitMenuItem);
@@ -179,13 +188,9 @@ class Tray {
await _updateSystemTray(
isStart: trayState.isStart,
tunEnable: trayState.tunEnable,
force: focus,
);
}
updateTrayTitle(
showTrayTitle: trayState.showTrayTitle,
traffic: globalState.appState.traffics.list.safeLast(Traffic()),
);
updateTrayTitle(showTrayTitle: trayState.showTrayTitle, traffic: traffic);
}
Future<void> updateTrayTitle({

View File

@@ -10,6 +10,15 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Utils {
static Utils? _instance;
Utils._internal();
factory Utils() {
_instance ??= Utils._internal();
return _instance!;
}
Color? getDelayColor(int? delay) {
if (delay == null) return null;
if (delay < 0) return Colors.red;
@@ -319,7 +328,7 @@ class Utils {
required Function function,
required void Function(T data, int elapsedMilliseconds) onWatch,
}) async {
if (kDebugMode) {
if (kDebugMode && watchExecution) {
final stopwatch = Stopwatch()..start();
final res = await function();
stopwatch.stop();
@@ -328,6 +337,21 @@ class Utils {
}
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();

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,8 @@ class CoreService extends CoreHandlerInterface {
Completer<Socket> _socketCompleter = Completer();
Completer<bool> _shutdownCompleter = Completer();
final Map<String, Completer> _callbackCompleterMap = {};
Process? _process;
@@ -35,6 +37,9 @@ class CoreService extends CoreHandlerInterface {
if (result.id?.isEmpty == true) {
coreEventManager.sendEvent(CoreEvent.fromJson(result.data));
}
if (completer?.isCompleted == true) {
return;
}
completer?.complete(data);
}
@@ -70,11 +75,15 @@ class CoreService extends CoreHandlerInterface {
.transform(uint8ListToListIntConverter)
.transform(utf8.decoder)
.transform(LineSplitter())
.listen((data) {
handleResult(ActionResult.fromJson(json.decode(data.trim())));
.listen((data) async {
final dataJson = await data.trim().commonToJSON<dynamic>();
handleResult(ActionResult.fromJson(dataJson));
})
.onDone(() {
_handleInvokeCrashEvent();
if (!_shutdownCompleter.isCompleted) {
_shutdownCompleter.complete(true);
}
});
}
@@ -86,7 +95,7 @@ class CoreService extends CoreHandlerInterface {
Future<void> start() async {
if (_process != null) {
await shutdown();
await shutdown(false);
}
final serverSocket = await _serverCompleter.future;
final arg = system.isWindows
@@ -112,7 +121,7 @@ class CoreService extends CoreHandlerInterface {
@override
destroy() async {
final server = await _serverCompleter.future;
await shutdown();
await shutdown(false);
await server.close();
await _deleteSocketFile();
return true;
@@ -126,9 +135,7 @@ class CoreService extends CoreHandlerInterface {
Future<void> _deleteSocketFile() async {
if (!system.isWindows) {
final file = File(unixSocketPath);
if (await file.exists()) {
await file.delete();
}
await file.safeDelete();
}
}
@@ -136,12 +143,16 @@ class CoreService extends CoreHandlerInterface {
if (_socketCompleter.isCompleted) {
final socket = await _socketCompleter.future;
_socketCompleter = Completer();
socket.close();
await socket.close();
}
}
@override
shutdown() async {
shutdown(bool isUser) async {
if (!_socketCompleter.isCompleted && _process == null) {
return false;
}
_shutdownCompleter = Completer();
await _destroySocket();
_clearCompleter();
if (system.isWindows) {
@@ -149,7 +160,11 @@ class CoreService extends CoreHandlerInterface {
}
_process?.kill();
_process = null;
return true;
if (isUser) {
return _shutdownCompleter.future;
} else {
return true;
}
}
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 RecoveryOption { all, onlyProfiles }
enum RestoreOption { all, onlyProfiles }
enum ChipType { action, delete }
@@ -260,8 +260,8 @@ enum AuthorizeCode { none, success, error }
enum WindowsHelperServiceStatus { none, presence, running }
enum FunctionTag {
updateClashConfig,
setupClashConfig,
updateConfig,
setupConfig,
updateStatus,
updateGroups,
addCheckIpNum,
@@ -281,6 +281,7 @@ enum FunctionTag {
requests,
autoScrollToEnd,
loadedProvider,
saveSharedFile,
}
enum DashboardWidget {
@@ -406,7 +407,7 @@ enum OverwriteType {
enum RuleTarget { DIRECT, REJECT, MATCH }
enum RecoveryStrategy { compatible, override }
enum RestoreStrategy { compatible, override }
enum CacheTag { logs, rules, requests, proxiesList }
@@ -418,4 +419,8 @@ enum ScrollPositionCacheKey { tools, profiles, proxiesList, proxiesTabList }
enum QueryTag { proxies, access }
enum LoadingTag { profiles, backup_restore, access, proxies }
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 isEditing;
final Rule rule;
final void Function(String id) onSelected;
final void Function() onSelected;
final void Function(Rule rule) onEdit;
const RuleItem({
@@ -31,7 +31,7 @@ class RuleItem extends StatelessWidget {
return CommonSelectedListItem(
isSelected: isSelected,
onSelected: () {
onSelected(rule.id);
onSelected();
},
title: Text(
rule.value,

View File

@@ -81,6 +81,7 @@ class MessageLookup extends MessageLookupByLibrary {
"action_tun": MessageLookupByLibrary.simpleMessage("TUN"),
"action_view": MessageLookupByLibrary.simpleMessage("Show/Hide"),
"add": MessageLookupByLibrary.simpleMessage("Add"),
"addProfile": MessageLookupByLibrary.simpleMessage("Add Profile"),
"addRule": MessageLookupByLibrary.simpleMessage("Add rule"),
"addedOriginRules": MessageLookupByLibrary.simpleMessage(
"Attach on the original rules",
@@ -164,11 +165,11 @@ class MessageLookup extends MessageLookupByLibrary {
"Auto update interval (minutes)",
),
"backup": MessageLookupByLibrary.simpleMessage("Backup"),
"backupAndRecovery": MessageLookupByLibrary.simpleMessage(
"Backup and Recovery",
"backupAndRestore": MessageLookupByLibrary.simpleMessage(
"Backup and Restore",
),
"backupAndRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"Sync data via WebDAV or file",
"backupAndRestoreDesc": MessageLookupByLibrary.simpleMessage(
"Sync data via WebDAV or files",
),
"backupSuccess": MessageLookupByLibrary.simpleMessage("Backup success"),
"basicConfig": MessageLookupByLibrary.simpleMessage("Basic configuration"),
@@ -269,6 +270,7 @@ class MessageLookup extends MessageLookupByLibrary {
"defaultText": MessageLookupByLibrary.simpleMessage("Default"),
"delay": MessageLookupByLibrary.simpleMessage("Delay"),
"delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"),
"delayTest": MessageLookupByLibrary.simpleMessage("Delay Test"),
"delete": MessageLookupByLibrary.simpleMessage("Delete"),
"deleteMultipTip": m1,
"deleteTip": m2,
@@ -425,6 +427,9 @@ class MessageLookup extends MessageLookupByLibrary {
"internet": MessageLookupByLibrary.simpleMessage("Internet"),
"interval": MessageLookupByLibrary.simpleMessage("Interval"),
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
"invalidBackupFile": MessageLookupByLibrary.simpleMessage(
"Invalid backup file",
),
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
"When turned on it will be able to receive IPv6 traffic",
@@ -450,9 +455,6 @@ class MessageLookup extends MessageLookupByLibrary {
"localBackupDesc": MessageLookupByLibrary.simpleMessage(
"Backup local data to local",
),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"Recovery data from file",
),
"log": MessageLookupByLibrary.simpleMessage("Log"),
"logLevel": MessageLookupByLibrary.simpleMessage("LogLevel"),
"logcat": MessageLookupByLibrary.simpleMessage("Logcat"),
@@ -504,6 +506,12 @@ class MessageLookup extends MessageLookupByLibrary {
"networkDetection": MessageLookupByLibrary.simpleMessage(
"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"),
"networkType": MessageLookupByLibrary.simpleMessage("Network type"),
"neutralScheme": MessageLookupByLibrary.simpleMessage("Neutral"),
@@ -564,6 +572,10 @@ class MessageLookup extends MessageLookupByLibrary {
"Override the original rule",
),
"overrideScript": MessageLookupByLibrary.simpleMessage("Override script"),
"overwriteTypeCustom": MessageLookupByLibrary.simpleMessage("Custom"),
"overwriteTypeCustomDesc": MessageLookupByLibrary.simpleMessage(
"Custom mode, fully customize proxy groups and rules",
),
"palette": MessageLookupByLibrary.simpleMessage("Palette"),
"password": MessageLookupByLibrary.simpleMessage("Password"),
"paste": MessageLookupByLibrary.simpleMessage("Paste"),
@@ -636,27 +648,13 @@ class MessageLookup extends MessageLookupByLibrary {
"Set the Clash listening port",
),
"proxyProviders": MessageLookupByLibrary.simpleMessage("Proxy providers"),
"pruneCache": MessageLookupByLibrary.simpleMessage("Prune cache"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("Pure black mode"),
"qrcode": MessageLookupByLibrary.simpleMessage("QR code"),
"qrcodeDesc": MessageLookupByLibrary.simpleMessage(
"Scan QR code to obtain profile",
),
"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"),
"redo": MessageLookupByLibrary.simpleMessage("redo"),
"regExp": MessageLookupByLibrary.simpleMessage("RegExp"),
@@ -668,9 +666,6 @@ class MessageLookup extends MessageLookupByLibrary {
"remoteDestination": MessageLookupByLibrary.simpleMessage(
"Remote destination",
),
"remoteRecoveryDesc": MessageLookupByLibrary.simpleMessage(
"Recovery data from WebDAV",
),
"remove": MessageLookupByLibrary.simpleMessage("Remove"),
"rename": MessageLookupByLibrary.simpleMessage("Rename"),
"request": MessageLookupByLibrary.simpleMessage("Request"),
@@ -695,6 +690,28 @@ class MessageLookup extends MessageLookupByLibrary {
"restartCoreTip": MessageLookupByLibrary.simpleMessage(
"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"),
"routeAddressDesc": MessageLookupByLibrary.simpleMessage(
"Config listen route address",
@@ -806,6 +823,9 @@ class MessageLookup extends MessageLookupByLibrary {
"Remove extra delays such as handshaking",
),
"unknown": MessageLookupByLibrary.simpleMessage("Unknown"),
"unknownNetworkError": MessageLookupByLibrary.simpleMessage(
"Unknown network error",
),
"unnamed": MessageLookupByLibrary.simpleMessage("Unnamed"),
"update": MessageLookupByLibrary.simpleMessage("Update"),
"upload": MessageLookupByLibrary.simpleMessage("Upload"),

View File

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

View File

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

View File

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

View File

@@ -999,26 +999,6 @@ class AppLocalizations {
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`
String get account {
return Intl.message('Account', name: 'account', desc: '', args: []);
@@ -1029,41 +1009,6 @@ class AppLocalizations {
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`
String get backupSuccess {
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`
String get localBackupDesc {
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`
String get mode {
return Intl.message('Mode', name: 'mode', desc: '', args: []);
@@ -2934,31 +2859,31 @@ class AppLocalizations {
return Intl.message('Contact me', name: 'contactMe', desc: '', args: []);
}
/// `Recovery strategy`
String get recoveryStrategy {
/// `Restore strategy`
String get restoreStrategy {
return Intl.message(
'Recovery strategy',
name: 'recoveryStrategy',
'Restore strategy',
name: 'restoreStrategy',
desc: '',
args: [],
);
}
/// `Override`
String get recoveryStrategy_override {
String get restoreStrategy_override {
return Intl.message(
'Override',
name: 'recoveryStrategy_override',
name: 'restoreStrategy_override',
desc: '',
args: [],
);
}
/// `Compatible`
String get recoveryStrategy_compatible {
String get restoreStrategy_compatible {
return Intl.message(
'Compatible',
name: 'recoveryStrategy_compatible',
name: 'restoreStrategy_compatible',
desc: '',
args: [],
);
@@ -3663,6 +3588,166 @@ class AppLocalizations {
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> {

View File

@@ -1,65 +1,31 @@
import 'dart:async';
import 'dart:io';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/plugins/tile.dart';
import 'package:fl_clash/pages/error.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'application.dart';
import 'common/common.dart';
import 'core/controller.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final version = await system.version;
await globalState.initApp(version);
HttpOverrides.global = FlClashHttpOverrides();
runApp(ProviderScope(child: const Application()));
}
@pragma('vm:entry-point')
Future<void> _service(List<String> flags) async {
WidgetsFlutterBinding.ensureInitialized();
globalState.isService = true;
await globalState.init();
await coreController.preload();
tile?.addListener(
_TileListenerWithService(
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();
try {
WidgetsFlutterBinding.ensureInitialized();
final version = await system.version;
final container = await globalState.init(version);
HttpOverrides.global = FlClashHttpOverrides();
runApp(
UncontrolledProviderScope(
container: container,
child: const Application(),
),
);
} catch (e, s) {
return runApp(
MaterialApp(
home: InitErrorScreen(error: e, stack: s),
),
);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/core/core.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/service.dart';
import 'package:fl_clash/providers/providers.dart';
@@ -27,9 +28,14 @@ class _AndroidContainerState extends ConsumerState<AndroidManager>
) {
app?.updateExcludeFromRecents(next);
}, fireImmediately: true);
ref.listenManual(androidStateProvider, (prev, next) {
ref.listenManual(sharedStateProvider, (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);

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/manager/window_manager.dart';
import 'package:fl_clash/providers/providers.dart';
@@ -25,26 +26,19 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
ref.listenManual(layoutChangeProvider, (prev, next) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (prev != next) {
globalState.computeHeightMapCache = {};
}
});
});
ref.listenManual(checkIpProvider, (prev, next) {
if (prev != next && next.b) {
detectionState.startCheck();
if (prev != next && next.a && next.c) {
ref.read(networkDetectionProvider.notifier).startCheck();
}
}, fireImmediately: true);
ref.listenManual(configStateProvider, (prev, next) {
});
ref.listenManual(configProvider, (prev, next) {
if (prev != next) {
globalState.appController.savePreferencesDebounce();
appController.savePreferencesDebounce();
}
});
ref.listenManual(needUpdateGroupsProvider, (prev, next) {
if (prev != next) {
globalState.appController.updateGroupsDebounce();
appController.updateGroupsDebounce();
}
});
if (window == null) {
@@ -73,20 +67,18 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
commonPrint.log('$state');
if (state == AppLifecycleState.resumed) {
render?.resume();
}
if (state == AppLifecycleState.resumed) {
WidgetsBinding.instance.addPostFrameCallback((_) {
detectionState.tryStartCheck();
appController.tryCheckIp();
if (system.isAndroid) {
appController.tryStartCore();
}
});
if (system.isAndroid) {
globalState.appController.tryStartCore();
}
}
}
@override
void didChangePlatformBrightness() {
globalState.appController.updateBrightness();
appController.updateBrightness();
}
@override
@@ -132,20 +124,20 @@ class AppSidebarContainer extends ConsumerWidget {
const AppSidebarContainer({super.key, required this.child});
Widget _buildLoading() {
return Consumer(
builder: (_, ref, _) {
final loading = ref.watch(loadingProvider);
final isMobileView = ref.watch(isMobileViewProvider);
return loading && !isMobileView
? RotatedBox(
quarterTurns: 1,
child: const LinearProgressIndicator(),
)
: Container();
},
);
}
// Widget _buildLoading() {
// return Consumer(
// builder: (_, ref, _) {
// final loading = ref.watch(loadingProvider);
// final isMobileView = ref.watch(isMobileViewProvider);
// return loading && !isMobileView
// ? RotatedBox(
// quarterTurns: 1,
// child: const LinearProgressIndicator(),
// )
// : Container();
// },
// );
// }
Widget _buildBackground({
required BuildContext context,
@@ -187,84 +179,74 @@ class AppSidebarContainer extends ConsumerWidget {
_buildBackground(
context: context,
child: SafeArea(
child: Stack(
alignment: Alignment.topRight,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (system.isMacOS) SizedBox(height: 22),
SizedBox(height: 10),
if (!system.isMacOS) ...[
ClipRect(child: AppIcon()),
SizedBox(height: 12),
],
Expanded(
child: ScrollConfiguration(
behavior: HiddenBarScrollBehavior(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: NavigationRail(
scrollable: true,
minExtendedWidth: 200,
backgroundColor: Colors.transparent,
selectedLabelTextStyle: context
.textTheme
.labelLarge!
.copyWith(
color: context.colorScheme.onSurface,
),
unselectedLabelTextStyle: context
.textTheme
.labelLarge!
.copyWith(
color: context.colorScheme.onSurface,
),
destinations: navigationItems
.map(
(e) => NavigationRailDestination(
icon: e.icon,
label: Text(Intl.message(e.label.name)),
),
)
.toList(),
onDestinationSelected: (index) {
globalState.appController.toPage(
navigationItems[index].label,
);
},
extended: false,
selectedIndex: currentIndex,
labelType: showLabel
? NavigationRailLabelType.all
: NavigationRailLabelType.none,
),
),
],
if (system.isMacOS) SizedBox(height: 22),
SizedBox(height: 10),
if (!system.isMacOS) ...[
ClipRect(child: AppIcon()),
SizedBox(height: 12),
],
Expanded(
child: ScrollConfiguration(
behavior: HiddenBarScrollBehavior(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: NavigationRail(
scrollable: true,
minExtendedWidth: 200,
backgroundColor: Colors.transparent,
selectedLabelTextStyle: context
.textTheme
.labelLarge!
.copyWith(color: context.colorScheme.onSurface),
unselectedLabelTextStyle: context
.textTheme
.labelLarge!
.copyWith(color: context.colorScheme.onSurface),
destinations: navigationItems
.map(
(e) => NavigationRailDestination(
icon: e.icon,
label: Text(Intl.message(e.label.name)),
),
)
.toList(),
onDestinationSelected: (index) {
appController.toPage(
navigationItems[index].label,
);
},
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/controller.dart';
import 'package:fl_clash/core/core.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
@@ -29,14 +30,17 @@ class _CoreContainerState extends ConsumerState<CoreManager>
void initState() {
super.initState();
coreEventManager.addListener(this);
ref.listenManual(needSetupProvider, (prev, next) {
if (prev != next) {
globalState.appController.handleChangeProfile();
}
});
ref.listenManual(
currentSetupStateProvider.select((state) => state?.profileId),
(prev, next) {
if (prev != next) {
appController.fullSetup();
}
},
);
ref.listenManual(updateParamsProvider, (prev, next) {
if (prev != next) {
globalState.appController.updateClashConfigDebounce();
appController.updateConfigDebounce();
}
});
ref.listenManual(appSettingProvider.select((state) => state.openLogs), (
@@ -60,7 +64,6 @@ class _CoreContainerState extends ConsumerState<CoreManager>
@override
Future<void> onDelay(Delay delay) async {
super.onDelay(delay);
final appController = globalState.appController;
appController.setDelay(delay);
debouncer.call(FunctionTag.updateDelay, () async {
appController.updateGroupsDebounce();
@@ -88,23 +91,21 @@ class _CoreContainerState extends ConsumerState<CoreManager>
.read(providersProvider.notifier)
.setProvider(await coreController.getExternalProvider(providerName));
debouncer.call(FunctionTag.loadedProvider, () async {
globalState.appController.updateGroupsDebounce();
appController.updateGroupsDebounce();
}, duration: const Duration(milliseconds: 5000));
super.onLoaded(providerName);
}
@override
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) {
return;
}
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);
}
}

View File

@@ -1,8 +1,8 @@
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/common.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -11,10 +11,7 @@ import 'package:hotkey_manager/hotkey_manager.dart';
class HotKeyManager extends ConsumerStatefulWidget {
final Widget child;
const HotKeyManager({
super.key,
required this.child,
});
const HotKeyManager({super.key, required this.child});
@override
ConsumerState<HotKeyManager> createState() => _HotKeyManagerState();
@@ -24,29 +21,25 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
@override
void initState() {
super.initState();
ref.listenManual(
hotKeyActionsProvider,
(prev, next) {
if (!hotKeyActionListEquality.equals(prev, next)) {
_updateHotKeys(hotKeyActions: next);
}
},
fireImmediately: true,
);
ref.listenManual(hotKeyActionsProvider, (prev, next) {
if (!hotKeyActionListEquality.equals(prev, next)) {
_updateHotKeys(hotKeyActions: next);
}
}, fireImmediately: true);
}
Future<void> _handleHotKeyAction(HotAction action) async {
switch (action) {
case HotAction.mode:
globalState.appController.updateMode();
appController.updateMode();
case HotAction.start:
globalState.appController.updateStart();
appController.updateStart();
case HotAction.view:
globalState.appController.updateVisible();
appController.updateVisible();
case HotAction.proxy:
globalState.appController.updateSystemProxy();
appController.updateSystemProxy();
case HotAction.tun:
globalState.appController.updateTun();
appController.updateTun();
}
}
@@ -54,27 +47,25 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
required List<HotKeyAction> hotKeyActions,
}) async {
await hotKeyManager.unregisterAll();
final hotkeyActionHandles = hotKeyActions.where(
(hotKeyAction) {
return hotKeyAction.key != null && hotKeyAction.modifiers.isNotEmpty;
},
).map<Future>(
(hotKeyAction) async {
final modifiers = hotKeyAction.modifiers
.map((item) => item.toHotKeyModifier())
.toList();
final hotKey = HotKey(
key: PhysicalKeyboardKey(hotKeyAction.key!),
modifiers: modifiers,
);
return await hotKeyManager.register(
hotKey,
keyDownHandler: (_) {
_handleHotKeyAction(hotKeyAction.action);
},
);
},
);
final hotkeyActionHandles = hotKeyActions
.where((hotKeyAction) {
return hotKeyAction.key != null && hotKeyAction.modifiers.isNotEmpty;
})
.map<Future>((hotKeyAction) async {
final modifiers = hotKeyAction.modifiers
.map((item) => item.toHotKeyModifier())
.toList();
final hotKey = HotKey(
key: PhysicalKeyboardKey(hotKeyAction.key!),
modifiers: modifiers,
);
return await hotKeyManager.register(
hotKey,
keyDownHandler: (_) {
_handleHotKeyAction(hotKeyAction.action);
},
);
});
await Future.wait(hotkeyActionHandles);
}
@@ -87,7 +78,7 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
child: Actions(
actions: {
CloseWindowIntent: CallbackAction<CloseWindowIntent>(
onInvoke: (_) => globalState.appController.handleBackOrExit(),
onInvoke: (_) => appController.handleBackOrExit(),
),
DoNothingIntent: CallbackAction<DoNothingIntent>(
onInvoke: (_) => null,
@@ -100,8 +91,6 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
@override
Widget build(BuildContext context) {
return _buildShortcuts(
widget.child,
);
return _buildShortcuts(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/providers/providers.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:flutter/material.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 {
const LoadingIndicator({super.key});
@override
Widget build(BuildContext context, ref) {
final loading = ref.watch(loadingProvider);
final isMobileView = ref.watch(isMobileViewProvider);
return AnimatedSwitcher(
switchInCurve: Curves.easeIn,
switchOutCurve: Curves.easeOut,
duration: midDuration,
transitionBuilder: (Widget child, Animation<double> animation) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(animation),
child: child,
);
},
child: loading && isMobileView
? Container(
height: 54,
margin: EdgeInsets.only(top: 8, left: 14, right: 14),
child: Material(
elevation: 3,
color: context.colorScheme.surfaceContainer,
surfaceTintColor: context.colorScheme.surfaceTint,
shape: const RoundedSuperellipseBorder(
borderRadius: BorderRadius.all(Radius.circular(14)),
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisSize: MainAxisSize.min,
spacing: 12,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
context.appLocalizations.loading,
style: context.textTheme.labelLarge?.copyWith(
color: context.colorScheme.onSurfaceVariant,
),
),
),
SizedBox(
height: 32,
width: 32,
child: CommonCircleLoading(),
),
],
),
),
),
)
: SizedBox(),
);
}
}
// class LoadingIndicator extends ConsumerWidget {
// const LoadingIndicator({super.key});
//
// @override
// Widget build(BuildContext context, ref) {
// final loading = ref.watch(loadingProvider);
// final isMobileView = ref.watch(isMobileViewProvider);
// return AnimatedSwitcher(
// switchInCurve: Curves.easeIn,
// switchOutCurve: Curves.easeOut,
// duration: midDuration,
// transitionBuilder: (Widget child, Animation<double> animation) {
// return SlideTransition(
// position: Tween<Offset>(
// begin: const Offset(1, 0),
// end: Offset.zero,
// ).animate(animation),
// child: child,
// );
// },
// child: loading && isMobileView
// ? Container(
// height: 54,
// margin: EdgeInsets.only(top: 8, left: 14, right: 14),
// child: Material(
// elevation: 3,
// color: context.colorScheme.surfaceContainer,
// surfaceTintColor: context.colorScheme.surfaceTint,
// shape: const RoundedSuperellipseBorder(
// borderRadius: BorderRadius.all(Radius.circular(14)),
// ),
// child: Padding(
// padding: EdgeInsets.symmetric(horizontal: 16),
// child: Row(
// mainAxisSize: MainAxisSize.min,
// spacing: 12,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Flexible(
// child: Text(
// context.appLocalizations.loading,
// style: context.textTheme.labelLarge?.copyWith(
// color: context.colorScheme.onSurfaceVariant,
// ),
// ),
// ),
// SizedBox(
// height: 32,
// width: 32,
// child: CommonCircleLoading(),
// ),
// ],
// ),
// ),
// ),
// )
// : SizedBox(),
// );
// }
// }

View File

@@ -2,6 +2,8 @@ import 'dart:math';
import 'package:fl_clash/common/common.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/state.dart';
import 'package:flutter/material.dart';
@@ -28,17 +30,27 @@ class ThemeManager extends ConsumerWidget {
final iconBrightness = brightness == Brightness.light
? Brightness.dark
: Brightness.light;
globalState.appState = globalState.appState.copyWith(
systemUiOverlayStyle: SystemUiOverlayStyle(
WidgetsBinding.instance.addPostFrameCallback((_) {
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,
statusBarIconBrightness: iconBrightness,
systemNavigationBarIconBrightness: iconBrightness,
systemNavigationBarColor: context.colorScheme.surface,
systemNavigationBarDividerColor: Colors.transparent,
),
);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: globalState.appState.systemUiOverlayStyle,
sized: false,
child: child,
);
@@ -98,7 +110,7 @@ class ThemeManager extends ConsumerWidget {
),
child: LayoutBuilder(
builder: (_, container) {
globalState.appController.updateViewSize(
appController.updateViewSize(
Size(container.maxWidth, container.maxHeight),
);
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/state.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class TileManager extends StatefulWidget {
class TileManager extends ConsumerStatefulWidget {
final Widget child;
const TileManager({super.key, required this.child});
@override
State<TileManager> createState() => _TileContainerState();
ConsumerState<TileManager> createState() => _TileContainerState();
}
class _TileContainerState extends State<TileManager> with TileListener {
class _TileContainerState extends ConsumerState<TileManager> with TileListener {
@override
Widget build(BuildContext context) {
return widget.child;
}
bool get isStart => ref.read(isStartProvider);
@override
Future<void> onStart() async {
if (globalState.appState.isStart) {
if (isStart && coreController.isCompleted) {
return;
}
globalState.appController.updateStatus(true);
appController.updateStatus(true);
app?.tip(appLocalizations.startVpn);
super.onStart();
}
@override
Future<void> onStop() async {
if (!globalState.appState.isStart) {
if (!isStart) {
return;
}
globalState.appController.updateStatus(false);
appController.updateStatus(false);
app?.tip(appLocalizations.stopVpn);
super.onStop();
}

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/selector.dart';
import 'package:flutter/services.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -36,10 +35,6 @@ abstract class AppState with _$AppState {
@Default(false) bool realTunEnable,
@Default(false) bool loading,
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,
}) = _AppState;
}

View File

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

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
@@ -215,15 +217,16 @@ extension TrackerInfosStateExt on TrackerInfosState {
const defaultDavFileName = 'backup.zip';
@freezed
abstract class DAV with _$DAV {
const factory DAV({
abstract class DAVProps with _$DAVProps {
const factory DAVProps({
required String uri,
required String user,
required String password,
@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
@@ -471,19 +474,6 @@ class PopupMenuItemData {
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 {
const CloseWindowIntent();
}
@@ -512,20 +502,24 @@ extension ResultExt on Result {
@freezed
abstract class Script with _$Script {
const factory Script({
required String id,
required int id,
required String label,
required String content,
required DateTime lastUpdateTime,
}) = _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.create({required String label}) {
return Script(
id: snowflake.id,
label: label,
lastUpdateTime: DateTime.now(),
);
}
}
extension ScriptsExt on List<Script> {
Script? get(String? id) {
Script? get(int? id) {
if (id == 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
abstract class DelayState with _$DelayState {
const factory DelayState({required int delay, required bool group}) =
@@ -562,3 +588,11 @@ extension DelayStateExt on DelayState {
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 defaultVpnProps = VpnProps();
const defaultNetworkProps = NetworkProps();
const defaultProxiesStyle = ProxiesStyle();
const defaultProxiesStyleProps = ProxiesStyleProps();
const defaultWindowProps = WindowProps();
const defaultAccessControl = AccessControl();
const defaultAccessControlProps = AccessControlProps();
final defaultThemeProps = ThemeProps(primaryColor: defaultPrimaryColor);
const List<DashboardWidget> defaultDashboardWidgets = [
@@ -82,7 +82,7 @@ abstract class AppSettingProps with _$AppSettingProps {
@Default(true) bool minimizeOnExit,
@Default(false) bool hidden,
@Default(false) bool developerMode,
@Default(RecoveryStrategy.compatible) RecoveryStrategy recoveryStrategy,
@Default(RestoreStrategy.compatible) RestoreStrategy restoreStrategy,
@Default(true) bool showTrayTitle,
}) = _AppSettingProps;
@@ -97,8 +97,8 @@ abstract class AppSettingProps with _$AppSettingProps {
}
@freezed
abstract class AccessControl with _$AccessControl {
const factory AccessControl({
abstract class AccessControlProps with _$AccessControlProps {
const factory AccessControlProps({
@Default(false) bool enable,
@Default(AccessControlMode.rejectSelected) AccessControlMode mode,
@Default([]) List<String> acceptList,
@@ -106,19 +106,19 @@ abstract class AccessControl with _$AccessControl {
@Default(AccessSortType.none) AccessSortType sort,
@Default(true) bool isFilterSystemApp,
@Default(true) bool isFilterNonInternetApp,
}) = _AccessControl;
}) = _AccessControlProps;
factory AccessControl.fromJson(Map<String, Object?> json) =>
_$AccessControlFromJson(json);
factory AccessControlProps.fromJson(Map<String, Object?> json) =>
_$AccessControlPropsFromJson(json);
}
extension AccessControlExt on AccessControl {
extension AccessControlPropsExt on AccessControlProps {
List<String> get currentList => switch (mode) {
AccessControlMode.acceptSelected => acceptList,
AccessControlMode.rejectSelected => rejectList,
};
AccessControl copyWithNewList(List<String> value) => switch (mode) {
AccessControlProps copyWithNewList(List<String> value) => switch (mode) {
AccessControlMode.acceptSelected => copyWith(acceptList: value),
AccessControlMode.rejectSelected => copyWith(rejectList: value),
};
@@ -151,7 +151,7 @@ abstract class VpnProps with _$VpnProps {
@Default(false) bool ipv6,
@Default(true) bool allowBypass,
@Default(false) bool dnsHijacking,
@Default(defaultAccessControl) AccessControl accessControl,
@Default(defaultAccessControlProps) AccessControlProps accessControlProps,
}) = _VpnProps;
factory VpnProps.fromJson(Map<String, Object?>? json) =>
@@ -173,17 +173,18 @@ abstract class NetworkProps with _$NetworkProps {
}
@freezed
abstract class ProxiesStyle with _$ProxiesStyle {
const factory ProxiesStyle({
abstract class ProxiesStyleProps with _$ProxiesStyleProps {
const factory ProxiesStyleProps({
@Default(ProxiesType.tab) ProxiesType type,
@Default(ProxiesSortType.none) ProxiesSortType sortType,
@Default(ProxiesLayout.standard) ProxiesLayout layout,
@Default(ProxiesIconStyle.standard) ProxiesIconStyle iconStyle,
@Default(ProxyCardType.expand) ProxyCardType cardType,
}) = _ProxiesStyle;
}) = _ProxiesStyleProps;
factory ProxiesStyle.fromJson(Map<String, Object?>? json) =>
json == null ? defaultProxiesStyle : _$ProxiesStyleFromJson(json);
factory ProxiesStyleProps.fromJson(Map<String, Object?>? json) => json == null
? defaultProxiesStyleProps
: _$ProxiesStylePropsFromJson(json);
}
@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
abstract class Config with _$Config {
const factory Config({
int? currentProfileId,
@Default(false) bool overrideDns,
@Default([]) List<HotKeyAction> hotKeyActions,
@JsonKey(fromJson: AppSettingProps.safeFromJson)
@Default(defaultAppSettingProps)
AppSettingProps appSetting,
@Default([]) List<Profile> profiles,
@Default([]) List<HotKeyAction> hotKeyActions,
String? currentProfileId,
@Default(false) bool overrideDns,
DAV? dav,
AppSettingProps appSettingProps,
DAVProps? davProps,
@Default(defaultNetworkProps) NetworkProps networkProps,
@Default(defaultVpnProps) VpnProps vpnProps,
@JsonKey(fromJson: ThemeProps.safeFromJson) required ThemeProps themeProps,
@Default(defaultProxiesStyle) ProxiesStyle proxiesStyle,
@Default(defaultProxiesStyleProps) ProxiesStyleProps proxiesStyleProps,
@Default(defaultWindowProps) WindowProps windowProps,
@Default(defaultClashConfig) ClashConfig patchClashConfig,
@Default([]) List<Script> scripts,
@Default([]) List<Rule> rules,
}) = _Config;
factory Config.fromJson(Map<String, Object?> json) => _$ConfigFromJson(json);
factory Config.compatibleFromJson(Map<String, Object?> json) {
try {
final accessControlMap = json['accessControl'];
final isAccessControl = json['isAccessControl'];
if (accessControlMap != null) {
(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);
factory Config.realFromJson(Map<String, Object?>? json) {
if (json == null) {
return Config(themeProps: defaultThemeProps);
}
return _$ConfigFromJson(json);
}
}

View File

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

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
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
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $AppStateCopyWith<AppState> get copyWith => _$AppStateCopyWithImpl<AppState>(thi
@override
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
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
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;
@useResult
$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
@@ -62,7 +62,7 @@ class _$AppStateCopyWithImpl<$Res>
/// Create a copy of AppState
/// 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(
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
@@ -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 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 SystemUiOverlayStyle,profileOverrideModel: freezed == profileOverrideModel ? _self.profileOverrideModel : profileOverrideModel // 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 SystemUiOverlayStyle,coreStatus: null == coreStatus ? _self.coreStatus : coreStatus // ignore: cast_nullable_to_non_nullable
as CoreStatus,
));
}
@@ -103,18 +99,6 @@ $TrafficCopyWith<$Res> get totalTraffic {
return $TrafficCopyWith<$Res>(_self.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) {
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();
}
@@ -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) {
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');
}
@@ -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) {
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;
}
@@ -253,7 +237,7 @@ return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_tha
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;
@@ -302,28 +286,6 @@ class _AppState implements AppState {
@override@JsonKey() final bool realTunEnable;
@override@JsonKey() final bool loading;
@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;
/// Create a copy of AppState
@@ -336,16 +298,16 @@ _$AppStateCopyWith<_AppState> get copyWith => __$AppStateCopyWithImpl<_AppState>
@override
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
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
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;
@override @useResult
$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
@@ -373,7 +335,7 @@ class __$AppStateCopyWithImpl<$Res>
/// Create a copy of AppState
/// 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(
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
@@ -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 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 SystemUiOverlayStyle,profileOverrideModel: freezed == profileOverrideModel ? _self.profileOverrideModel : profileOverrideModel // 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 SystemUiOverlayStyle,coreStatus: null == coreStatus ? _self.coreStatus : coreStatus // ignore: cast_nullable_to_non_nullable
as CoreStatus,
));
}
@@ -415,18 +373,6 @@ $TrafficCopyWith<$Res> get totalTraffic {
return $TrafficCopyWith<$Res>(_self.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
mixin _$Rule {
String get id; String get value;
int get id; String get value; String? get order;
/// Create a copy of Rule
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -2711,16 +2711,16 @@ $RuleCopyWith<Rule> get copyWith => _$RuleCopyWithImpl<Rule>(this as Rule, _$ide
@override
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)
@override
int get hashCode => Object.hash(runtimeType,id,value);
int get hashCode => Object.hash(runtimeType,id,value,order);
@override
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;
@useResult
$Res call({
String id, String value
int id, String value, String? order
});
@@ -2748,11 +2748,12 @@ class _$RuleCopyWithImpl<$Res>
/// Create a copy of Rule
/// 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(
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 String,
as int,value: null == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
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) {
case _Rule() when $default != null:
return $default(_that.id,_that.value);case _:
return $default(_that.id,_that.value,_that.order);case _:
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) {
case _Rule():
return $default(_that.id,_that.value);case _:
return $default(_that.id,_that.value,_that.order);case _:
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) {
case _Rule() when $default != null:
return $default(_that.id,_that.value);case _:
return $default(_that.id,_that.value,_that.order);case _:
return null;
}
@@ -2893,11 +2894,12 @@ return $default(_that.id,_that.value);case _:
@JsonSerializable()
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);
@override final String id;
@override final int id;
@override final String value;
@override final String? order;
/// Create a copy of Rule
/// with the given fields replaced by the non-null parameter values.
@@ -2912,16 +2914,16 @@ Map<String, dynamic> toJson() {
@override
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)
@override
int get hashCode => Object.hash(runtimeType,id,value);
int get hashCode => Object.hash(runtimeType,id,value,order);
@override
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;
@override @useResult
$Res call({
String id, String value
int id, String value, String? order
});
@@ -2949,11 +2951,12 @@ class __$RuleCopyWithImpl<$Res>
/// Create a copy of Rule
/// 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(
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 String,
as int,value: null == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
as String,order: freezed == order ? _self.order : order // ignore: cast_nullable_to_non_nullable
as String?,
));
}

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