diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 0c4c9c85f..97607f126 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -105,6 +105,7 @@
+
diff --git a/android/app/src/main/kotlin/com/follow/clash/BroadcastReceiver.kt b/android/app/src/main/kotlin/com/follow/clash/BroadcastReceiver.kt
index 93e621e48..e23647d26 100644
--- a/android/app/src/main/kotlin/com/follow/clash/BroadcastReceiver.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/BroadcastReceiver.kt
@@ -24,6 +24,14 @@ class BroadcastReceiver : BroadcastReceiver() {
State.handleStopServiceAction()
}
}
+
+ BroadcastAction.MODE_CHANGED.action -> {
+ val mode = intent.getStringExtra("mode") ?: return
+ GlobalState.application.updateSharedStateMode(mode)
+ GlobalState.launch {
+ State.handleChangeModeAction(mode)
+ }
+ }
}
}
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/follow/clash/Ext.kt b/android/app/src/main/kotlin/com/follow/clash/Ext.kt
index 7cabf5b82..c858b3101 100644
--- a/android/app/src/main/kotlin/com/follow/clash/Ext.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/Ext.kt
@@ -37,6 +37,17 @@ val Application.sharedState: SharedState
}
}
+fun Application.updateSharedStateMode(mode: String) {
+ try {
+ val sp = getSharedPreferences("FlutterSharedPreferences", MODE_PRIVATE)
+ val res = sp.getString("flutter.sharedState", "") ?: return
+ val state = Gson().fromJson(res, SharedState::class.java) ?: return
+ val updated = state.copy(mode = mode)
+ sp.edit().putString("flutter.sharedState", Gson().toJson(updated)).apply()
+ } catch (_: Exception) {
+ }
+}
+
private var lastToast: Toast? = null
diff --git a/android/app/src/main/kotlin/com/follow/clash/State.kt b/android/app/src/main/kotlin/com/follow/clash/State.kt
index f3e1b472c..8375de4cb 100644
--- a/android/app/src/main/kotlin/com/follow/clash/State.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/State.kt
@@ -120,8 +120,11 @@ object State {
Service.updateNotificationParams(
NotificationParams(
title = sharedState.currentProfileName,
- stopText = sharedState.stopText,
- onlyStatisticsProxy = sharedState.onlyStatisticsProxy
+ onlyStatisticsProxy = sharedState.onlyStatisticsProxy,
+ currentMode = sharedState.mode,
+ ruleText = sharedState.ruleText,
+ globalText = sharedState.globalText,
+ directText = sharedState.directText,
)
)
Service.setCrashlytics(sharedState.crashlytics)
@@ -181,6 +184,12 @@ object State {
}
}
+ suspend fun handleChangeModeAction(mode: String) {
+ tilePlugin?.handleChangeMode(mode)
+ sharedState = sharedState.copy(mode = mode)
+ syncState()
+ }
+
fun handleStopService() {
GlobalState.launch {
runLock.withLock {
diff --git a/android/app/src/main/kotlin/com/follow/clash/models/State.kt b/android/app/src/main/kotlin/com/follow/clash/models/State.kt
index 35e461b77..798d3edc2 100644
--- a/android/app/src/main/kotlin/com/follow/clash/models/State.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/models/State.kt
@@ -12,6 +12,10 @@ data class SharedState(
val onlyStatisticsProxy: Boolean = false,
val vpnOptions: VpnOptions? = null,
val setupParams: SetupParams? = null,
+ val mode: String = "rule",
+ val ruleText: String = "Rule",
+ val globalText: String = "Global",
+ val directText: String = "Direct",
)
data class SetupParams(
diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/TilePlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/TilePlugin.kt
index f7a5732b3..765542a22 100644
--- a/android/app/src/main/kotlin/com/follow/clash/plugins/TilePlugin.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/plugins/TilePlugin.kt
@@ -28,6 +28,10 @@ class TilePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
channel.invokeMethodOnMainThread("stop", null)
}
+ fun handleChangeMode(mode: String) {
+ channel.invokeMethodOnMainThread("changeMode", mode)
+ }
+
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {}
}
\ No newline at end of file
diff --git a/android/common/src/main/java/com/follow/clash/common/Enums.kt b/android/common/src/main/java/com/follow/clash/common/Enums.kt
index 91310af04..801ee23e0 100644
--- a/android/common/src/main/java/com/follow/clash/common/Enums.kt
+++ b/android/common/src/main/java/com/follow/clash/common/Enums.kt
@@ -7,11 +7,15 @@ enum class QuickAction {
STOP,
START,
TOGGLE,
+ MODE_RULE,
+ MODE_GLOBAL,
+ MODE_DIRECT,
}
enum class BroadcastAction {
SERVICE_CREATED,
SERVICE_DESTROYED,
+ MODE_CHANGED,
}
enum class AccessControlMode {
diff --git a/android/service/src/main/java/com/follow/clash/service/models/NotificationParams.kt b/android/service/src/main/java/com/follow/clash/service/models/NotificationParams.kt
index 7196005d2..e5d7fcb24 100644
--- a/android/service/src/main/java/com/follow/clash/service/models/NotificationParams.kt
+++ b/android/service/src/main/java/com/follow/clash/service/models/NotificationParams.kt
@@ -6,6 +6,9 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class NotificationParams(
val title: String = "FlClash",
- val stopText: String = "STOP",
val onlyStatisticsProxy: Boolean = false,
+ val currentMode: String = "rule",
+ val ruleText: String = "Rule",
+ val globalText: String = "Global",
+ val directText: String = "Direct",
) : Parcelable
\ No newline at end of file
diff --git a/android/service/src/main/java/com/follow/clash/service/modules/NotificationModule.kt b/android/service/src/main/java/com/follow/clash/service/modules/NotificationModule.kt
index 35b5a363e..92a8b3c4b 100644
--- a/android/service/src/main/java/com/follow/clash/service/modules/NotificationModule.kt
+++ b/android/service/src/main/java/com/follow/clash/service/modules/NotificationModule.kt
@@ -1,6 +1,7 @@
package com.follow.clash.service.modules
import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
+import android.app.PendingIntent
import android.app.Service
import android.app.Service.STOP_FOREGROUND_REMOVE
import android.content.Intent
@@ -10,8 +11,9 @@ import androidx.core.app.NotificationCompat
import androidx.core.content.getSystemService
import com.follow.clash.common.Components
import com.follow.clash.common.GlobalState
+import com.follow.clash.common.BroadcastAction
import com.follow.clash.common.QuickAction
-import com.follow.clash.common.quickIntent
+import com.follow.clash.common.action
import com.follow.clash.common.receiveBroadcastFlow
import com.follow.clash.common.startForeground
import com.follow.clash.common.tickerFlow
@@ -27,38 +29,80 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
data class ExtendedNotificationParams(
val title: String,
- val stopText: String,
val onlyStatisticsProxy: Boolean,
val contentText: String,
+ val currentMode: String,
+ val ruleText: String,
+ val globalText: String,
+ val directText: String,
)
val NotificationParams.extended: ExtendedNotificationParams
get() = ExtendedNotificationParams(
- title, stopText, onlyStatisticsProxy, Core.getSpeedTrafficText(onlyStatisticsProxy)
+ title, onlyStatisticsProxy, Core.getSpeedTrafficText(onlyStatisticsProxy),
+ currentMode, ruleText, globalText, directText
)
class NotificationModule(private val service: Service) : Module() {
private val scope = CoroutineScope(Dispatchers.Default)
+ private fun modeBroadcastPendingIntent(action: QuickAction): PendingIntent {
+ val intent = Intent(action.action).setPackage(GlobalState.packageName)
+ return PendingIntent.getBroadcast(
+ service,
+ action.ordinal,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+ )
+ }
+
+ private fun changeModeViaCore(mode: String) {
+ Core.invokeAction(
+ """{"id":"changeMode#${System.currentTimeMillis()}","method":"updateConfig","data":"{\"mode\":\"$mode\"}"}"""
+ ) {}
+ val currentParams = State.notificationParamsFlow.value
+ if (currentParams != null) {
+ State.notificationParamsFlow.tryEmit(currentParams.copy(currentMode = mode))
+ }
+ val intent = Intent().apply {
+ action = BroadcastAction.MODE_CHANGED.action
+ putExtra("mode", mode)
+ setPackage(GlobalState.packageName)
+ }
+ service.sendBroadcast(intent, GlobalState.RECEIVE_BROADCASTS_PERMISSIONS)
+ }
+
override fun onInstall() {
scope.launch {
val screenFlow = service.receiveBroadcastFlow {
addAction(Intent.ACTION_SCREEN_ON)
addAction(Intent.ACTION_SCREEN_OFF)
+ addAction(QuickAction.MODE_RULE.action)
+ addAction(QuickAction.MODE_GLOBAL.action)
+ addAction(QuickAction.MODE_DIRECT.action)
}.map { intent ->
- intent.action == Intent.ACTION_SCREEN_ON
+ when (intent.action) {
+ QuickAction.MODE_RULE.action -> { changeModeViaCore("rule"); return@map null }
+ QuickAction.MODE_GLOBAL.action -> { changeModeViaCore("global"); return@map null }
+ QuickAction.MODE_DIRECT.action -> { changeModeViaCore("direct"); return@map null }
+ Intent.ACTION_SCREEN_ON -> true
+ else -> false
+ }
}.onStart {
emit(isScreenOn())
}
+ val filteredScreenFlow = screenFlow.filterNotNull()
+
combine(
- tickerFlow(1000, 0), State.notificationParamsFlow, screenFlow
+ tickerFlow(1000, 0), State.notificationParamsFlow, filteredScreenFlow
) { _, params, screenOn ->
params?.extended to screenOn
}.filter { (params, screenOn) -> params != null && screenOn }
@@ -104,14 +148,34 @@ class NotificationModule(private val service: Service) : Module() {
}
}
+ private fun currentModeLabel(params: ExtendedNotificationParams): String {
+ return when (params.currentMode) {
+ "rule" -> params.ruleText
+ "global" -> params.globalText
+ "direct" -> params.directText
+ else -> params.currentMode
+ }
+ }
+
private fun update(params: ExtendedNotificationParams) {
+ val contentText = "${currentModeLabel(params)} ยท ${params.contentText}"
service.startForeground(
with(notificationBuilder) {
setContentTitle(params.title)
- setContentText(params.contentText)
+ setContentText(contentText)
+ setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
clearActions()
addAction(
- 0, params.stopText, QuickAction.STOP.quickIntent.toPendingIntent
+ 0, params.ruleText,
+ modeBroadcastPendingIntent(QuickAction.MODE_RULE)
+ )
+ addAction(
+ 0, params.globalText,
+ modeBroadcastPendingIntent(QuickAction.MODE_GLOBAL)
+ )
+ addAction(
+ 0, params.directText,
+ modeBroadcastPendingIntent(QuickAction.MODE_DIRECT)
).build()
})
}
diff --git a/core/common.go b/core/common.go
index 410c45b78..f186dd1e9 100644
--- a/core/common.go
+++ b/core/common.go
@@ -205,8 +205,13 @@ func updateConfig(params *UpdateParams) {
adapter.UnifiedDelay.Store(general.UnifiedDelay)
}
if params.Mode != nil {
+ oldMode := general.Mode
general.Mode = *params.Mode
tunnel.SetMode(general.Mode)
+ if oldMode != general.Mode {
+ closeConnections()
+ resolver.ResetConnection()
+ }
}
if params.LogLevel != nil {
general.LogLevel = *params.LogLevel
diff --git a/lib/manager/tile_manager.dart b/lib/manager/tile_manager.dart
index 18e1d621c..7b7bcf905 100644
--- a/lib/manager/tile_manager.dart
+++ b/lib/manager/tile_manager.dart
@@ -1,6 +1,7 @@
import 'package:fl_clash/common/app_localizations.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/core/controller.dart';
+import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/plugins/tile.dart';
import 'package:fl_clash/providers/providers.dart';
@@ -44,6 +45,16 @@ class _TileContainerState extends ConsumerState with TileListener {
super.onStop();
}
+ @override
+ void onChangeMode(String mode) {
+ final modeEnum = Mode.values.firstWhere(
+ (m) => m.name == mode,
+ orElse: () => Mode.rule,
+ );
+ appController.changeMode(modeEnum);
+ super.onChangeMode(mode);
+ }
+
@override
void initState() {
super.initState();
diff --git a/lib/models/generated/state.freezed.dart b/lib/models/generated/state.freezed.dart
index 4404e7382..003a71edc 100644
--- a/lib/models/generated/state.freezed.dart
+++ b/lib/models/generated/state.freezed.dart
@@ -8456,7 +8456,7 @@ $VpnPropsCopyWith<$Res> get vpnProps {
/// @nodoc
mixin _$SharedState {
- SetupParams? get setupParams; VpnOptions? get vpnOptions; String get stopTip; String get startTip; String get currentProfileName; String get stopText; bool get onlyStatisticsProxy; bool get crashlytics;
+ SetupParams? get setupParams; VpnOptions? get vpnOptions; String get stopTip; String get startTip; String get currentProfileName; String get stopText; bool get onlyStatisticsProxy; bool get crashlytics; String get mode; String get ruleText; String get globalText; String get directText;
/// Create a copy of SharedState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -8469,16 +8469,16 @@ $SharedStateCopyWith get copyWith => _$SharedStateCopyWithImpl Object.hash(runtimeType,setupParams,vpnOptions,stopTip,startTip,currentProfileName,stopText,onlyStatisticsProxy,crashlytics);
+int get hashCode => Object.hash(runtimeType,setupParams,vpnOptions,stopTip,startTip,currentProfileName,stopText,onlyStatisticsProxy,crashlytics,mode,ruleText,globalText,directText);
@override
String toString() {
- return 'SharedState(setupParams: $setupParams, vpnOptions: $vpnOptions, stopTip: $stopTip, startTip: $startTip, currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy, crashlytics: $crashlytics)';
+ return 'SharedState(setupParams: $setupParams, vpnOptions: $vpnOptions, stopTip: $stopTip, startTip: $startTip, currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy, crashlytics: $crashlytics, mode: $mode, ruleText: $ruleText, globalText: $globalText, directText: $directText)';
}
@@ -8489,7 +8489,7 @@ abstract mixin class $SharedStateCopyWith<$Res> {
factory $SharedStateCopyWith(SharedState value, $Res Function(SharedState) _then) = _$SharedStateCopyWithImpl;
@useResult
$Res call({
- SetupParams? setupParams, VpnOptions? vpnOptions, String stopTip, String startTip, String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics
+ SetupParams? setupParams, VpnOptions? vpnOptions, String stopTip, String startTip, String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics, String mode, String ruleText, String globalText, String directText
});
@@ -8506,7 +8506,7 @@ class _$SharedStateCopyWithImpl<$Res>
/// Create a copy of SharedState
/// with the given fields replaced by the non-null parameter values.
-@pragma('vm:prefer-inline') @override $Res call({Object? setupParams = freezed,Object? vpnOptions = freezed,Object? stopTip = null,Object? startTip = null,Object? currentProfileName = null,Object? stopText = null,Object? onlyStatisticsProxy = null,Object? crashlytics = null,}) {
+@pragma('vm:prefer-inline') @override $Res call({Object? setupParams = freezed,Object? vpnOptions = freezed,Object? stopTip = null,Object? startTip = null,Object? currentProfileName = null,Object? stopText = null,Object? onlyStatisticsProxy = null,Object? crashlytics = null,Object? mode = null,Object? ruleText = null,Object? globalText = null,Object? directText = null,}) {
return _then(_self.copyWith(
setupParams: freezed == setupParams ? _self.setupParams : setupParams // ignore: cast_nullable_to_non_nullable
as SetupParams?,vpnOptions: freezed == vpnOptions ? _self.vpnOptions : vpnOptions // ignore: cast_nullable_to_non_nullable
@@ -8516,7 +8516,11 @@ as String,currentProfileName: null == currentProfileName ? _self.currentProfileN
as String,stopText: null == stopText ? _self.stopText : stopText // ignore: cast_nullable_to_non_nullable
as String,onlyStatisticsProxy: null == onlyStatisticsProxy ? _self.onlyStatisticsProxy : onlyStatisticsProxy // ignore: cast_nullable_to_non_nullable
as bool,crashlytics: null == crashlytics ? _self.crashlytics : crashlytics // ignore: cast_nullable_to_non_nullable
-as bool,
+as bool,mode: null == mode ? _self.mode : mode // ignore: cast_nullable_to_non_nullable
+as String,ruleText: null == ruleText ? _self.ruleText : ruleText // ignore: cast_nullable_to_non_nullable
+as String,globalText: null == globalText ? _self.globalText : globalText // ignore: cast_nullable_to_non_nullable
+as String,directText: null == directText ? _self.directText : directText // ignore: cast_nullable_to_non_nullable
+as String,
));
}
/// Create a copy of SharedState
@@ -8625,10 +8629,10 @@ return $default(_that);case _:
/// }
/// ```
-@optionalTypeArgs TResult maybeWhen(TResult Function( SetupParams? setupParams, VpnOptions? vpnOptions, String stopTip, String startTip, String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics)? $default,{required TResult orElse(),}) {final _that = this;
+@optionalTypeArgs TResult maybeWhen(TResult Function( SetupParams? setupParams, VpnOptions? vpnOptions, String stopTip, String startTip, String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics, String mode, String ruleText, String globalText, String directText)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SharedState() when $default != null:
-return $default(_that.setupParams,_that.vpnOptions,_that.stopTip,_that.startTip,_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics);case _:
+return $default(_that.setupParams,_that.vpnOptions,_that.stopTip,_that.startTip,_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics,_that.mode,_that.ruleText,_that.globalText,_that.directText);case _:
return orElse();
}
@@ -8646,10 +8650,10 @@ return $default(_that.setupParams,_that.vpnOptions,_that.stopTip,_that.startTip,
/// }
/// ```
-@optionalTypeArgs TResult when(TResult Function( SetupParams? setupParams, VpnOptions? vpnOptions, String stopTip, String startTip, String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics) $default,) {final _that = this;
+@optionalTypeArgs TResult when(TResult Function( SetupParams? setupParams, VpnOptions? vpnOptions, String stopTip, String startTip, String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics, String mode, String ruleText, String globalText, String directText) $default,) {final _that = this;
switch (_that) {
case _SharedState():
-return $default(_that.setupParams,_that.vpnOptions,_that.stopTip,_that.startTip,_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics);case _:
+return $default(_that.setupParams,_that.vpnOptions,_that.stopTip,_that.startTip,_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics,_that.mode,_that.ruleText,_that.globalText,_that.directText);case _:
throw StateError('Unexpected subclass');
}
@@ -8666,10 +8670,10 @@ return $default(_that.setupParams,_that.vpnOptions,_that.stopTip,_that.startTip,
/// }
/// ```
-@optionalTypeArgs TResult? whenOrNull(TResult? Function( SetupParams? setupParams, VpnOptions? vpnOptions, String stopTip, String startTip, String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics)? $default,) {final _that = this;
+@optionalTypeArgs TResult? whenOrNull(TResult? Function( SetupParams? setupParams, VpnOptions? vpnOptions, String stopTip, String startTip, String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics, String mode, String ruleText, String globalText, String directText)? $default,) {final _that = this;
switch (_that) {
case _SharedState() when $default != null:
-return $default(_that.setupParams,_that.vpnOptions,_that.stopTip,_that.startTip,_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics);case _:
+return $default(_that.setupParams,_that.vpnOptions,_that.stopTip,_that.startTip,_that.currentProfileName,_that.stopText,_that.onlyStatisticsProxy,_that.crashlytics,_that.mode,_that.ruleText,_that.globalText,_that.directText);case _:
return null;
}
@@ -8681,7 +8685,7 @@ return $default(_that.setupParams,_that.vpnOptions,_that.stopTip,_that.startTip,
@JsonSerializable()
class _SharedState implements SharedState {
- const _SharedState({this.setupParams, this.vpnOptions, required this.stopTip, required this.startTip, required this.currentProfileName, required this.stopText, required this.onlyStatisticsProxy, required this.crashlytics});
+ const _SharedState({this.setupParams, this.vpnOptions, required this.stopTip, required this.startTip, required this.currentProfileName, required this.stopText, required this.onlyStatisticsProxy, required this.crashlytics, this.mode = 'rule', this.ruleText = 'Rule', this.globalText = 'Global', this.directText = 'Direct'});
factory _SharedState.fromJson(Map json) => _$SharedStateFromJson(json);
@override final SetupParams? setupParams;
@@ -8692,6 +8696,10 @@ class _SharedState implements SharedState {
@override final String stopText;
@override final bool onlyStatisticsProxy;
@override final bool crashlytics;
+@override@JsonKey() final String mode;
+@override@JsonKey() final String ruleText;
+@override@JsonKey() final String globalText;
+@override@JsonKey() final String directText;
/// Create a copy of SharedState
/// with the given fields replaced by the non-null parameter values.
@@ -8706,16 +8714,16 @@ Map toJson() {
@override
bool operator ==(Object other) {
- return identical(this, other) || (other.runtimeType == runtimeType&&other is _SharedState&&(identical(other.setupParams, setupParams) || other.setupParams == setupParams)&&(identical(other.vpnOptions, vpnOptions) || other.vpnOptions == vpnOptions)&&(identical(other.stopTip, stopTip) || other.stopTip == stopTip)&&(identical(other.startTip, startTip) || other.startTip == startTip)&&(identical(other.currentProfileName, currentProfileName) || other.currentProfileName == currentProfileName)&&(identical(other.stopText, stopText) || other.stopText == stopText)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics));
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is _SharedState&&(identical(other.setupParams, setupParams) || other.setupParams == setupParams)&&(identical(other.vpnOptions, vpnOptions) || other.vpnOptions == vpnOptions)&&(identical(other.stopTip, stopTip) || other.stopTip == stopTip)&&(identical(other.startTip, startTip) || other.startTip == startTip)&&(identical(other.currentProfileName, currentProfileName) || other.currentProfileName == currentProfileName)&&(identical(other.stopText, stopText) || other.stopText == stopText)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.ruleText, ruleText) || other.ruleText == ruleText)&&(identical(other.globalText, globalText) || other.globalText == globalText)&&(identical(other.directText, directText) || other.directText == directText));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
-int get hashCode => Object.hash(runtimeType,setupParams,vpnOptions,stopTip,startTip,currentProfileName,stopText,onlyStatisticsProxy,crashlytics);
+int get hashCode => Object.hash(runtimeType,setupParams,vpnOptions,stopTip,startTip,currentProfileName,stopText,onlyStatisticsProxy,crashlytics,mode,ruleText,globalText,directText);
@override
String toString() {
- return 'SharedState(setupParams: $setupParams, vpnOptions: $vpnOptions, stopTip: $stopTip, startTip: $startTip, currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy, crashlytics: $crashlytics)';
+ return 'SharedState(setupParams: $setupParams, vpnOptions: $vpnOptions, stopTip: $stopTip, startTip: $startTip, currentProfileName: $currentProfileName, stopText: $stopText, onlyStatisticsProxy: $onlyStatisticsProxy, crashlytics: $crashlytics, mode: $mode, ruleText: $ruleText, globalText: $globalText, directText: $directText)';
}
@@ -8726,7 +8734,7 @@ abstract mixin class _$SharedStateCopyWith<$Res> implements $SharedStateCopyWith
factory _$SharedStateCopyWith(_SharedState value, $Res Function(_SharedState) _then) = __$SharedStateCopyWithImpl;
@override @useResult
$Res call({
- SetupParams? setupParams, VpnOptions? vpnOptions, String stopTip, String startTip, String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics
+ SetupParams? setupParams, VpnOptions? vpnOptions, String stopTip, String startTip, String currentProfileName, String stopText, bool onlyStatisticsProxy, bool crashlytics, String mode, String ruleText, String globalText, String directText
});
@@ -8743,7 +8751,7 @@ class __$SharedStateCopyWithImpl<$Res>
/// Create a copy of SharedState
/// with the given fields replaced by the non-null parameter values.
-@override @pragma('vm:prefer-inline') $Res call({Object? setupParams = freezed,Object? vpnOptions = freezed,Object? stopTip = null,Object? startTip = null,Object? currentProfileName = null,Object? stopText = null,Object? onlyStatisticsProxy = null,Object? crashlytics = null,}) {
+@override @pragma('vm:prefer-inline') $Res call({Object? setupParams = freezed,Object? vpnOptions = freezed,Object? stopTip = null,Object? startTip = null,Object? currentProfileName = null,Object? stopText = null,Object? onlyStatisticsProxy = null,Object? crashlytics = null,Object? mode = null,Object? ruleText = null,Object? globalText = null,Object? directText = null,}) {
return _then(_SharedState(
setupParams: freezed == setupParams ? _self.setupParams : setupParams // ignore: cast_nullable_to_non_nullable
as SetupParams?,vpnOptions: freezed == vpnOptions ? _self.vpnOptions : vpnOptions // ignore: cast_nullable_to_non_nullable
@@ -8753,7 +8761,11 @@ as String,currentProfileName: null == currentProfileName ? _self.currentProfileN
as String,stopText: null == stopText ? _self.stopText : stopText // ignore: cast_nullable_to_non_nullable
as String,onlyStatisticsProxy: null == onlyStatisticsProxy ? _self.onlyStatisticsProxy : onlyStatisticsProxy // ignore: cast_nullable_to_non_nullable
as bool,crashlytics: null == crashlytics ? _self.crashlytics : crashlytics // ignore: cast_nullable_to_non_nullable
-as bool,
+as bool,mode: null == mode ? _self.mode : mode // ignore: cast_nullable_to_non_nullable
+as String,ruleText: null == ruleText ? _self.ruleText : ruleText // ignore: cast_nullable_to_non_nullable
+as String,globalText: null == globalText ? _self.globalText : globalText // ignore: cast_nullable_to_non_nullable
+as String,directText: null == directText ? _self.directText : directText // ignore: cast_nullable_to_non_nullable
+as String,
));
}
diff --git a/lib/models/generated/state.g.dart b/lib/models/generated/state.g.dart
index 17ca8f0b7..6f2865a03 100644
--- a/lib/models/generated/state.g.dart
+++ b/lib/models/generated/state.g.dart
@@ -19,6 +19,10 @@ _SharedState _$SharedStateFromJson(Map json) => _SharedState(
stopText: json['stopText'] as String,
onlyStatisticsProxy: json['onlyStatisticsProxy'] as bool,
crashlytics: json['crashlytics'] as bool,
+ mode: json['mode'] as String? ?? 'rule',
+ ruleText: json['ruleText'] as String? ?? 'Rule',
+ globalText: json['globalText'] as String? ?? 'Global',
+ directText: json['directText'] as String? ?? 'Direct',
);
Map _$SharedStateToJson(_SharedState instance) =>
@@ -31,4 +35,8 @@ Map _$SharedStateToJson(_SharedState instance) =>
'stopText': instance.stopText,
'onlyStatisticsProxy': instance.onlyStatisticsProxy,
'crashlytics': instance.crashlytics,
+ 'mode': instance.mode,
+ 'ruleText': instance.ruleText,
+ 'globalText': instance.globalText,
+ 'directText': instance.directText,
};
diff --git a/lib/models/state.dart b/lib/models/state.dart
index 7d52cbc88..59765e6d4 100644
--- a/lib/models/state.dart
+++ b/lib/models/state.dart
@@ -316,6 +316,10 @@ abstract class SharedState with _$SharedState {
required String stopText,
required bool onlyStatisticsProxy,
required bool crashlytics,
+ @Default('rule') String mode,
+ @Default('Rule') String ruleText,
+ @Default('Global') String globalText,
+ @Default('Direct') String directText,
}) = _SharedState;
factory SharedState.fromJson(Map json) =>
diff --git a/lib/plugins/tile.dart b/lib/plugins/tile.dart
index a2c3a1ac0..6317b0d32 100644
--- a/lib/plugins/tile.dart
+++ b/lib/plugins/tile.dart
@@ -11,6 +11,8 @@ abstract mixin class TileListener {
void onStop() {}
void onDetached() {}
+
+ void onChangeMode(String mode) {}
}
class Tile {
@@ -36,6 +38,10 @@ class Tile {
case 'detached':
listener.onDetached();
break;
+ case 'changeMode':
+ final mode = call.arguments as String;
+ listener.onChangeMode(mode);
+ break;
}
}
}
diff --git a/lib/providers/generated/state.g.dart b/lib/providers/generated/state.g.dart
index 204c927fd..b7f8f542a 100644
--- a/lib/providers/generated/state.g.dart
+++ b/lib/providers/generated/state.g.dart
@@ -2177,7 +2177,7 @@ final class SharedStateProvider
}
}
-String _$sharedStateHash() => r'864fdf3f750fb6d1beadcb5f9226a614a9cb2caa';
+String _$sharedStateHash() => r'10b965c3af3156ea1f1c497373af43102d9c9972';
@ProviderFor(overlayTopOffset)
const overlayTopOffsetProvider = OverlayTopOffsetProvider._();
diff --git a/lib/providers/state.dart b/lib/providers/state.dart
index 1db85a149..9bb5063ab 100644
--- a/lib/providers/state.dart
+++ b/lib/providers/state.dart
@@ -603,9 +603,9 @@ SharedState sharedState(Ref ref) {
final bypassDomain = ref.watch(
networkSettingProvider.select((state) => state.bypassDomain),
);
- final clashConfigVM2 = ref.watch(
+ final clashConfigVM3 = ref.watch(
patchClashConfigProvider.select(
- (state) => VM2(state.tun.stack.name, state.mixedPort),
+ (state) => VM3(state.tun.stack.name, state.mixedPort, state.mode),
),
);
final vpnSetting = ref.watch(vpnSettingProvider);
@@ -614,8 +614,9 @@ SharedState sharedState(Ref ref) {
final onlyStatisticsProxy = appSettingVM3.a;
final crashlytics = appSettingVM3.b;
final testUrl = appSettingVM3.c;
- final stack = clashConfigVM2.a;
- final port = clashConfigVM2.b;
+ final stack = clashConfigVM3.a;
+ final port = clashConfigVM3.b;
+ final mode = clashConfigVM3.c;
return SharedState(
currentProfileName: currentProfileName,
onlyStatisticsProxy: onlyStatisticsProxy,
@@ -623,6 +624,10 @@ SharedState sharedState(Ref ref) {
crashlytics: crashlytics,
stopTip: appLocalizations.stopVpn,
startTip: appLocalizations.startVpn,
+ mode: mode.name,
+ ruleText: appLocalizations.rule,
+ globalText: appLocalizations.global,
+ directText: appLocalizations.direct,
setupParams: SetupParams(selectedMap: selectedMap, testUrl: testUrl),
vpnOptions: VpnOptions(
enable: vpnSetting.enable,