Compare commits

..

1 Commits

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

Update flutter and pub dependencies
2025-08-27 16:28:34 +08:00
45 changed files with 313 additions and 283 deletions

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<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" />
@@ -112,7 +112,9 @@
android:exported="true"
android:permission="${applicationId}.permission.RECEIVE_BROADCASTS">
<intent-filter>
<action android:name="${applicationId}.action.CREATE_VPN" />
<action android:name="${applicationId}.intent.action.START" />
<action android:name="${applicationId}.intent.action.STOP" />
<action android:name="${applicationId}.intent.action.TOGGLE" />
</intent-filter>
</receiver>

View File

@@ -14,11 +14,21 @@ class BroadcastReceiver : BroadcastReceiver(),
CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
BroadcastAction.CREATE_VPN.action -> {
BroadcastAction.START.action -> {
launch {
State.handleStartServiceAction()
}
}
BroadcastAction.STOP.action -> {
State.handleStopServiceAction()
}
BroadcastAction.TOGGLE.action -> {
launch {
State.handleToggleAction()
}
}
}
}
}

View File

@@ -27,7 +27,7 @@ suspend fun Drawable.getBase64(): String {
suspend fun <T> MethodChannel.awaitResult(
method: String, arguments: Any? = null
): T? = withContext(Dispatchers.Main) { // 切换到主线程
): T? = withContext(Dispatchers.Main) {
suspendCancellableCoroutine { continuation ->
invokeMethod(method, arguments, object : MethodChannel.Result {
override fun success(result: Any?) {

View File

@@ -1,24 +1,22 @@
package com.follow.clash
import android.os.Bundle
import android.os.PersistableBundle
import com.follow.clash.common.GlobalState
import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ServicePlugin
import com.follow.clash.plugins.TilePlugin
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
GlobalState.launch {
State.destroyServiceEngine()
}
}
class MainActivity : FlutterActivity(),
CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
launch {
State.destroyServiceEngine()
}
super.configureFlutterEngine(flutterEngine)
flutterEngine.plugins.add(AppPlugin())
flutterEngine.plugins.add(ServicePlugin())

View File

@@ -53,12 +53,18 @@ object State {
suspend fun handleStartServiceAction() {
tilePlugin?.handleStart()
if (flutterEngine != null) {
return
}
startServiceWithEngine()
}
suspend fun handleStopServiceAction() {
fun handleStopServiceAction() {
tilePlugin?.handleStop()
destroyServiceEngine()
if (flutterEngine != null || serviceFlutterEngine != null) {
return
}
handleStopService()
}
fun handleStartService() {
@@ -73,15 +79,16 @@ object State {
suspend fun destroyServiceEngine() {
runLock.withLock {
serviceFlutterEngine?.destroy()
serviceFlutterEngine = null
withContext(Dispatchers.Main) {
runCatching {
serviceFlutterEngine?.destroy()
serviceFlutterEngine = null
}
}
}
}
suspend fun startServiceWithEngine() {
if (flutterEngine != null) {
return
}
runLock.withLock {
withContext(Dispatchers.Main) {
serviceFlutterEngine = FlutterEngine(GlobalState.application)
@@ -100,6 +107,9 @@ object State {
private fun startService() {
GlobalState.launch {
runLock.withLock {
if (runStateFlow.value == RunState.PENDING || runStateFlow.value == RunState.START) {
return@launch
}
runStateFlow.tryEmit(RunState.PENDING)
if (servicePlugin == null) {
return@launch
@@ -109,7 +119,7 @@ object State {
return@launch
}
appPlugin?.prepare(options.enable) {
servicePlugin?.startService(options, true)
Service.startService(options, true)
runStateFlow.tryEmit(RunState.START)
runTime = System.currentTimeMillis()
}
@@ -121,11 +131,15 @@ object State {
fun handleStopService() {
GlobalState.launch {
runLock.withLock {
if (runStateFlow.value == RunState.PENDING || runStateFlow.value == RunState.STOP) {
return@launch
}
runStateFlow.tryEmit(RunState.PENDING)
servicePlugin?.stopService()
Service.stopService()
runStateFlow.tryEmit(RunState.STOP)
runTime = 0
}
destroyServiceEngine()
}
}

View File

@@ -21,9 +21,7 @@ class TempActivity : Activity(),
}
QuickAction.STOP.action -> {
launch {
State.handleStopServiceAction()
}
State.handleStopServiceAction()
}
QuickAction.TOGGLE.action -> {

View File

@@ -1,5 +1,6 @@
package com.follow.clash.plugins
import com.follow.clash.RunState
import com.follow.clash.Service
import com.follow.clash.State
import com.follow.clash.awaitResult
@@ -88,14 +89,6 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
return Gson().fromJson(res, VpnOptions::class.java)
}
suspend fun startService(options: VpnOptions, inApp: Boolean) {
Service.startService(options, inApp)
}
suspend fun stopService() {
Service.stopService()
}
val semaphore = Semaphore(10)
fun handleSendEvent(value: String?) {
@@ -107,6 +100,7 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
private fun onServiceCrash() {
State.runStateFlow.tryEmit(RunState.STOP)
flutterMethodChannel.invokeMethodOnMainThread<Any>("crash", null)
}
@@ -127,8 +121,8 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
fun handleInit(result: MethodChannel.Result) {
Service.bind()
launch {
Service.bind()
Service.setMessageCallback {
handleSendEvent(it)
}

View File

@@ -10,4 +10,7 @@ object Components {
val TEMP_ACTIVITY =
ComponentName(GlobalState.packageName, "${PACKAGE_NAME}.TempActivity")
val BROADCAST_RECEIVER =
ComponentName(GlobalState.packageName, "${PACKAGE_NAME}.BroadcastReceiver")
}

View File

@@ -10,7 +10,9 @@ enum class QuickAction {
}
enum class BroadcastAction {
CREATE_VPN,
START,
STOP,
TOGGLE,
}
enum class AccessControlMode {

View File

@@ -42,18 +42,27 @@ fun Service.startForegroundCompat(id: Int, notification: Notification) {
}
}
val Enum<*>.action: String
val QuickAction.action: String
get() = "${GlobalState.application.packageName}.action.${this.name}"
val QuickAction.quickIntent: Intent
get() = Intent().apply {
Log.d("[quickIntent]", Components.TEMP_ACTIVITY.toString())
setComponent(Components.TEMP_ACTIVITY)
setPackage(GlobalState.packageName)
action = this@quickIntent.action
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
}
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)
action = this@quickIntent.action
}
fun BroadcastAction.sendBroadcast() {
val intent = Intent().apply {
action = this@sendBroadcast.action

View File

@@ -14,6 +14,7 @@ import kotlinx.coroutines.withTimeoutOrNull
class ServiceDelegate<T>(
private val intent: Intent,
private val onServiceDisconnected: (() -> Unit)? = null,
private val onServiceCrash: (() -> Unit)? = null,
private val interfaceCreator: (IBinder) -> T,
) : CoroutineScope by CoroutineScope(SupervisorJob() + Dispatchers.Default) {
@@ -31,6 +32,7 @@ class ServiceDelegate<T>(
is BindServiceEvent.Disconnected -> {
_service.value = null
onServiceDisconnected?.invoke()
}
is BindServiceEvent.Crashed -> {

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@@ -11,7 +11,9 @@ JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject thiz, jint fd, jobject cb,
jstring address, jstring dns) {
const auto interface = new_global(cb);
startTUN(interface, fd, get_string(address), get_string(dns));
scoped_string addressChar = get_string(address);
scoped_string dnsChar = get_string(dns);
startTUN(interface, fd, addressChar, dnsChar);
}
extern "C"
@@ -29,14 +31,16 @@ 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) {
updateDns(get_string(dns));
scoped_string dnsChar = get_string(dns);
updateDns(dnsChar);
}
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);
invokeAction(interface, get_string(data));
scoped_string dataChar = get_string(data);
invokeAction(interface, dataChar);
}
extern "C"
@@ -93,13 +97,14 @@ call_tun_interface_resolve_process_impl(void *tun_interface, const int protocol,
const int uid) {
ATTACH_JNI();
const auto packageName = reinterpret_cast<jstring>(env->CallObjectMethod(
static_cast<jobject>(tun_interface),
m_tun_interface_resolve_process,
protocol,
new_string(source),
new_string(target),
uid));
return get_string(packageName);
static_cast<jobject>(tun_interface),
m_tun_interface_resolve_process,
protocol,
new_string(source),
new_string(target),
uid));
scoped_string packageNameChar = get_string(packageName);
return packageNameChar;
}
static void call_invoke_interface_result_impl(void *invoke_interface, const char *data) {
@@ -184,4 +189,4 @@ extern "C"
JNIEXPORT void JNICALL
Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean suspended) {
}
#endif
#endif

View File

@@ -6,7 +6,7 @@ import com.follow.clash.common.sendBroadcast
interface IBaseService {
fun handleCreate() {
if (!State.inApp) {
BroadcastAction.CREATE_VPN.sendBroadcast()
BroadcastAction.START.sendBroadcast()
} else {
State.inApp = false
}

View File

@@ -27,6 +27,10 @@ class RemoteService : Service(),
}
}
fun onServiceDisconnected() {
handleStopService()
}
private fun handleStartService() {
launch {
val nextIntent = when (State.options?.enable == true) {
@@ -35,7 +39,7 @@ class RemoteService : Service(),
}
if (intent != nextIntent) {
delegate?.unbind()
delegate = ServiceDelegate(nextIntent, {}) { binder ->
delegate = ServiceDelegate(nextIntent, ::onServiceDisconnected) { binder ->
when (binder) {
is VpnService.LocalBinder -> binder.getService()
is CommonService.LocalBinder -> binder.getService()

View File

@@ -11,9 +11,9 @@ import android.os.RemoteException
import android.util.Log
import androidx.core.content.getSystemService
import com.follow.clash.common.AccessControlMode
import com.follow.clash.common.QuickAction
import com.follow.clash.common.quickIntent
import com.follow.clash.common.toPendingIntent
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
import com.follow.clash.service.models.getIpv4RouteAddress
@@ -44,6 +44,7 @@ class VpnService : SystemVpnService(), IBaseService,
super.onCreate()
handleCreate()
}
private val connectivity by lazy {
getSystemService<ConnectivityManager>()
}
@@ -107,7 +108,8 @@ class VpnService : SystemVpnService(), IBaseService,
try {
val isSuccess = super.onTransact(code, data, reply, flags)
if (!isSuccess) {
QuickAction.STOP.quickIntent.toPendingIntent.send()
GlobalState.log("onTransact error ===>")
BroadcastAction.STOP.sendBroadcast()
}
return isSuccess
} catch (e: RemoteException) {

View File

@@ -93,6 +93,7 @@ class NotificationModule(private val service: Service) : Module() {
with(notificationBuilder) {
setContentTitle(params.title)
setContentText(contentText)
setPriority(NotificationCompat.PRIORITY_HIGH)
clearActions()
addAction(
0,

View File

@@ -426,5 +426,7 @@
"disconnected": "Disconnected",
"connecting": "Connecting...",
"restartCoreTip": "Are you sure you want to restart the core?",
"forceRestartCoreTip": "Are you sure you want to force restart the core?"
"forceRestartCoreTip": "Are you sure you want to force restart the core?",
"dnsHijacking": "DNS hijacking",
"coreStatus": "Core status"
}

View File

@@ -427,5 +427,7 @@
"disconnected": "切断済み",
"connecting": "接続中...",
"restartCoreTip": "コアを再起動してもよろしいですか?",
"forceRestartCoreTip": "コアを強制再起動してもよろしいですか?"
"forceRestartCoreTip": "コアを強制再起動してもよろしいですか?",
"dnsHijacking": "DNSハイジャッキング",
"coreStatus": "コアステータス"
}

View File

@@ -427,5 +427,7 @@
"disconnected": "Отключено",
"connecting": "Подключение...",
"restartCoreTip": "Вы уверены, что хотите перезапустить ядро?",
"forceRestartCoreTip": "Вы уверены, что хотите принудительно перезапустить ядро?"
"forceRestartCoreTip": "Вы уверены, что хотите принудительно перезапустить ядро?",
"dnsHijacking": "DNS-перехват",
"coreStatus": "Основной статус"
}

View File

@@ -427,5 +427,7 @@
"disconnected": "已断开",
"connecting": "连接中...",
"restartCoreTip": "您确定要重启核心吗?",
"forceRestartCoreTip": "您确定要强制重启核心吗?"
"forceRestartCoreTip": "您确定要强制重启核心吗?",
"dnsHijacking": "DNS劫持",
"coreStatus": "核心状态"
}

View File

@@ -16,8 +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))
defer releaseObject(unsafe.Pointer(res))
return takeCString(res)
return parseCString(res)
}
func invokeResult(callback unsafe.Pointer, data string) {
@@ -30,7 +29,7 @@ func releaseObject(callback unsafe.Pointer) {
C.release_object(callback)
}
func takeCString(s *C.char) string {
defer releaseObject(unsafe.Pointer(s))
func parseCString(s *C.char) string {
//defer C.free(unsafe.Pointer(s))
return C.GoString(s)
}

View File

@@ -166,7 +166,6 @@ func (result ActionResult) send() {
if result.Method != messageMethod {
releaseObject(result.callback)
}
}
func nextHandle(action *Action, result ActionResult) bool {
@@ -182,7 +181,7 @@ func nextHandle(action *Action, result ActionResult) bool {
//export invokeAction
func invokeAction(callback unsafe.Pointer, paramsChar *C.char) {
params := takeCString(paramsChar)
params := parseCString(paramsChar)
var action = &Action{}
err := json.Unmarshal([]byte(params), action)
if err != nil {
@@ -199,7 +198,7 @@ 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), takeCString(addressChar), takeCString(dnsChar))
handleStartTun(callback, int(fd), parseCString(addressChar), parseCString(dnsChar))
return true
}
@@ -250,5 +249,5 @@ func forceGC() {
//export updateDns
func updateDns(s *C.char) {
handleUpdateDns(takeCString(s))
handleUpdateDns(parseCString(s))
}

View File

@@ -4,6 +4,8 @@ mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
set value(T value) {
if (ref.mounted) {
state = value;
} else {
onUpdate(value);
}
}

View File

@@ -965,7 +965,6 @@ class AppController {
commonPrint.log('$futureFunction ===> $e');
if (realSilence) {
globalState.showNotifier(e.toString());
globalState.showNotifier(e.toString());
} else {
globalState.showMessage(
title: title ?? appLocalizations.tip,

View File

@@ -73,7 +73,6 @@ class CoreService extends CoreHandlerInterface {
}
void _handleInvokeCrashEvent() {
_socketCompleter = Completer();
coreEventManager.sendEvent(CoreEvent(type: CoreEventType.crash));
}
@@ -134,6 +133,7 @@ class CoreService extends CoreHandlerInterface {
@override
shutdown() async {
await _destroySocket();
_clearCompleter();
if (system.isWindows) {
await request.stopCoreByHelper();
}
@@ -142,11 +142,11 @@ class CoreService extends CoreHandlerInterface {
return true;
}
// void _clearCompleter() {
// for (final completer in _callbackCompleterMap.values) {
// completer.safeCompleter(null);
// }
// }
void _clearCompleter() {
for (final completer in _callbackCompleterMap.values) {
completer.safeCompleter(null);
}
}
@override
Future<bool> preload() async {

View File

@@ -196,6 +196,7 @@ class MessageLookup extends MessageLookupByLibrary {
"copySuccess": MessageLookupByLibrary.simpleMessage("Copy success"),
"core": MessageLookupByLibrary.simpleMessage("Core"),
"coreInfo": MessageLookupByLibrary.simpleMessage("Core info"),
"coreStatus": MessageLookupByLibrary.simpleMessage("Core status"),
"country": MessageLookupByLibrary.simpleMessage("Country"),
"crashTest": MessageLookupByLibrary.simpleMessage("Crash test"),
"create": MessageLookupByLibrary.simpleMessage("Create"),
@@ -250,6 +251,7 @@ class MessageLookup extends MessageLookupByLibrary {
"dnsDesc": MessageLookupByLibrary.simpleMessage(
"Update DNS related settings",
),
"dnsHijacking": MessageLookupByLibrary.simpleMessage("DNS hijacking"),
"dnsMode": MessageLookupByLibrary.simpleMessage("DNS mode"),
"doYouWantToPass": MessageLookupByLibrary.simpleMessage(
"Do you want to pass",

View File

@@ -148,6 +148,7 @@ class MessageLookup extends MessageLookupByLibrary {
"copySuccess": MessageLookupByLibrary.simpleMessage("コピー成功"),
"core": MessageLookupByLibrary.simpleMessage("コア"),
"coreInfo": MessageLookupByLibrary.simpleMessage("コア情報"),
"coreStatus": MessageLookupByLibrary.simpleMessage("コアステータス"),
"country": MessageLookupByLibrary.simpleMessage(""),
"crashTest": MessageLookupByLibrary.simpleMessage("クラッシュテスト"),
"create": MessageLookupByLibrary.simpleMessage("作成"),
@@ -188,6 +189,7 @@ class MessageLookup extends MessageLookupByLibrary {
"discoverNewVersion": MessageLookupByLibrary.simpleMessage("新バージョンを発見"),
"discovery": MessageLookupByLibrary.simpleMessage("新しいバージョンを発見"),
"dnsDesc": MessageLookupByLibrary.simpleMessage("DNS関連設定の更新"),
"dnsHijacking": MessageLookupByLibrary.simpleMessage("DNSハイジャッキング"),
"dnsMode": MessageLookupByLibrary.simpleMessage("DNSモード"),
"doYouWantToPass": MessageLookupByLibrary.simpleMessage("通過させますか?"),
"domain": MessageLookupByLibrary.simpleMessage("ドメイン"),

View File

@@ -201,6 +201,7 @@ class MessageLookup extends MessageLookupByLibrary {
"copySuccess": MessageLookupByLibrary.simpleMessage("Копирование успешно"),
"core": MessageLookupByLibrary.simpleMessage("Ядро"),
"coreInfo": MessageLookupByLibrary.simpleMessage("Информация о ядре"),
"coreStatus": MessageLookupByLibrary.simpleMessage("Основной статус"),
"country": MessageLookupByLibrary.simpleMessage("Страна"),
"crashTest": MessageLookupByLibrary.simpleMessage("Тест на сбои"),
"create": MessageLookupByLibrary.simpleMessage("Создать"),
@@ -257,6 +258,7 @@ class MessageLookup extends MessageLookupByLibrary {
"dnsDesc": MessageLookupByLibrary.simpleMessage(
"Обновление настроек, связанных с DNS",
),
"dnsHijacking": MessageLookupByLibrary.simpleMessage("DNS-перехват"),
"dnsMode": MessageLookupByLibrary.simpleMessage("Режим DNS"),
"doYouWantToPass": MessageLookupByLibrary.simpleMessage(
"Вы хотите пропустить",

View File

@@ -138,6 +138,7 @@ class MessageLookup extends MessageLookupByLibrary {
"copySuccess": MessageLookupByLibrary.simpleMessage("复制成功"),
"core": MessageLookupByLibrary.simpleMessage("内核"),
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
"coreStatus": MessageLookupByLibrary.simpleMessage("核心状态"),
"country": MessageLookupByLibrary.simpleMessage("区域"),
"crashTest": MessageLookupByLibrary.simpleMessage("崩溃测试"),
"create": MessageLookupByLibrary.simpleMessage("创建"),
@@ -174,6 +175,7 @@ class MessageLookup extends MessageLookupByLibrary {
"discoverNewVersion": MessageLookupByLibrary.simpleMessage("发现新版本"),
"discovery": MessageLookupByLibrary.simpleMessage("发现新版本"),
"dnsDesc": MessageLookupByLibrary.simpleMessage("更新DNS相关设置"),
"dnsHijacking": MessageLookupByLibrary.simpleMessage("DNS劫持"),
"dnsMode": MessageLookupByLibrary.simpleMessage("DNS模式"),
"doYouWantToPass": MessageLookupByLibrary.simpleMessage("是否要通过"),
"domain": MessageLookupByLibrary.simpleMessage("域名"),

View File

@@ -3304,6 +3304,21 @@ class AppLocalizations {
args: [],
);
}
/// `DNS hijacking`
String get dnsHijacking {
return Intl.message(
'DNS hijacking',
name: 'dnsHijacking',
desc: '',
args: [],
);
}
/// `Core status`
String get coreStatus {
return Intl.message('Core status', name: 'coreStatus', desc: '', args: []);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -29,7 +29,8 @@ Future<void> _service(List<String> flags) async {
_TileListenerWithService(
onStop: () async {
await app?.tip(appLocalizations.stopVpn);
globalState.handleStop();
await globalState.handleStop();
exit(0);
},
),
);

View File

@@ -96,6 +96,7 @@ 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

@@ -100,17 +100,14 @@ const defaultBypassPrivateRouteAddress = [
'f000::/5',
'f800::/6',
'fe00::/9',
'fec0::/10'
'fec0::/10',
];
@freezed
abstract class ProxyGroup with _$ProxyGroup {
const factory ProxyGroup({
required String name,
@JsonKey(
fromJson: GroupType.parseProfileType,
)
required GroupType type,
@JsonKey(fromJson: GroupType.parseProfileType) required GroupType type,
List<String>? proxies,
List<String>? use,
int? interval,
@@ -132,9 +129,7 @@ abstract class ProxyGroup with _$ProxyGroup {
@freezed
abstract class RuleProvider with _$RuleProvider {
const factory RuleProvider({
required String name,
}) = _RuleProvider;
const factory RuleProvider({required String name}) = _RuleProvider;
factory RuleProvider.fromJson(Map<String, Object?> json) =>
_$RuleProviderFromJson(json);
@@ -206,14 +201,11 @@ extension TunExt on Tun {
? defaultBypassPrivateRouteAddress
: routeAddress;
return switch (system.isDesktop) {
true => copyWith(
autoRoute: true,
routeAddress: [],
),
true => copyWith(autoRoute: true, routeAddress: []),
false => copyWith(
autoRoute: mRouteAddress.isEmpty ? true : false,
routeAddress: mRouteAddress,
),
autoRoute: mRouteAddress.isEmpty ? true : false,
routeAddress: mRouteAddress,
),
};
}
}
@@ -225,11 +217,7 @@ abstract class FallbackFilter with _$FallbackFilter {
@Default('CN') @JsonKey(name: 'geoip-code') String geoipCode,
@Default(['gfw']) List<String> geosite,
@Default(['240.0.0.0/4']) List<String> ipcidr,
@Default([
'+.google.com',
'+.facebook.com',
'+.youtube.com',
])
@Default(['+.google.com', '+.facebook.com', '+.youtube.com'])
List<String> domain,
}) = _FallbackFilter;
@@ -256,32 +244,20 @@ abstract class Dns with _$Dns {
@Default('198.18.0.1/16')
@JsonKey(name: 'fake-ip-range')
String fakeIpRange,
@Default([
'*.lan',
'localhost.ptlogin2.qq.com',
])
@Default(['*.lan', 'localhost.ptlogin2.qq.com'])
@JsonKey(name: 'fake-ip-filter')
List<String> fakeIpFilter,
@Default({
'www.baidu.com': '114.114.114.114',
'+.internal.crop.com': '10.0.0.1',
'geosite:cn': 'https://doh.pub/dns-query'
'geosite:cn': 'https://doh.pub/dns-query',
})
@JsonKey(name: 'nameserver-policy')
Map<String, String> nameserverPolicy,
@Default([
'https://doh.pub/dns-query',
'https://dns.alidns.com/dns-query',
])
@Default(['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query'])
List<String> nameserver,
@Default([
'tls://8.8.4.4',
'tls://1.1.1.1',
])
List<String> fallback,
@Default([
'https://doh.pub/dns-query',
])
@Default(['tls://8.8.4.4', 'tls://1.1.1.1']) List<String> fallback,
@Default(['https://doh.pub/dns-query'])
@JsonKey(name: 'proxy-server-nameserver')
List<String> proxyServerNameserver,
@Default(FallbackFilter())
@@ -351,9 +327,7 @@ abstract class ParsedRule with _$ParsedRule {
factory ParsedRule.parseString(String value) {
final splits = value.split(',');
final shortSplits = splits
.where(
(item) => !item.contains('src') && !item.contains('no-resolve'),
)
.where((item) => !item.contains('src') && !item.contains('no-resolve'))
.toList();
final ruleAction = RuleAction.values.firstWhere(
(item) => item.value == shortSplits.first,
@@ -398,23 +372,17 @@ extension ParsedRuleExt on ParsedRule {
if (ruleAction.hasParams) ...[
if (src) 'src',
if (noResolve) 'no-resolve',
]
],
].join(',');
}
}
@freezed
abstract class Rule with _$Rule {
const factory Rule({
required String id,
required String value,
}) = _Rule;
const factory Rule({required String id, required String value}) = _Rule;
factory Rule.value(String value) {
return Rule(
value: value,
id: utils.uuidV4,
);
return Rule(value: value, id: utils.uuidV4);
}
factory Rule.fromJson(Map<String, Object?> json) => _$RuleFromJson(json);
@@ -422,9 +390,7 @@ abstract class Rule with _$Rule {
@freezed
abstract class SubRule with _$SubRule {
const factory SubRule({
required String name,
}) = _SubRule;
const factory SubRule({required String name}) = _SubRule;
factory SubRule.fromJson(Map<String, Object?> json) =>
_$SubRuleFromJson(json);
@@ -434,11 +400,7 @@ List<Rule> _genRule(List<dynamic>? rules) {
if (rules == null) {
return [];
}
return rules
.map(
(item) => Rule.value(item),
)
.toList();
return rules.map((item) => Rule.value(item)).toList();
}
List<RuleProvider> _genRuleProviders(Map<String, dynamic> json) {
@@ -446,13 +408,7 @@ List<RuleProvider> _genRuleProviders(Map<String, dynamic> json) {
}
List<SubRule> _genSubRules(Map<String, dynamic> json) {
return json.entries
.map(
(entry) => SubRule(
name: entry.key,
),
)
.toList();
return json.entries.map((entry) => SubRule(name: entry.key)).toList();
}
@freezed
@@ -484,7 +440,7 @@ abstract class ClashConfig with _$ClashConfig {
@Default(false) @JsonKey(name: 'allow-lan') bool allowLan,
@Default(LogLevel.error) @JsonKey(name: 'log-level') LogLevel logLevel,
@Default(false) bool ipv6,
@Default(FindProcessMode.off)
@Default(FindProcessMode.always)
@JsonKey(
name: 'find-process-mode',
unknownEnumValue: FindProcessMode.always,

View File

@@ -97,9 +97,9 @@ extension TrackerInfoExt on TrackerInfo {
final process = metadata.process;
final uid = metadata.uid;
if (uid != 0) {
return '$process($uid)';
return '$process($uid)'.trim();
}
return process;
return process.trim();
}
}
@@ -236,7 +236,7 @@ extension TrafficExt on Traffic {
}
String get desc {
return '${up.traffic.show}${down.traffic.show}';
return '${up.traffic.show} ${down.traffic.show} ';
}
num get speed => up + down;
@@ -249,7 +249,7 @@ abstract class TrafficShow with _$TrafficShow {
}
extension TrafficShowExt on TrafficShow {
String get show => '$value $unit';
String get show => '$value$unit';
}
@freezed

View File

@@ -3765,7 +3765,7 @@ return $default(_that.mixedPort,_that.socksPort,_that.port,_that.redirPort,_that
@JsonSerializable()
class _ClashConfig implements ClashConfig {
const _ClashConfig({@JsonKey(name: 'mixed-port') this.mixedPort = defaultMixedPort, @JsonKey(name: 'socks-port') this.socksPort = 0, @JsonKey(name: 'port') this.port = 0, @JsonKey(name: 'redir-port') this.redirPort = 0, @JsonKey(name: 'tproxy-port') this.tproxyPort = 0, this.mode = Mode.rule, @JsonKey(name: 'allow-lan') this.allowLan = false, @JsonKey(name: 'log-level') this.logLevel = LogLevel.error, this.ipv6 = false, @JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) this.findProcessMode = FindProcessMode.off, @JsonKey(name: 'keep-alive-interval') this.keepAliveInterval = defaultKeepAliveInterval, @JsonKey(name: 'unified-delay') this.unifiedDelay = true, @JsonKey(name: 'tcp-concurrent') this.tcpConcurrent = true, @JsonKey(fromJson: Tun.safeFormJson) this.tun = defaultTun, @JsonKey(fromJson: Dns.safeDnsFromJson) this.dns = defaultDns, @JsonKey(name: 'geox-url', fromJson: GeoXUrl.safeFormJson) this.geoXUrl = defaultGeoXUrl, @JsonKey(name: 'geodata-loader') this.geodataLoader = GeodataLoader.memconservative, @JsonKey(name: 'proxy-groups') final List<ProxyGroup> proxyGroups = const [], final List<String> rule = const [], @JsonKey(name: 'global-ua') this.globalUa, @JsonKey(name: 'external-controller') this.externalController = ExternalControllerStatus.close, final HostsMap hosts = const {}}): _proxyGroups = proxyGroups,_rule = rule,_hosts = hosts;
const _ClashConfig({@JsonKey(name: 'mixed-port') this.mixedPort = defaultMixedPort, @JsonKey(name: 'socks-port') this.socksPort = 0, @JsonKey(name: 'port') this.port = 0, @JsonKey(name: 'redir-port') this.redirPort = 0, @JsonKey(name: 'tproxy-port') this.tproxyPort = 0, this.mode = Mode.rule, @JsonKey(name: 'allow-lan') this.allowLan = false, @JsonKey(name: 'log-level') this.logLevel = LogLevel.error, this.ipv6 = false, @JsonKey(name: 'find-process-mode', unknownEnumValue: FindProcessMode.always) this.findProcessMode = FindProcessMode.always, @JsonKey(name: 'keep-alive-interval') this.keepAliveInterval = defaultKeepAliveInterval, @JsonKey(name: 'unified-delay') this.unifiedDelay = true, @JsonKey(name: 'tcp-concurrent') this.tcpConcurrent = true, @JsonKey(fromJson: Tun.safeFormJson) this.tun = defaultTun, @JsonKey(fromJson: Dns.safeDnsFromJson) this.dns = defaultDns, @JsonKey(name: 'geox-url', fromJson: GeoXUrl.safeFormJson) this.geoXUrl = defaultGeoXUrl, @JsonKey(name: 'geodata-loader') this.geodataLoader = GeodataLoader.memconservative, @JsonKey(name: 'proxy-groups') final List<ProxyGroup> proxyGroups = const [], final List<String> rule = const [], @JsonKey(name: 'global-ua') this.globalUa, @JsonKey(name: 'external-controller') this.externalController = ExternalControllerStatus.close, final HostsMap hosts = const {}}): _proxyGroups = proxyGroups,_rule = rule,_hosts = hosts;
factory _ClashConfig.fromJson(Map<String, dynamic> json) => _$ClashConfigFromJson(json);
@override@JsonKey(name: 'mixed-port') final int mixedPort;

View File

@@ -342,7 +342,7 @@ _ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => _ClashConfig(
json['find-process-mode'],
unknownValue: FindProcessMode.always,
) ??
FindProcessMode.off,
FindProcessMode.always,
keepAliveInterval:
(json['keep-alive-interval'] as num?)?.toInt() ??
defaultKeepAliveInterval,

View File

@@ -28,7 +28,7 @@ class Logs extends _$Logs with AutoDisposeNotifierMixin {
}
void addLog(Log value) {
state = state.copyWith()..add(value);
this.value = state.copyWith()..add(value);
}
@override
@@ -50,7 +50,7 @@ class Requests extends _$Requests with AutoDisposeNotifierMixin {
}
void addRequest(TrackerInfo value) {
state = state.copyWith()..add(value);
this.value = state.copyWith()..add(value);
}
}
@@ -70,7 +70,8 @@ class Providers extends _$Providers with AutoDisposeNotifierMixin {
if (provider == null) return;
final index = state.indexWhere((item) => item.name == provider.name);
if (index == -1) return;
state = List.from(state)..[index] = provider;
final newState = List<ExternalProvider>.from(state)..[index] = provider;
value = newState;
}
}
@@ -101,7 +102,7 @@ class SystemBrightness extends _$SystemBrightness
}
void setState(Brightness value) {
state = value;
this.value = value;
}
}
@@ -118,11 +119,11 @@ class Traffics extends _$Traffics with AutoDisposeNotifierMixin {
}
void addTraffic(Traffic value) {
state = state.copyWith()..add(value);
this.value = state.copyWith()..add(value);
}
void clear() {
state = state.copyWith()..clear();
value = state.copyWith()..clear();
}
}
@@ -150,12 +151,6 @@ class LocalIp extends _$LocalIp with AutoDisposeNotifierMixin {
onUpdate(value) {
globalState.appState = globalState.appState.copyWith(localIp: value);
}
@override
set state(String? value) {
super.state = value;
globalState.appState = globalState.appState.copyWith(localIp: state);
}
}
@riverpod
@@ -332,7 +327,7 @@ class DelayDataSource extends _$DelayDataSource with AutoDisposeNotifierMixin {
newDelayMap[delay.url] = {};
}
newDelayMap[delay.url]![delay.name] = delay.value;
state = newDelayMap;
value = newDelayMap;
}
}
}
@@ -375,7 +370,7 @@ class ProfileOverrideState extends _$ProfileOverrideState
if (value == null) {
return;
}
state = value;
this.value = value;
}
}
@@ -403,6 +398,6 @@ class QueryMap extends _$QueryMap with AutoDisposeNotifierMixin {
}
void updateQuery(QueryTag tag, String value) {
state = Map.from(globalState.appState.queryMap)..[tag] = value;
this.value = Map.from(globalState.appState.queryMap)..[tag] = value;
}
}

View File

@@ -14,13 +14,11 @@ class AppSetting extends _$AppSetting with AutoDisposeNotifierMixin {
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(
appSetting: value,
);
globalState.config = globalState.config.copyWith(appSetting: value);
}
void updateState(AppSettingProps Function(AppSettingProps state) builder) {
state = builder(state);
value = builder(state);
}
}
@@ -33,13 +31,11 @@ class WindowSetting extends _$WindowSetting with AutoDisposeNotifierMixin {
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(
windowProps: value,
);
globalState.config = globalState.config.copyWith(windowProps: value);
}
void updateState(WindowProps Function(WindowProps state) builder) {
state = builder(state);
value = builder(state);
}
}
@@ -52,13 +48,11 @@ class VpnSetting extends _$VpnSetting with AutoDisposeNotifierMixin {
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(
vpnProps: value,
);
globalState.config = globalState.config.copyWith(vpnProps: value);
}
void updateState(VpnProps Function(VpnProps state) builder) {
state = builder(state);
value = builder(state);
}
}
@@ -71,13 +65,11 @@ class NetworkSetting extends _$NetworkSetting with AutoDisposeNotifierMixin {
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(
networkProps: value,
);
globalState.config = globalState.config.copyWith(networkProps: value);
}
void updateState(NetworkProps Function(NetworkProps state) builder) {
state = builder(state);
value = builder(state);
}
}
@@ -90,13 +82,11 @@ class ThemeSetting extends _$ThemeSetting with AutoDisposeNotifierMixin {
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(
themeProps: value,
);
globalState.config = globalState.config.copyWith(themeProps: value);
}
void updateState(ThemeProps Function(ThemeProps state) builder) {
state = builder(state);
value = builder(state);
}
}
@@ -109,15 +99,15 @@ class Profiles extends _$Profiles with AutoDisposeNotifierMixin {
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(
profiles: value,
);
globalState.config = globalState.config.copyWith(profiles: value);
}
String? _getLabel(String? label, String id) {
final realLabel = label ?? id;
final hasDup = state.indexWhere(
(element) => element.label == realLabel && element.id != id) !=
final hasDup =
state.indexWhere(
(element) => element.label == realLabel && element.id != id,
) !=
-1;
if (hasDup) {
return _getLabel(utils.getOverwriteLabel(realLabel), id);
@@ -128,8 +118,9 @@ class Profiles extends _$Profiles with AutoDisposeNotifierMixin {
void setProfile(Profile profile) {
final List<Profile> profilesTemp = List.from(state);
final index =
profilesTemp.indexWhere((element) => element.id == profile.id);
final index = profilesTemp.indexWhere(
(element) => element.id == profile.id,
);
final updateProfile = profile.copyWith(
label: _getLabel(profile.label, profile.id),
);
@@ -138,21 +129,23 @@ class Profiles extends _$Profiles with AutoDisposeNotifierMixin {
} else {
profilesTemp[index] = updateProfile;
}
state = profilesTemp;
value = profilesTemp;
}
void updateProfile(
String profileId, Profile Function(Profile profile) builder) {
String profileId,
Profile Function(Profile profile) builder,
) {
final List<Profile> profilesTemp = List.from(state);
final index = profilesTemp.indexWhere((element) => element.id == profileId);
if (index != -1) {
profilesTemp[index] = builder(profilesTemp[index]);
}
state = profilesTemp;
value = profilesTemp;
}
void deleteProfileById(String id) {
state = state.where((element) => element.id != id).toList();
value = state.where((element) => element.id != id).toList();
}
}
@@ -166,9 +159,7 @@ class CurrentProfileId extends _$CurrentProfileId
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(
currentProfileId: value,
);
globalState.config = globalState.config.copyWith(currentProfileId: value);
}
}
@@ -181,13 +172,11 @@ class AppDAVSetting extends _$AppDAVSetting with AutoDisposeNotifierMixin {
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(
dav: value,
);
globalState.config = globalState.config.copyWith(dav: value);
}
void updateState(DAV? Function(DAV? state) builder) {
state = builder(state);
value = builder(state);
}
}
@@ -200,9 +189,7 @@ class OverrideDns extends _$OverrideDns with AutoDisposeNotifierMixin {
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(
overrideDns: value,
);
globalState.config = globalState.config.copyWith(overrideDns: value);
}
}
@@ -215,9 +202,7 @@ class HotKeyActions extends _$HotKeyActions with AutoDisposeNotifierMixin {
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(
hotKeyActions: value,
);
globalState.config = globalState.config.copyWith(hotKeyActions: value);
}
}
@@ -231,13 +216,11 @@ class ProxiesStyleSetting extends _$ProxiesStyleSetting
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(
proxiesStyle: value,
);
globalState.config = globalState.config.copyWith(proxiesStyle: value);
}
void updateState(ProxiesStyle Function(ProxiesStyle state) builder) {
state = builder(state);
value = builder(state);
}
}
@@ -250,9 +233,7 @@ class ScriptState extends _$ScriptState with AutoDisposeNotifierMixin {
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(
scriptProps: value,
);
globalState.config = globalState.config.copyWith(scriptProps: value);
}
void setScript(Script script) {
@@ -263,15 +244,11 @@ class ScriptState extends _$ScriptState with AutoDisposeNotifierMixin {
} else {
list.add(script);
}
state = state.copyWith(
scripts: list,
);
value = state.copyWith(scripts: list);
}
void setId(String id) {
state = state.copyWith(
currentId: state.currentId != id ? id : null,
);
value = state.copyWith(currentId: state.currentId != id ? id : null);
}
void del(String id) {
@@ -281,10 +258,7 @@ class ScriptState extends _$ScriptState with AutoDisposeNotifierMixin {
list.removeAt(index);
}
final nextId = id == state.currentId ? null : state.currentId;
state = state.copyWith(
scripts: list,
currentId: nextId,
);
state = state.copyWith(scripts: list, currentId: nextId);
}
bool isExits(String label) {
@@ -305,13 +279,11 @@ class PatchClashConfig extends _$PatchClashConfig
if (newState == null) {
return;
}
state = newState;
value = newState;
}
@override
onUpdate(value) {
globalState.config = globalState.config.copyWith(
patchClashConfig: value,
);
globalState.config = globalState.config.copyWith(patchClashConfig: value);
}
}

View File

@@ -258,6 +258,28 @@ class BypassDomainItem extends StatelessWidget {
}
}
class DNSHijackingItem extends ConsumerWidget {
const DNSHijackingItem({super.key});
@override
Widget build(BuildContext context, ref) {
final dnsHijacking = ref.watch(
vpnSettingProvider.select((state) => state.dnsHijacking),
);
return ListItem<RouteMode>.switchItem(
title: Text(appLocalizations.dnsHijacking),
delegate: SwitchDelegate(
value: dnsHijacking,
onChanged: (value) async {
ref
.read(vpnSettingProvider.notifier)
.updateState((state) => state.copyWith(dnsHijacking: value));
},
),
);
}
}
class RouteModeItem extends ConsumerWidget {
const RouteModeItem({super.key});
@@ -344,6 +366,7 @@ final networkItems = [
const BypassDomainItem(),
const AllowBypassItem(),
const Ipv6Item(),
const DNSHijackingItem(),
],
),
if (system.isDesktop)

View File

@@ -50,6 +50,7 @@ class _RequestsViewState extends ConsumerState<RequestsView> {
next,
) {
_requests = next;
updateRequestsThrottler();
});
}

View File

@@ -63,58 +63,64 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
Consumer(
builder: (_, ref, _) {
final coreStatus = ref.watch(coreStatusProvider);
return FilledButton.icon(
onPressed: coreStatus != CoreStatus.disconnected
? () {}
: _handleConnection,
onLongPress: coreStatus != CoreStatus.connected
? null
: _handleConnection,
style: FilledButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 12),
backgroundColor: switch (coreStatus) {
CoreStatus.connecting => null,
CoreStatus.connected => Colors.greenAccent,
CoreStatus.disconnected => context.colorScheme.error,
},
foregroundColor: switch (coreStatus) {
CoreStatus.connecting => null,
CoreStatus.connected => context.colorScheme.onPrimary,
CoreStatus.disconnected => context.colorScheme.onError,
},
),
icon: SizedBox(
height: globalState.measure.bodyMediumHeight,
width: globalState.measure.bodyMediumHeight,
child: FadeRotationScaleBox(
child: switch (coreStatus) {
CoreStatus.connecting => Padding(
padding: EdgeInsets.all(2),
child: CircularProgressIndicator(
key: ValueKey(CoreStatus.connecting),
strokeWidth: 3,
color: context.colorScheme.onPrimary,
backgroundColor: Colors.transparent,
),
),
CoreStatus.connected => Icon(
Icons.check_sharp,
fontWeight: FontWeight.w900,
key: ValueKey(CoreStatus.connected),
),
CoreStatus.disconnected => Icon(
Icons.restart_alt_sharp,
fontWeight: FontWeight.w900,
key: ValueKey(CoreStatus.disconnected),
),
return Tooltip(
message: appLocalizations.coreStatus,
child: FilledButton.icon(
onPressed: coreStatus == CoreStatus.connecting
? () {}
: _handleConnection,
style: FilledButton.styleFrom(
visualDensity: VisualDensity.compact,
padding: EdgeInsets.symmetric(horizontal: 12),
backgroundColor: switch (coreStatus) {
CoreStatus.connecting => null,
CoreStatus.connected => Colors.greenAccent,
CoreStatus.disconnected => context.colorScheme.error,
},
foregroundColor: switch (coreStatus) {
CoreStatus.connecting => null,
CoreStatus.connected => switch (Theme.brightnessOf(
context,
)) {
Brightness.light => context.colorScheme.onSurfaceVariant,
Brightness.dark => null,
},
CoreStatus.disconnected => context.colorScheme.onError,
},
),
icon: SizedBox(
height: globalState.measure.bodyMediumHeight,
width: globalState.measure.bodyMediumHeight,
child: FadeRotationScaleBox(
child: switch (coreStatus) {
CoreStatus.connecting => Padding(
padding: EdgeInsets.all(2),
child: CircularProgressIndicator(
key: ValueKey(CoreStatus.connecting),
strokeWidth: 3,
color: context.colorScheme.onPrimary,
backgroundColor: Colors.transparent,
),
),
CoreStatus.connected => Icon(
Icons.check_sharp,
fontWeight: FontWeight.w900,
key: ValueKey(CoreStatus.connected),
),
CoreStatus.disconnected => Icon(
Icons.restart_alt_sharp,
fontWeight: FontWeight.w900,
key: ValueKey(CoreStatus.disconnected),
),
},
),
),
label: Text(switch (coreStatus) {
CoreStatus.connecting => appLocalizations.connecting,
CoreStatus.connected => appLocalizations.connected,
CoreStatus.disconnected => appLocalizations.disconnected,
}),
),
label: Text(switch (coreStatus) {
CoreStatus.connecting => appLocalizations.connecting,
CoreStatus.connected => appLocalizations.connected,
CoreStatus.disconnected => appLocalizations.disconnected,
}),
);
},
),

View File

@@ -413,10 +413,10 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: ef7d2a085c1b1d69d17b6842d0734aad90156de08df6bd3c12496d0bd6ddf8e2
sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22
url: "https://pub.dev"
source: hosted
version: "10.3.1"
version: "10.3.2"
file_selector_linux:
dependency: transitive
description:
@@ -834,10 +834,10 @@ packages:
dependency: transitive
description:
name: macos_window_utils
sha256: "18745e56b4c0444d802dadbafca98797828b813ee2488e367dd8f84f1ec4c217"
sha256: d4df3501fd32ac0d2d7590cb6a8e4758337d061c8fa0db816fdd636be63a8438
url: "https://pub.dev"
source: hosted
version: "1.8.4"
version: "1.9.0"
matcher:
dependency: transitive
description:
@@ -1455,10 +1455,10 @@ packages:
dependency: "direct main"
description:
name: tray_manager
sha256: ad18c4cd73003097d182884bacb0578ad2865f3ab842a0ad00f6d043ed49eaf0
sha256: "537e539f48cd82d8ee2240d4330158c7b44c7e043e8e18b5811f2f8f6b7df25a"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
version: "0.5.1"
typed_data:
dependency: transitive
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+2025082501
version: 0.8.88+2025082601
environment:
sdk: '>=3.8.0 <4.0.0'