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,