Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
<intent-filter>
<action android:name="${applicationId}.intent.action.SERVICE_CREATED" />
<action android:name="${applicationId}.intent.action.SERVICE_DESTROYED" />
<action android:name="${applicationId}.intent.action.MODE_CHANGED" />
</intent-filter>
</receiver>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
}
11 changes: 11 additions & 0 deletions android/app/src/main/kotlin/com/follow/clash/Ext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 11 additions & 2 deletions android/app/src/main/kotlin/com/follow/clash/State.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions android/app/src/main/kotlin/com/follow/clash/models/State.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class TilePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
channel.invokeMethodOnMainThread<Any>("stop", null)
}

fun handleChangeMode(mode: String) {
channel.invokeMethodOnMainThread<Any>("changeMode", mode)
}


override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {}
}
4 changes: 4 additions & 0 deletions android/common/src/main/java/com/follow/clash/common/Enums.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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 }
Expand Down Expand Up @@ -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()
})
}
Expand Down
5 changes: 5 additions & 0 deletions core/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions lib/manager/tile_manager.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -44,6 +45,16 @@ class _TileContainerState extends ConsumerState<TileManager> 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();
Expand Down
Loading