Compare commits

..

1 Commits

Author SHA1 Message Date
chen08209
f06abecb3e Add android separates the core process
Support core status check and force restart

Optimize proxies page and access page

Update flutter and pub dependencies
2025-09-03 10:08:26 +08:00
88 changed files with 2038 additions and 1197 deletions

View File

@@ -54,7 +54,7 @@ Support the following actions
com.follow.clash.action.STOP
com.follow.clash.action.CHANGE
com.follow.clash.action.TOGGLE
```
## Download

View File

@@ -54,7 +54,7 @@ on Mobile:
com.follow.clash.action.STOP
com.follow.clash.action.CHANGE
com.follow.clash.action.TOGGLE
```
## Download

View File

@@ -1,9 +1,9 @@
include: package:flutter_lints/flutter.yaml
analyzer:
plugins:
- custom_lint
exclude:
- lib/l10n/intl/**
errors:
invalid_annotation_target: ignore
linter:
rules:

View File

@@ -29,6 +29,7 @@ android {
ndkVersion = libs.versions.ndkVersion.get()
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
@@ -53,6 +54,12 @@ android {
}
}
packaging {
jniLibs {
useLegacyPackaging = true
}
}
buildTypes {
debug {
isMinifyEnabled = false

View File

@@ -2,10 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<permission
android:name="${applicationId}.permission.RECEIVE_BROADCASTS"
android:protectionLevel="signature" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
@@ -24,28 +20,23 @@
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:name=".Application"
android:banner="@mipmap/ic_banner"
android:extractNativeLibs="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="FlClash">
<activity
android:name="com.follow.clash.MainActivity"
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
@@ -118,29 +109,6 @@
</intent-filter>
</receiver>
<provider
android:name=".FilesProvider"
android:authorities="${applicationId}.files"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS"
android:process=":background">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<meta-data
android:name="flutterEmbedding"
android:value="2" />

View File

@@ -1,28 +1,76 @@
package com.follow.clash
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.util.Base64
import androidx.core.graphics.drawable.toBitmap
import com.follow.clash.common.GlobalState
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume
suspend fun Drawable.getBase64(): String {
val drawable = this
return withContext(Dispatchers.IO) {
val bitmap = drawable.toBitmap()
val byteArrayOutputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.NO_WRAP)
private const val ICON_TTL_DAYS = 1L
suspend fun PackageManager.getPackageIconPath(packageName: String): String =
withContext(Dispatchers.IO) {
val cacheDir = GlobalState.application.cacheDir
val iconDir = File(cacheDir, "icons").apply { mkdirs() }
return@withContext try {
val pkgInfo = getPackageInfo(packageName, 0)
val lastUpdateTime = pkgInfo.lastUpdateTime
val iconFile = File(iconDir, "${packageName}_${lastUpdateTime}.webp")
if (iconFile.exists() && !isExpired(iconFile)) {
return@withContext iconFile.absolutePath
}
iconDir.listFiles()?.forEach { file ->
if (file.name.startsWith(packageName + "_")) file.delete()
}
val icon = getApplicationIcon(packageName)
saveDrawableToFile(icon, iconFile)
iconFile.absolutePath
} catch (_: Exception) {
val defaultIconFile = File(iconDir, "default_icon.webp")
if (!defaultIconFile.exists()) {
saveDrawableToFile(defaultActivityIcon, defaultIconFile)
}
defaultIconFile.absolutePath
}
}
private fun saveDrawableToFile(drawable: Drawable, file: File) {
val bitmap = drawable.toBitmap()
try {
val format = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
Bitmap.CompressFormat.WEBP_LOSSY
}
else -> {
Bitmap.CompressFormat.WEBP
}
}
FileOutputStream(file).use { fos ->
bitmap.compress(format, 90, fos)
}
} finally {
if (!bitmap.isRecycled) bitmap.recycle()
}
}
private fun isExpired(file: File): Boolean {
val now = System.currentTimeMillis()
val age = now - file.lastModified()
return age > TimeUnit.DAYS.toMillis(ICON_TTL_DAYS)
}
suspend fun <T> MethodChannel.awaitResult(
@@ -51,15 +99,12 @@ inline fun <reified T : FlutterPlugin> FlutterEngine.plugin(): T? {
fun <T> MethodChannel.invokeMethodOnMainThread(
method: String,
arguments: Any? = null,
callback: ((Result<T>) -> Unit)? = null
method: String, arguments: Any? = null, callback: ((Result<T>) -> Unit)? = null
) {
Handler(Looper.getMainLooper()).post {
invokeMethod(method, arguments, object : MethodChannel.Result {
override fun success(result: Any?) {
@Suppress("UNCHECKED_CAST")
callback?.invoke(Result.success(result as T))
@Suppress("UNCHECKED_CAST") callback?.invoke(Result.success(result as T))
}
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {

View File

@@ -3,45 +3,40 @@ package com.follow.clash
import com.follow.clash.common.ServiceDelegate
import com.follow.clash.common.intent
import com.follow.clash.service.ICallbackInterface
import com.follow.clash.service.IMessageInterface
import com.follow.clash.service.IRemoteInterface
import com.follow.clash.service.RemoteService
import com.follow.clash.service.models.NotificationParams
import com.follow.clash.service.models.VpnOptions
import java.util.concurrent.atomic.AtomicBoolean
object Service {
private val delegate by lazy {
ServiceDelegate<IRemoteInterface>(
RemoteService::class.intent, ::handleOnServiceCrash
RemoteService::class.intent, ::handleServiceDisconnected
) {
IRemoteInterface.Stub.asInterface(it)
}
}
var onServiceCrash: (() -> Unit)? = null
var onServiceDisconnected: ((String) -> Unit)? = null
private fun handleOnServiceCrash() {
bindingState.set(false)
onServiceCrash?.let {
it()
private fun handleServiceDisconnected(message: String) {
onServiceDisconnected?.let {
it(message)
}
}
private val bindingState = AtomicBoolean(false)
fun bind() {
if (bindingState.compareAndSet(false, true)) {
delegate.bind()
}
delegate.bind()
}
suspend fun invokeAction(
data: String, cb: (result: String?) -> Unit
) {
delegate.useService {
data: String, cb: (result: ByteArray?, isSuccess: Boolean) -> Unit
): Result<Unit> {
return delegate.useService {
it.invokeAction(data, object : ICallbackInterface.Stub() {
override fun onResult(result: String?) {
cb(result)
override fun onResult(result: ByteArray?, isSuccess: Boolean) {
cb(result, isSuccess)
}
})
}
@@ -49,17 +44,17 @@ object Service {
suspend fun updateNotificationParams(
params: NotificationParams
) {
delegate.useService {
): Result<Unit> {
return delegate.useService {
it.updateNotificationParams(params)
}
}
suspend fun setMessageCallback(
cb: (result: String?) -> Unit
) {
delegate.useService {
it.setMessageCallback(object : ICallbackInterface.Stub() {
): Result<Unit> {
return delegate.useService {
it.setMessageCallback(object : IMessageInterface.Stub() {
override fun onResult(result: String?) {
cb(result)
}

View File

@@ -119,9 +119,9 @@ object State {
return@launch
}
appPlugin?.prepare(options.enable) {
runTime = System.currentTimeMillis()
Service.startService(options, true)
runStateFlow.tryEmit(RunState.START)
runTime = System.currentTimeMillis()
}
}
}

View File

@@ -22,7 +22,7 @@ import com.follow.clash.common.Components
import com.follow.clash.common.GlobalState
import com.follow.clash.common.QuickAction
import com.follow.clash.common.quickIntent
import com.follow.clash.getBase64
import com.follow.clash.getPackageIconPath
import com.follow.clash.models.Package
import com.google.gson.Gson
import io.flutter.embedding.android.FlutterActivity
@@ -150,26 +150,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
}
"getPackageIcon" -> {
scope.launch {
val packageName = call.argument<String>("packageName")
if (packageName == null) {
result.success(null)
return@launch
}
val packageIcon = getPackageIcon(packageName)
packageIcon.let {
if (it != null) {
result.success(it)
return@launch
}
if (iconMap["default"] == null) {
iconMap["default"] =
GlobalState.application.packageManager?.defaultActivityIcon?.getBase64()
}
result.success(iconMap["default"])
return@launch
}
}
handleGetPackageIcon(call, result)
}
"tip" -> {
@@ -184,6 +165,19 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
}
}
private fun handleGetPackageIcon(call: MethodCall, result: Result) {
scope.launch {
val packageName = call.argument<String>("packageName")
if (packageName == null) {
result.success("")
return@launch
}
val path =
GlobalState.application.packageManager.getPackageIconPath(packageName)
result.success(path)
}
}
private fun initShortcuts(label: String) {
val shortcut = with(ShortcutInfoCompat.Builder(GlobalState.application, "toggle")) {
setShortLabel(label)
@@ -223,18 +217,6 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
}
}
private suspend fun getPackageIcon(packageName: String): String? {
val packageManager = GlobalState.application.packageManager
if (iconMap[packageName] == null) {
iconMap[packageName] = try {
packageManager?.getApplicationIcon(packageName)?.getBase64()
} catch (_: Exception) {
null
}
}
return iconMap[packageName]
}
private fun getPackages(): List<Package> {
val packageManager = GlobalState.application.packageManager

View File

@@ -5,6 +5,7 @@ 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.formatString
import com.follow.clash.invokeMethodOnMainThread
import com.follow.clash.models.AppState
import com.follow.clash.service.models.NotificationParams
@@ -68,8 +69,12 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
private fun handleInvokeAction(call: MethodCall, result: MethodChannel.Result) {
launch {
val data = call.arguments<String>()!!
Service.invokeAction(data) {
result.success(it)
val res = mutableListOf<ByteArray>()
Service.invokeAction(data) { byteArray, isSuccess ->
res.add(byteArray ?: byteArrayOf())
if (isSuccess) {
result.success(res.formatString())
}
}
}
}
@@ -99,9 +104,9 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
}
private fun onServiceCrash() {
private fun onServiceDisconnected(message: String) {
State.runStateFlow.tryEmit(RunState.STOP)
flutterMethodChannel.invokeMethodOnMainThread<Any>("crash", null)
flutterMethodChannel.invokeMethodOnMainThread<Any>("crash", message)
}
private fun handleSyncState(call: MethodCall, result: MethodChannel.Result) {
@@ -114,10 +119,12 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
stopText = params.stopText,
onlyStatisticsProxy = params.onlyStatisticsProxy
)
)
result.success(true)
).onSuccess {
result.success("")
}.onFailure {
result.success(it.message)
}
}
}
fun handleInit(result: MethodChannel.Result) {
@@ -125,10 +132,14 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
launch {
Service.setMessageCallback {
handleSendEvent(it)
}.onSuccess {
result.success("")
}.onFailure {
result.success(it.message)
}
result.success(true)
}
Service.onServiceCrash = ::onServiceCrash
Service.onServiceDisconnected = ::onServiceDisconnected
}
private fun handleGetRunTime(result: MethodChannel.Result) {

View File

@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<permission
android:name="${applicationId}.permission.RECEIVE_BROADCASTS"
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.RECEIVE_BROADCASTS" />
</manifest>

View File

@@ -13,14 +13,21 @@ import android.content.Context.RECEIVER_NOT_EXPORTED
import android.content.Intent
import android.content.IntentFilter
import android.content.ServiceConnection
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.os.RemoteException
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.retryWhen
import kotlinx.coroutines.withContext
import java.nio.charset.Charset
import kotlin.reflect.KClass
//fun Context.startForegroundServiceCompat(intent: Intent?) {
@@ -36,19 +43,23 @@ val KClass<*>.intent: Intent
fun Service.startForegroundCompat(id: Int, notification: Notification) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(id, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC)
startForeground(id, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else {
startForeground(id, notification)
}
}
val ComponentName.intent: Intent
get() = Intent().apply {
setComponent(this@intent)
setPackage(GlobalState.packageName)
}
val QuickAction.action: String
get() = "${GlobalState.application.packageName}.action.${this.name}"
val QuickAction.quickIntent: Intent
get() = Intent().apply {
setComponent(Components.TEMP_ACTIVITY)
setPackage(GlobalState.packageName)
get() = Components.TEMP_ACTIVITY.intent.apply {
action = this@quickIntent.action
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
}
@@ -57,9 +68,7 @@ val BroadcastAction.action: String
get() = "${GlobalState.application.packageName}.intent.action.${this.name}"
val BroadcastAction.quickIntent: Intent
get() = Intent().apply {
setComponent(Components.BROADCAST_RECEIVER)
setPackage(GlobalState.packageName)
get() = Components.BROADCAST_RECEIVER.intent.apply {
action = this@quickIntent.action
}
@@ -125,62 +134,54 @@ fun Context.receiveBroadcastFlow(
}
sealed class BindServiceEvent<out T : IBinder> {
data class Connected<T : IBinder>(val binder: T) : BindServiceEvent<T>()
object Disconnected : BindServiceEvent<Nothing>()
object Crashed : BindServiceEvent<Nothing>()
}
inline fun <reified T : IBinder> Context.bindServiceFlow(
intent: Intent,
flags: Int = Context.BIND_AUTO_CREATE,
): Flow<BindServiceEvent<T>> = callbackFlow {
var currentBinder: IBinder? = null
val deathRecipient = IBinder.DeathRecipient {
trySend(BindServiceEvent.Crashed)
}
maxRetries: Int = 5,
retryDelayMillis: Long = 200L
): Flow<Pair<IBinder?, String>> = callbackFlow {
val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
if (binder != null) {
try {
binder.linkToDeath(deathRecipient, 0)
currentBinder = binder
@Suppress("UNCHECKED_CAST") val casted = binder as? T
if (casted != null) {
trySend(BindServiceEvent.Connected(casted))
trySend(Pair(casted, ""))
} else {
GlobalState.log("Binder is not of type ${T::class.java}")
trySend(BindServiceEvent.Disconnected)
trySend(Pair(null, "Binder is not of type ${T::class.java}"))
}
} catch (e: RemoteException) {
GlobalState.log("Failed to link to death: ${e.message}")
binder.unlinkToDeath(deathRecipient, 0)
trySend(BindServiceEvent.Disconnected)
trySend(Pair(null, "Failed to link to death: ${e.message}"))
}
} else {
trySend(BindServiceEvent.Disconnected)
trySend(Pair(null, "Binder empty"))
}
}
override fun onServiceDisconnected(name: ComponentName?) {
GlobalState.log("Service disconnected")
currentBinder?.unlinkToDeath(deathRecipient, 0)
currentBinder = null
trySend(BindServiceEvent.Disconnected)
trySend(Pair(null, "Service disconnected"))
}
}
if (!bindService(intent, connection, flags)) {
GlobalState.log("Failed to bind service")
trySend(BindServiceEvent.Disconnected)
close()
return@callbackFlow
val success = withContext(Dispatchers.Main) {
bindService(intent, connection, flags)
}
if (!success) {
throw IllegalStateException("bindService() failed, will retry")
}
awaitClose {
currentBinder?.unlinkToDeath(deathRecipient, 0)
unbindService(connection)
Handler(Looper.getMainLooper()).post {
unbindService(connection)
}
}
}.retryWhen { cause, attempt ->
if (attempt < maxRetries && cause is Exception) {
delay(retryDelayMillis)
true
} else {
false
}
}
@@ -201,4 +202,37 @@ val Long.formatBytes: String
} else {
"%.1f${units[unitIndex]}".format(size)
}
}
}
fun String.chunkedForAidl(charset: Charset = Charsets.UTF_8): List<ByteArray> {
val allBytes = toByteArray(charset)
val total = allBytes.size
val maxBytes = when {
total <= 100 * 1024 -> total
total <= 1024 * 1024 -> 64 * 1024
total <= 10 * 1024 * 1024 -> 128 * 1024
else -> 256 * 1024
}
val result = mutableListOf<ByteArray>()
var index = 0
while (index < total) {
val end = minOf(index + maxBytes, total)
result.add(allBytes.copyOfRange(index, end))
index = end
}
return result
}
fun <T : List<ByteArray>> T.formatString(charset: Charset = Charsets.UTF_8): String {
val totalSize = this.sumOf { it.size }
val combined = ByteArray(totalSize)
var offset = 0
forEach { byteArray ->
byteArray.copyInto(combined, offset)
offset += byteArray.size
}
return String(combined, charset)
}

View File

@@ -6,74 +6,67 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.retryWhen
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.coroutines.withTimeout
import java.util.concurrent.atomic.AtomicBoolean
class ServiceDelegate<T>(
private val intent: Intent,
private val onServiceDisconnected: (() -> Unit)? = null,
private val onServiceCrash: (() -> Unit)? = null,
private val onServiceDisconnected: ((String) -> Unit)? = null,
private val interfaceCreator: (IBinder) -> T,
) : CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
private val _service = MutableStateFlow<T?>(null)
private val _bindingState = AtomicBoolean(false)
val service: StateFlow<T?> = _service
private var _serviceState = MutableStateFlow<Pair<T?, String>?>(null)
private var bindJob: Job? = null
private fun handleBindEvent(event: BindServiceEvent<IBinder>) {
when (event) {
is BindServiceEvent.Connected -> {
_service.value = event.binder.let(interfaceCreator)
}
val serviceState: StateFlow<Pair<T?, String>?> = _serviceState
private var job: Job? = null
is BindServiceEvent.Disconnected -> {
_service.value = null
onServiceDisconnected?.invoke()
}
is BindServiceEvent.Crashed -> {
_service.value = null
onServiceCrash?.invoke()
}
private fun handleBind(data: Pair<IBinder?, String>) {
data.first?.let {
_serviceState.value = Pair(interfaceCreator(it), data.second)
} ?: run {
_serviceState.value = Pair(null, data.second)
unbind()
onServiceDisconnected?.invoke(data.second)
_bindingState.set(false)
}
}
fun bind() {
unbind()
bindJob = launch {
GlobalState.application.bindServiceFlow<IBinder>(intent).collect { it ->
handleBindEvent(it)
if (_bindingState.compareAndSet(false, true)) {
job?.cancel()
job = null
_serviceState.value = null
job = launch {
GlobalState.application.bindServiceFlow<IBinder>(intent).collect { handleBind(it) }
}
}
}
suspend inline fun <R> useService(
crossinline block: (T) -> R
timeoutMillis: Long = 5000, crossinline block: (T) -> R
): Result<R> {
return withTimeoutOrNull(10_000) {
service.filterNotNull().retryWhen { _, _ ->
delay(200)
true
}.first()
}?.let { s ->
try {
Result.success(block(s))
} catch (e: Exception) {
Result.failure(e)
return runCatching {
withTimeout(timeoutMillis) {
val state = serviceState.filterNotNull().first()
state.first?.let {
block(it)
} ?: throw Exception(state.second)
}
} ?: Result.failure(Exception("Service connection timeout"))
}
}
fun unbind() {
_service.value = null
bindJob?.cancel()
bindJob = null
if (_bindingState.compareAndSet(true, false)) {
job?.cancel()
job = null
_serviceState.value = null
}
}
}

View File

@@ -9,16 +9,14 @@
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject thiz, jint fd, jobject cb,
jstring address, jstring dns) {
jstring stack, jstring address, jstring dns) {
const auto interface = new_global(cb);
scoped_string addressChar = get_string(address);
scoped_string dnsChar = get_string(dns);
startTUN(interface, fd, addressChar, dnsChar);
startTUN(interface, fd, get_string(stack), get_string(address), get_string(dns));
}
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_stopTun(JNIEnv *) {
Java_com_follow_clash_core_Core_stopTun(JNIEnv *env, jobject thiz) {
stopTun();
}
@@ -31,16 +29,14 @@ Java_com_follow_clash_core_Core_forceGC(JNIEnv *env, jobject thiz) {
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_updateDNS(JNIEnv *env, jobject thiz, jstring dns) {
scoped_string dnsChar = get_string(dns);
updateDns(dnsChar);
updateDns(get_string(dns));
}
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_invokeAction(JNIEnv *env, jobject thiz, jstring data, jobject cb) {
const auto interface = new_global(cb);
scoped_string dataChar = get_string(data);
invokeAction(interface, dataChar);
invokeAction(interface, get_string(data));
}
extern "C"
@@ -54,16 +50,14 @@ extern "C"
JNIEXPORT jstring JNICALL
Java_com_follow_clash_core_Core_getTraffic(JNIEnv *env, jobject thiz,
const jboolean only_statistics_proxy) {
scoped_string res = getTraffic(only_statistics_proxy);
return new_string(res);
return new_string(getTraffic(only_statistics_proxy));
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_follow_clash_core_Core_getTotalTraffic(JNIEnv *env, jobject thiz,
const jboolean only_statistics_proxy) {
scoped_string res = getTotalTraffic(only_statistics_proxy);
return new_string(res);
return new_string(getTotalTraffic(only_statistics_proxy));
}
extern "C"
@@ -83,6 +77,10 @@ static void release_jni_object_impl(void *obj) {
del_global(static_cast<jobject>(obj));
}
static void free_string_impl(char *str) {
free(str);
}
static void call_tun_interface_protect_impl(void *tun_interface, const int fd) {
ATTACH_JNI();
env->CallVoidMethod(static_cast<jobject>(tun_interface),
@@ -103,8 +101,7 @@ call_tun_interface_resolve_process_impl(void *tun_interface, const int protocol,
new_string(source),
new_string(target),
uid));
scoped_string packageNameChar = get_string(packageName);
return packageNameChar;
return get_string(packageName);
}
static void call_invoke_interface_result_impl(void *invoke_interface, const char *data) {
@@ -139,6 +136,7 @@ JNI_OnLoad(JavaVM *vm, void *) {
resolve_process_func = &call_tun_interface_resolve_process_impl;
result_func = &call_invoke_interface_result_impl;
release_object_func = &release_jni_object_impl;
free_string_func = &free_string_impl;
return JNI_VERSION_1_6;
}
@@ -146,7 +144,7 @@ JNI_OnLoad(JavaVM *vm, void *) {
extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject thiz, jint fd, jobject cb,
jstring address, jstring dns) {
jstring stack, jstring address, jstring dns) {
}
extern "C"

View File

@@ -8,6 +8,7 @@ data object Core {
private external fun startTun(
fd: Int,
cb: TunInterface,
stack: String,
address: String,
dns: String,
)
@@ -29,6 +30,7 @@ data object Core {
fd: Int,
protect: (Int) -> Boolean,
resolverProcess: (protocol: Int, source: InetSocketAddress, target: InetSocketAddress, uid: Int) -> String,
stack: String,
address: String,
dns: String,
) {
@@ -53,6 +55,7 @@ data object Core {
)
}
},
stack,
address,
dns
)

View File

@@ -5,11 +5,11 @@
<application>
<service
android:name="com.follow.clash.service.VpnService"
android:name=".VpnService"
android:exported="false"
android:foregroundServiceType="dataSync"
android:foregroundServiceType="specialUse"
android:permission="android.permission.BIND_VPN_SERVICE"
android:process=":background">
android:process=":remote">
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
@@ -19,18 +19,31 @@
</service>
<service
android:name="com.follow.clash.service.CommonService"
android:name=".CommonService"
android:exported="false"
android:foregroundServiceType="dataSync"
android:process=":background">
android:foregroundServiceType="specialUse"
android:process=":remote">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="service" />
</service>
<service
android:name="com.follow.clash.service.RemoteService"
android:name=".RemoteService"
android:enabled="true"
android:exported="false"
android:process=":background" />
android:process=":remote" />
<provider
android:name=".FilesProvider"
android:authorities="${applicationId}.files"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS"
android:process=":remote">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
</application>
</manifest>

View File

@@ -2,5 +2,5 @@
package com.follow.clash.service;
interface ICallbackInterface {
void onResult(String result);
void onResult(in byte[] result, boolean isSuccess);
}

View File

@@ -0,0 +1,6 @@
// IMessageInterface.aidl
package com.follow.clash.service;
interface IMessageInterface {
void onResult(String result);
}

View File

@@ -2,6 +2,7 @@
package com.follow.clash.service;
import com.follow.clash.service.ICallbackInterface;
import com.follow.clash.service.IMessageInterface;
import com.follow.clash.service.models.VpnOptions;
import com.follow.clash.service.models.NotificationParams;
@@ -10,5 +11,5 @@ interface IRemoteInterface {
void updateNotificationParams(in NotificationParams params);
void startService(in VpnOptions options,in boolean inApp);
void stopService();
void setMessageCallback(in ICallbackInterface messageCallback);
void setMessageCallback(in IMessageInterface messageCallback);
}

View File

@@ -1,35 +1,33 @@
package com.follow.clash
package com.follow.clash.service
import android.database.Cursor
import android.database.MatrixCursor
import android.os.CancellationSignal
import android.os.ParcelFileDescriptor
import android.provider.DocumentsContract.Document
import android.provider.DocumentsContract.Root
import android.provider.DocumentsContract
import android.provider.DocumentsProvider
import java.io.File
import java.io.FileNotFoundException
class FilesProvider : DocumentsProvider() {
companion object {
private const val DEFAULT_ROOT_ID = "0"
private val DEFAULT_DOCUMENT_COLUMNS = arrayOf(
Document.COLUMN_DOCUMENT_ID,
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_MIME_TYPE,
Document.COLUMN_FLAGS,
Document.COLUMN_SIZE,
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE,
DocumentsContract.Document.COLUMN_FLAGS,
DocumentsContract.Document.COLUMN_SIZE,
)
private val DEFAULT_ROOT_COLUMNS = arrayOf(
Root.COLUMN_ROOT_ID,
Root.COLUMN_FLAGS,
Root.COLUMN_ICON,
Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY,
Root.COLUMN_DOCUMENT_ID
DocumentsContract.Root.COLUMN_ROOT_ID,
DocumentsContract.Root.COLUMN_FLAGS,
DocumentsContract.Root.COLUMN_ICON,
DocumentsContract.Root.COLUMN_TITLE,
DocumentsContract.Root.COLUMN_SUMMARY,
DocumentsContract.Root.COLUMN_DOCUMENT_ID
)
}
@@ -40,12 +38,12 @@ class FilesProvider : DocumentsProvider() {
override fun queryRoots(projection: Array<String>?): Cursor {
return MatrixCursor(projection ?: DEFAULT_ROOT_COLUMNS).apply {
newRow().apply {
add(Root.COLUMN_ROOT_ID, DEFAULT_ROOT_ID)
add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY)
add(Root.COLUMN_ICON, R.mipmap.ic_launcher)
add(Root.COLUMN_TITLE, "FlClash")
add(Root.COLUMN_SUMMARY, "Data")
add(Root.COLUMN_DOCUMENT_ID, "/")
add(DocumentsContract.Root.COLUMN_ROOT_ID, DEFAULT_ROOT_ID)
add(DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_LOCAL_ONLY)
add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_service)
add(DocumentsContract.Root.COLUMN_TITLE, "FlClash")
add(DocumentsContract.Root.COLUMN_SUMMARY, "Data")
add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, "/")
}
}
}
@@ -87,20 +85,20 @@ class FilesProvider : DocumentsProvider() {
private fun includeFile(result: MatrixCursor, file: File) {
result.newRow().apply {
add(Document.COLUMN_DOCUMENT_ID, file.absolutePath)
add(Document.COLUMN_DISPLAY_NAME, file.name)
add(Document.COLUMN_SIZE, file.length())
add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, file.absolutePath)
add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, file.name)
add(DocumentsContract.Document.COLUMN_SIZE, file.length())
add(
Document.COLUMN_FLAGS,
Document.FLAG_SUPPORTS_WRITE or Document.FLAG_SUPPORTS_DELETE
DocumentsContract.Document.COLUMN_FLAGS,
DocumentsContract.Document.FLAG_SUPPORTS_WRITE or DocumentsContract.Document.FLAG_SUPPORTS_DELETE
)
add(Document.COLUMN_MIME_TYPE, getDocumentType(file))
add(DocumentsContract.Document.COLUMN_MIME_TYPE, getDocumentType(file))
}
}
private fun getDocumentType(file: File): String {
return if (file.isDirectory) {
Document.MIME_TYPE_DIR
DocumentsContract.Document.MIME_TYPE_DIR
} else {
"application/octet-stream"
}

View File

@@ -4,6 +4,7 @@ import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.follow.clash.common.ServiceDelegate
import com.follow.clash.common.chunkedForAidl
import com.follow.clash.common.intent
import com.follow.clash.core.Core
import com.follow.clash.service.models.NotificationParams
@@ -22,13 +23,14 @@ class RemoteService : Service(),
launch {
delegate?.useService { service ->
service.stop()
delegate?.unbind()
}
delegate?.unbind()
}
}
fun onServiceDisconnected() {
handleStopService()
private fun handleServiceDisconnected(message: String) {
intent = null
delegate = null
}
private fun handleStartService() {
@@ -39,7 +41,7 @@ class RemoteService : Service(),
}
if (intent != nextIntent) {
delegate?.unbind()
delegate = ServiceDelegate(nextIntent, ::onServiceDisconnected) { binder ->
delegate = ServiceDelegate(nextIntent, ::handleServiceDisconnected) { binder ->
when (binder) {
is VpnService.LocalBinder -> binder.getService()
is CommonService.LocalBinder -> binder.getService()
@@ -55,9 +57,15 @@ class RemoteService : Service(),
}
}
private val binder: IRemoteInterface.Stub = object : IRemoteInterface.Stub() {
private val binder = object : IRemoteInterface.Stub() {
override fun invokeAction(data: String, callback: ICallbackInterface) {
Core.invokeAction(data, callback::onResult)
Core.invokeAction(data) {
val chunks = it?.chunkedForAidl() ?: listOf()
val totalSize = chunks.size
chunks.forEachIndexed { index, chunk ->
callback.onResult(chunk, totalSize - 1 == index)
}
}
}
override fun updateNotificationParams(params: NotificationParams?) {
@@ -76,7 +84,7 @@ class RemoteService : Service(),
handleStopService()
}
override fun setMessageCallback(messageCallback: ICallbackInterface) {
override fun setMessageCallback(messageCallback: IMessageInterface) {
setMessageCallback(messageCallback::onResult)
}
}

View File

@@ -12,7 +12,6 @@ import android.util.Log
import androidx.core.content.getSystemService
import com.follow.clash.common.AccessControlMode
import com.follow.clash.common.BroadcastAction
import com.follow.clash.common.GlobalState
import com.follow.clash.common.sendBroadcast
import com.follow.clash.core.Core
import com.follow.clash.service.models.VpnOptions
@@ -108,7 +107,6 @@ class VpnService : SystemVpnService(), IBaseService,
try {
val isSuccess = super.onTransact(code, data, reply, flags)
if (!isSuccess) {
GlobalState.log("onTransact error ===>")
BroadcastAction.STOP.sendBroadcast()
}
return isSuccess
@@ -222,6 +220,7 @@ class VpnService : SystemVpnService(), IBaseService,
fd,
protect = this::protect,
resolverProcess = this::resolverProcess,
options.stack,
options.address,
options.dns
)

View File

@@ -23,6 +23,7 @@ data class VpnOptions(
val allowBypass: Boolean,
val systemProxy: Boolean,
val bypassDomain: List<String>,
val stack: String,
val routeAddress: List<String>,
) : Parcelable

View File

@@ -28,6 +28,7 @@ class NetworkObserveModule(private val service: Service) : Module() {
private val connectivity by lazy {
service.getSystemService<ConnectivityManager>()
}
private var preDnsList = listOf<String>()
private val request = NetworkRequest.Builder().apply {
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
@@ -61,6 +62,7 @@ class NetworkObserveModule(private val service: Service) : Module() {
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
networkInfos[network]?.dnsList = linkProperties.dnsServers
onUpdateNetwork()
setUnderlyingNetworks(network)
super.onLinkPropertiesChanged(network, linkProperties)
}
@@ -96,6 +98,10 @@ class NetworkObserveModule(private val service: Service) : Module() {
fun onUpdateNetwork() {
val dnsList = (networkInfos.asSequence().minByOrNull { networkToInt(it) }?.value?.dnsList
?: emptyList()).map { x -> x.asSocketAddressText(53) }
if (dnsList == preDnsList) {
return
}
preDnsList = dnsList
Core.updateDNS(dnsList.joinToString { "," })
}

View File

@@ -25,18 +25,30 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.zip
import kotlinx.coroutines.launch
data class ExtendedNotificationParams(
val title: String,
val stopText: String,
val onlyStatisticsProxy: Boolean,
val contentText: String,
)
val NotificationParams.extended: ExtendedNotificationParams
get() = ExtendedNotificationParams(
title, stopText, onlyStatisticsProxy, Core.getSpeedTrafficText(onlyStatisticsProxy)
)
class NotificationModule(private val service: Service) : Module() {
private val scope = CoroutineScope(Dispatchers.Default)
override fun onInstall() {
State.notificationParamsFlow.value?.let {
update(it)
update(it.extended)
}
scope.launch {
val screenFlow = service.receiveBroadcastFlow {
@@ -48,11 +60,12 @@ class NotificationModule(private val service: Service) : Module() {
emit(isScreenOn())
}
tickerFlow(1000, 0)
.combine(State.notificationParamsFlow.zip(screenFlow) { params, screenOn ->
params to screenOn
}) { _, (params, screenOn) -> params to screenOn }
.filter { (params, screenOn) -> params != null && screenOn }
combine(
tickerFlow(1000, 0), State.notificationParamsFlow, screenFlow
) { _, params, screenOn ->
params?.extended to screenOn
}.filter { (params, screenOn) -> params != null && screenOn }
.distinctUntilChanged { old, new -> old.first == new.first && old.second == new.second }
.collect { (params, _) ->
update(params!!)
}
@@ -87,18 +100,15 @@ class NotificationModule(private val service: Service) : Module() {
}
}
private fun update(params: NotificationParams) {
val contentText = Core.getSpeedTrafficText(params.onlyStatisticsProxy)
private fun update(params: ExtendedNotificationParams) {
service.startForeground(
with(notificationBuilder) {
setContentTitle(params.title)
setContentText(contentText)
setContentText(params.contentText)
setPriority(NotificationCompat.PRIORITY_HIGH)
clearActions()
addAction(
0,
params.stopText,
QuickAction.STOP.quickIntent.toPendingIntent
0, params.stopText, QuickAction.STOP.quickIntent.toPendingIntent
).build()
})
}

View File

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

View File

@@ -181,6 +181,10 @@ func handleAction(action *Action, result ActionResult) {
case crashMethod:
result.success(true)
handleCrash()
case deleteFile:
path := action.Data.(string)
handleDelFile(path, result)
return
default:
nextHandle(action, result)
}

View File

@@ -2,6 +2,8 @@
void (*release_object_func)(void *obj);
void (*free_string_func)(char *data);
void (*protect_func)(void *tun_interface, int fd);
char* (*resolve_process_func)(void *tun_interface,int protocol, const char *source, const char *target, int uid);
@@ -20,6 +22,10 @@ void release_object(void *obj) {
release_object_func(obj);
}
void free_string(char *data) {
free_string_func(data);
}
void result(void *invoke_Interface, const char *data) {
return result_func(invoke_Interface, data);
}

View File

@@ -16,7 +16,7 @@ func resolveProcess(callback unsafe.Pointer, protocol int, source, target string
t := C.CString(target)
defer C.free(unsafe.Pointer(t))
res := C.resolve_process(callback, C.int(protocol), s, t, C.int(uid))
return parseCString(res)
return takeCString(res)
}
func invokeResult(callback unsafe.Pointer, data string) {
@@ -29,7 +29,7 @@ func releaseObject(callback unsafe.Pointer) {
C.release_object(callback)
}
func parseCString(s *C.char) string {
//defer C.free(unsafe.Pointer(s))
func takeCString(s *C.char) string {
defer C.free_string(s)
return C.GoString(s)
}

View File

@@ -4,6 +4,8 @@
extern void (*release_object_func)(void *obj);
extern void (*free_string_func)(char *data);
extern void (*protect_func)(void *tun_interface, int fd);
extern char* (*resolve_process_func)(void *tun_interface, int protocol, const char *source, const char *target, int uid);
@@ -16,4 +18,6 @@ extern char* resolve_process(void *tun_interface, int protocol, const char *sour
extern void release_object(void *obj);
extern void free_string(char *data);
extern void result(void *invoke_Interface, const char *data);

View File

@@ -23,6 +23,7 @@ import (
rp "github.com/metacubex/mihomo/rules/provider"
"github.com/metacubex/mihomo/tunnel"
"os"
"path/filepath"
"sync"
)
@@ -159,7 +160,6 @@ func patchSelectGroup(mapping map[string]string) {
func defaultSetupParams() *SetupParams {
return &SetupParams{
Config: config.DefaultRawConfig(),
TestURL: "https://www.gstatic.com/generate_204",
SelectedMap: map[string]string{},
}
@@ -235,12 +235,30 @@ 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 {
runLock.Lock()
defer runLock.Unlock()
var err error
constant.DefaultTestURL = params.TestURL
currentConfig, err = config.ParseRawConfig(params.Config)
currentConfig, err = parseWithPath(filepath.Join(constant.Path.HomeDir(), "config.json"))
if err != nil {
currentConfig, _ = config.ParseRawConfig(config.DefaultRawConfig())
}

View File

@@ -4,7 +4,6 @@ import (
"encoding/json"
"github.com/metacubex/mihomo/adapter/provider"
P "github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
@@ -18,7 +17,6 @@ type InitParams struct {
}
type SetupParams struct {
Config *config.RawConfig `json:"config"`
SelectedMap map[string]string `json:"selected-map"`
TestURL string `json:"test-url"`
}
@@ -101,6 +99,7 @@ const (
crashMethod Method = "crash"
setupConfigMethod Method = "setupConfig"
getConfigMethod Method = "getConfig"
deleteFile Method = "deleteFile"
)
type Method string

View File

@@ -10,7 +10,6 @@ require (
)
require (
github.com/3andne/restls-client-go v0.1.6 // 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
@@ -19,15 +18,15 @@ 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.8.3 // indirect
github.com/enfein/mieru/v3 v3.16.1 // indirect
github.com/ebitengine/purego v0.8.4 // indirect
github.com/enfein/mieru/v3 v3.19.1 // 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.2 // 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
@@ -42,35 +41,36 @@ require (
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
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a // indirect
github.com/metacubex/ascon v0.1.0 // indirect
github.com/metacubex/bart v0.20.5 // indirect
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/fswatch v0.1.1 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
github.com/metacubex/quic-go v0.53.1-0.20250628094454-fda5262d1d9c // indirect
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 // indirect
github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/sing v0.5.4 // indirect
github.com/metacubex/sing-mux v0.3.2 // indirect
github.com/metacubex/restls-client-go v0.1.7 // indirect
github.com/metacubex/sing v0.5.5 // indirect
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac // indirect
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb // indirect
github.com/metacubex/sing-shadowsocks v0.2.11 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.5 // indirect
github.com/metacubex/sing-shadowsocks v0.2.12 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.6 // indirect
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
github.com/metacubex/sing-tun v0.4.7-0.20250721020617-8e7c37ed3d97 // indirect
github.com/metacubex/sing-vmess v0.2.3 // indirect
github.com/metacubex/sing-tun v0.4.7 // indirect
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee // indirect
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 // indirect
github.com/metacubex/utls v1.8.0 // indirect
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617 // indirect
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142 // indirect
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // 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
@@ -110,5 +110,4 @@ require (
golang.org/x/tools v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
)

View File

@@ -1,5 +1,3 @@
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
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=
@@ -24,10 +22,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/enfein/mieru/v3 v3.16.1 h1:CfIt1pQCCQbohkw+HBD2o8V9tnhZvB5yuXGGQIXTLOs=
github.com/enfein/mieru/v3 v3.16.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/enfein/mieru/v3 v3.19.1 h1:19b9kgFC7oJXX9RLEO5Pi1gO6yek5cWlpK7IJVUoE8I=
github.com/enfein/mieru/v3 v3.19.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
@@ -41,8 +39,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.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
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=
@@ -80,24 +78,24 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a h1:c1QSGpacSeQdBdWcEKZKGuWLcqIG2wxHEygAcXuDwS4=
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM=
github.com/metacubex/ascon v0.1.0/go.mod h1:eV5oim4cVPPdEL8/EYaTZ0iIKARH9pnhAK/fcT5Kacc=
github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM=
github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b h1:j7dadXD8I2KTmMt8jg1JcaP1ANL3JEObJPdANKcSYPY=
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b/go.mod h1:+WmP0VJZDkDszvpa83HzfUp6QzARl/IKkMorH4+nODw=
github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cqY=
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/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
@@ -108,37 +106,39 @@ github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
github.com/metacubex/quic-go v0.53.1-0.20250628094454-fda5262d1d9c h1:ABQzmOaZddM3q0OYeoZEc0XF+KW+dUdPNvY/c5rsunI=
github.com/metacubex/quic-go v0.53.1-0.20250628094454-fda5262d1d9c/go.mod h1:eWlAK3zsKI0P8UhYpXlIsl3mtW4D6MpMNuYLIu8CKWI=
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 h1:8JVlYuE8uSJAvmyCd4TjvDxs57xjb0WxEoaWafK5+qs=
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c=
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.4 h1:a4kAOZmF+OXosbzPEcrSc5QD35/ex+MNuZsrcuWskHk=
github.com/metacubex/sing v0.5.4/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
github.com/metacubex/sing v0.5.5 h1:m5U8iHvRAUxlme3FZlE/LPIGHjU8oMCUzXWGbQQAC1E=
github.com/metacubex/sing v0.5.5/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac h1:wDH/Jh/yqWbzPktqJP+Y1cUG8hchcrzKzUxJiSpnaQs=
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb h1:U/m3h8lp/j7i8zFgfvScLdZa1/Y8dd74oO7iZaQq80s=
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb/go.mod h1:B60FxaPHjR1SeQB0IiLrgwgvKsaoASfOWdiqhLjmMGA=
github.com/metacubex/sing-shadowsocks v0.2.11 h1:p2NGNOdF95e6XvdDKipLj1FRRqR8dnbfC/7pw2CCTlw=
github.com/metacubex/sing-shadowsocks v0.2.11/go.mod h1:bT1PCTV316zFnlToRMk5zt9HmIQYRBveiT71mplYPfc=
github.com/metacubex/sing-shadowsocks2 v0.2.5 h1:MnPn0hbcDkSJt6TlpI15XImHKK6IqaOwBUGPKyMnJnE=
github.com/metacubex/sing-shadowsocks2 v0.2.5/go.mod h1:Zyh+rAQRyevYfG/COCvDs1c/YMhGqCuknn7QrGmoQIw=
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.6 h1:ZR1kYT0f0Vi64iQSS09OdhFfppiNkh7kjgRdMm0SB98=
github.com/metacubex/sing-shadowsocks2 v0.2.6/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.7-0.20250721020617-8e7c37ed3d97 h1:YYpc60UZE2G0pUeHbRw9erDrUDZrPQy8QzWFqA3kHsk=
github.com/metacubex/sing-tun v0.4.7-0.20250721020617-8e7c37ed3d97/go.mod h1:2YywXPWW8Z97kTH7RffOeykKzU+l0aiKlglWV1PAS64=
github.com/metacubex/sing-vmess v0.2.3 h1:QKLdIk5A2FcR3Y7m2/JO1XhfzgDA8tF4W9/ffsH9opo=
github.com/metacubex/sing-vmess v0.2.3/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
github.com/metacubex/sing-tun v0.4.7 h1:ZDY/W+1c7PeWWKeKRyUo18fySF/TWjB0i5ui81Ar778=
github.com/metacubex/sing-tun v0.4.7/go.mod h1:xHecZRwBnKWe6zG9amAK9cXf91lF6blgjBqm+VvOrmU=
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 h1:WZepq4TOZa6WewB8tGAZrrL+bL2R2ivoBzuEgAeolWc=
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0/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-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac=
github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617 h1:yN3mQ4cT9sPUciw/rO0Isc/8QlO86DB6g9SEMRgQ8Cw=
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142 h1:csEbKOzRAxJXffOeZnnS3/kA/F55JiTbKv5jcYqCXms=
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142/go.mod h1:67I3skhEY4Sya8f1YxELwWPoeQdXqZCrWNYLvq8gn2U=
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/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
@@ -191,7 +191,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
@@ -271,5 +271,3 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=

View File

@@ -20,6 +20,7 @@ import (
"github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/mihomo/tunnel/statistic"
"net"
"os"
"runtime"
"sort"
"strconv"
@@ -454,6 +455,33 @@ func handleUpdateConfig(bytes []byte) string {
return ""
}
func handleDelFile(path string, result ActionResult) {
go func() {
fileInfo, err := os.Stat(path)
if err != nil {
if !os.IsNotExist(err) {
result.success(err.Error())
}
result.success("")
return
}
if fileInfo.IsDir() {
err = os.RemoveAll(path)
if err != nil {
result.success(err.Error())
return
}
} else {
err = os.Remove(path)
if err != nil {
result.success(err.Error())
return
}
}
result.success("")
}()
}
func handleSetupConfig(bytes []byte) string {
var params = defaultSetupParams()
err := UnmarshalJson(bytes, params)

View File

@@ -36,11 +36,11 @@ type TunHandler struct {
limit *semaphore.Weighted
}
func (th *TunHandler) start(fd int, address, dns string) {
func (th *TunHandler) start(fd int, stack, address, dns string) {
_ = th.limit.Acquire(context.TODO(), 4)
defer th.limit.Release(4)
th.initHook()
tunListener := t.Start(fd, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack, address, dns)
tunListener := t.Start(fd, stack, address, dns)
if tunListener != nil {
log.Infoln("TUN address: %v", tunListener.Address())
th.listener = tunListener
@@ -136,7 +136,7 @@ func handleStopTun() {
}
}
func handleStartTun(callback unsafe.Pointer, fd int, address, dns string) {
func handleStartTun(callback unsafe.Pointer, fd int, stack, address, dns string) {
handleStopTun()
tunLock.Lock()
defer tunLock.Unlock()
@@ -145,7 +145,7 @@ func handleStartTun(callback unsafe.Pointer, fd int, address, dns string) {
callback: callback,
limit: semaphore.NewWeighted(4),
}
tunHandler.start(fd, address, dns)
tunHandler.start(fd, stack, address, dns)
}
}
@@ -181,7 +181,7 @@ func nextHandle(action *Action, result ActionResult) bool {
//export invokeAction
func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
params := parseCString(paramsChar)
params := takeCString(paramsChar)
var action = &Action{}
err := json.Unmarshal([]byte(params), action)
if err != nil {
@@ -197,8 +197,8 @@ func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
}
//export startTUN
func startTUN(callback unsafe.Pointer, fd C.int, addressChar, dnsChar *C.char) bool {
handleStartTun(callback, int(fd), parseCString(addressChar), parseCString(dnsChar))
func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar *C.char) bool {
handleStartTun(callback, int(fd), takeCString(stackChar), takeCString(addressChar), takeCString(dnsChar))
return true
}
@@ -212,12 +212,16 @@ func setMessageCallback(callback unsafe.Pointer) {
//export getTotalTraffic
func getTotalTraffic(onlyStatisticsProxy bool) *C.char {
return C.CString(handleGetTotalTraffic(onlyStatisticsProxy))
data := C.CString(handleGetTotalTraffic(onlyStatisticsProxy))
defer C.free(unsafe.Pointer(data))
return data
}
//export getTraffic
func getTraffic(onlyStatisticsProxy bool) *C.char {
return C.CString(handleGetTraffic(onlyStatisticsProxy))
data := C.CString(handleGetTraffic(onlyStatisticsProxy))
defer C.free(unsafe.Pointer(data))
return data
}
func sendMessage(message Message) {
@@ -249,5 +253,5 @@ func forceGC() {
//export updateDns
func updateDns(s *C.char) {
handleUpdateDns(parseCString(s))
handleUpdateDns(takeCString(s))
}

View File

@@ -14,9 +14,13 @@ import (
"strings"
)
func Start(fd int, device string, stack constant.TUNStack, address, dns string) *sing_tun.Listener {
func Start(fd int, stack string, address, dns string) *sing_tun.Listener {
var prefix4 []netip.Prefix
var prefix6 []netip.Prefix
tunStack, ok := constant.StackTypeMapping[strings.ToLower(stack)]
if !ok {
tunStack = constant.TunSystem
}
for _, a := range strings.Split(address, ",") {
a = strings.TrimSpace(a)
if len(a) == 0 {
@@ -45,8 +49,8 @@ func Start(fd int, device string, stack constant.TUNStack, address, dns string)
options := LC.Tun{
Enable: true,
Device: device,
Stack: stack,
Device: "FlClash",
Stack: tunStack,
DNSHijack: dnsHijack,
AutoRoute: false,
AutoDetectInterface: false,

View File

@@ -107,57 +107,57 @@ class ApplicationState extends ConsumerState<Application> {
@override
Widget build(context) {
return _buildPlatformState(
_buildState(
Consumer(
builder: (_, ref, child) {
final locale = ref.watch(
appSettingProvider.select((state) => state.locale),
);
final themeProps = ref.watch(themeSettingProvider);
return MaterialApp(
debugShowCheckedModeBanner: false,
navigatorKey: globalState.navigatorKey,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
builder: (_, child) {
return AppEnvManager(
child: _buildApp(
return Consumer(
builder: (_, ref, child) {
final locale = ref.watch(
appSettingProvider.select((state) => state.locale),
);
final themeProps = ref.watch(themeSettingProvider);
return MaterialApp(
debugShowCheckedModeBanner: false,
navigatorKey: globalState.navigatorKey,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
builder: (_, child) {
return AppEnvManager(
child: _buildApp(
_buildPlatformState(
_buildState(
AppSidebarContainer(child: _buildPlatformApp(child!)),
),
);
},
scrollBehavior: BaseScrollBehavior(),
title: appName,
locale: utils.getLocaleForString(locale),
supportedLocales: AppLocalizations.delegate.supportedLocales,
themeMode: themeProps.themeMode,
theme: ThemeData(
useMaterial3: true,
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.light,
primaryColor: themeProps.primaryColor,
),
),
darkTheme: ThemeData(
useMaterial3: true,
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.dark,
primaryColor: themeProps.primaryColor,
).toPureBlack(themeProps.pureBlack),
),
home: child!,
);
},
child: const HomePage(),
),
),
scrollBehavior: BaseScrollBehavior(),
title: appName,
locale: utils.getLocaleForString(locale),
supportedLocales: AppLocalizations.delegate.supportedLocales,
themeMode: themeProps.themeMode,
theme: ThemeData(
useMaterial3: true,
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.light,
primaryColor: themeProps.primaryColor,
),
),
darkTheme: ThemeData(
useMaterial3: true,
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.dark,
primaryColor: themeProps.primaryColor,
).toPureBlack(themeProps.pureBlack),
),
home: child!,
);
},
child: const HomePage(),
);
}

190
lib/common/cache.dart Normal file
View File

@@ -0,0 +1,190 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:fl_clash/common/common.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
class LocalImageCacheManager extends CacheManager {
static const key = 'LocalImageCacheData';
static final LocalImageCacheManager _instance = LocalImageCacheManager._();
factory LocalImageCacheManager() {
return _instance;
}
LocalImageCacheManager._()
: super(
Config(
key,
stalePeriod: Duration(days: 30),
maxNrOfCacheObjects: 1000,
fileService: _LocalImageCacheFileService(),
),
);
}
class _LocalImageCacheFileService extends FileService {
_LocalImageCacheFileService();
@override
Future<FileServiceResponse> get(
String url, {
Map<String, String>? headers,
}) async {
final response = await request.dio.get<ResponseBody>(
url,
options: Options(headers: headers, responseType: ResponseType.stream),
);
return _LocalImageResponse(response);
}
}
class _LocalImageResponse implements FileServiceResponse {
_LocalImageResponse(this._response);
final DateTime _receivedTime = DateTime.now();
final Response<ResponseBody> _response;
@override
int get statusCode => _response.statusCode ?? 0;
@override
Stream<List<int>> get content =>
_response.data?.stream.transform(uint8ListToListIntConverter) ??
Stream.empty();
@override
int? get contentLength => _response.data?.contentLength;
@override
DateTime get validTill {
// Without a cache-control header we keep the file for a week
var ageDuration = const Duration(days: 7);
final controlHeader = _response.headers.value(
HttpHeaders.cacheControlHeader,
);
if (controlHeader != null) {
final controlSettings = controlHeader.split(',');
for (final setting in controlSettings) {
final sanitizedSetting = setting.trim().toLowerCase();
if (sanitizedSetting.startsWith('max-age=')) {
final validSeconds =
int.tryParse(sanitizedSetting.split('=')[1]) ?? 0;
if (validSeconds > 0) {
ageDuration = Duration(seconds: validSeconds);
}
}
}
}
if (ageDuration > const Duration(days: 7)) {
return _receivedTime.add(ageDuration);
}
return _receivedTime.add(const Duration(days: 7));
}
@override
String? get eTag => _response.headers.value(HttpHeaders.etagHeader);
@override
String get fileExtension {
var fileExtension = '';
final contentTypeHeader = _response.headers.value(
HttpHeaders.contentTypeHeader,
);
if (contentTypeHeader != null) {
final contentType = ContentType.parse(contentTypeHeader);
fileExtension = contentType.fileExtension;
}
return fileExtension;
}
}
extension ContentTypeConverter on ContentType {
String get fileExtension => mimeTypes[mimeType] ?? '.$subType';
}
const mimeTypes = {
'application/vnd.android.package-archive': '.apk',
'application/epub+zip': '.epub',
'application/gzip': '.gz',
'application/java-archive': '.jar',
'application/json': '.json',
'application/ld+json': '.jsonld',
'application/msword': '.doc',
'application/octet-stream': '.bin',
'application/ogg': '.ogx',
'application/pdf': '.pdf',
'application/php': '.php',
'application/rtf': '.rtf',
'application/vnd.amazon.ebook': '.azw',
'application/vnd.apple.installer+xml': '.mpkg',
'application/vnd.mozilla.xul+xml': '.xul',
'application/vnd.ms-excel': '.xls',
'application/vnd.ms-fontobject': '.eot',
'application/vnd.ms-powerpoint': '.ppt',
'application/vnd.oasis.opendocument.presentation': '.odp',
'application/vnd.oasis.opendocument.spreadsheet': '.ods',
'application/vnd.oasis.opendocument.text': '.odt',
'application/vnd.openxmlformats-officedocument.presentationml.presentation':
'.pptx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
'.docx',
'application/vnd.rar': '.rar',
'application/vnd.visio': '.vsd',
'application/x-7z-compressed': '.7z',
'application/x-abiword': '.abw',
'application/x-bzip': '.bz',
'application/x-bzip2': '.bz2',
'application/x-csh': '.csh',
'application/x-freearc': '.arc',
'application/x-sh': '.sh',
'application/x-shockwave-flash': '.swf',
'application/x-tar': '.tar',
'application/xhtml+xml': '.xhtml',
'application/xml': '.xml',
'application/zip': '.zip',
'audio/3gpp': '.3gp',
'audio/3gpp2': '.3g2',
'audio/aac': '.aac',
'audio/x-aac': '.aac',
'audio/midi': '.midi',
'audio/x-midi': '.midi',
'audio/x-m4a': '.m4a',
'audio/m4a': '.m4a',
'audio/mpeg': '.mp3',
'audio/ogg': '.oga',
'audio/opus': '.opus',
'audio/wav': '.wav',
'audio/x-wav': '.wav',
'audio/webm': '.weba',
'font/otf': '.otf',
'font/ttf': '.ttf',
'font/woff': '.woff',
'font/woff2': '.woff2',
'image/bmp': '.bmp',
'image/gif': '.gif',
'image/jpeg': '.jpg',
'image/png': '.png',
'image/svg+xml': '.svg',
'image/tiff': '.tiff',
'image/vnd.microsoft.icon': '.ico',
'image/webp': '.webp',
'text/calendar': '.ics',
'text/css': '.css',
'text/csv': '.csv',
'text/html': '.html',
'text/javascript': '.js',
'text/plain': '.txt',
'text/xml': '.xml',
'video/3gpp': '.3gp',
'video/3gpp2': '.3g2',
'video/mp2t': '.ts',
'video/mpeg': '.mpeg',
'video/ogg': '.ogv',
'video/webm': '.webm',
'video/x-msvideo': '.avi',
'video/quicktime': '.mov',
};

View File

@@ -1,6 +1,8 @@
export 'android.dart';
export 'app_localizations.dart';
export 'cache.dart';
export 'color.dart';
export 'compute.dart';
export 'constant.dart';
export 'context.dart';
export 'converter.dart';

114
lib/common/compute.dart Normal file
View File

@@ -0,0 +1,114 @@
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'string.dart';
import 'utils.dart';
List<Group> computeSort({
required List<Group> groups,
required ProxiesSortType sortType,
required DelayMap delayMap,
required SelectedMap selectedMap,
required String defaultTestUrl,
}) {
return groups.map((group) {
final proxies = group.all;
final newProxies = switch (sortType) {
ProxiesSortType.none => proxies,
ProxiesSortType.delay => _sortOfDelay(
groups: groups,
proxies: proxies,
delayMap: delayMap,
selectedMap: selectedMap,
testUrl: group.testUrl.getSafeValue(defaultTestUrl),
),
ProxiesSortType.name => _sortOfName(proxies),
};
return group.copyWith(all: newProxies);
}).toList();
}
DelayState computeProxyDelayState({
required String proxyName,
required String testUrl,
required List<Group> groups,
required SelectedMap 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 SelectedMap selectedMap,
}) {
return _getRealSelectedProxyState(
SelectedProxyState(proxyName: proxyName),
groups: groups,
selectedMap: selectedMap,
);
}
SelectedProxyState _getRealSelectedProxyState(
SelectedProxyState state, {
required List<Group> groups,
required SelectedMap selectedMap,
}) {
if (state.proxyName.isEmpty) return state;
final index = groups.indexWhere((element) => element.name == state.proxyName);
final newState = state.copyWith(group: true);
if (index == -1) return newState;
final group = groups[index];
final currentSelectedName = group.getCurrentSelectedName(
selectedMap[newState.proxyName] ?? '',
);
if (currentSelectedName.isEmpty) {
return newState;
}
return _getRealSelectedProxyState(
newState.copyWith(proxyName: currentSelectedName, testUrl: group.testUrl),
groups: groups,
selectedMap: selectedMap,
);
}
List<Proxy> _sortOfDelay({
required List<Group> groups,
required List<Proxy> proxies,
required DelayMap delayMap,
required SelectedMap 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) =>
utils.sortByChar(utils.getPinyin(a.name), utils.getPinyin(b.name)),
);
}

View File

@@ -36,8 +36,8 @@ const geoIpFileName = 'GeoIP.dat';
const geoSiteFileName = 'GeoSite.dat';
final double kHeaderHeight = system.isDesktop
? !system.isMacOS
? 40
: 28
? 40
: 28
: 0;
const profilesDirectoryName = 'profiles';
const localhost = '127.0.0.1';
@@ -84,7 +84,7 @@ const profilesStoreKey = PageStorageKey<String>('profiles');
const defaultPrimaryColor = 0XFFD8C0C3;
double getWidgetHeight(num lines) {
return max(lines * 84 + (lines - 1) * 16, 0).ap;
return max(lines * 80 + (lines - 1) * 16, 0).ap;
}
const maxLength = 1000;

View File

@@ -9,23 +9,17 @@ class Debouncer {
FunctionTag tag,
Function func, {
List<dynamic>? args,
Duration duration = const Duration(milliseconds: 600),
Duration? duration,
}) {
final timer = _operations[tag];
if (timer != null) {
timer.cancel();
}
_operations[tag] = Timer(
duration,
() {
_operations[tag]?.cancel();
_operations.remove(tag);
Function.apply(
func,
args,
);
},
);
_operations[tag] = Timer(duration ?? const Duration(milliseconds: 600), () {
_operations[tag]?.cancel();
_operations.remove(tag);
Function.apply(func, args);
});
}
void cancel(dynamic tag) {
@@ -47,17 +41,11 @@ class Throttler {
if (timer != null) {
return true;
}
_operations[tag] = Timer(
duration,
() {
_operations[tag]?.cancel();
_operations.remove(tag);
Function.apply(
func,
args,
);
},
);
_operations[tag] = Timer(duration, () {
_operations[tag]?.cancel();
_operations.remove(tag);
Function.apply(func, args);
});
return false;
}

View File

@@ -1,4 +1,5 @@
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
set value(T value) {
@@ -20,3 +21,26 @@ mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
void onUpdate(T value) {}
}
mixin AnyNotifierMixin<T> on AnyNotifier<T, T> {
T get value;
set value(T value) {
if (ref.mounted) {
state = value;
} else {
onUpdate(value);
}
}
@override
bool updateShouldNotify(previous, next) {
final res = super.updateShouldNotify(previous, next);
if (res) {
onUpdate(next);
}
return res;
}
void onUpdate(T value) {}
}

View File

@@ -15,14 +15,12 @@ class Navigation {
keep: false,
icon: Icon(Icons.space_dashboard),
label: PageLabel.dashboard,
builder: (_) =>
const DashboardView(key: GlobalObjectKey(PageLabel.dashboard)),
builder: (_) => const DashboardView(),
),
NavigationItem(
icon: const Icon(Icons.article),
label: PageLabel.proxies,
builder: (_) =>
const ProxiesView(key: GlobalObjectKey(PageLabel.proxies)),
builder: (_) => const ProxiesView(),
modes: hasProxies
? [NavigationItemMode.mobile, NavigationItemMode.desktop]
: [],
@@ -30,22 +28,19 @@ class Navigation {
NavigationItem(
icon: Icon(Icons.folder),
label: PageLabel.profiles,
builder: (_) =>
const ProfilesView(key: GlobalObjectKey(PageLabel.profiles)),
builder: (_) => const ProfilesView(),
),
NavigationItem(
icon: Icon(Icons.view_timeline),
label: PageLabel.requests,
builder: (_) =>
const RequestsView(key: GlobalObjectKey(PageLabel.requests)),
builder: (_) => const RequestsView(),
description: 'requestsDesc',
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
),
NavigationItem(
icon: Icon(Icons.ballot),
label: PageLabel.connections,
builder: (_) =>
const ConnectionsView(key: GlobalObjectKey(PageLabel.connections)),
builder: (_) => const ConnectionsView(),
description: 'connectionsDesc',
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
),
@@ -53,14 +48,13 @@ class Navigation {
icon: Icon(Icons.storage),
label: PageLabel.resources,
description: 'resourcesDesc',
builder: (_) =>
const ResourcesView(key: GlobalObjectKey(PageLabel.resources)),
builder: (_) => const ResourcesView(),
modes: [NavigationItemMode.more],
),
NavigationItem(
icon: const Icon(Icons.adb),
label: PageLabel.logs,
builder: (_) => const LogsView(key: GlobalObjectKey(PageLabel.logs)),
builder: (_) => const LogsView(),
description: 'logsDesc',
modes: openLogs
? [NavigationItemMode.desktop, NavigationItemMode.more]
@@ -69,7 +63,7 @@ class Navigation {
NavigationItem(
icon: Icon(Icons.construction),
label: PageLabel.tools,
builder: (_) => const ToolsView(key: GlobalObjectKey(PageLabel.tools)),
builder: (_) => const ToolsView(),
modes: [NavigationItemMode.desktop, NavigationItemMode.mobile],
),
];

View File

@@ -10,6 +10,7 @@ class AppPath {
Completer<Directory> dataDir = Completer();
Completer<Directory> downloadDir = Completer();
Completer<Directory> tempDir = Completer();
Completer<Directory> cacheDir = Completer();
late String appDirPath;
AppPath._internal() {
@@ -23,6 +24,9 @@ class AppPath {
getDownloadsDirectory().then((value) {
downloadDir.complete(value);
});
getApplicationCacheDirectory().then((value) {
cacheDir.complete(value);
});
}
factory AppPath() {
@@ -58,8 +62,13 @@ class AppPath {
}
Future<String> get lockFilePath async {
final directory = await dataDir.future;
return join(directory.path, 'FlClash.lock');
final homeDirPath = await appPath.homeDirPath;
return join(homeDirPath, 'FlClash.lock');
}
Future<String> get configFilePath async {
final homeDirPath = await appPath.homeDirPath;
return join(homeDirPath, 'config.json');
}
Future<String> get sharedPreferencesPath async {
@@ -77,6 +86,11 @@ class AppPath {
return join(directory, '$id.yaml');
}
Future<String> getIconsCacheDir() async {
final directory = await cacheDir.future;
return join(directory.path, 'icons');
}
Future<String> getProvidersRootPath() async {
final directory = await profilesPath;
return join(directory, 'providers');

View File

@@ -11,35 +11,29 @@ import 'package:fl_clash/state.dart';
import 'package:flutter/cupertino.dart';
class Request {
late final Dio _dio;
late final Dio dio;
late final Dio _clashDio;
String? userAgent;
Request() {
_dio = Dio(
BaseOptions(
headers: {
'User-Agent': browserUa,
},
),
);
dio = Dio(BaseOptions(headers: {'User-Agent': browserUa}));
_clashDio = Dio();
_clashDio.httpClientAdapter = IOHttpClientAdapter(createHttpClient: () {
final client = HttpClient();
client.findProxy = (Uri uri) {
client.userAgent = globalState.ua;
return FlClashHttpOverrides.handleFindProxy(uri);
};
return client;
});
_clashDio.httpClientAdapter = IOHttpClientAdapter(
createHttpClient: () {
final client = HttpClient();
client.findProxy = (Uri uri) {
client.userAgent = globalState.ua;
return FlClashHttpOverrides.handleFindProxy(uri);
};
return client;
},
);
}
Future<Response> getFileResponseForUrl(String url) async {
final response = await _clashDio.get(
url,
options: Options(
responseType: ResponseType.bytes,
),
options: Options(responseType: ResponseType.bytes),
);
return response;
}
@@ -47,20 +41,16 @@ class Request {
Future<Response> getTextResponseForUrl(String url) async {
final response = await _clashDio.get(
url,
options: Options(
responseType: ResponseType.plain,
),
options: Options(responseType: ResponseType.plain),
);
return response;
}
Future<MemoryImage?> getImage(String url) async {
if (url.isEmpty) return null;
final response = await _dio.get<Uint8List>(
final response = await dio.get<Uint8List>(
url,
options: Options(
responseType: ResponseType.bytes,
),
options: Options(responseType: ResponseType.bytes),
);
final data = response.data;
if (data == null) return null;
@@ -68,11 +58,9 @@ class Request {
}
Future<Map<String, dynamic>?> checkForUpdate() async {
final response = await _dio.get(
final response = await dio.get(
'https://api.github.com/repos/$repository/releases/latest',
options: Options(
responseType: ResponseType.json,
),
options: Options(responseType: ResponseType.json),
);
if (response.statusCode != 200) return null;
final data = response.data as Map<String, dynamic>;
@@ -85,9 +73,12 @@ class Request {
}
final Map<String, IpInfo Function(Map<String, dynamic>)> _ipInfoSources = {
'https://ipwho.is/': IpInfo.fromIpwhoIsJson,
'https://api.ip.sb/geoip/': IpInfo.fromIpSbJson,
'https://ipwho.is/': IpInfo.fromIpWhoIsJson,
'https://api.myip.com/': IpInfo.fromMyIpJson,
'https://ipapi.co/json/': IpInfo.fromIpApiCoJson,
'https://ident.me/json/': IpInfo.fromIdentMeJson,
'http://ip-api.com/json/': IpInfo.fromIpAPIJson,
'https://api.ip.sb/geoip/': IpInfo.fromIpSbJson,
'https://ipinfo.io/json/': IpInfo.fromIpInfoIoJson,
};
@@ -101,27 +92,27 @@ class Request {
}
}
final future = Dio().get<Map<String, dynamic>>(
final future = dio.get<Map<String, dynamic>>(
source.key,
cancelToken: cancelToken,
options: Options(
responseType: ResponseType.json,
),
options: Options(responseType: ResponseType.json),
);
future.then((res) {
if (res.statusCode == HttpStatus.ok && res.data != null) {
completer.complete(Result.success(source.value(res.data!)));
} else {
failureCount++;
handleFailRes();
}
}).catchError((e) {
failureCount++;
if (e is DioException && e.type == DioExceptionType.cancel) {
completer.complete(Result.error('cancelled'));
}
handleFailRes();
});
future
.then((res) {
if (res.statusCode == HttpStatus.ok && res.data != null) {
completer.complete(Result.success(source.value(res.data!)));
return;
}
failureCount++;
handleFailRes();
})
.catchError((e) {
failureCount++;
if (e is DioException && e.type == DioExceptionType.cancel) {
completer.complete(Result.error('cancelled'));
}
handleFailRes();
});
return completer.future;
});
final res = await Future.any(futures);
@@ -131,18 +122,12 @@ class Request {
Future<bool> pingHelper() async {
try {
final response = await _dio
final response = await dio
.get(
'http://$localhost:$helperPort/ping',
options: Options(
responseType: ResponseType.plain,
),
options: Options(responseType: ResponseType.plain),
)
.timeout(
const Duration(
milliseconds: 2000,
),
);
.timeout(const Duration(milliseconds: 2000));
if (response.statusCode != HttpStatus.ok) {
return false;
}
@@ -154,22 +139,13 @@ class Request {
Future<bool> startCoreByHelper(String arg) async {
try {
final response = await _dio
final response = await dio
.post(
'http://$localhost:$helperPort/start',
data: json.encode({
'path': appPath.corePath,
'arg': arg,
}),
options: Options(
responseType: ResponseType.plain,
),
data: json.encode({'path': appPath.corePath, 'arg': arg}),
options: Options(responseType: ResponseType.plain),
)
.timeout(
const Duration(
milliseconds: 2000,
),
);
.timeout(const Duration(milliseconds: 2000));
if (response.statusCode != HttpStatus.ok) {
return false;
}
@@ -182,18 +158,12 @@ class Request {
Future<bool> stopCoreByHelper() async {
try {
final response = await _dio
final response = await dio
.post(
'http://$localhost:$helperPort/stop',
options: Options(
responseType: ResponseType.plain,
),
options: Options(responseType: ResponseType.plain),
)
.timeout(
const Duration(
milliseconds: 2000,
),
);
.timeout(const Duration(milliseconds: 2000));
if (response.statusCode != HttpStatus.ok) {
return false;
}

View File

@@ -21,16 +21,15 @@ class Utils {
String get id {
final timestamp = DateTime.now().microsecondsSinceEpoch;
final random = Random();
final randomStr =
String.fromCharCodes(List.generate(8, (_) => random.nextInt(26) + 97));
final randomStr = String.fromCharCodes(
List.generate(8, (_) => random.nextInt(26) + 97),
);
return '$timestamp$randomStr';
}
String getDateStringLast2(int value) {
var valueRaw = '0$value';
return valueRaw.substring(
valueRaw.length - 2,
);
return valueRaw.substring(valueRaw.length - 2);
}
String generateRandomString({int minLength = 10, int maxLength = 100}) {
@@ -43,8 +42,9 @@ class Utils {
String result = '';
for (int i = 0; i < length; i++) {
if (random.nextBool()) {
result +=
String.fromCharCode(0x4E00 + random.nextInt(0x9FA5 - 0x4E00 + 1));
result += String.fromCharCode(
0x4E00 + random.nextInt(0x9FA5 - 0x4E00 + 1),
);
} else {
result += latinChars[random.nextInt(latinChars.length)];
}
@@ -60,8 +60,9 @@ class Utils {
bytes[6] = (bytes[6] & 0x0F) | 0x40;
bytes[8] = (bytes[8] & 0x3F) | 0x80;
final hex =
bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
final hex = bytes
.map((byte) => byte.toRadixString(16).padLeft(2, '0'))
.join();
return '${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(12, 16)}-${hex.substring(16, 20)}-${hex.substring(20, 32)}';
}
@@ -102,9 +103,10 @@ class Utils {
}
if (localSplit.length == 3) {
return Locale.fromSubtags(
languageCode: localSplit[0],
scriptCode: localSplit[1],
countryCode: localSplit[2]);
languageCode: localSplit[0],
scriptCode: localSplit[1],
countryCode: localSplit[2],
);
}
return null;
}
@@ -141,9 +143,7 @@ class Utils {
}
}
String getTrayIconPath({
required Brightness brightness,
}) {
String getTrayIconPath({required Brightness brightness}) {
if (system.isMacOS) {
return 'assets/images/icon_white.png';
}
@@ -188,16 +188,20 @@ class Utils {
if (disposition == null) return null;
final parseValue = HeaderValue.parse(disposition);
final parameters = parseValue.parameters;
final fileNamePointKey = parameters.keys
.firstWhere((key) => key == 'filename*', orElse: () => '');
final fileNamePointKey = parameters.keys.firstWhere(
(key) => key == 'filename*',
orElse: () => '',
);
if (fileNamePointKey.isNotEmpty) {
final res = parameters[fileNamePointKey]?.split("''") ?? [];
if (res.length >= 2) {
return Uri.decodeComponent(res[1]);
}
}
final fileNameKey = parameters.keys
.firstWhere((key) => key == 'filename', orElse: () => '');
final fileNameKey = parameters.keys.firstWhere(
(key) => key == 'filename',
orElse: () => '',
);
if (fileNameKey.isEmpty) return null;
return parameters[fileNameKey];
}
@@ -236,19 +240,7 @@ class Utils {
return max((viewWidth / 320).floor(), 1);
}
final _indexPrimary = [
50,
100,
200,
300,
400,
500,
600,
700,
800,
850,
900,
];
final _indexPrimary = [50, 100, 200, 300, 400, 500, 600, 700, 800, 850, 900];
MaterialColor _createPrimarySwatch(Color color) {
final Map<int, Color> swatch = <int, Color>{};
@@ -302,16 +294,15 @@ class Utils {
}
Future<String?> getLocalIpAddress() async {
List<NetworkInterface> interfaces = await NetworkInterface.list(
includeLoopback: false,
)
..sort((a, b) {
if (a.isWifi && !b.isWifi) return -1;
if (!a.isWifi && b.isWifi) return 1;
if (a.includesIPv4 && !b.includesIPv4) return -1;
if (!a.includesIPv4 && b.includesIPv4) return 1;
return 0;
});
List<NetworkInterface> interfaces =
await NetworkInterface.list(includeLoopback: false)
..sort((a, b) {
if (a.isWifi && !b.isWifi) return -1;
if (!a.isWifi && b.isWifi) return 1;
if (a.includesIPv4 && !b.includesIPv4) return -1;
if (!a.includesIPv4 && b.includesIPv4) return 1;
return 0;
});
for (final interface in interfaces) {
final addresses = interface.addresses;
if (addresses.isEmpty) {
@@ -329,59 +320,9 @@ class Utils {
SingleActivator controlSingleActivator(LogicalKeyboardKey trigger) {
final control = system.isMacOS ? false : true;
return SingleActivator(
trigger,
control: control,
meta: !control,
);
return SingleActivator(trigger, control: control, meta: !control);
}
// dynamic convertYamlNode(dynamic node) {
// if (node is YamlMap) {
// final map = <String, dynamic>{};
// YamlNode? mergeKeyNode;
// for (final entry in node.nodes.entries) {
// if (entry.key is YamlScalar &&
// (entry.key as YamlScalar).value == '<<') {
// mergeKeyNode = entry.value;
// break;
// }
// }
// if (mergeKeyNode != null) {
// final mergeValue = mergeKeyNode.value;
// if (mergeValue is YamlMap) {
// map.addAll(convertYamlNode(mergeValue) as Map<String, dynamic>);
// } else if (mergeValue is YamlList) {
// for (final node in mergeValue.nodes) {
// if (node.value is YamlMap) {
// map.addAll(convertYamlNode(node.value) as Map<String, dynamic>);
// }
// }
// }
// }
//
// node.nodes.forEach((key, value) {
// String stringKey;
// if (key is YamlScalar) {
// stringKey = key.value.toString();
// } else {
// stringKey = key.toString();
// }
// map[stringKey] = convertYamlNode(value.value);
// });
// return map;
// } else if (node is YamlList) {
// final list = <dynamic>[];
// for (final item in node.nodes) {
// list.add(convertYamlNode(item.value));
// }
// return list;
// } else if (node is YamlScalar) {
// return node.value;
// }
// return node;
// }
FutureOr<T> handleWatch<T>(Function function) async {
if (kDebugMode) {
final stopwatch = Stopwatch()..start();

View File

@@ -8,6 +8,7 @@ import 'package:fl_clash/common/archive.dart';
import 'package:fl_clash/core/core.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/plugins/service.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/dialog.dart';
@@ -41,8 +42,8 @@ class AppController {
});
}
void updateGroupsDebounce() {
debouncer.call(FunctionTag.updateGroups, updateGroups);
void updateGroupsDebounce([Duration? duration]) {
debouncer.call(FunctionTag.updateGroups, updateGroups, duration: duration);
}
void addCheckIpNumDebounce() {
@@ -77,6 +78,7 @@ class AppController {
await coreController.preload();
await _initCore();
_ref.read(coreStatusProvider.notifier).value = CoreStatus.connected;
_ref.read(initProvider.notifier).value = true;
if (_ref.read(isStartProvider)) {
await globalState.handleStart();
}
@@ -231,10 +233,6 @@ class AppController {
return currentGroupName;
}
ProxyCardState getProxyCardState(String proxyName) {
return _ref.read(getProxyCardStateProvider(proxyName));
}
String? getSelectedProxyName(String groupName) {
return _ref.read(getSelectedProxyNameProvider(groupName));
}
@@ -303,10 +301,7 @@ class AppController {
}
final realTunEnable = _ref.read(realTunEnableProvider);
final realPatchConfig = patchConfig.copyWith.tun(enable: realTunEnable);
final params = await globalState.getSetupParams(
pathConfig: realPatchConfig,
);
final message = await coreController.setupConfig(params);
final message = await coreController.setupConfig(realPatchConfig);
lastProfileModified = await _ref.read(
currentProfileProvider.select((state) => state?.profileLastModified),
);
@@ -369,7 +364,22 @@ class AppController {
try {
_ref.read(groupsProvider.notifier).value = await retry(
task: () async {
return await coreController.getProxiesGroups();
final sortType = _ref.read(
proxiesStyleSettingProvider.select((state) => state.sortType),
);
final delayMap = _ref.read(delayDataSourceProvider);
final testUrl = _ref.read(
appSettingProvider.select((state) => state.testUrl),
);
final selectedMap = _ref.read(
currentProfileProvider.select((state) => state?.selectedMap ?? {}),
);
return await coreController.getProxiesGroups(
selectedMap: selectedMap,
sortType: sortType,
delayMap: delayMap,
defaultTestUrl: testUrl,
);
},
retryIf: (res) => res.isEmpty,
);
@@ -526,11 +536,9 @@ class AppController {
}
};
updateTray(true);
await _initCore();
await _initStatus();
autoLaunch?.updateStatus(_ref.read(appSettingProvider).autoLaunch);
autoUpdateProfiles();
autoCheckUpdate();
autoLaunch?.updateStatus(_ref.read(appSettingProvider).autoLaunch);
if (!_ref.read(appSettingProvider).silentLaunch) {
window?.show();
} else {
@@ -538,6 +546,18 @@ class AppController {
}
await _handlePreference();
await _handlerDisclaimer();
final message = await coreController.preload();
if (message.isNotEmpty) {
_ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
if (context.mounted) {
context.showNotifier(message);
}
return;
}
_ref.read(coreStatusProvider.notifier).value = CoreStatus.connected;
await service?.syncAndroidState(globalState.getAndroidState());
await _initCore();
await _initStatus();
_ref.read(initProvider.notifier).value = true;
}
@@ -694,47 +714,7 @@ class AppController {
_ref.read(providersProvider.notifier).setProvider(provider);
}
List<Proxy> _sortOfName(List<Proxy> proxies) {
return List.of(proxies)..sort(
(a, b) =>
utils.sortByChar(utils.getPinyin(a.name), utils.getPinyin(b.name)),
);
}
List<Proxy> _sortOfDelay({required List<Proxy> proxies, String? testUrl}) {
return List.of(proxies)..sort((a, b) {
final aDelay = _ref.read(
getDelayProvider(proxyName: a.name, testUrl: testUrl),
);
final bDelay = _ref.read(
getDelayProvider(proxyName: b.name, testUrl: testUrl),
);
if (aDelay == null && bDelay == null) {
return 0;
}
if (aDelay == null || aDelay == -1) {
return 1;
}
if (bDelay == null || bDelay == -1) {
return -1;
}
return aDelay.compareTo(bDelay);
});
}
List<Proxy> getSortProxies({
required List<Proxy> proxies,
required ProxiesSortType sortType,
String? testUrl,
}) {
return switch (sortType) {
ProxiesSortType.none => proxies,
ProxiesSortType.delay => _sortOfDelay(proxies: proxies, testUrl: testUrl),
ProxiesSortType.name => _sortOfName(proxies),
};
}
Future<Null> clearEffect(String profileId) async {
Future<void> clearEffect(String profileId) async {
final profilePath = await appPath.getProfilePath(profileId);
final providersDirPath = await appPath.getProvidersDirPath(profileId);
return await Isolate.run(() async {
@@ -743,11 +723,7 @@ class AppController {
if (isExists) {
profileFile.delete(recursive: true);
}
final providersFileDir = File(providersDirPath);
final providersFileIsExists = await providersFileDir.exists();
if (providersFileIsExists) {
providersFileDir.delete(recursive: true);
}
await coreController.deleteFile(providersDirPath);
});
}

View File

@@ -8,6 +8,7 @@ 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';
@@ -28,7 +29,7 @@ class CoreController {
return _instance!;
}
Future<bool> preload() {
Future<String> preload() {
return _interface.preload();
}
@@ -83,29 +84,45 @@ class CoreController {
return await _interface.updateConfig(updateParams);
}
Future<String> setupConfig(SetupParams setupParams) async {
return await _interface.setupConfig(setupParams);
Future<String> setupConfig(ClashConfig clashConfig) async {
await globalState.genConfigFile(clashConfig);
final params = await globalState.getSetupParams();
return await _interface.setupConfig(params);
}
Future<List<Group>> getProxiesGroups() async {
Future<List<Group>> getProxiesGroups({
required ProxiesSortType sortType,
required DelayMap delayMap,
required SelectedMap selectedMap,
required String defaultTestUrl,
}) async {
final proxies = await _interface.getProxies();
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();
return groupsRaw.map((e) => Group.fromJson(e)).toList();
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,
sortType: sortType,
delayMap: delayMap,
selectedMap: selectedMap,
defaultTestUrl: defaultTestUrl,
);
});
}
FutureOr<String> changeProxy(ChangeProxyParams changeProxyParams) async {
@@ -154,9 +171,6 @@ class CoreController {
if (externalProvidersRawString.isEmpty) {
return null;
}
if (externalProvidersRawString.isEmpty) {
return null;
}
return ExternalProvider.fromJson(json.decode(externalProvidersRawString));
}
@@ -258,6 +272,10 @@ class CoreController {
Future<void> crash() async {
await _interface.crash();
}
Future<String> deleteFile(String path) async {
return await _interface.deleteFile(path);
}
}
final coreController = CoreController();

View File

@@ -9,7 +9,7 @@ import 'package:fl_clash/models/models.dart';
mixin CoreInterface {
Future<bool> init(InitParams params);
Future<bool> preload();
Future<String> preload();
Future<bool> shutdown();
@@ -68,6 +68,8 @@ mixin CoreInterface {
FutureOr<bool> closeConnection(String id);
FutureOr<String> deleteFile(String path);
FutureOr<bool> closeConnections();
FutureOr<bool> resetConnections();
@@ -163,7 +165,7 @@ abstract class CoreHandlerInterface with CoreInterface {
@override
Future<Map> getProxies() async {
var map = await _invoke<Map>(method: ActionMethod.getProxies);
final map = await _invoke<Map>(method: ActionMethod.getProxies);
return map ?? {};
}
@@ -263,6 +265,12 @@ abstract class CoreHandlerInterface with CoreInterface {
'';
}
@override
Future<String> deleteFile(String path) async {
return await _invoke<String>(method: ActionMethod.deleteFile, data: path) ??
'';
}
@override
resetTraffic() {
_invoke(method: ActionMethod.resetTraffic);
@@ -298,6 +306,7 @@ abstract class CoreHandlerInterface with CoreInterface {
return await _invoke<String>(
method: ActionMethod.asyncTestDelay,
data: json.encode(delayParams),
timeout: Duration(seconds: 6),
) ??
json.encode(Delay(name: proxyName, value: -1, url: url));
}

View File

@@ -15,10 +15,12 @@ class CoreLib extends CoreHandlerInterface {
CoreLib._internal();
@override
Future<bool> preload() async {
await service?.init();
_connectedCompleter.complete(true);
return true;
Future<String> preload() async {
final res = await service?.init();
if (res?.isEmpty == true) {
_connectedCompleter.complete(true);
}
return res ?? '';
}
factory CoreLib() {

View File

@@ -149,10 +149,10 @@ class CoreService extends CoreHandlerInterface {
}
@override
Future<bool> preload() async {
Future<String> preload() async {
await _serverCompleter.future;
await start();
return true;
return '';
}
@override

View File

@@ -2,6 +2,7 @@
import 'dart:io';
import 'package:fl_clash/common/color.dart';
import 'package:fl_clash/common/system.dart';
import 'package:fl_clash/views/dashboard/widgets/widgets.dart';
import 'package:fl_clash/widgets/widgets.dart';
@@ -95,7 +96,7 @@ extension LogLevelExt on LogLevel {
LogLevel.silent => Colors.grey.shade700,
LogLevel.debug => Colors.grey.shade400,
LogLevel.info => null,
LogLevel.warning => Colors.yellowAccent,
LogLevel.warning => Colors.orangeAccent.darken(),
LogLevel.error => Colors.redAccent,
};
}
@@ -242,6 +243,7 @@ enum ActionMethod {
getMemory,
crash,
setupConfig,
deleteFile,
///Android,
setState,

View File

@@ -28,10 +28,9 @@ class AppLocalizations {
static const AppLocalizationDelegate delegate = AppLocalizationDelegate();
static Future<AppLocalizations> load(Locale locale) {
final name =
(locale.countryCode?.isEmpty ?? false)
? locale.languageCode
: locale.toString();
final name = (locale.countryCode?.isEmpty ?? false)
? locale.languageCode
: locale.toString();
final localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) {
Intl.defaultLocale = localeName;

View File

@@ -24,13 +24,13 @@ Future<void> main() async {
Future<void> _service(List<String> flags) async {
WidgetsFlutterBinding.ensureInitialized();
await globalState.init();
await service?.init();
await coreController.preload();
await service?.syncAndroidState(globalState.getAndroidState());
tile?.addListener(
_TileListenerWithService(
onStop: () async {
await app?.tip(appLocalizations.stopVpn);
await globalState.handleStop();
exit(0);
},
),
);
@@ -41,9 +41,8 @@ Future<void> _service(List<String> flags) async {
final clashConfig = globalState.config.patchClashConfig.copyWith.tun(
enable: false,
);
final params = await globalState.getSetupParams(pathConfig: clashConfig);
await coreController.setupConfig(params);
await globalState.handleStart();
await coreController.setupConfig(clashConfig);
});
}

View File

@@ -43,6 +43,11 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
globalState.appController.savePreferencesDebounce();
}
});
ref.listenManual(needUpdateGroupsProvider, (prev, next) {
if (prev != next) {
globalState.appController.updateGroupsDebounce();
}
});
if (window == null) {
return;
}

View File

@@ -96,7 +96,6 @@ class _CoreContainerState extends ConsumerState<CoreManager>
if (ref.read(coreStatusProvider) != CoreStatus.connected) {
return;
}
context.showNotifier('Core crash');
ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
await coreController.shutdown();
super.onCrash();

View File

@@ -1,5 +1,3 @@
// ignore_for_file: invalid_annotation_target
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

View File

@@ -1,5 +1,3 @@
// ignore_for_file: invalid_annotation_target
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
@@ -365,13 +363,41 @@ abstract class IpInfo with _$IpInfo {
};
}
static IpInfo fromIpwhoIsJson(Map<String, dynamic> json) {
static IpInfo fromIpWhoIsJson(Map<String, dynamic> json) {
return switch (json) {
{'ip': final String ip, 'country_code': final String countryCode} =>
IpInfo(ip: ip, countryCode: countryCode),
_ => throw const FormatException('invalid json'),
};
}
static IpInfo fromMyIpJson(Map<String, dynamic> json) {
return switch (json) {
{'ip': final String ip, 'cc': final String countryCode} => IpInfo(
ip: ip,
countryCode: countryCode,
),
_ => throw const FormatException('invalid json'),
};
}
static IpInfo fromIpAPIJson(Map<String, dynamic> json) {
return switch (json) {
{'query': final String ip, 'countryCode': final String countryCode} =>
IpInfo(ip: ip, countryCode: countryCode),
_ => throw const FormatException('invalid json'),
};
}
static IpInfo fromIdentMeJson(Map<String, dynamic> json) {
return switch (json) {
{'ip': final String ip, 'cc': final String countryCode} => IpInfo(
ip: ip,
countryCode: countryCode,
),
_ => throw const FormatException('invalid json'),
};
}
}
@freezed
@@ -464,3 +490,29 @@ abstract class Script with _$Script {
factory Script.fromJson(Map<String, Object?> json) => _$ScriptFromJson(json);
}
@freezed
abstract class DelayState with _$DelayState {
const factory DelayState({required int delay, required bool group}) =
_DelayState;
}
extension DelayStateExt on DelayState {
int get priority {
if (delay > 0) return 0;
if (delay == 0) return 1;
return 2;
}
int compareTo(DelayState other) {
if (priority != other.priority) {
return priority.compareTo(other.priority);
}
if (delay != other.delay) {
return delay.compareTo(other.delay);
}
if (group && !group) return -1;
if (!group && group) return 1;
return 0;
}
}

View File

@@ -1,5 +1,3 @@
// ignore_for_file: invalid_annotation_target
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
@@ -27,7 +25,7 @@ const defaultBypassDomain = [
'172.2*',
'172.30.*',
'172.31.*',
'192.168.*'
'192.168.*',
];
const defaultAppSettingProps = AppSettingProps();
@@ -36,9 +34,7 @@ const defaultNetworkProps = NetworkProps();
const defaultProxiesStyle = ProxiesStyle();
const defaultWindowProps = WindowProps();
const defaultAccessControl = AccessControl();
final defaultThemeProps = ThemeProps(
primaryColor: defaultPrimaryColor,
);
final defaultThemeProps = ThemeProps(primaryColor: defaultPrimaryColor);
const List<DashboardWidget> defaultDashboardWidgets = [
DashboardWidget.networkSpeed,
@@ -115,9 +111,9 @@ abstract class AccessControl with _$AccessControl {
extension AccessControlExt on AccessControl {
List<String> get currentList => switch (mode) {
AccessControlMode.acceptSelected => acceptList,
AccessControlMode.rejectSelected => rejectList,
};
AccessControlMode.acceptSelected => acceptList,
AccessControlMode.rejectSelected => rejectList,
};
}
@freezed

View File

@@ -1,5 +1,3 @@
// ignore_for_file: invalid_annotation_target
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -10,7 +8,6 @@ part 'generated/core.g.dart';
@freezed
abstract class SetupParams with _$SetupParams {
const factory SetupParams({
@JsonKey(name: 'config') required Map<String, dynamic> config,
@JsonKey(name: 'selected-map') required Map<String, String> selectedMap,
@JsonKey(name: 'test-url') required String testUrl,
}) = _SetupParams;
@@ -51,6 +48,7 @@ abstract class VpnOptions with _$VpnOptions {
required bool allowBypass,
required bool systemProxy,
required List<String> bypassDomain,
required String stack,
@Default([]) List<String> routeAddress,
}) = _VpnOptions;

View File

@@ -5768,6 +5768,266 @@ as String,
}
}
/// @nodoc
mixin _$DelayState {
int get delay; bool get group;
/// Create a copy of DelayState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$DelayStateCopyWith<DelayState> get copyWith => _$DelayStateCopyWithImpl<DelayState>(this as DelayState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DelayState&&(identical(other.delay, delay) || other.delay == delay)&&(identical(other.group, group) || other.group == group));
}
@override
int get hashCode => Object.hash(runtimeType,delay,group);
@override
String toString() {
return 'DelayState(delay: $delay, group: $group)';
}
}
/// @nodoc
abstract mixin class $DelayStateCopyWith<$Res> {
factory $DelayStateCopyWith(DelayState value, $Res Function(DelayState) _then) = _$DelayStateCopyWithImpl;
@useResult
$Res call({
int delay, bool group
});
}
/// @nodoc
class _$DelayStateCopyWithImpl<$Res>
implements $DelayStateCopyWith<$Res> {
_$DelayStateCopyWithImpl(this._self, this._then);
final DelayState _self;
final $Res Function(DelayState) _then;
/// Create a copy of DelayState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? delay = null,Object? group = null,}) {
return _then(_self.copyWith(
delay: null == delay ? _self.delay : delay // ignore: cast_nullable_to_non_nullable
as int,group: null == group ? _self.group : group // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [DelayState].
extension DelayStatePatterns on DelayState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _DelayState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _DelayState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _DelayState value) $default,){
final _that = this;
switch (_that) {
case _DelayState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _DelayState value)? $default,){
final _that = this;
switch (_that) {
case _DelayState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int delay, bool group)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _DelayState() when $default != null:
return $default(_that.delay,_that.group);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int delay, bool group) $default,) {final _that = this;
switch (_that) {
case _DelayState():
return $default(_that.delay,_that.group);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int delay, bool group)? $default,) {final _that = this;
switch (_that) {
case _DelayState() when $default != null:
return $default(_that.delay,_that.group);case _:
return null;
}
}
}
/// @nodoc
class _DelayState implements DelayState {
const _DelayState({required this.delay, required this.group});
@override final int delay;
@override final bool group;
/// Create a copy of DelayState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$DelayStateCopyWith<_DelayState> get copyWith => __$DelayStateCopyWithImpl<_DelayState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DelayState&&(identical(other.delay, delay) || other.delay == delay)&&(identical(other.group, group) || other.group == group));
}
@override
int get hashCode => Object.hash(runtimeType,delay,group);
@override
String toString() {
return 'DelayState(delay: $delay, group: $group)';
}
}
/// @nodoc
abstract mixin class _$DelayStateCopyWith<$Res> implements $DelayStateCopyWith<$Res> {
factory _$DelayStateCopyWith(_DelayState value, $Res Function(_DelayState) _then) = __$DelayStateCopyWithImpl;
@override @useResult
$Res call({
int delay, bool group
});
}
/// @nodoc
class __$DelayStateCopyWithImpl<$Res>
implements _$DelayStateCopyWith<$Res> {
__$DelayStateCopyWithImpl(this._self, this._then);
final _DelayState _self;
final $Res Function(_DelayState) _then;
/// Create a copy of DelayState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? delay = null,Object? group = null,}) {
return _then(_DelayState(
delay: null == delay ? _self.delay : delay // ignore: cast_nullable_to_non_nullable
as int,group: null == group ? _self.group : group // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View File

@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SetupParams {
@JsonKey(name: 'config') Map<String, dynamic> get config;@JsonKey(name: 'selected-map') Map<String, String> get selectedMap;@JsonKey(name: 'test-url') String get testUrl;
@JsonKey(name: 'selected-map') Map<String, String> get selectedMap;@JsonKey(name: 'test-url') String get testUrl;
/// Create a copy of SetupParams
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -28,16 +28,16 @@ $SetupParamsCopyWith<SetupParams> get copyWith => _$SetupParamsCopyWithImpl<Setu
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SetupParams&&const DeepCollectionEquality().equals(other.config, config)&&const DeepCollectionEquality().equals(other.selectedMap, selectedMap)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SetupParams&&const DeepCollectionEquality().equals(other.selectedMap, selectedMap)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(config),const DeepCollectionEquality().hash(selectedMap),testUrl);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(selectedMap),testUrl);
@override
String toString() {
return 'SetupParams(config: $config, selectedMap: $selectedMap, testUrl: $testUrl)';
return 'SetupParams(selectedMap: $selectedMap, testUrl: $testUrl)';
}
@@ -48,7 +48,7 @@ abstract mixin class $SetupParamsCopyWith<$Res> {
factory $SetupParamsCopyWith(SetupParams value, $Res Function(SetupParams) _then) = _$SetupParamsCopyWithImpl;
@useResult
$Res call({
@JsonKey(name: 'config') Map<String, dynamic> config,@JsonKey(name: 'selected-map') Map<String, String> selectedMap,@JsonKey(name: 'test-url') String testUrl
@JsonKey(name: 'selected-map') Map<String, String> selectedMap,@JsonKey(name: 'test-url') String testUrl
});
@@ -65,10 +65,9 @@ class _$SetupParamsCopyWithImpl<$Res>
/// Create a copy of SetupParams
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? config = null,Object? selectedMap = null,Object? testUrl = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? selectedMap = null,Object? testUrl = null,}) {
return _then(_self.copyWith(
config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,selectedMap: null == selectedMap ? _self.selectedMap : selectedMap // ignore: cast_nullable_to_non_nullable
selectedMap: null == selectedMap ? _self.selectedMap : selectedMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>,testUrl: null == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
as String,
));
@@ -155,10 +154,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(name: 'config') Map<String, dynamic> config, @JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SetupParams() when $default != null:
return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
return $default(_that.selectedMap,_that.testUrl);case _:
return orElse();
}
@@ -176,10 +175,10 @@ return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(name: 'config') Map<String, dynamic> config, @JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl) $default,) {final _that = this;
switch (_that) {
case _SetupParams():
return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
return $default(_that.selectedMap,_that.testUrl);case _:
throw StateError('Unexpected subclass');
}
@@ -196,10 +195,10 @@ return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(name: 'config') Map<String, dynamic> config, @JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@JsonKey(name: 'selected-map') Map<String, String> selectedMap, @JsonKey(name: 'test-url') String testUrl)? $default,) {final _that = this;
switch (_that) {
case _SetupParams() when $default != null:
return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
return $default(_that.selectedMap,_that.testUrl);case _:
return null;
}
@@ -211,16 +210,9 @@ return $default(_that.config,_that.selectedMap,_that.testUrl);case _:
@JsonSerializable()
class _SetupParams implements SetupParams {
const _SetupParams({@JsonKey(name: 'config') required final Map<String, dynamic> config, @JsonKey(name: 'selected-map') required final Map<String, String> selectedMap, @JsonKey(name: 'test-url') required this.testUrl}): _config = config,_selectedMap = selectedMap;
const _SetupParams({@JsonKey(name: 'selected-map') required final Map<String, String> selectedMap, @JsonKey(name: 'test-url') required this.testUrl}): _selectedMap = selectedMap;
factory _SetupParams.fromJson(Map<String, dynamic> json) => _$SetupParamsFromJson(json);
final Map<String, dynamic> _config;
@override@JsonKey(name: 'config') Map<String, dynamic> get config {
if (_config is EqualUnmodifiableMapView) return _config;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_config);
}
final Map<String, String> _selectedMap;
@override@JsonKey(name: 'selected-map') Map<String, String> get selectedMap {
if (_selectedMap is EqualUnmodifiableMapView) return _selectedMap;
@@ -243,16 +235,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SetupParams&&const DeepCollectionEquality().equals(other._config, _config)&&const DeepCollectionEquality().equals(other._selectedMap, _selectedMap)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SetupParams&&const DeepCollectionEquality().equals(other._selectedMap, _selectedMap)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_config),const DeepCollectionEquality().hash(_selectedMap),testUrl);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_selectedMap),testUrl);
@override
String toString() {
return 'SetupParams(config: $config, selectedMap: $selectedMap, testUrl: $testUrl)';
return 'SetupParams(selectedMap: $selectedMap, testUrl: $testUrl)';
}
@@ -263,7 +255,7 @@ abstract mixin class _$SetupParamsCopyWith<$Res> implements $SetupParamsCopyWith
factory _$SetupParamsCopyWith(_SetupParams value, $Res Function(_SetupParams) _then) = __$SetupParamsCopyWithImpl;
@override @useResult
$Res call({
@JsonKey(name: 'config') Map<String, dynamic> config,@JsonKey(name: 'selected-map') Map<String, String> selectedMap,@JsonKey(name: 'test-url') String testUrl
@JsonKey(name: 'selected-map') Map<String, String> selectedMap,@JsonKey(name: 'test-url') String testUrl
});
@@ -280,10 +272,9 @@ class __$SetupParamsCopyWithImpl<$Res>
/// Create a copy of SetupParams
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? config = null,Object? selectedMap = null,Object? testUrl = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? selectedMap = null,Object? testUrl = null,}) {
return _then(_SetupParams(
config: null == config ? _self._config : config // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,selectedMap: null == selectedMap ? _self._selectedMap : selectedMap // ignore: cast_nullable_to_non_nullable
selectedMap: null == selectedMap ? _self._selectedMap : selectedMap // ignore: cast_nullable_to_non_nullable
as Map<String, String>,testUrl: null == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
as String,
));
@@ -604,7 +595,7 @@ $TunCopyWith<$Res> get tun {
/// @nodoc
mixin _$VpnOptions {
bool get enable; int get port; bool get ipv6; bool get dnsHijacking; AccessControl get accessControl; bool get allowBypass; bool get systemProxy; List<String> get bypassDomain; List<String> get routeAddress;
bool get enable; int get port; bool get ipv6; bool get dnsHijacking; AccessControl get accessControl; bool get allowBypass; bool get systemProxy; List<String> get bypassDomain; String get stack; List<String> get routeAddress;
/// Create a copy of VpnOptions
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -617,16 +608,16 @@ $VpnOptionsCopyWith<VpnOptions> get copyWith => _$VpnOptionsCopyWithImpl<VpnOpti
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is VpnOptions&&(identical(other.enable, enable) || other.enable == enable)&&(identical(other.port, port) || other.port == port)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.dnsHijacking, dnsHijacking) || other.dnsHijacking == dnsHijacking)&&(identical(other.accessControl, accessControl) || other.accessControl == accessControl)&&(identical(other.allowBypass, allowBypass) || other.allowBypass == allowBypass)&&(identical(other.systemProxy, systemProxy) || other.systemProxy == systemProxy)&&const DeepCollectionEquality().equals(other.bypassDomain, bypassDomain)&&const DeepCollectionEquality().equals(other.routeAddress, routeAddress));
return identical(this, other) || (other.runtimeType == runtimeType&&other is VpnOptions&&(identical(other.enable, enable) || other.enable == enable)&&(identical(other.port, port) || other.port == port)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.dnsHijacking, dnsHijacking) || other.dnsHijacking == dnsHijacking)&&(identical(other.accessControl, accessControl) || other.accessControl == accessControl)&&(identical(other.allowBypass, allowBypass) || other.allowBypass == allowBypass)&&(identical(other.systemProxy, systemProxy) || other.systemProxy == systemProxy)&&const DeepCollectionEquality().equals(other.bypassDomain, bypassDomain)&&(identical(other.stack, stack) || other.stack == stack)&&const DeepCollectionEquality().equals(other.routeAddress, routeAddress));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,enable,port,ipv6,dnsHijacking,accessControl,allowBypass,systemProxy,const DeepCollectionEquality().hash(bypassDomain),const DeepCollectionEquality().hash(routeAddress));
int get hashCode => Object.hash(runtimeType,enable,port,ipv6,dnsHijacking,accessControl,allowBypass,systemProxy,const DeepCollectionEquality().hash(bypassDomain),stack,const DeepCollectionEquality().hash(routeAddress));
@override
String toString() {
return 'VpnOptions(enable: $enable, port: $port, ipv6: $ipv6, dnsHijacking: $dnsHijacking, accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, routeAddress: $routeAddress)';
return 'VpnOptions(enable: $enable, port: $port, ipv6: $ipv6, dnsHijacking: $dnsHijacking, accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, stack: $stack, routeAddress: $routeAddress)';
}
@@ -637,7 +628,7 @@ abstract mixin class $VpnOptionsCopyWith<$Res> {
factory $VpnOptionsCopyWith(VpnOptions value, $Res Function(VpnOptions) _then) = _$VpnOptionsCopyWithImpl;
@useResult
$Res call({
bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress
bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress
});
@@ -654,7 +645,7 @@ class _$VpnOptionsCopyWithImpl<$Res>
/// Create a copy of VpnOptions
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? enable = null,Object? port = null,Object? ipv6 = null,Object? dnsHijacking = null,Object? accessControl = null,Object? allowBypass = null,Object? systemProxy = null,Object? bypassDomain = null,Object? routeAddress = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? enable = null,Object? port = null,Object? ipv6 = null,Object? dnsHijacking = null,Object? accessControl = null,Object? allowBypass = null,Object? systemProxy = null,Object? bypassDomain = null,Object? stack = null,Object? routeAddress = null,}) {
return _then(_self.copyWith(
enable: null == enable ? _self.enable : enable // ignore: cast_nullable_to_non_nullable
as bool,port: null == port ? _self.port : port // ignore: cast_nullable_to_non_nullable
@@ -664,7 +655,8 @@ as bool,accessControl: null == accessControl ? _self.accessControl : accessContr
as AccessControl,allowBypass: null == allowBypass ? _self.allowBypass : allowBypass // ignore: cast_nullable_to_non_nullable
as bool,systemProxy: null == systemProxy ? _self.systemProxy : systemProxy // ignore: cast_nullable_to_non_nullable
as bool,bypassDomain: null == bypassDomain ? _self.bypassDomain : bypassDomain // ignore: cast_nullable_to_non_nullable
as List<String>,routeAddress: null == routeAddress ? _self.routeAddress : routeAddress // ignore: cast_nullable_to_non_nullable
as List<String>,stack: null == stack ? _self.stack : stack // ignore: cast_nullable_to_non_nullable
as String,routeAddress: null == routeAddress ? _self.routeAddress : routeAddress // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
@@ -759,10 +751,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _VpnOptions() when $default != null:
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.routeAddress);case _:
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.stack,_that.routeAddress);case _:
return orElse();
}
@@ -780,10 +772,10 @@ return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.acce
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress) $default,) {final _that = this;
switch (_that) {
case _VpnOptions():
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.routeAddress);case _:
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.stack,_that.routeAddress);case _:
throw StateError('Unexpected subclass');
}
@@ -800,10 +792,10 @@ return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.acce
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress)? $default,) {final _that = this;
switch (_that) {
case _VpnOptions() when $default != null:
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.routeAddress);case _:
return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.accessControl,_that.allowBypass,_that.systemProxy,_that.bypassDomain,_that.stack,_that.routeAddress);case _:
return null;
}
@@ -815,7 +807,7 @@ return $default(_that.enable,_that.port,_that.ipv6,_that.dnsHijacking,_that.acce
@JsonSerializable()
class _VpnOptions implements VpnOptions {
const _VpnOptions({required this.enable, required this.port, required this.ipv6, required this.dnsHijacking, required this.accessControl, required this.allowBypass, required this.systemProxy, required final List<String> bypassDomain, final List<String> routeAddress = const []}): _bypassDomain = bypassDomain,_routeAddress = routeAddress;
const _VpnOptions({required this.enable, required this.port, required this.ipv6, required this.dnsHijacking, required this.accessControl, required this.allowBypass, required this.systemProxy, required final List<String> bypassDomain, required this.stack, final List<String> routeAddress = const []}): _bypassDomain = bypassDomain,_routeAddress = routeAddress;
factory _VpnOptions.fromJson(Map<String, dynamic> json) => _$VpnOptionsFromJson(json);
@override final bool enable;
@@ -832,6 +824,7 @@ class _VpnOptions implements VpnOptions {
return EqualUnmodifiableListView(_bypassDomain);
}
@override final String stack;
final List<String> _routeAddress;
@override@JsonKey() List<String> get routeAddress {
if (_routeAddress is EqualUnmodifiableListView) return _routeAddress;
@@ -853,16 +846,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _VpnOptions&&(identical(other.enable, enable) || other.enable == enable)&&(identical(other.port, port) || other.port == port)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.dnsHijacking, dnsHijacking) || other.dnsHijacking == dnsHijacking)&&(identical(other.accessControl, accessControl) || other.accessControl == accessControl)&&(identical(other.allowBypass, allowBypass) || other.allowBypass == allowBypass)&&(identical(other.systemProxy, systemProxy) || other.systemProxy == systemProxy)&&const DeepCollectionEquality().equals(other._bypassDomain, _bypassDomain)&&const DeepCollectionEquality().equals(other._routeAddress, _routeAddress));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _VpnOptions&&(identical(other.enable, enable) || other.enable == enable)&&(identical(other.port, port) || other.port == port)&&(identical(other.ipv6, ipv6) || other.ipv6 == ipv6)&&(identical(other.dnsHijacking, dnsHijacking) || other.dnsHijacking == dnsHijacking)&&(identical(other.accessControl, accessControl) || other.accessControl == accessControl)&&(identical(other.allowBypass, allowBypass) || other.allowBypass == allowBypass)&&(identical(other.systemProxy, systemProxy) || other.systemProxy == systemProxy)&&const DeepCollectionEquality().equals(other._bypassDomain, _bypassDomain)&&(identical(other.stack, stack) || other.stack == stack)&&const DeepCollectionEquality().equals(other._routeAddress, _routeAddress));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,enable,port,ipv6,dnsHijacking,accessControl,allowBypass,systemProxy,const DeepCollectionEquality().hash(_bypassDomain),const DeepCollectionEquality().hash(_routeAddress));
int get hashCode => Object.hash(runtimeType,enable,port,ipv6,dnsHijacking,accessControl,allowBypass,systemProxy,const DeepCollectionEquality().hash(_bypassDomain),stack,const DeepCollectionEquality().hash(_routeAddress));
@override
String toString() {
return 'VpnOptions(enable: $enable, port: $port, ipv6: $ipv6, dnsHijacking: $dnsHijacking, accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, routeAddress: $routeAddress)';
return 'VpnOptions(enable: $enable, port: $port, ipv6: $ipv6, dnsHijacking: $dnsHijacking, accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, stack: $stack, routeAddress: $routeAddress)';
}
@@ -873,7 +866,7 @@ abstract mixin class _$VpnOptionsCopyWith<$Res> implements $VpnOptionsCopyWith<$
factory _$VpnOptionsCopyWith(_VpnOptions value, $Res Function(_VpnOptions) _then) = __$VpnOptionsCopyWithImpl;
@override @useResult
$Res call({
bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, List<String> routeAddress
bool enable, int port, bool ipv6, bool dnsHijacking, AccessControl accessControl, bool allowBypass, bool systemProxy, List<String> bypassDomain, String stack, List<String> routeAddress
});
@@ -890,7 +883,7 @@ class __$VpnOptionsCopyWithImpl<$Res>
/// Create a copy of VpnOptions
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? enable = null,Object? port = null,Object? ipv6 = null,Object? dnsHijacking = null,Object? accessControl = null,Object? allowBypass = null,Object? systemProxy = null,Object? bypassDomain = null,Object? routeAddress = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? enable = null,Object? port = null,Object? ipv6 = null,Object? dnsHijacking = null,Object? accessControl = null,Object? allowBypass = null,Object? systemProxy = null,Object? bypassDomain = null,Object? stack = null,Object? routeAddress = null,}) {
return _then(_VpnOptions(
enable: null == enable ? _self.enable : enable // ignore: cast_nullable_to_non_nullable
as bool,port: null == port ? _self.port : port // ignore: cast_nullable_to_non_nullable
@@ -900,7 +893,8 @@ as bool,accessControl: null == accessControl ? _self.accessControl : accessContr
as AccessControl,allowBypass: null == allowBypass ? _self.allowBypass : allowBypass // ignore: cast_nullable_to_non_nullable
as bool,systemProxy: null == systemProxy ? _self.systemProxy : systemProxy // ignore: cast_nullable_to_non_nullable
as bool,bypassDomain: null == bypassDomain ? _self._bypassDomain : bypassDomain // ignore: cast_nullable_to_non_nullable
as List<String>,routeAddress: null == routeAddress ? _self._routeAddress : routeAddress // ignore: cast_nullable_to_non_nullable
as List<String>,stack: null == stack ? _self.stack : stack // ignore: cast_nullable_to_non_nullable
as String,routeAddress: null == routeAddress ? _self._routeAddress : routeAddress // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}

View File

@@ -7,14 +7,12 @@ part of '../core.dart';
// **************************************************************************
_SetupParams _$SetupParamsFromJson(Map<String, dynamic> json) => _SetupParams(
config: json['config'] as Map<String, dynamic>,
selectedMap: Map<String, String>.from(json['selected-map'] as Map),
testUrl: json['test-url'] as String,
);
Map<String, dynamic> _$SetupParamsToJson(_SetupParams instance) =>
<String, dynamic>{
'config': instance.config,
'selected-map': instance.selectedMap,
'test-url': instance.testUrl,
};
@@ -91,6 +89,7 @@ _VpnOptions _$VpnOptionsFromJson(Map<String, dynamic> json) => _VpnOptions(
bypassDomain: (json['bypassDomain'] as List<dynamic>)
.map((e) => e as String)
.toList(),
stack: json['stack'] as String,
routeAddress:
(json['routeAddress'] as List<dynamic>?)
?.map((e) => e as String)
@@ -108,6 +107,7 @@ Map<String, dynamic> _$VpnOptionsToJson(_VpnOptions instance) =>
'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy,
'bypassDomain': instance.bypassDomain,
'stack': instance.stack,
'routeAddress': instance.routeAddress,
};
@@ -291,6 +291,7 @@ const _$ActionMethodEnumMap = {
ActionMethod.getMemory: 'getMemory',
ActionMethod.crash: 'crash',
ActionMethod.setupConfig: 'setupConfig',
ActionMethod.deleteFile: 'deleteFile',
ActionMethod.setState: 'setState',
ActionMethod.startTun: 'startTun',
ActionMethod.stopTun: 'stopTun',

View File

@@ -2982,7 +2982,7 @@ as List<NavigationItem>,
/// @nodoc
mixin _$ProxiesListState {
List<Group> get groups; Set<String> get currentUnfoldSet; ProxiesSortType get proxiesSortType; ProxyCardType get proxyCardType; num get sortNum; int get columns;
List<Group> get groups; Set<String> get currentUnfoldSet; ProxyCardType get proxyCardType; int get columns;
/// Create a copy of ProxiesListState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -2993,16 +2993,16 @@ $ProxiesListStateCopyWith<ProxiesListState> get copyWith => _$ProxiesListStateCo
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxiesListState&&const DeepCollectionEquality().equals(other.groups, groups)&&const DeepCollectionEquality().equals(other.currentUnfoldSet, currentUnfoldSet)&&(identical(other.proxiesSortType, proxiesSortType) || other.proxiesSortType == proxiesSortType)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.columns, columns) || other.columns == columns));
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxiesListState&&const DeepCollectionEquality().equals(other.groups, groups)&&const DeepCollectionEquality().equals(other.currentUnfoldSet, currentUnfoldSet)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.columns, columns) || other.columns == columns));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(groups),const DeepCollectionEquality().hash(currentUnfoldSet),proxiesSortType,proxyCardType,sortNum,columns);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(groups),const DeepCollectionEquality().hash(currentUnfoldSet),proxyCardType,columns);
@override
String toString() {
return 'ProxiesListState(groups: $groups, currentUnfoldSet: $currentUnfoldSet, proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, columns: $columns)';
return 'ProxiesListState(groups: $groups, currentUnfoldSet: $currentUnfoldSet, proxyCardType: $proxyCardType, columns: $columns)';
}
@@ -3013,7 +3013,7 @@ abstract mixin class $ProxiesListStateCopyWith<$Res> {
factory $ProxiesListStateCopyWith(ProxiesListState value, $Res Function(ProxiesListState) _then) = _$ProxiesListStateCopyWithImpl;
@useResult
$Res call({
List<Group> groups, Set<String> currentUnfoldSet, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns
List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns
});
@@ -3030,14 +3030,12 @@ class _$ProxiesListStateCopyWithImpl<$Res>
/// Create a copy of ProxiesListState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? groups = null,Object? currentUnfoldSet = null,Object? proxiesSortType = null,Object? proxyCardType = null,Object? sortNum = null,Object? columns = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? groups = null,Object? currentUnfoldSet = null,Object? proxyCardType = null,Object? columns = null,}) {
return _then(_self.copyWith(
groups: null == groups ? _self.groups : groups // ignore: cast_nullable_to_non_nullable
as List<Group>,currentUnfoldSet: null == currentUnfoldSet ? _self.currentUnfoldSet : currentUnfoldSet // ignore: cast_nullable_to_non_nullable
as Set<String>,proxiesSortType: null == proxiesSortType ? _self.proxiesSortType : proxiesSortType // ignore: cast_nullable_to_non_nullable
as ProxiesSortType,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,sortNum: null == sortNum ? _self.sortNum : sortNum // ignore: cast_nullable_to_non_nullable
as num,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as Set<String>,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as int,
));
}
@@ -3123,10 +3121,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Group> groups, Set<String> currentUnfoldSet, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ProxiesListState() when $default != null:
return $default(_that.groups,_that.currentUnfoldSet,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.columns);case _:
return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType,_that.columns);case _:
return orElse();
}
@@ -3144,10 +3142,10 @@ return $default(_that.groups,_that.currentUnfoldSet,_that.proxiesSortType,_that.
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Group> groups, Set<String> currentUnfoldSet, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns) $default,) {final _that = this;
switch (_that) {
case _ProxiesListState():
return $default(_that.groups,_that.currentUnfoldSet,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.columns);case _:
return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType,_that.columns);case _:
throw StateError('Unexpected subclass');
}
@@ -3164,10 +3162,10 @@ return $default(_that.groups,_that.currentUnfoldSet,_that.proxiesSortType,_that.
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Group> groups, Set<String> currentUnfoldSet, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns)? $default,) {final _that = this;
switch (_that) {
case _ProxiesListState() when $default != null:
return $default(_that.groups,_that.currentUnfoldSet,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.columns);case _:
return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType,_that.columns);case _:
return null;
}
@@ -3179,7 +3177,7 @@ return $default(_that.groups,_that.currentUnfoldSet,_that.proxiesSortType,_that.
class _ProxiesListState implements ProxiesListState {
const _ProxiesListState({required final List<Group> groups, required final Set<String> currentUnfoldSet, required this.proxiesSortType, required this.proxyCardType, required this.sortNum, required this.columns}): _groups = groups,_currentUnfoldSet = currentUnfoldSet;
const _ProxiesListState({required final List<Group> groups, required final Set<String> currentUnfoldSet, required this.proxyCardType, required this.columns}): _groups = groups,_currentUnfoldSet = currentUnfoldSet;
final List<Group> _groups;
@@ -3196,9 +3194,7 @@ class _ProxiesListState implements ProxiesListState {
return EqualUnmodifiableSetView(_currentUnfoldSet);
}
@override final ProxiesSortType proxiesSortType;
@override final ProxyCardType proxyCardType;
@override final num sortNum;
@override final int columns;
/// Create a copy of ProxiesListState
@@ -3211,16 +3207,16 @@ _$ProxiesListStateCopyWith<_ProxiesListState> get copyWith => __$ProxiesListStat
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxiesListState&&const DeepCollectionEquality().equals(other._groups, _groups)&&const DeepCollectionEquality().equals(other._currentUnfoldSet, _currentUnfoldSet)&&(identical(other.proxiesSortType, proxiesSortType) || other.proxiesSortType == proxiesSortType)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.columns, columns) || other.columns == columns));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxiesListState&&const DeepCollectionEquality().equals(other._groups, _groups)&&const DeepCollectionEquality().equals(other._currentUnfoldSet, _currentUnfoldSet)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.columns, columns) || other.columns == columns));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_groups),const DeepCollectionEquality().hash(_currentUnfoldSet),proxiesSortType,proxyCardType,sortNum,columns);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_groups),const DeepCollectionEquality().hash(_currentUnfoldSet),proxyCardType,columns);
@override
String toString() {
return 'ProxiesListState(groups: $groups, currentUnfoldSet: $currentUnfoldSet, proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, columns: $columns)';
return 'ProxiesListState(groups: $groups, currentUnfoldSet: $currentUnfoldSet, proxyCardType: $proxyCardType, columns: $columns)';
}
@@ -3231,7 +3227,7 @@ abstract mixin class _$ProxiesListStateCopyWith<$Res> implements $ProxiesListSta
factory _$ProxiesListStateCopyWith(_ProxiesListState value, $Res Function(_ProxiesListState) _then) = __$ProxiesListStateCopyWithImpl;
@override @useResult
$Res call({
List<Group> groups, Set<String> currentUnfoldSet, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns
List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns
});
@@ -3248,14 +3244,12 @@ class __$ProxiesListStateCopyWithImpl<$Res>
/// Create a copy of ProxiesListState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? groups = null,Object? currentUnfoldSet = null,Object? proxiesSortType = null,Object? proxyCardType = null,Object? sortNum = null,Object? columns = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? groups = null,Object? currentUnfoldSet = null,Object? proxyCardType = null,Object? columns = null,}) {
return _then(_ProxiesListState(
groups: null == groups ? _self._groups : groups // ignore: cast_nullable_to_non_nullable
as List<Group>,currentUnfoldSet: null == currentUnfoldSet ? _self._currentUnfoldSet : currentUnfoldSet // ignore: cast_nullable_to_non_nullable
as Set<String>,proxiesSortType: null == proxiesSortType ? _self.proxiesSortType : proxiesSortType // ignore: cast_nullable_to_non_nullable
as ProxiesSortType,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,sortNum: null == sortNum ? _self.sortNum : sortNum // ignore: cast_nullable_to_non_nullable
as num,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as Set<String>,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as int,
));
}
@@ -3266,7 +3260,7 @@ as int,
/// @nodoc
mixin _$ProxiesTabState {
List<Group> get groups; String? get currentGroupName; ProxiesSortType get proxiesSortType; ProxyCardType get proxyCardType; num get sortNum; int get columns;
List<Group> get groups; String? get currentGroupName; ProxyCardType get proxyCardType; int get columns;
/// Create a copy of ProxiesTabState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -3277,16 +3271,16 @@ $ProxiesTabStateCopyWith<ProxiesTabState> get copyWith => _$ProxiesTabStateCopyW
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxiesTabState&&const DeepCollectionEquality().equals(other.groups, groups)&&(identical(other.currentGroupName, currentGroupName) || other.currentGroupName == currentGroupName)&&(identical(other.proxiesSortType, proxiesSortType) || other.proxiesSortType == proxiesSortType)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.columns, columns) || other.columns == columns));
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxiesTabState&&const DeepCollectionEquality().equals(other.groups, groups)&&(identical(other.currentGroupName, currentGroupName) || other.currentGroupName == currentGroupName)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.columns, columns) || other.columns == columns));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(groups),currentGroupName,proxiesSortType,proxyCardType,sortNum,columns);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(groups),currentGroupName,proxyCardType,columns);
@override
String toString() {
return 'ProxiesTabState(groups: $groups, currentGroupName: $currentGroupName, proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, columns: $columns)';
return 'ProxiesTabState(groups: $groups, currentGroupName: $currentGroupName, proxyCardType: $proxyCardType, columns: $columns)';
}
@@ -3297,7 +3291,7 @@ abstract mixin class $ProxiesTabStateCopyWith<$Res> {
factory $ProxiesTabStateCopyWith(ProxiesTabState value, $Res Function(ProxiesTabState) _then) = _$ProxiesTabStateCopyWithImpl;
@useResult
$Res call({
List<Group> groups, String? currentGroupName, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns
List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns
});
@@ -3314,14 +3308,12 @@ class _$ProxiesTabStateCopyWithImpl<$Res>
/// Create a copy of ProxiesTabState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? groups = null,Object? currentGroupName = freezed,Object? proxiesSortType = null,Object? proxyCardType = null,Object? sortNum = null,Object? columns = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? groups = null,Object? currentGroupName = freezed,Object? proxyCardType = null,Object? columns = null,}) {
return _then(_self.copyWith(
groups: null == groups ? _self.groups : groups // ignore: cast_nullable_to_non_nullable
as List<Group>,currentGroupName: freezed == currentGroupName ? _self.currentGroupName : currentGroupName // ignore: cast_nullable_to_non_nullable
as String?,proxiesSortType: null == proxiesSortType ? _self.proxiesSortType : proxiesSortType // ignore: cast_nullable_to_non_nullable
as ProxiesSortType,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,sortNum: null == sortNum ? _self.sortNum : sortNum // ignore: cast_nullable_to_non_nullable
as num,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as String?,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as int,
));
}
@@ -3407,10 +3399,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Group> groups, String? currentGroupName, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ProxiesTabState() when $default != null:
return $default(_that.groups,_that.currentGroupName,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.columns);case _:
return $default(_that.groups,_that.currentGroupName,_that.proxyCardType,_that.columns);case _:
return orElse();
}
@@ -3428,10 +3420,10 @@ return $default(_that.groups,_that.currentGroupName,_that.proxiesSortType,_that.
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Group> groups, String? currentGroupName, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns) $default,) {final _that = this;
switch (_that) {
case _ProxiesTabState():
return $default(_that.groups,_that.currentGroupName,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.columns);case _:
return $default(_that.groups,_that.currentGroupName,_that.proxyCardType,_that.columns);case _:
throw StateError('Unexpected subclass');
}
@@ -3448,10 +3440,10 @@ return $default(_that.groups,_that.currentGroupName,_that.proxiesSortType,_that.
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Group> groups, String? currentGroupName, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns)? $default,) {final _that = this;
switch (_that) {
case _ProxiesTabState() when $default != null:
return $default(_that.groups,_that.currentGroupName,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.columns);case _:
return $default(_that.groups,_that.currentGroupName,_that.proxyCardType,_that.columns);case _:
return null;
}
@@ -3463,7 +3455,7 @@ return $default(_that.groups,_that.currentGroupName,_that.proxiesSortType,_that.
class _ProxiesTabState implements ProxiesTabState {
const _ProxiesTabState({required final List<Group> groups, required this.currentGroupName, required this.proxiesSortType, required this.proxyCardType, required this.sortNum, required this.columns}): _groups = groups;
const _ProxiesTabState({required final List<Group> groups, required this.currentGroupName, required this.proxyCardType, required this.columns}): _groups = groups;
final List<Group> _groups;
@@ -3474,9 +3466,7 @@ class _ProxiesTabState implements ProxiesTabState {
}
@override final String? currentGroupName;
@override final ProxiesSortType proxiesSortType;
@override final ProxyCardType proxyCardType;
@override final num sortNum;
@override final int columns;
/// Create a copy of ProxiesTabState
@@ -3489,16 +3479,16 @@ _$ProxiesTabStateCopyWith<_ProxiesTabState> get copyWith => __$ProxiesTabStateCo
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxiesTabState&&const DeepCollectionEquality().equals(other._groups, _groups)&&(identical(other.currentGroupName, currentGroupName) || other.currentGroupName == currentGroupName)&&(identical(other.proxiesSortType, proxiesSortType) || other.proxiesSortType == proxiesSortType)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.columns, columns) || other.columns == columns));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxiesTabState&&const DeepCollectionEquality().equals(other._groups, _groups)&&(identical(other.currentGroupName, currentGroupName) || other.currentGroupName == currentGroupName)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.columns, columns) || other.columns == columns));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_groups),currentGroupName,proxiesSortType,proxyCardType,sortNum,columns);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_groups),currentGroupName,proxyCardType,columns);
@override
String toString() {
return 'ProxiesTabState(groups: $groups, currentGroupName: $currentGroupName, proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, columns: $columns)';
return 'ProxiesTabState(groups: $groups, currentGroupName: $currentGroupName, proxyCardType: $proxyCardType, columns: $columns)';
}
@@ -3509,7 +3499,7 @@ abstract mixin class _$ProxiesTabStateCopyWith<$Res> implements $ProxiesTabState
factory _$ProxiesTabStateCopyWith(_ProxiesTabState value, $Res Function(_ProxiesTabState) _then) = __$ProxiesTabStateCopyWithImpl;
@override @useResult
$Res call({
List<Group> groups, String? currentGroupName, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, int columns
List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns
});
@@ -3526,14 +3516,12 @@ class __$ProxiesTabStateCopyWithImpl<$Res>
/// Create a copy of ProxiesTabState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? groups = null,Object? currentGroupName = freezed,Object? proxiesSortType = null,Object? proxyCardType = null,Object? sortNum = null,Object? columns = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? groups = null,Object? currentGroupName = freezed,Object? proxyCardType = null,Object? columns = null,}) {
return _then(_ProxiesTabState(
groups: null == groups ? _self._groups : groups // ignore: cast_nullable_to_non_nullable
as List<Group>,currentGroupName: freezed == currentGroupName ? _self.currentGroupName : currentGroupName // ignore: cast_nullable_to_non_nullable
as String?,proxiesSortType: null == proxiesSortType ? _self.proxiesSortType : proxiesSortType // ignore: cast_nullable_to_non_nullable
as ProxiesSortType,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,sortNum: null == sortNum ? _self.sortNum : sortNum // ignore: cast_nullable_to_non_nullable
as num,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as String?,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as int,
));
}
@@ -5733,40 +5721,40 @@ as double,
}
/// @nodoc
mixin _$ProxyCardState {
mixin _$SelectedProxyState {
String get proxyName; String? get testUrl;
/// Create a copy of ProxyCardState
String get proxyName; bool get group; String? get testUrl;
/// Create a copy of SelectedProxyState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ProxyCardStateCopyWith<ProxyCardState> get copyWith => _$ProxyCardStateCopyWithImpl<ProxyCardState>(this as ProxyCardState, _$identity);
$SelectedProxyStateCopyWith<SelectedProxyState> get copyWith => _$SelectedProxyStateCopyWithImpl<SelectedProxyState>(this as SelectedProxyState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxyCardState&&(identical(other.proxyName, proxyName) || other.proxyName == proxyName)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SelectedProxyState&&(identical(other.proxyName, proxyName) || other.proxyName == proxyName)&&(identical(other.group, group) || other.group == group)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
}
@override
int get hashCode => Object.hash(runtimeType,proxyName,testUrl);
int get hashCode => Object.hash(runtimeType,proxyName,group,testUrl);
@override
String toString() {
return 'ProxyCardState(proxyName: $proxyName, testUrl: $testUrl)';
return 'SelectedProxyState(proxyName: $proxyName, group: $group, testUrl: $testUrl)';
}
}
/// @nodoc
abstract mixin class $ProxyCardStateCopyWith<$Res> {
factory $ProxyCardStateCopyWith(ProxyCardState value, $Res Function(ProxyCardState) _then) = _$ProxyCardStateCopyWithImpl;
abstract mixin class $SelectedProxyStateCopyWith<$Res> {
factory $SelectedProxyStateCopyWith(SelectedProxyState value, $Res Function(SelectedProxyState) _then) = _$SelectedProxyStateCopyWithImpl;
@useResult
$Res call({
String proxyName, String? testUrl
String proxyName, bool group, String? testUrl
});
@@ -5774,19 +5762,20 @@ $Res call({
}
/// @nodoc
class _$ProxyCardStateCopyWithImpl<$Res>
implements $ProxyCardStateCopyWith<$Res> {
_$ProxyCardStateCopyWithImpl(this._self, this._then);
class _$SelectedProxyStateCopyWithImpl<$Res>
implements $SelectedProxyStateCopyWith<$Res> {
_$SelectedProxyStateCopyWithImpl(this._self, this._then);
final ProxyCardState _self;
final $Res Function(ProxyCardState) _then;
final SelectedProxyState _self;
final $Res Function(SelectedProxyState) _then;
/// Create a copy of ProxyCardState
/// Create a copy of SelectedProxyState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? proxyName = null,Object? testUrl = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? proxyName = null,Object? group = null,Object? testUrl = freezed,}) {
return _then(_self.copyWith(
proxyName: null == proxyName ? _self.proxyName : proxyName // ignore: cast_nullable_to_non_nullable
as String,testUrl: freezed == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
as String,group: null == group ? _self.group : group // ignore: cast_nullable_to_non_nullable
as bool,testUrl: freezed == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
as String?,
));
}
@@ -5794,8 +5783,8 @@ as String?,
}
/// Adds pattern-matching-related methods to [ProxyCardState].
extension ProxyCardStatePatterns on ProxyCardState {
/// Adds pattern-matching-related methods to [SelectedProxyState].
extension SelectedProxyStatePatterns on SelectedProxyState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
@@ -5808,10 +5797,10 @@ extension ProxyCardStatePatterns on ProxyCardState {
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ProxyCardState value)? $default,{required TResult orElse(),}){
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SelectedProxyState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ProxyCardState() when $default != null:
case _SelectedProxyState() when $default != null:
return $default(_that);case _:
return orElse();
@@ -5830,10 +5819,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ProxyCardState value) $default,){
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SelectedProxyState value) $default,){
final _that = this;
switch (_that) {
case _ProxyCardState():
case _SelectedProxyState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
@@ -5851,10 +5840,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ProxyCardState value)? $default,){
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SelectedProxyState value)? $default,){
final _that = this;
switch (_that) {
case _ProxyCardState() when $default != null:
case _SelectedProxyState() when $default != null:
return $default(_that);case _:
return null;
@@ -5872,10 +5861,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String proxyName, String? testUrl)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String proxyName, bool group, String? testUrl)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ProxyCardState() when $default != null:
return $default(_that.proxyName,_that.testUrl);case _:
case _SelectedProxyState() when $default != null:
return $default(_that.proxyName,_that.group,_that.testUrl);case _:
return orElse();
}
@@ -5893,10 +5882,10 @@ return $default(_that.proxyName,_that.testUrl);case _:
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String proxyName, String? testUrl) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String proxyName, bool group, String? testUrl) $default,) {final _that = this;
switch (_that) {
case _ProxyCardState():
return $default(_that.proxyName,_that.testUrl);case _:
case _SelectedProxyState():
return $default(_that.proxyName,_that.group,_that.testUrl);case _:
throw StateError('Unexpected subclass');
}
@@ -5913,10 +5902,10 @@ return $default(_that.proxyName,_that.testUrl);case _:
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String proxyName, String? testUrl)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String proxyName, bool group, String? testUrl)? $default,) {final _that = this;
switch (_that) {
case _ProxyCardState() when $default != null:
return $default(_that.proxyName,_that.testUrl);case _:
case _SelectedProxyState() when $default != null:
return $default(_that.proxyName,_that.group,_that.testUrl);case _:
return null;
}
@@ -5927,44 +5916,45 @@ return $default(_that.proxyName,_that.testUrl);case _:
/// @nodoc
class _ProxyCardState implements ProxyCardState {
const _ProxyCardState({required this.proxyName, this.testUrl});
class _SelectedProxyState implements SelectedProxyState {
const _SelectedProxyState({required this.proxyName, this.group = false, this.testUrl});
@override final String proxyName;
@override@JsonKey() final bool group;
@override final String? testUrl;
/// Create a copy of ProxyCardState
/// Create a copy of SelectedProxyState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ProxyCardStateCopyWith<_ProxyCardState> get copyWith => __$ProxyCardStateCopyWithImpl<_ProxyCardState>(this, _$identity);
_$SelectedProxyStateCopyWith<_SelectedProxyState> get copyWith => __$SelectedProxyStateCopyWithImpl<_SelectedProxyState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxyCardState&&(identical(other.proxyName, proxyName) || other.proxyName == proxyName)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SelectedProxyState&&(identical(other.proxyName, proxyName) || other.proxyName == proxyName)&&(identical(other.group, group) || other.group == group)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl));
}
@override
int get hashCode => Object.hash(runtimeType,proxyName,testUrl);
int get hashCode => Object.hash(runtimeType,proxyName,group,testUrl);
@override
String toString() {
return 'ProxyCardState(proxyName: $proxyName, testUrl: $testUrl)';
return 'SelectedProxyState(proxyName: $proxyName, group: $group, testUrl: $testUrl)';
}
}
/// @nodoc
abstract mixin class _$ProxyCardStateCopyWith<$Res> implements $ProxyCardStateCopyWith<$Res> {
factory _$ProxyCardStateCopyWith(_ProxyCardState value, $Res Function(_ProxyCardState) _then) = __$ProxyCardStateCopyWithImpl;
abstract mixin class _$SelectedProxyStateCopyWith<$Res> implements $SelectedProxyStateCopyWith<$Res> {
factory _$SelectedProxyStateCopyWith(_SelectedProxyState value, $Res Function(_SelectedProxyState) _then) = __$SelectedProxyStateCopyWithImpl;
@override @useResult
$Res call({
String proxyName, String? testUrl
String proxyName, bool group, String? testUrl
});
@@ -5972,19 +5962,20 @@ $Res call({
}
/// @nodoc
class __$ProxyCardStateCopyWithImpl<$Res>
implements _$ProxyCardStateCopyWith<$Res> {
__$ProxyCardStateCopyWithImpl(this._self, this._then);
class __$SelectedProxyStateCopyWithImpl<$Res>
implements _$SelectedProxyStateCopyWith<$Res> {
__$SelectedProxyStateCopyWithImpl(this._self, this._then);
final _ProxyCardState _self;
final $Res Function(_ProxyCardState) _then;
final _SelectedProxyState _self;
final $Res Function(_SelectedProxyState) _then;
/// Create a copy of ProxyCardState
/// Create a copy of SelectedProxyState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? proxyName = null,Object? testUrl = freezed,}) {
return _then(_ProxyCardState(
@override @pragma('vm:prefer-inline') $Res call({Object? proxyName = null,Object? group = null,Object? testUrl = freezed,}) {
return _then(_SelectedProxyState(
proxyName: null == proxyName ? _self.proxyName : proxyName // ignore: cast_nullable_to_non_nullable
as String,testUrl: freezed == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
as String,group: null == group ? _self.group : group // ignore: cast_nullable_to_non_nullable
as bool,testUrl: freezed == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
as String?,
));
}

View File

@@ -1,4 +1,3 @@
// ignore_for_file: invalid_annotation_target
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

View File

@@ -102,9 +102,7 @@ abstract class ProxiesListState with _$ProxiesListState {
const factory ProxiesListState({
required List<Group> groups,
required Set<String> currentUnfoldSet,
required ProxiesSortType proxiesSortType,
required ProxyCardType proxyCardType,
required num sortNum,
required int columns,
}) = _ProxiesListState;
}
@@ -114,9 +112,7 @@ abstract class ProxiesTabState with _$ProxiesTabState {
const factory ProxiesTabState({
required List<Group> groups,
required String? currentGroupName,
required ProxiesSortType proxiesSortType,
required ProxyCardType proxyCardType,
required num sortNum,
required int columns,
}) = _ProxiesTabState;
}
@@ -233,9 +229,12 @@ abstract class DashboardState with _$DashboardState {
}
@freezed
abstract class ProxyCardState with _$ProxyCardState {
const factory ProxyCardState({required String proxyName, String? testUrl}) =
_ProxyCardState;
abstract class SelectedProxyState with _$SelectedProxyState {
const factory SelectedProxyState({
required String proxyName,
@Default(false) bool group,
String? testUrl,
}) = _SelectedProxyState;
}
@freezed

View File

@@ -27,19 +27,15 @@ class HomePage extends StatelessWidget {
pageBuilder: (_, index) {
final navigationItem = state.navigationItems[index];
final navigationView = navigationItem.builder(context);
final view = isMobile
? KeepScope(
keep: navigationItem.keep,
child: navigationView,
)
: KeepScope(
keep: navigationItem.keep,
child: Navigator(
onGenerateRoute: (_) {
return CommonRoute(builder: (_) => navigationView);
},
final view = KeepScope(
keep: navigationItem.keep,
child: isMobile
? navigationView
: Navigator(
pages: [MaterialPage(child: navigationView)],
onDidRemovePage: (_) {},
),
);
);
return view;
},
);

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:fl_clash/common/app_localizations.dart';
@@ -38,45 +39,48 @@ class App {
}
Future<List<Package>> getPackages() async {
final packagesString =
await methodChannel.invokeMethod<String>('getPackages');
final packagesString = await methodChannel.invokeMethod<String>(
'getPackages',
);
return Isolate.run<List<Package>>(() {
final List<dynamic> packagesRaw =
packagesString != null ? json.decode(packagesString) : [];
final List<dynamic> packagesRaw = packagesString != null
? json.decode(packagesString)
: [];
return packagesRaw.map((e) => Package.fromJson(e)).toSet().toList();
});
}
Future<List<String>> getChinaPackageNames() async {
final packageNamesString =
await methodChannel.invokeMethod<String>('getChinaPackageNames');
final packageNamesString = await methodChannel.invokeMethod<String>(
'getChinaPackageNames',
);
return Isolate.run<List<String>>(() {
final List<dynamic> packageNamesRaw =
packageNamesString != null ? json.decode(packageNamesString) : [];
final List<dynamic> packageNamesRaw = packageNamesString != null
? json.decode(packageNamesString)
: [];
return packageNamesRaw.map((e) => e.toString()).toList();
});
}
Future<bool?> requestNotificationsPermission() async {
return await methodChannel
.invokeMethod<bool>('requestNotificationsPermission');
return await methodChannel.invokeMethod<bool>(
'requestNotificationsPermission',
);
}
Future<bool> openFile(String path) async {
return await methodChannel.invokeMethod<bool>('openFile', {
'path': path,
}) ??
return await methodChannel.invokeMethod<bool>('openFile', {'path': path}) ??
false;
}
Future<ImageProvider?> getPackageIcon(String packageName) async {
final base64 = await methodChannel.invokeMethod<String>('getPackageIcon', {
final path = await methodChannel.invokeMethod<String>('getPackageIcon', {
'packageName': packageName,
});
if (base64 == null) {
if (path == null) {
return null;
}
return MemoryImage(base64Decode(base64));
return FileImage(File(path));
}
Future<bool?> tip(String? message) async {

View File

@@ -76,16 +76,16 @@ class Service {
return await methodChannel.invokeMethod<bool>('stop') ?? false;
}
Future<bool> syncAndroidState(AndroidState state) async {
return await methodChannel.invokeMethod<bool>(
Future<String> syncAndroidState(AndroidState state) async {
return await methodChannel.invokeMethod<String>(
'syncState',
json.encode(state),
) ??
false;
'';
}
Future<bool> init() async {
return await methodChannel.invokeMethod<bool>('init') ?? false;
Future<String> init() async {
return await methodChannel.invokeMethod<String>('init') ?? '';
}
Future<DateTime?> getRunTime() async {

View File

@@ -55,7 +55,10 @@ class Requests extends _$Requests with AutoDisposeNotifierMixin {
}
@riverpod
class Providers extends _$Providers with AutoDisposeNotifierMixin {
class Providers extends _$Providers with AnyNotifierMixin {
@override
List<ExternalProvider> get value => globalState.appState.providers;
@override
List<ExternalProvider> build() {
return globalState.appState.providers;
@@ -67,10 +70,13 @@ class Providers extends _$Providers with AutoDisposeNotifierMixin {
}
void setProvider(ExternalProvider? provider) {
if (!ref.mounted) {
return;
}
if (provider == null) return;
final index = state.indexWhere((item) => item.name == provider.name);
final index = value.indexWhere((item) => item.name == provider.name);
if (index == -1) return;
final newState = List<ExternalProvider>.from(state)..[index] = provider;
final newState = List<ExternalProvider>.from(value)..[index] = provider;
value = newState;
}
}

View File

@@ -90,7 +90,7 @@ final class LogsProvider extends $NotifierProvider<Logs, FixedList<Log>> {
}
}
String _$logsHash() => r'0a32e067292d449d61af59a689cb26691f4afe44';
String _$logsHash() => r'a671cf70f13d38cae75dc51841b651fe2d2dad9a';
abstract class _$Logs extends $Notifier<FixedList<Log>> {
FixedList<Log> build();
@@ -143,7 +143,7 @@ final class RequestsProvider
}
}
String _$requestsHash() => r'526f2c1da1347fd2e6e3e23aac0335fcfe0cb28a';
String _$requestsHash() => r'8642621b8b5f2e56f3abb04554c058fb30389795';
abstract class _$Requests extends $Notifier<FixedList<TrackerInfo>> {
FixedList<TrackerInfo> build();
@@ -197,7 +197,7 @@ final class ProvidersProvider
}
}
String _$providersHash() => r'4292240629a99470b2e72426dde3b9049b9b57e0';
String _$providersHash() => r'9cb491314be6dca0d9ff2d09aa276d19a92895af';
abstract class _$Providers extends $Notifier<List<ExternalProvider>> {
List<ExternalProvider> build();
@@ -304,7 +304,7 @@ final class SystemBrightnessProvider
}
}
String _$systemBrightnessHash() => r'46eb2d23b05405723efc29480e8f258bf2d8138b';
String _$systemBrightnessHash() => r'2fb112459d5f505768f8c33b314aa62cf1fb0a0a';
abstract class _$SystemBrightness extends $Notifier<Brightness> {
Brightness build();
@@ -357,7 +357,7 @@ final class TrafficsProvider
}
}
String _$trafficsHash() => r'8b86eb718fed5776de174c51fd5b231957011fe6';
String _$trafficsHash() => r'7df7d01f39e9fa1bf629221c9f73273757fa535a';
abstract class _$Traffics extends $Notifier<FixedList<Traffic>> {
FixedList<Traffic> build();
@@ -462,7 +462,7 @@ final class LocalIpProvider extends $NotifierProvider<LocalIp, String?> {
}
}
String _$localIpHash() => r'2dd4afdb29db4791ebd80d976f9ea31c62959199';
String _$localIpHash() => r'25ff07ff9ae316eac7ef39c29d9ae2714b7ba323';
abstract class _$LocalIp extends $Notifier<String?> {
String? build();
@@ -1199,7 +1199,7 @@ final class DelayDataSourceProvider
}
}
String _$delayDataSourceHash() => r'1b94dcfdb9e1eb4c0b7ca69d933f2299d1f94ed5';
String _$delayDataSourceHash() => r'0cc7064c6e7e7a1823df1c5b339001ae49ee54f1';
abstract class _$DelayDataSource extends $Notifier<DelayMap> {
DelayMap build();
@@ -1308,7 +1308,7 @@ final class ProfileOverrideStateProvider
}
String _$profileOverrideStateHash() =>
r'c57bb59c3900b6ee752d6c1cd5c9c0ccc6d1f45e';
r'6bcf739e034cc39623dc63bf304189d63fc19404';
abstract class _$ProfileOverrideState extends $Notifier<ProfileOverrideModel?> {
ProfileOverrideModel? build();
@@ -1414,7 +1414,7 @@ final class QueryMapProvider
}
}
String _$queryMapHash() => r'102d489d31d312f082d7117f80a6de8318eaaf75';
String _$queryMapHash() => r'f64a1bf5fcd4f85986d8ba3c956e397abc4f2d5d';
abstract class _$QueryMap extends $Notifier<Map<QueryTag, String>> {
Map<QueryTag, String> build();

View File

@@ -38,7 +38,7 @@ final class AppSettingProvider
}
}
String _$appSettingHash() => r'13a93334e18b97f5d52eb3e05bbc7b0b8a5c453e';
String _$appSettingHash() => r'7ec7fbf146e690dea42cf854fa4452b2652d8a46';
abstract class _$AppSetting extends $Notifier<AppSettingProps> {
AppSettingProps build();
@@ -91,7 +91,7 @@ final class WindowSettingProvider
}
}
String _$windowSettingHash() => r'9bf31c7e08fab84213f31e249270f9d730bdf711';
String _$windowSettingHash() => r'fc0e5c4ec95a57a24e0e656fc2fab6f31add31e7';
abstract class _$WindowSetting extends $Notifier<WindowProps> {
WindowProps build();
@@ -143,7 +143,7 @@ final class VpnSettingProvider extends $NotifierProvider<VpnSetting, VpnProps> {
}
}
String _$vpnSettingHash() => r'3dae8b56504bfb906aca546c5a5389d79d259a5e';
String _$vpnSettingHash() => r'1dad4881ae7bcec76678585ac7b84f820b2ca92b';
abstract class _$VpnSetting extends $Notifier<VpnProps> {
VpnProps build();
@@ -196,7 +196,7 @@ final class NetworkSettingProvider
}
}
String _$networkSettingHash() => r'5a30d4cbfaba94cc29ad08dc1771ebb368b4ba14';
String _$networkSettingHash() => r'6ac5959ad478247fd60329221743cccc7a7d010b';
abstract class _$NetworkSetting extends $Notifier<NetworkProps> {
NetworkProps build();
@@ -249,7 +249,7 @@ final class ThemeSettingProvider
}
}
String _$themeSettingHash() => r'0b5620b696d73260d94f63cbfb65857acd2000f0';
String _$themeSettingHash() => r'0ddad89cb63fc2b2094dd82262c76d972c2def5c';
abstract class _$ThemeSetting extends $Notifier<ThemeProps> {
ThemeProps build();
@@ -302,7 +302,7 @@ final class ProfilesProvider
}
}
String _$profilesHash() => r'3203cc7de88b91fff86b79c75c2cacd8116fffb7';
String _$profilesHash() => r'aad57222a4a0bd16f2c70f9eb8ba0053d1a26d0f';
abstract class _$Profiles extends $Notifier<List<Profile>> {
List<Profile> build();
@@ -408,7 +408,7 @@ final class AppDAVSettingProvider
}
}
String _$appDAVSettingHash() => r'4bf293ac0d1fba157f60df920b7ffd5afefaab26';
String _$appDAVSettingHash() => r'fa8de5d89d7a11f34f3f8e20b71cf164e5e11888';
abstract class _$AppDAVSetting extends $Notifier<DAV?> {
DAV? build();
@@ -567,7 +567,7 @@ final class ProxiesStyleSettingProvider
}
String _$proxiesStyleSettingHash() =>
r'54ebf20a8d4455b2d7a65824f375c4c02a5fba28';
r'4ff62951ddc8289220191850516b6751ee69d642';
abstract class _$ProxiesStyleSetting extends $Notifier<ProxiesStyle> {
ProxiesStyle build();
@@ -620,7 +620,7 @@ final class ScriptStateProvider
}
}
String _$scriptStateHash() => r'afbb70d1dd7e577b2377ecd8ab35d0905c1d0e87';
String _$scriptStateHash() => r'4770c34c3d24451fef95e372450e4a333b419977';
abstract class _$ScriptState extends $Notifier<ScriptProps> {
ScriptProps build();
@@ -673,7 +673,7 @@ final class PatchClashConfigProvider
}
}
String _$patchClashConfigHash() => r'd9acdd0ace673fc1c1460b63d7a27c5787713c14';
String _$patchClashConfigHash() => r'b355bd89969d4d119631fdf117df230a71493fa8';
abstract class _$PatchClashConfig extends $Notifier<ClashConfig> {
ClashConfig build();

View File

@@ -698,7 +698,7 @@ final class ProxiesListStateProvider
}
}
String _$proxiesListStateHash() => r'1438dbcaf0d45fbd2cb7fdde81cf1dc06bbd992f';
String _$proxiesListStateHash() => r'b16ad96516ece78f6cb22f558a0535000b784317';
@ProviderFor(proxiesTabState)
const proxiesTabStateProvider = ProxiesTabStateProvider._();
@@ -740,7 +740,7 @@ final class ProxiesTabStateProvider
}
}
String _$proxiesTabStateHash() => r'591fd8c2fa0df4c6a4832ffa04eb8d62453ba225';
String _$proxiesTabStateHash() => r'143b106d74da618327cbac48af15078efd8cabee';
@ProviderFor(isStart)
const isStartProvider = IsStartProvider._();
@@ -1248,7 +1248,7 @@ final class GetDelayProvider extends $FunctionalProvider<int?, int?, int?>
}
}
String _$getDelayHash() => r'b5920ac7de0aaadb8ff63fac993bd90ff87cd25a';
String _$getDelayHash() => r'15df90fb31665501b21ea671a72e35beaf32141b';
final class GetDelayFamily extends $Family
with
@@ -1512,55 +1512,62 @@ final class GetProxiesColumnsProvider extends $FunctionalProvider<int, int, int>
String _$getProxiesColumnsHash() => r'725066b5fc21f590a4c2656a1fd5e14ab7079079';
@ProviderFor(getProxyCardState)
const getProxyCardStateProvider = GetProxyCardStateFamily._();
@ProviderFor(realSelectedProxyState)
const realSelectedProxyStateProvider = RealSelectedProxyStateFamily._();
final class GetProxyCardStateProvider
extends $FunctionalProvider<ProxyCardState, ProxyCardState, ProxyCardState>
with $Provider<ProxyCardState> {
const GetProxyCardStateProvider._({
required GetProxyCardStateFamily super.from,
final class RealSelectedProxyStateProvider
extends
$FunctionalProvider<
SelectedProxyState,
SelectedProxyState,
SelectedProxyState
>
with $Provider<SelectedProxyState> {
const RealSelectedProxyStateProvider._({
required RealSelectedProxyStateFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'getProxyCardStateProvider',
name: r'realSelectedProxyStateProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$getProxyCardStateHash();
String debugGetCreateSourceHash() => _$realSelectedProxyStateHash();
@override
String toString() {
return r'getProxyCardStateProvider'
return r'realSelectedProxyStateProvider'
''
'($argument)';
}
@$internal
@override
$ProviderElement<ProxyCardState> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
$ProviderElement<SelectedProxyState> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
ProxyCardState create(Ref ref) {
SelectedProxyState create(Ref ref) {
final argument = this.argument as String;
return getProxyCardState(ref, argument);
return realSelectedProxyState(ref, argument);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(ProxyCardState value) {
Override overrideWithValue(SelectedProxyState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<ProxyCardState>(value),
providerOverride: $SyncValueProvider<SelectedProxyState>(value),
);
}
@override
bool operator ==(Object other) {
return other is GetProxyCardStateProvider && other.argument == argument;
return other is RealSelectedProxyStateProvider &&
other.argument == argument;
}
@override
@@ -1569,24 +1576,25 @@ final class GetProxyCardStateProvider
}
}
String _$getProxyCardStateHash() => r'0f131148cb5ed60c9c4c4f31fbe32f114ac346bb';
String _$realSelectedProxyStateHash() =>
r'42fa131419f0a26e30c4f5269bf020893b7f828c';
final class GetProxyCardStateFamily extends $Family
with $FunctionalFamilyOverride<ProxyCardState, String> {
const GetProxyCardStateFamily._()
final class RealSelectedProxyStateFamily extends $Family
with $FunctionalFamilyOverride<SelectedProxyState, String> {
const RealSelectedProxyStateFamily._()
: super(
retry: null,
name: r'getProxyCardStateProvider',
name: r'realSelectedProxyStateProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
GetProxyCardStateProvider call(String proxyName) =>
GetProxyCardStateProvider._(argument: proxyName, from: this);
RealSelectedProxyStateProvider call(String proxyName) =>
RealSelectedProxyStateProvider._(argument: proxyName, from: this);
@override
String toString() => r'getProxyCardStateProvider';
String toString() => r'realSelectedProxyStateProvider';
}
@ProviderFor(getProxyName)
@@ -1801,7 +1809,7 @@ final class GetProxyDescProvider
}
}
String _$getProxyDescHash() => r'c173fe2393d9c4f5d5d17480e69f9126bb76a17d';
String _$getProxyDescHash() => r'4579b55bf7e9fbcfdf91b91619bd0320c585f23d';
final class GetProxyDescFamily extends $Family
with $FunctionalFamilyOverride<String, Proxy> {
@@ -2205,6 +2213,55 @@ final class AutoSetSystemDnsStateProvider
String _$autoSetSystemDnsStateHash() =>
r'2e0976e079100325b1ca797285df48a94c2c066c';
@ProviderFor(needUpdateGroups)
const needUpdateGroupsProvider = NeedUpdateGroupsProvider._();
final class NeedUpdateGroupsProvider
extends
$FunctionalProvider<
VM3<bool, int, ProxiesSortType>,
VM3<bool, int, ProxiesSortType>,
VM3<bool, int, ProxiesSortType>
>
with $Provider<VM3<bool, int, ProxiesSortType>> {
const NeedUpdateGroupsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'needUpdateGroupsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$needUpdateGroupsHash();
@$internal
@override
$ProviderElement<VM3<bool, int, ProxiesSortType>> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
VM3<bool, int, ProxiesSortType> create(Ref ref) {
return needUpdateGroups(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(VM3<bool, int, ProxiesSortType> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<VM3<bool, int, ProxiesSortType>>(
value,
),
);
}
}
String _$needUpdateGroupsHash() => r'1d1fbf135b4b5d2a2ee984e421ccffe7c4bb0a47';
@ProviderFor(androidState)
const androidStateProvider = AndroidStateProvider._();

View File

@@ -268,20 +268,15 @@ ProxiesListState proxiesListState(Ref ref) {
final query = ref.watch(queryProvider(QueryTag.proxies));
final currentGroups = ref.watch(filterGroupsStateProvider(query));
final currentUnfoldSet = ref.watch(unfoldSetProvider);
final vm2 = ref.watch(
proxiesStyleSettingProvider.select(
(state) => VM2(a: state.sortType, b: state.cardType),
),
final cardType = ref.watch(
proxiesStyleSettingProvider.select((state) => state.cardType),
);
final sortNum = ref.watch(sortNumProvider);
final columns = ref.watch(getProxiesColumnsProvider);
return ProxiesListState(
groups: currentGroups.value,
currentUnfoldSet: currentUnfoldSet,
proxiesSortType: vm2.a,
proxyCardType: vm2.b,
sortNum: sortNum,
proxyCardType: cardType,
columns: columns,
);
}
@@ -293,19 +288,14 @@ ProxiesTabState proxiesTabState(Ref ref) {
final currentGroupName = ref.watch(
currentProfileProvider.select((state) => state?.currentGroupName),
);
final vm2 = ref.watch(
proxiesStyleSettingProvider.select(
(state) => VM2(a: state.sortType, b: state.cardType),
),
final cardType = ref.watch(
proxiesStyleSettingProvider.select((state) => state.cardType),
);
final sortNum = ref.watch(sortNumProvider);
final columns = ref.watch(getProxiesColumnsProvider);
return ProxiesTabState(
groups: currentGroups.value,
currentGroupName: currentGroupName,
proxiesSortType: vm2.a,
proxyCardType: vm2.b,
sortNum: sortNum,
proxyCardType: cardType,
columns: columns,
);
}
@@ -416,14 +406,14 @@ String getRealTestUrl(Ref ref, [String? testUrl]) {
@riverpod
int? getDelay(Ref ref, {required String proxyName, String? testUrl}) {
final currentTestUrl = ref.watch(getRealTestUrlProvider(testUrl));
final proxyCardState = ref.watch(getProxyCardStateProvider(proxyName));
final proxyState = ref.watch(realSelectedProxyStateProvider(proxyName));
final delay = ref.watch(
delayDataSourceProvider.select((state) {
final delayMap =
state[proxyCardState.testUrl.getSafeValue(currentTestUrl)];
return delayMap?[proxyCardState.proxyName];
final delayMap = state[proxyState.testUrl.getSafeValue(currentTestUrl)];
return delayMap?[proxyState.proxyName];
}),
);
return delay;
}
@@ -470,41 +460,14 @@ int getProxiesColumns(Ref ref) {
return utils.getProxiesColumns(viewWidth, proxiesLayout);
}
ProxyCardState _getProxyCardState(
List<Group> groups,
SelectedMap selectedMap,
ProxyCardState proxyDelayState,
) {
if (proxyDelayState.proxyName.isEmpty) return proxyDelayState;
final index = groups.indexWhere(
(element) => element.name == proxyDelayState.proxyName,
);
if (index == -1) return proxyDelayState;
final group = groups[index];
final currentSelectedName = group.getCurrentSelectedName(
selectedMap[proxyDelayState.proxyName] ?? '',
);
if (currentSelectedName.isEmpty) {
return proxyDelayState;
}
return _getProxyCardState(
groups,
selectedMap,
proxyDelayState.copyWith(
proxyName: currentSelectedName,
testUrl: group.testUrl,
),
);
}
@riverpod
ProxyCardState getProxyCardState(Ref ref, String proxyName) {
SelectedProxyState realSelectedProxyState(Ref ref, String proxyName) {
final groups = ref.watch(groupsProvider);
final selectedMap = ref.watch(selectedMapProvider);
return _getProxyCardState(
groups,
selectedMap,
ProxyCardState(proxyName: proxyName),
return computeRealSelectedProxyState(
proxyName,
groups: groups,
selectedMap: selectedMap,
);
}
@@ -534,7 +497,7 @@ String getProxyDesc(Ref ref, Proxy proxy) {
final groups = ref.watch(groupsProvider);
final index = groups.indexWhere((element) => element.name == proxy.name);
if (index == -1) return proxy.type;
final state = ref.watch(getProxyCardStateProvider(proxy.name));
final state = ref.watch(realSelectedProxyStateProvider(proxy.name));
return "${proxy.type}(${state.proxyName.isNotEmpty ? state.proxyName : '*'})";
}
}
@@ -638,6 +601,18 @@ VM2<bool, bool> autoSetSystemDnsState(Ref ref) {
return VM2(a: isStart ? realTunEnable : false, b: autoSetSystemDns);
}
@riverpod
VM3<bool, int, ProxiesSortType> needUpdateGroups(Ref ref) {
final isProxies = ref.watch(
currentPageLabelProvider.select((state) => state == PageLabel.proxies),
);
final sortNum = ref.watch(sortNumProvider);
final sortType = ref.watch(
proxiesStyleSettingProvider.select((state) => state.sortType),
);
return VM3(a: isProxies, b: sortNum, c: sortType);
}
@riverpod
AndroidState androidState(Ref ref) {
final currentProfileName = ref.watch(

View File

@@ -1,6 +1,8 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ffi' show Pointer;
import 'dart:io';
import 'dart:isolate';
import 'package:animations/animations.dart';
import 'package:dio/dio.dart';
@@ -16,6 +18,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_js/flutter_js.dart';
import 'package:material_color_utilities/palettes/core_palette.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path/path.dart';
import 'package:url_launcher/url_launcher.dart';
import 'common/common.dart';
@@ -76,8 +79,39 @@ class GlobalState {
);
await _initDynamicColor();
await init();
appState = appState.copyWith(coreStatus: CoreStatus.connected);
await window?.init(version);
_shakingStore();
}
Future<void> _shakingStore() async {
final profileIds = config.profiles.map((item) => item.id);
final providersRootPath = await appPath.getProvidersRootPath();
final profilesRootPath = await appPath.profilesPath;
Isolate.run(() async {
final profilesDir = Directory(profilesRootPath);
final providersDir = Directory(providersRootPath);
final List<FileSystemEntity> entities = [];
if (await profilesDir.exists()) {
entities.addAll(
profilesDir.listSync().where(
(item) => !item.path.contains('providers'),
),
);
}
if (await providersDir.exists()) {
entities.addAll(providersDir.listSync());
}
final deleteFutures = entities.map((entity) async {
if (!profileIds.contains(basenameWithoutExtension(entity.path))) {
final res = await coreController.deleteFile(entity.path);
if (res.isNotEmpty) {
throw res;
}
}
return true;
});
await Future.wait(deleteFutures);
});
}
Future<void> _initDynamicColor() async {
@@ -98,8 +132,6 @@ class GlobalState {
utils.getLocaleForString(config.appSetting.locale) ??
WidgetsBinding.instance.platformDispatcher.locale,
);
await coreController.preload();
await service?.syncAndroidState(globalState.getAndroidState());
}
String get ua => config.patchClashConfig.globalUa ?? packageInfo.ua;
@@ -199,6 +231,7 @@ class GlobalState {
final networkProps = config.networkProps;
final port = config.patchClashConfig.mixedPort;
return VpnOptions(
stack: config.patchClashConfig.tun.stack.name,
enable: vpnProps.enable,
systemProxy: networkProps.systemProxy,
port: port,
@@ -253,16 +286,35 @@ class GlobalState {
}
}
Future<SetupParams> getSetupParams({required ClashConfig pathConfig}) async {
final clashConfig = await patchRawConfig(patchConfig: pathConfig);
Future<SetupParams> getSetupParams() async {
final params = SetupParams(
config: clashConfig,
selectedMap: config.currentProfile?.selectedMap ?? {},
testUrl: config.appSetting.testUrl,
);
return params;
}
Future<void> genConfigFile(ClashConfig pathConfig) async {
final configFilePath = await appPath.configFilePath;
final config = await patchRawConfig(patchConfig: pathConfig);
final res = await Isolate.run<String>(() async {
try {
final res = json.encode(config);
final file = File(configFilePath);
if (!await file.exists()) {
await file.create(recursive: true);
}
await file.writeAsString(res);
return '';
} catch (e) {
return e.toString();
}
});
if (res.isNotEmpty) {
throw res;
}
}
AndroidState getAndroidState() {
return AndroidState(
currentProfileName: config.currentProfile?.label ?? '',
@@ -386,7 +438,7 @@ class GlobalState {
entry.value.splitByMultipleSeparators;
}
}
var rules = [];
List rules = [];
if (rawConfig['rules'] != null) {
rules = rawConfig['rules'];
}

View File

@@ -93,7 +93,7 @@ class OutboundModeV2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
final height = getWidgetHeight(0.72);
final height = getWidgetHeight(1);
return SizedBox(
height: height,
child: CommonCard(
@@ -110,39 +110,53 @@ class OutboundModeV2 extends StatelessWidget {
};
return Container(
constraints: BoxConstraints.expand(),
child: CommonTabBar<Mode>(
children: Map.fromEntries(
Mode.values.map(
(item) => MapEntry(
item,
Container(
clipBehavior: Clip.antiAlias,
alignment: Alignment.center,
decoration: BoxDecoration(),
height: height - 16,
child: Text(
Intl.message(item.name),
style: Theme.of(context).textTheme.titleSmall
?.adjustSize(1)
.copyWith(
color: item == mode
? _getTextColor(context, item)
: null,
padding: EdgeInsets.all(4.ap),
color: thumbColor.opacity38,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: context.colorScheme.surfaceContainer,
),
padding: EdgeInsets.all(12.ap),
child: LayoutBuilder(
builder: (_, constraints) {
return CommonTabBar<Mode>(
children: Map.fromEntries(
Mode.values.map(
(item) => MapEntry(
item,
Container(
clipBehavior: Clip.antiAlias,
alignment: Alignment.center,
decoration: BoxDecoration(),
height: constraints.maxHeight,
padding: EdgeInsets.all(4),
child: Text(
Intl.message(item.name),
style: Theme.of(context).textTheme.titleSmall
?.adjustSize(1)
.copyWith(
color: item == mode
? _getTextColor(context, item)
: null,
),
),
),
),
),
),
),
),
padding: EdgeInsets.symmetric(horizontal: 0),
groupValue: mode,
onValueChanged: (value) {
if (value == null) {
return;
}
globalState.appController.changeMode(value);
},
thumbColor: thumbColor,
);
},
),
padding: EdgeInsets.symmetric(horizontal: 8),
groupValue: mode,
onValueChanged: (value) {
if (value == null) {
return;
}
globalState.appController.changeMode(value);
},
thumbColor: thumbColor,
),
);
},

View File

@@ -58,7 +58,7 @@ class _StartButtonState extends ConsumerState<StartButton>
void updateController() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (isStart) {
if (isStart && mounted) {
_controller.forward();
} else {
_controller.reverse();

View File

@@ -39,6 +39,9 @@ class ProxyCard extends StatelessWidget {
getDelayProvider(proxyName: proxy.name, testUrl: testUrl),
);
return FadeThroughBox(
alignment: type == ProxyCardType.expand
? Alignment.centerLeft
: Alignment.centerRight,
child: delay == 0 || delay == null
? SizedBox(
height: measure.labelSmallHeight,

View File

@@ -22,13 +22,25 @@ double getItemHeight(ProxyCardType proxyCardType) {
Future<void> proxyDelayTest(Proxy proxy, [String? testUrl]) async {
final appController = globalState.appController;
final state = appController.getProxyCardState(proxy.name);
final url = state.testUrl.getSafeValue(appController.getRealTestUrl(testUrl));
final groups = globalState.appState.groups;
final selectedMap = globalState.config.currentProfile?.selectedMap ?? {};
final state = computeRealSelectedProxyState(
proxy.name,
groups: groups,
selectedMap: selectedMap,
);
final currentTestUrl = state.testUrl.getSafeValue(
appController.getRealTestUrl(testUrl),
);
if (state.proxyName.isEmpty) {
return;
}
appController.setDelay(Delay(url: url, name: state.proxyName, value: 0));
appController.setDelay(await coreController.getDelay(url, state.proxyName));
appController.setDelay(
Delay(url: currentTestUrl, name: state.proxyName, value: 0),
);
appController.setDelay(
await coreController.getDelay(currentTestUrl, state.proxyName),
);
}
Future<void> delayTest(List<Proxy> proxies, [String? testUrl]) async {
@@ -36,7 +48,13 @@ Future<void> delayTest(List<Proxy> proxies, [String? testUrl]) async {
final proxyNames = proxies.map((proxy) => proxy.name).toSet().toList();
final delayProxies = proxyNames.map<Future>((proxyName) async {
final state = appController.getProxyCardState(proxyName);
final groups = globalState.appState.groups;
final selectedMap = globalState.config.currentProfile?.selectedMap ?? {};
final state = computeRealSelectedProxyState(
proxyName,
groups: groups,
selectedMap: selectedMap,
);
final url = state.testUrl.getSafeValue(
appController.getRealTestUrl(testUrl),
);

View File

@@ -28,7 +28,6 @@ class _ProxiesListViewState extends State<ProxiesListView> {
null,
);
List<double> _headerOffset = [];
GroupNameProxiesMap _lastGroupNameProxiesMap = {};
@override
void initState() {
@@ -84,6 +83,7 @@ class _ProxiesListViewState extends State<ProxiesListView> {
final tempUnfoldSet = Set<String>.from(currentUnfoldSet);
if (tempUnfoldSet.contains(groupName)) {
tempUnfoldSet.remove(groupName);
_autoScrollToGroup(groupName);
} else {
tempUnfoldSet.add(groupName);
}
@@ -118,10 +118,8 @@ class _ProxiesListViewState extends State<ProxiesListView> {
required int columns,
required Set<String> currentUnfoldSet,
required ProxyCardType cardType,
required ProxiesSortType sortType,
}) {
final items = <Widget>[];
final GroupNameProxiesMap groupNameProxiesMap = {};
for (final group in groups) {
final groupName = group.name;
final isExpand = currentUnfoldSet.contains(groupName);
@@ -137,25 +135,23 @@ class _ProxiesListViewState extends State<ProxiesListView> {
const SizedBox(height: 8),
]);
if (isExpand) {
final sortedProxies = globalState.appController.getSortProxies(
proxies: group.all,
sortType: sortType,
testUrl: group.testUrl,
);
groupNameProxiesMap[groupName] = sortedProxies;
final chunks = sortedProxies.chunks(columns);
final proxies = group.all;
final chunks = proxies.chunks(columns);
final rows = chunks
.map<Widget>((proxies) {
final children = proxies
.map<Widget>(
(proxy) => Flexible(
child: ProxyCard(
testUrl: group.testUrl,
type: cardType,
groupType: group.type,
key: ValueKey('$groupName.${proxy.name}'),
proxy: proxy,
groupName: groupName,
child: SizedBox(
height: getItemHeight(cardType),
child: ProxyCard(
testUrl: group.testUrl,
type: cardType,
groupType: group.type,
key: ValueKey('$groupName.${proxy.name}'),
proxy: proxy,
groupName: groupName,
),
),
),
)
@@ -171,7 +167,6 @@ class _ProxiesListViewState extends State<ProxiesListView> {
items.addAll([...rows, const SizedBox(height: 8)]);
}
}
_lastGroupNameProxiesMap = groupNameProxiesMap;
return items;
}
@@ -197,30 +192,44 @@ class _ProxiesListViewState extends State<ProxiesListView> {
);
}
void _scrollToGroupSelected(String groupName) {
double _getGroupOffset(String groupName) {
if (_controller.position.maxScrollExtent == 0) {
return;
return 0;
}
final appController = globalState.appController;
final currentGroups = appController.getCurrentGroups();
final groupNames = currentGroups.map((e) => e.name).toList();
final findIndex = groupNames.indexWhere((item) => item == groupName);
final index = findIndex != -1 ? findIndex : 0;
final currentInitOffset = _headerOffset[index];
final proxies = _lastGroupNameProxiesMap[groupName];
_controller.animateTo(
min(
currentInitOffset +
8 +
getScrollToSelectedOffset(
groupName: groupName,
proxies: proxies ?? [],
),
_controller.position.maxScrollExtent,
),
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
final currentGroups = globalState.appController.getCurrentGroups();
final findIndex = currentGroups.indexWhere(
(item) => item.name == groupName,
);
final index = findIndex != -1 ? findIndex : 0;
return _headerOffset[index];
}
void _autoScrollToGroup(String groupName) {
_controller.jumpTo(_getGroupOffset(groupName));
}
void _scrollToGroupSelected(String groupName) {
final currentInitOffset = _getGroupOffset(groupName);
final currentGroups = globalState.appController.getCurrentGroups();
final proxies = currentGroups.getGroup(groupName)?.all;
_jumpTo(
currentInitOffset +
8 +
getScrollToSelectedOffset(
groupName: groupName,
proxies: proxies ?? [],
),
);
}
void _jumpTo(double offset) {
if (mounted && _controller.hasClients) {
_controller.animateTo(
min(offset, _controller.position.maxScrollExtent),
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
}
}
@override
@@ -240,7 +249,6 @@ class _ProxiesListViewState extends State<ProxiesListView> {
currentUnfoldSet: state.currentUnfoldSet,
columns: state.columns,
cardType: state.proxyCardType,
sortType: state.proxiesSortType,
);
final itemsOffset = _getItemHeightList(items, state.proxyCardType);
return CommonScrollBar(

View File

@@ -3,7 +3,6 @@ import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/models/widget.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/views/proxies/list.dart';
import 'package:fl_clash/views/proxies/providers.dart';
import 'package:fl_clash/widgets/widgets.dart';
@@ -138,11 +137,6 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> {
},
fireImmediately: true,
);
ref.listenManual(currentPageLabelProvider, (prev, next) {
if (prev != next) {
globalState.appController.updateGroupsDebounce();
}
});
}
@override

View File

@@ -2,15 +2,14 @@ import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/models/config.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/experimental/scope.dart';
import '../../models/common.dart';
import 'card.dart';
import 'common.dart';
@@ -61,7 +60,7 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
Future<void> delayTestCurrentGroup() async {
final currentGroupName = globalState.appController.getCurrentGroupName();
final currentState = _keyMap[currentGroupName]?.currentState;
await delayTest(currentState?.proxies ?? [], currentState?.testUrl);
await delayTest(currentState?.currentProxies ?? [], currentState?.testUrl);
}
Widget _buildMoreButton() {
@@ -182,15 +181,11 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
final children = groups.map((group) {
final key = GlobalObjectKey<_ProxyGroupViewState>(group.name);
keyMap[group.name] = key;
return KeepScope(
keep: true,
child: ProxyGroupView(
key: key,
group: group,
columns: state.columns,
cardType: state.proxyCardType,
sortType: state.proxiesSortType,
),
return ProxyGroupView(
key: key,
group: group,
columns: state.columns,
cardType: state.proxyCardType,
);
}).toList();
_keyMap = keyMap;
@@ -252,19 +247,16 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
}
}
@Dependencies([proxiesTabState, proxiesTabControllerState])
class ProxyGroupView extends ConsumerStatefulWidget {
final Group group;
final int columns;
final ProxyCardType cardType;
final ProxiesSortType sortType;
const ProxyGroupView({
super.key,
required this.group,
required this.columns,
required this.cardType,
required this.sortType,
});
@override
@@ -274,7 +266,7 @@ class ProxyGroupView extends ConsumerStatefulWidget {
class _ProxyGroupViewState extends ConsumerState<ProxyGroupView> {
late final ScrollController _controller;
List<Proxy> proxies = [];
List<Proxy> currentProxies = [];
String? testUrl;
@override
@@ -308,7 +300,7 @@ class _ProxyGroupViewState extends ConsumerState<ProxyGroupView> {
16 +
getScrollToSelectedOffset(
groupName: widget.group.name,
proxies: proxies,
proxies: currentProxies,
),
_controller.position.maxScrollExtent,
),
@@ -321,45 +313,36 @@ class _ProxyGroupViewState extends ConsumerState<ProxyGroupView> {
Widget build(BuildContext context) {
final group = widget.group;
final proxies = group.all;
final sortedProxies = globalState.appController.getSortProxies(
proxies: proxies,
sortType: widget.sortType,
testUrl: group.testUrl,
);
this.proxies = sortedProxies;
testUrl = group.testUrl;
return Align(
alignment: Alignment.topCenter,
child: CommonScrollBar(
currentProxies = proxies;
return CommonScrollBar(
controller: _controller,
child: GridView.builder(
key: _getPageStorageKey(),
controller: _controller,
child: GridView.builder(
key: _getPageStorageKey(),
controller: _controller,
padding: const EdgeInsets.only(
top: 16,
left: 16,
right: 16,
bottom: 96,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: widget.columns,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
mainAxisExtent: getItemHeight(widget.cardType),
),
itemCount: sortedProxies.length,
itemBuilder: (_, index) {
final proxy = sortedProxies[index];
return ProxyCard(
testUrl: group.testUrl,
groupType: group.type,
type: widget.cardType,
proxy: proxy,
groupName: group.name,
);
},
padding: const EdgeInsets.only(
top: 16,
left: 16,
right: 16,
bottom: 96,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: widget.columns,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
mainAxisExtent: getItemHeight(widget.cardType),
),
itemCount: currentProxies.length,
itemBuilder: (_, index) {
final proxy = currentProxies[index];
return ProxyCard(
testUrl: group.testUrl,
groupType: group.type,
type: widget.cardType,
proxy: proxy,
groupName: group.name,
);
},
),
);
}

View File

@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_svg/svg.dart';
class CommonTargetIcon extends StatelessWidget {
@@ -17,6 +18,7 @@ class CommonTargetIcon extends StatelessWidget {
if (src.isEmpty) {
return _defaultIcon();
}
final base64 = src.getBase64;
if (base64 != null) {
return Image.memory(
@@ -27,18 +29,8 @@ class CommonTargetIcon extends StatelessWidget {
},
);
}
return FutureBuilder(
future: DefaultCacheManager().getSingleFile(src),
builder: (_, snapshot) {
final data = snapshot.data;
if (data == null) {
return SizedBox();
}
return src.isSvg
? SvgPicture.file(data, errorBuilder: (_, _, _) => _defaultIcon())
: Image.file(data, errorBuilder: (_, _, _) => _defaultIcon());
},
);
return ImageCacheWidget(src: src, defaultWidget: _defaultIcon());
}
@override
@@ -46,3 +38,46 @@ class CommonTargetIcon extends StatelessWidget {
return SizedBox(width: size, height: size, child: _buildIcon());
}
}
class ImageCacheWidget extends StatefulWidget {
final String src;
final Widget defaultWidget;
const ImageCacheWidget({
super.key,
required this.src,
required this.defaultWidget,
});
@override
State<ImageCacheWidget> createState() => _ImageCacheWidgetState();
}
class _ImageCacheWidgetState extends State<ImageCacheWidget> {
late Future<File> _imageFuture;
@override
void initState() {
super.initState();
_imageFuture = LocalImageCacheManager().getSingleFile(widget.src);
}
@override
Widget build(BuildContext context) {
return FutureBuilder<File>(
future: _imageFuture,
builder: (context, snapshot) {
final data = snapshot.data;
if (data == null) {
return SizedBox();
}
return widget.src.isSvg
? SvgPicture.file(
data,
errorBuilder: (_, _, _) => widget.defaultWidget,
)
: Image.file(data, errorBuilder: (_, _, _) => widget.defaultWidget);
},
);
}
}

View File

@@ -274,7 +274,7 @@ packages:
source: hosted
version: "3.0.6"
custom_lint:
dependency: "direct dev"
dependency: transitive
description:
name: custom_lint
sha256: "78085fbe842de7c5bef92de811ca81536968dbcbbcdac5c316711add2d15e796"
@@ -770,10 +770,10 @@ packages:
dependency: "direct dev"
description:
name: json_serializable
sha256: ce2cf974ccdee13be2a510832d7fba0b94b364e0b0395dee42abaa51b855be27
sha256: "3f2913b7c2430afe8ac5afe6fb15c1de4a60af4f630625e6e238f80ba4b80cbd"
url: "https://pub.dev"
source: hosted
version: "6.10.0"
version: "6.11.0"
launch_at_startup:
dependency: "direct main"
description:

View File

@@ -1,7 +1,7 @@
name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none'
version: 0.8.88+2025082601
version: 0.8.88+2025083102
environment:
sdk: '>=3.8.0 <4.0.0'
@@ -73,7 +73,6 @@ dev_dependencies:
freezed: ^3.2.0
riverpod_generator: ^3.0.0-dev.17
riverpod_lint: ^3.0.0-dev.17
custom_lint: ^0.8.0
flutter:
uses-material-design: true