Compare commits
1 Commits
v0.8.88-pr
...
v0.8.88-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61465f5178 |
@@ -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>
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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?) {
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,9 +21,7 @@ class TempActivity : Activity(),
|
||||
}
|
||||
|
||||
QuickAction.STOP.action -> {
|
||||
launch {
|
||||
State.handleStopServiceAction()
|
||||
}
|
||||
State.handleStopServiceAction()
|
||||
}
|
||||
|
||||
QuickAction.TOGGLE.action -> {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -10,4 +10,7 @@ object Components {
|
||||
|
||||
val TEMP_ACTIVITY =
|
||||
ComponentName(GlobalState.packageName, "${PACKAGE_NAME}.TempActivity")
|
||||
|
||||
val BROADCAST_RECEIVER =
|
||||
ComponentName(GlobalState.packageName, "${PACKAGE_NAME}.BroadcastReceiver")
|
||||
}
|
||||
@@ -10,7 +10,9 @@ enum class QuickAction {
|
||||
}
|
||||
|
||||
enum class BroadcastAction {
|
||||
CREATE_VPN,
|
||||
START,
|
||||
STOP,
|
||||
TOGGLE,
|
||||
}
|
||||
|
||||
enum class AccessControlMode {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -427,5 +427,7 @@
|
||||
"disconnected": "切断済み",
|
||||
"connecting": "接続中...",
|
||||
"restartCoreTip": "コアを再起動してもよろしいですか?",
|
||||
"forceRestartCoreTip": "コアを強制再起動してもよろしいですか?"
|
||||
"forceRestartCoreTip": "コアを強制再起動してもよろしいですか?",
|
||||
"dnsHijacking": "DNSハイジャッキング",
|
||||
"coreStatus": "コアステータス"
|
||||
}
|
||||
@@ -427,5 +427,7 @@
|
||||
"disconnected": "Отключено",
|
||||
"connecting": "Подключение...",
|
||||
"restartCoreTip": "Вы уверены, что хотите перезапустить ядро?",
|
||||
"forceRestartCoreTip": "Вы уверены, что хотите принудительно перезапустить ядро?"
|
||||
"forceRestartCoreTip": "Вы уверены, что хотите принудительно перезапустить ядро?",
|
||||
"dnsHijacking": "DNS-перехват",
|
||||
"coreStatus": "Основной статус"
|
||||
}
|
||||
@@ -427,5 +427,7 @@
|
||||
"disconnected": "已断开",
|
||||
"connecting": "连接中...",
|
||||
"restartCoreTip": "您确定要重启核心吗?",
|
||||
"forceRestartCoreTip": "您确定要强制重启核心吗?"
|
||||
"forceRestartCoreTip": "您确定要强制重启核心吗?",
|
||||
"dnsHijacking": "DNS劫持",
|
||||
"coreStatus": "核心状态"
|
||||
}
|
||||
|
||||
Submodule core/Clash.Meta updated: 82906c837a...6fe704ae11
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
|
||||
set value(T value) {
|
||||
if (ref.mounted) {
|
||||
state = value;
|
||||
} else {
|
||||
onUpdate(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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("ドメイン"),
|
||||
|
||||
@@ -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(
|
||||
"Вы хотите пропустить",
|
||||
|
||||
@@ -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("域名"),
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -50,6 +50,7 @@ class _RequestsViewState extends ConsumerState<RequestsView> {
|
||||
next,
|
||||
) {
|
||||
_requests = next;
|
||||
updateRequestsThrottler();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
12
pubspec.lock
12
pubspec.lock
@@ -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:
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user