diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index e7fda7f..f4d078f 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -19,7 +19,7 @@ jobs:
os: windows-latest
arch: amd64
- platform: linux
- os: ubuntu-latest
+ os: ubuntu-22.04
arch: amd64
- platform: macos
os: macos-13
@@ -201,6 +201,7 @@ jobs:
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TAG: ${{ github.ref_name }}
+ RUN_ID: ${{ github.run_id }}
run: |
python -m pip install --upgrade pip
pip install requests
@@ -211,6 +212,14 @@ jobs:
version=$(echo "${{ github.ref_name }}" | sed 's/^v//')
sed "s|VERSION|$version|g" ./.github/release_template.md >> release.md
+ - name: Generate sha256
+ if: env.IS_STABLE == 'true'
+ run: |
+ cd ./dist
+ for file in $(find . -type f -not -name "*.sha256"); do
+ sha256sum "$file" > "${file}.sha256"
+ done
+
- name: Release
if: ${{ env.IS_STABLE == 'true' }}
uses: softprops/action-gh-release@v2
diff --git a/README.md b/README.md
index b45f03c..4255f2c 100644
--- a/README.md
+++ b/README.md
@@ -41,8 +41,8 @@ on Mobile:
⚠️ Make sure to install the following dependencies before using them
```bash
- sudo apt-get install appindicator3-0.1 libappindicator3-dev
- sudo apt-get install keybinder-3.0
+ sudo apt-get install libayatana-appindicator3-dev
+ sudo apt-get install libkeybinder-3.0-dev
```
### Android
diff --git a/README_zh_CN.md b/README_zh_CN.md
index 59ccb34..54d60af 100644
--- a/README_zh_CN.md
+++ b/README_zh_CN.md
@@ -41,8 +41,8 @@ on Mobile:
⚠️ 使用前请确保安装以下依赖
```bash
- sudo apt-get install appindicator3-0.1 libappindicator3-dev
- sudo apt-get install keybinder-3.0
+ sudo apt-get install libayatana-appindicator3-dev
+ sudo apt-get install libkeybinder-3.0-dev
```
### Android
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 606a14a..ece0d77 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -44,6 +44,7 @@
+
diff --git a/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt b/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt
index d2b8d4c..052b958 100644
--- a/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt
@@ -20,6 +20,10 @@ enum class RunState {
object GlobalState {
val runLock = ReentrantLock()
+ const val NOTIFICATION_CHANNEL = "FlClash"
+
+ const val NOTIFICATION_ID = 1
+
val runState: MutableLiveData = MutableLiveData(RunState.STOP)
var flutterEngine: FlutterEngine? = null
private var serviceEngine: FlutterEngine? = null
diff --git a/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt b/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt
index 6e186da..c404697 100644
--- a/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt
@@ -1,6 +1,5 @@
package com.follow.clash
-import com.follow.clash.core.Core
import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ServicePlugin
import com.follow.clash.plugins.TilePlugin
diff --git a/android/app/src/main/kotlin/com/follow/clash/extensions/Ext.kt b/android/app/src/main/kotlin/com/follow/clash/extensions/Ext.kt
index 2f1eb91..0aa09de 100644
--- a/android/app/src/main/kotlin/com/follow/clash/extensions/Ext.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/extensions/Ext.kt
@@ -27,7 +27,6 @@ import java.util.concurrent.locks.ReentrantLock
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
-
suspend fun Drawable.getBase64(): String {
val drawable = this
return withContext(Dispatchers.IO) {
diff --git a/android/app/src/main/kotlin/com/follow/clash/models/Package.kt b/android/app/src/main/kotlin/com/follow/clash/models/Package.kt
index 967b5f3..0a6d6c7 100644
--- a/android/app/src/main/kotlin/com/follow/clash/models/Package.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/models/Package.kt
@@ -3,6 +3,7 @@ package com.follow.clash.models
data class Package(
val packageName: String,
val label: String,
- val isSystem: Boolean,
+ val system: Boolean,
+ val internet: Boolean,
val lastUpdateTime: Long,
)
diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt
index 12bdb39..757c193 100644
--- a/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt
@@ -293,19 +293,17 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
if (packages.isNotEmpty()) return packages
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA or PackageManager.GET_PERMISSIONS)
?.filter {
- it.packageName != FlClashApplication.getAppContext().packageName && (
- it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
- || it.packageName == "android"
- )
+ it.packageName != FlClashApplication.getAppContext().packageName || it.packageName == "android"
}?.map {
- Package(
- packageName = it.packageName,
- label = it.applicationInfo?.loadLabel(packageManager).toString(),
- isSystem = (it.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM)) == 1,
- lastUpdateTime = it.lastUpdateTime
- )
- }?.let { packages.addAll(it) }
+ Package(
+ packageName = it.packageName,
+ label = it.applicationInfo?.loadLabel(packageManager).toString(),
+ system = (it.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM)) == 1,
+ lastUpdateTime = it.lastUpdateTime,
+ internet = it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
+ )
+ }?.let { packages.addAll(it) }
return packages
}
diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt
index e316110..5ae81c1 100644
--- a/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt
@@ -168,8 +168,10 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
try {
if (GlobalState.runState.value != RunState.START) return
val data = flutterMethodChannel.awaitResult("getStartForegroundParams")
- val startForegroundParams = Gson().fromJson(
+ val startForegroundParams = if (data != null) Gson().fromJson(
data, StartForegroundParams::class.java
+ ) else StartForegroundParams(
+ title = "", content = ""
)
if (lastStartForegroundParams != startForegroundParams) {
lastStartForegroundParams = startForegroundParams
diff --git a/android/app/src/main/kotlin/com/follow/clash/services/BaseServiceInterface.kt b/android/app/src/main/kotlin/com/follow/clash/services/BaseServiceInterface.kt
index 44ca809..5a8bd72 100644
--- a/android/app/src/main/kotlin/com/follow/clash/services/BaseServiceInterface.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/services/BaseServiceInterface.kt
@@ -1,6 +1,26 @@
package com.follow.clash.services
+import android.annotation.SuppressLint
+import android.app.Notification
+import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Intent
+import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
+import android.os.Build
+import androidx.core.app.NotificationCompat
+import com.follow.clash.GlobalState
+import com.follow.clash.MainActivity
+import com.follow.clash.R
+import com.follow.clash.extensions.getActionPendingIntent
import com.follow.clash.models.VpnOptions
+import io.flutter.Log
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
interface BaseServiceInterface {
@@ -9,4 +29,70 @@ interface BaseServiceInterface {
fun stop()
suspend fun startForeground(title: String, content: String)
+}
+
+fun Service.createFlClashNotificationBuilder(): Deferred =
+ CoroutineScope(Dispatchers.Main).async {
+ val stopText = GlobalState.getText("stop")
+ val intent = Intent(this@createFlClashNotificationBuilder, MainActivity::class.java)
+
+ val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
+ PendingIntent.getActivity(
+ this@createFlClashNotificationBuilder,
+ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+ )
+ } else {
+ PendingIntent.getActivity(
+ this@createFlClashNotificationBuilder, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+ )
+ }
+
+ with(
+ NotificationCompat.Builder(
+ this@createFlClashNotificationBuilder, GlobalState.NOTIFICATION_CHANNEL
+ )
+ ) {
+ setSmallIcon(R.drawable.ic_stat_name)
+ setContentTitle("FlClash")
+ setContentIntent(pendingIntent)
+ setCategory(NotificationCompat.CATEGORY_SERVICE)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
+ }
+ setOngoing(true)
+ addAction(
+ 0, stopText, getActionPendingIntent("STOP")
+ )
+ setShowWhen(false)
+ setOnlyAlertOnce(true)
+ }
+ }
+
+@SuppressLint("ForegroundServiceType")
+fun Service.startForeground(notification: Notification) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val manager = getSystemService(NotificationManager::class.java)
+ var channel = manager?.getNotificationChannel(GlobalState.NOTIFICATION_CHANNEL)
+ if (channel == null) {
+ Log.d("[FlClash]","createNotificationChannel===>")
+ channel = NotificationChannel(
+ GlobalState.NOTIFICATION_CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW
+ )
+ manager?.createNotificationChannel(channel)
+ }
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ try {
+ startForeground(
+ GlobalState.NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC
+ )
+ } catch (_: Exception) {
+ startForeground(GlobalState.NOTIFICATION_ID, notification)
+ }
+ } else {
+ startForeground(GlobalState.NOTIFICATION_ID, notification)
+ }
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/follow/clash/services/FlClashService.kt b/android/app/src/main/kotlin/com/follow/clash/services/FlClashService.kt
index fa0ba54..2f09531 100644
--- a/android/app/src/main/kotlin/com/follow/clash/services/FlClashService.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/services/FlClashService.kt
@@ -1,29 +1,51 @@
package com.follow.clash.services
import android.annotation.SuppressLint
-import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
-import android.app.NotificationChannel
-import android.app.NotificationManager
-import android.app.PendingIntent
import android.app.Service
import android.content.Intent
-import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.os.Binder
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.follow.clash.GlobalState
-import com.follow.clash.MainActivity
-import com.follow.clash.extensions.getActionPendingIntent
import com.follow.clash.models.VpnOptions
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
class FlClashService : Service(), BaseServiceInterface {
+ override fun start(options: VpnOptions) = 0
+
+ override fun stop() {
+ stopSelf()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ stopForeground(STOP_FOREGROUND_REMOVE)
+ }
+ }
+
+ private var cachedBuilder: NotificationCompat.Builder? = null
+
+ private suspend fun notificationBuilder(): NotificationCompat.Builder {
+ if (cachedBuilder == null) {
+ cachedBuilder = createFlClashNotificationBuilder().await()
+ }
+ return cachedBuilder!!
+ }
+
+ @SuppressLint("ForegroundServiceType")
+ override suspend fun startForeground(title: String, content: String) {
+ startForeground(
+ notificationBuilder()
+ .setContentTitle(title)
+ .setContentText(content).build()
+ )
+ }
+
+ override fun onTrimMemory(level: Int) {
+ super.onTrimMemory(level)
+ GlobalState.getCurrentVPNPlugin()?.requestGc()
+ }
+
+
private val binder = LocalBinder()
inner class LocalBinder : Binder() {
@@ -38,93 +60,8 @@ class FlClashService : Service(), BaseServiceInterface {
return super.onUnbind(intent)
}
- private val CHANNEL = "FlClash"
-
- private val notificationId: Int = 1
-
- private val notificationBuilderDeferred: Deferred by lazy {
- CoroutineScope(Dispatchers.Main).async {
- val stopText = GlobalState.getText("stop")
-
- val intent = Intent(
- this@FlClashService, MainActivity::class.java
- )
-
- val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
- PendingIntent.getActivity(
- this@FlClashService,
- 0,
- intent,
- PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
- )
- } else {
- PendingIntent.getActivity(
- this@FlClashService,
- 0,
- intent,
- PendingIntent.FLAG_UPDATE_CURRENT
- )
- }
-
- with(NotificationCompat.Builder(this@FlClashService, CHANNEL)) {
- setSmallIcon(com.follow.clash.R.drawable.ic_stat_name)
- setContentTitle("FlClash")
- setContentIntent(pendingIntent)
- setCategory(NotificationCompat.CATEGORY_SERVICE)
- priority = NotificationCompat.PRIORITY_MIN
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
- }
- addAction(
- 0,
- stopText, // 使用 suspend 函数获取的文本
- getActionPendingIntent("STOP")
- )
- setOngoing(true)
- setShowWhen(false)
- setOnlyAlertOnce(true)
- setAutoCancel(true)
- }
- }
- }
-
- private suspend fun getNotificationBuilder(): NotificationCompat.Builder {
- return notificationBuilderDeferred.await()
- }
-
- override fun start(options: VpnOptions) = 0
-
- override fun stop() {
- stopSelf()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- stopForeground(STOP_FOREGROUND_REMOVE)
- }
- }
-
-
- @SuppressLint("ForegroundServiceType")
- override suspend fun startForeground(title: String, content: String) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val manager = getSystemService(NotificationManager::class.java)
- var channel = manager?.getNotificationChannel(CHANNEL)
- if (channel == null) {
- channel =
- NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
- manager?.createNotificationChannel(channel)
- }
- }
- val notification =
- getNotificationBuilder()
- .setContentTitle(title)
- .setContentText(content).build()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- try {
- startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC)
- } catch (_: Exception) {
- startForeground(notificationId, notification)
- }
- } else {
- startForeground(notificationId, notification)
- }
+ override fun onDestroy() {
+ stop()
+ super.onDestroy()
}
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt b/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt
index 37d39a0..cb6ab40 100644
--- a/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt
+++ b/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt
@@ -1,12 +1,7 @@
package com.follow.clash.services
import android.annotation.SuppressLint
-import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
-import android.app.NotificationChannel
-import android.app.NotificationManager
-import android.app.PendingIntent
import android.content.Intent
-import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.net.ProxyInfo
import android.net.VpnService
import android.os.Binder
@@ -17,18 +12,13 @@ import android.os.RemoteException
import android.util.Log
import androidx.core.app.NotificationCompat
import com.follow.clash.GlobalState
-import com.follow.clash.MainActivity
-import com.follow.clash.R
-import com.follow.clash.extensions.getActionPendingIntent
import com.follow.clash.extensions.getIpv4RouteAddress
import com.follow.clash.extensions.getIpv6RouteAddress
import com.follow.clash.extensions.toCIDR
import com.follow.clash.models.AccessControlMode
import com.follow.clash.models.VpnOptions
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
import kotlinx.coroutines.launch
@@ -43,6 +33,10 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
if (options.ipv4Address.isNotEmpty()) {
val cidr = options.ipv4Address.toCIDR()
addAddress(cidr.address, cidr.prefixLength)
+ Log.d(
+ "addAddress",
+ "address: ${cidr.address} prefixLength:${cidr.prefixLength}"
+ )
val routeAddress = options.getIpv4RouteAddress()
if (routeAddress.isNotEmpty()) {
try {
@@ -59,26 +53,39 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
} else {
addRoute("0.0.0.0", 0)
}
+ } else {
+ addRoute("0.0.0.0", 0)
}
- if (options.ipv6Address.isNotEmpty()) {
- val cidr = options.ipv6Address.toCIDR()
- addAddress(cidr.address, cidr.prefixLength)
- val routeAddress = options.getIpv6RouteAddress()
- if (routeAddress.isNotEmpty()) {
- try {
- routeAddress.forEach { i ->
- Log.d(
- "addRoute6",
- "address: ${i.address} prefixLength:${i.prefixLength}"
- )
- addRoute(i.address, i.prefixLength)
+ try {
+ if (options.ipv6Address.isNotEmpty()) {
+ val cidr = options.ipv6Address.toCIDR()
+ Log.d(
+ "addAddress6",
+ "address: ${cidr.address} prefixLength:${cidr.prefixLength}"
+ )
+ addAddress(cidr.address, cidr.prefixLength)
+ val routeAddress = options.getIpv6RouteAddress()
+ if (routeAddress.isNotEmpty()) {
+ try {
+ routeAddress.forEach { i ->
+ Log.d(
+ "addRoute6",
+ "address: ${i.address} prefixLength:${i.prefixLength}"
+ )
+ addRoute(i.address, i.prefixLength)
+ }
+ } catch (_: Exception) {
+ addRoute("::", 0)
}
- } catch (_: Exception) {
+ } else {
addRoute("::", 0)
}
- } else {
- addRoute("::", 0)
}
+ }catch (_:Exception){
+ Log.d(
+ "addAddress6",
+ "IPv6 is not supported."
+ )
}
addDnsServer(options.dnsServerAddress)
setMtu(9000)
@@ -128,82 +135,22 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
}
}
- private val CHANNEL = "FlClash"
+ private var cachedBuilder: NotificationCompat.Builder? = null
- private val notificationId: Int = 1
-
- private val notificationBuilderDeferred: Deferred by lazy {
- CoroutineScope(Dispatchers.Main).async {
- val stopText = GlobalState.getText("stop")
- val intent = Intent(this@FlClashVpnService, MainActivity::class.java)
-
- val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
- PendingIntent.getActivity(
- this@FlClashVpnService,
- 0,
- intent,
- PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
- )
- } else {
- PendingIntent.getActivity(
- this@FlClashVpnService,
- 0,
- intent,
- PendingIntent.FLAG_UPDATE_CURRENT
- )
- }
-
- with(NotificationCompat.Builder(this@FlClashVpnService, CHANNEL)) {
- setSmallIcon(R.drawable.ic_stat_name)
- setContentTitle("FlClash")
- setContentIntent(pendingIntent)
- setCategory(NotificationCompat.CATEGORY_SERVICE)
- priority = NotificationCompat.PRIORITY_MIN
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
- }
- setOngoing(true)
- addAction(
- 0,
- stopText,
- getActionPendingIntent("STOP")
- )
- setShowWhen(false)
- setOnlyAlertOnce(true)
- setAutoCancel(true)
- }
+ private suspend fun notificationBuilder(): NotificationCompat.Builder {
+ if (cachedBuilder == null) {
+ cachedBuilder = createFlClashNotificationBuilder().await()
}
- }
-
- private suspend fun getNotificationBuilder(): NotificationCompat.Builder {
- return notificationBuilderDeferred.await()
+ return cachedBuilder!!
}
@SuppressLint("ForegroundServiceType")
override suspend fun startForeground(title: String, content: String) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val manager = getSystemService(NotificationManager::class.java)
- var channel = manager?.getNotificationChannel(CHANNEL)
- if (channel == null) {
- channel =
- NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
- manager?.createNotificationChannel(channel)
- }
- }
- val notification =
- getNotificationBuilder()
+ startForeground(
+ notificationBuilder()
.setContentTitle(title)
- .setContentText(content)
- .build()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- try {
- startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC)
- } catch (_: Exception) {
- startForeground(notificationId, notification)
- }
- } else {
- startForeground(notificationId, notification)
- }
+ .setContentText(content).build()
+ )
}
override fun onTrimMemory(level: Int) {
diff --git a/android/core/build.gradle.kts b/android/core/build.gradle.kts
index 63c5e41..6c1404e 100644
--- a/android/core/build.gradle.kts
+++ b/android/core/build.gradle.kts
@@ -1,5 +1,3 @@
-import com.android.build.gradle.tasks.MergeSourceSetFolders
-
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
@@ -37,13 +35,17 @@ android {
}
}
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
- }
kotlinOptions {
- jvmTarget = "11"
+ jvmTarget = "17"
}
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+}
+dependencies {
+ implementation("androidx.annotation:annotation-jvm:1.9.1")
}
val copyNativeLibs by tasks.register("copyNativeLibs") {
@@ -58,8 +60,4 @@ afterEvaluate {
tasks.named("preBuild") {
dependsOn(copyNativeLibs)
}
-}
-
-dependencies {
- implementation("androidx.core:core-ktx:1.16.0")
}
\ No newline at end of file
diff --git a/android/core/src/main/cpp/CMakeLists.txt b/android/core/src/main/cpp/CMakeLists.txt
index 0246d86..7c29f76 100644
--- a/android/core/src/main/cpp/CMakeLists.txt
+++ b/android/core/src/main/cpp/CMakeLists.txt
@@ -21,6 +21,8 @@ if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
-Wl,--strip-all
-Wl,--exclude-libs=ALL
)
+
+ add_compile_options(-fvisibility=hidden -fvisibility-inlines-hidden)
endif ()
set(LIB_CLASH_PATH "${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libclash.so")
diff --git a/lib/l10n/arb/intl_en.arb b/arb/intl_en.arb
similarity index 96%
rename from lib/l10n/arb/intl_en.arb
rename to arb/intl_en.arb
index 0d8f281..bbf21f1 100644
--- a/lib/l10n/arb/intl_en.arb
+++ b/arb/intl_en.arb
@@ -385,5 +385,20 @@
"expressiveScheme": "Expressive",
"contentScheme": "Content",
"rainbowScheme": "Rainbow",
- "fruitSaladScheme": "FruitSalad"
+ "fruitSaladScheme": "FruitSalad",
+ "developerMode": "Developer mode",
+ "developerModeEnableTip": "Developer mode is enabled.",
+ "messageTest": "Message test",
+ "messageTestTip": "This is a message.",
+ "crashTest": "Crash test",
+ "clearData": "Clear Data",
+ "textScale": "Text Scaling",
+ "internet": "Internet",
+ "systemApp": "System APP",
+ "noNetworkApp": "No network APP",
+ "contactMe": "Contact me",
+ "recoveryStrategy": "Recovery strategy",
+ "recoveryStrategy_override": "Override",
+ "recoveryStrategy_compatible": "Compatible",
+ "logsTest": "Logs test"
}
\ No newline at end of file
diff --git a/lib/l10n/arb/intl_ja.arb b/arb/intl_ja.arb
similarity index 95%
rename from lib/l10n/arb/intl_ja.arb
rename to arb/intl_ja.arb
index 7282952..c3093a9 100644
--- a/lib/l10n/arb/intl_ja.arb
+++ b/arb/intl_ja.arb
@@ -385,5 +385,21 @@
"expressiveScheme": "エクスプレッシブ",
"contentScheme": "コンテンツテーマ",
"rainbowScheme": "レインボー",
- "fruitSaladScheme": "フルーツサラダ"
+ "fruitSaladScheme": "フルーツサラダ",
+ "developerMode": "デベロッパーモード",
+ "developerModeEnableTip": "デベロッパーモードが有効になりました。",
+ "messageTest": "メッセージテスト",
+ "messageTestTip": "これはメッセージです。",
+ "crashTest": "クラッシュテスト",
+ "clearData": "データを消去",
+ "zoom": "ズーム",
+ "textScale": "テキストスケーリング",
+ "internet": "インターネット",
+ "systemApp": "システムアプリ",
+ "noNetworkApp": "ネットワークなしアプリ",
+ "contactMe": "連絡する",
+ "recoveryStrategy": "リカバリー戦略",
+ "recoveryStrategy_override": "オーバーライド",
+ "recoveryStrategy_compatible": "互換性",
+ "logsTest": "ログテスト"
}
\ No newline at end of file
diff --git a/lib/l10n/arb/intl_ru.arb b/arb/intl_ru.arb
similarity index 96%
rename from lib/l10n/arb/intl_ru.arb
rename to arb/intl_ru.arb
index 132ec01..31ad5a2 100644
--- a/lib/l10n/arb/intl_ru.arb
+++ b/arb/intl_ru.arb
@@ -385,5 +385,21 @@
"expressiveScheme": "Экспрессивные",
"contentScheme": "Контентная тема",
"rainbowScheme": "Радужные",
- "fruitSaladScheme": "Фруктовый микс"
+ "fruitSaladScheme": "Фруктовый микс",
+ "developerMode": "Режим разработчика",
+ "developerModeEnableTip": "Режим разработчика активирован.",
+ "messageTest": "Тестирование сообщения",
+ "messageTestTip": "Это сообщение.",
+ "crashTest": "Тест на сбои",
+ "clearData": "Очистить данные",
+ "zoom": "Масштаб",
+ "textScale": "Масштабирование текста",
+ "internet": "Интернет",
+ "systemApp": "Системное приложение",
+ "noNetworkApp": "Приложение без сети",
+ "contactMe": "Свяжитесь со мной",
+ "recoveryStrategy": "Стратегия восстановления",
+ "recoveryStrategy_override": "Переопределение",
+ "recoveryStrategy_compatible": "Совместимый",
+ "logsTest": "Тест журналов"
}
\ No newline at end of file
diff --git a/lib/l10n/arb/intl_zh_CN.arb b/arb/intl_zh_CN.arb
similarity index 95%
rename from lib/l10n/arb/intl_zh_CN.arb
rename to arb/intl_zh_CN.arb
index 00a7749..3b7e279 100644
--- a/lib/l10n/arb/intl_zh_CN.arb
+++ b/arb/intl_zh_CN.arb
@@ -385,5 +385,21 @@
"expressiveScheme": "表现力",
"contentScheme": "内容主题",
"rainbowScheme": "彩虹",
- "fruitSaladScheme": "果缤纷"
+ "fruitSaladScheme": "果缤纷",
+ "developerMode": "开发者模式",
+ "developerModeEnableTip": "开发者模式已启用。",
+ "messageTest": "消息测试",
+ "messageTestTip": "这是一条消息。",
+ "crashTest": "崩溃测试",
+ "clearData": "清除数据",
+ "zoom": "缩放",
+ "textScale": "文本缩放",
+ "internet": "互联网",
+ "systemApp": "系统应用",
+ "noNetworkApp": "无网络应用",
+ "contactMe": "联系我",
+ "recoveryStrategy": "恢复策略",
+ "recoveryStrategy_override": "覆盖",
+ "recoveryStrategy_compatible": "兼容",
+ "logsTest": "日志测试"
}
diff --git a/core/Clash.Meta b/core/Clash.Meta
index f19dad5..88a1848 160000
--- a/core/Clash.Meta
+++ b/core/Clash.Meta
@@ -1 +1 @@
-Subproject commit f19dad529f7d8ac652053f9d090e6780e199eab2
+Subproject commit 88a1848dfbdf2fda2ca50f90cda145887aaaaae4
diff --git a/core/action.go b/core/action.go
index d170124..def5cd1 100644
--- a/core/action.go
+++ b/core/action.go
@@ -166,6 +166,9 @@ func handleAction(action *Action, result func(data interface{})) {
data := action.Data.(string)
handleSetState(data)
result(true)
+ case crashMethod:
+ result(true)
+ handleCrash()
default:
handle := nextHandle(action, result)
if handle {
diff --git a/core/common.go b/core/common.go
index 2076bd2..506ebb5 100644
--- a/core/common.go
+++ b/core/common.go
@@ -78,7 +78,6 @@ func getRawConfigWithId(id string) *config.RawConfig {
path := getProfilePath(id)
bytes, err := readFile(path)
if err != nil {
- log.Errorln("profile is not exist")
return config.DefaultRawConfig()
}
prof, err := config.UnmarshalRawConfig(bytes)
diff --git a/core/constant.go b/core/constant.go
index 1da3e33..f3a9ab9 100644
--- a/core/constant.go
+++ b/core/constant.go
@@ -82,6 +82,7 @@ const (
getRunTimeMethod Method = "getRunTime"
getCurrentProfileNameMethod Method = "getCurrentProfileName"
getProfileMethod Method = "getProfile"
+ crashMethod Method = "crash"
)
type Method string
diff --git a/core/go.mod b/core/go.mod
index aeb042f..d7a5220 100644
--- a/core/go.mod
+++ b/core/go.mod
@@ -7,6 +7,7 @@ replace github.com/metacubex/mihomo => ./Clash.Meta
require (
github.com/metacubex/mihomo v0.0.0-00010101000000-000000000000
github.com/samber/lo v1.49.1
+ golang.org/x/sync v0.11.0
)
require (
@@ -52,20 +53,20 @@ require (
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect
github.com/metacubex/bart v0.19.0 // indirect
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 // indirect
- github.com/metacubex/chacha v0.1.1 // indirect
+ github.com/metacubex/chacha v0.1.2 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 // indirect
github.com/metacubex/randv2 v0.2.0 // indirect
- github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 // indirect
- github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 // indirect
+ github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c // indirect
github.com/metacubex/sing-shadowsocks v0.2.8 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.2 // indirect
- github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 // indirect
+ github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63 // indirect
+ github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5 // indirect
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 // indirect
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect
- github.com/metacubex/utls v1.6.8-alpha.4 // indirect
+ github.com/metacubex/utls v1.7.0-alpha.1 // indirect
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
@@ -84,7 +85,6 @@ require (
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/sing v0.5.2 // indirect
github.com/sagernet/sing-mux v0.2.1 // indirect
- github.com/sagernet/sing-shadowtls v0.1.5 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
@@ -107,7 +107,6 @@ require (
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.35.0 // indirect
- golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.7.0 // indirect
diff --git a/core/go.sum b/core/go.sum
index 4a7fccb..ebec92d 100644
--- a/core/go.sum
+++ b/core/go.sum
@@ -101,8 +101,8 @@ github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY
github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
-github.com/metacubex/chacha v0.1.1 h1:OHIv11Nd9CISAIzegpjfupIoZp9DYm6uQw41RxvmU/c=
-github.com/metacubex/chacha v0.1.1/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
+github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc=
+github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
@@ -111,24 +111,24 @@ github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 h1:B+AP/Pj2/j
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996/go.mod h1:ExVjGyEwTUjCFqx+5uxgV7MOoA3fZI+th4D40H35xmY=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
-github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 h1:aHsYiTvubfgMa3JMTDY//hDXVvFWrHg6ARckR52ttZs=
-github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629/go.mod h1:TTeIOZLdGmzc07Oedn++vWUUfkZoXLF4sEMxWuhBFr8=
-github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 h1:UkPoRAnoBQMn7IK5qpoIV3OejU15q+rqel3NrbSCFKA=
-github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
+github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c h1:OB3WmMA8YPJjE36RjD9X8xlrWGJ4orxbf2R/KAE28b0=
+github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
-github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 h1:B211C+i/I8CWf4I/BaAV0mmkEHrDBJ0XR9EWxjPbFEg=
-github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
+github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63 h1:vy/8ZYYtWUXYnOnw/NF8ThG1W/RqM/h5rkun+OXZMH0=
+github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63/go.mod h1:eDZ2JpkSkewGmUlCoLSn2MRFn1D0jKPIys/6aogFx7U=
+github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5 h1:hcsz5e5lqhBxn3iQQDIF60FLZ8PQT542GTQZ+1wcIGo=
+github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
-github.com/metacubex/utls v1.6.8-alpha.4 h1:5EvsCHxDNneaOtAyc8CztoNSpmonLvkvuGs01lIeeEI=
-github.com/metacubex/utls v1.6.8-alpha.4/go.mod h1:MEZ5WO/VLKYs/s/dOzEK/mlXOQxc04ESeLzRgjmLYtk=
+github.com/metacubex/utls v1.7.0-alpha.1 h1:oMFsPh2oTlALJ7vKXPJuqgy0YeiZ+q/LLw+ZdxZ80l4=
+github.com/metacubex/utls v1.7.0-alpha.1/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
@@ -174,8 +174,6 @@ github.com/sagernet/sing v0.5.2 h1:2OZQJNKGtji/66QLxbf/T/dqtK/3+fF/zuHH9tsGK7M=
github.com/sagernet/sing v0.5.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo=
github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE=
-github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo=
-github.com/sagernet/sing-shadowtls v0.1.5/go.mod h1:tvrDPTGLrSM46Wnf7mSr+L8NHvgvF8M4YnJF790rZX4=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
diff --git a/core/hub.go b/core/hub.go
index e92c374..06095e9 100644
--- a/core/hub.go
+++ b/core/hub.go
@@ -442,6 +442,10 @@ func handleSetState(params string) {
_ = json.Unmarshal([]byte(params), state.CurrentState)
}
+func handleCrash() {
+ panic("handle invoke crash")
+}
+
func init() {
adapter.UrlTestHook = func(url string, name string, delay uint16) {
delayData := &Delay{
diff --git a/core/lib_android.go b/core/lib_android.go
index 97587de..9ed8541 100644
--- a/core/lib_android.go
+++ b/core/lib_android.go
@@ -98,13 +98,13 @@ func handleStopTun() {
}
}
-func handleStartTun(fd int, callback unsafe.Pointer) bool {
+func handleStartTun(fd int, callback unsafe.Pointer) {
handleStopTun()
+ tunLock.Lock()
+ defer tunLock.Unlock()
now := time.Now()
runTime = &now
if fd != 0 {
- tunLock.Lock()
- defer tunLock.Unlock()
tunHandler = &TunHandler{
callback: callback,
limit: semaphore.NewWeighted(4),
@@ -113,13 +113,11 @@ func handleStartTun(fd int, callback unsafe.Pointer) bool {
tunListener, _ := t.Start(fd, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack)
if tunListener != nil {
log.Infoln("TUN address: %v", tunListener.Address())
+ tunHandler.listener = tunListener
} else {
removeTunHook()
- return false
}
- tunHandler.listener = tunListener
}
- return true
}
func handleGetRunTime() string {
@@ -228,7 +226,10 @@ func quickStart(initParamsChar *C.char, paramsChar *C.char, stateParamsChar *C.c
//export startTUN
func startTUN(fd C.int, callback unsafe.Pointer) bool {
- return handleStartTun(int(fd), callback)
+ go func() {
+ handleStartTun(int(fd), callback)
+ }()
+ return true
}
//export getRunTime
@@ -238,7 +239,9 @@ func getRunTime() *C.char {
//export stopTun
func stopTun() {
- handleStopTun()
+ go func() {
+ handleStopTun()
+ }()
}
//export getCurrentProfileName
diff --git a/core/state/state.go b/core/state/state.go
index 9bbb6ca..62beaf5 100644
--- a/core/state/state.go
+++ b/core/state/state.go
@@ -24,7 +24,6 @@ type AccessControl struct {
Mode string `json:"mode"`
AcceptList []string `json:"acceptList"`
RejectList []string `json:"rejectList"`
- IsFilterSystemApp bool `json:"isFilterSystemApp"`
}
type AndroidVpnRawOptions struct {
diff --git a/lib/application.dart b/lib/application.dart
index 1a3027d..0f62f2d 100644
--- a/lib/application.dart
+++ b/lib/application.dart
@@ -1,6 +1,5 @@
import 'dart:async';
-import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/l10n/l10n.dart';
@@ -14,7 +13,6 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'controller.dart';
-import 'models/models.dart';
import 'pages/pages.dart';
class Application extends ConsumerStatefulWidget {
@@ -27,7 +25,6 @@ class Application extends ConsumerStatefulWidget {
}
class ApplicationState extends ConsumerState {
- late ColorSchemes systemColorSchemes;
Timer? _autoUpdateGroupTaskTimer;
Timer? _autoUpdateProfilesTaskTimer;
@@ -132,19 +129,6 @@ class ApplicationState extends ConsumerState {
);
}
- _updateSystemColorSchemes(
- ColorScheme? lightDynamic,
- ColorScheme? darkDynamic,
- ) {
- systemColorSchemes = ColorSchemes(
- lightColorScheme: lightDynamic,
- darkColorScheme: darkDynamic,
- );
- WidgetsBinding.instance.addPostFrameCallback((_) {
- globalState.appController.updateSystemColorSchemes(systemColorSchemes);
- });
- }
-
@override
Widget build(context) {
return _buildPlatformState(
@@ -154,49 +138,44 @@ class ApplicationState extends ConsumerState {
final locale =
ref.watch(appSettingProvider.select((state) => state.locale));
final themeProps = ref.watch(themeSettingProvider);
- return DynamicColorBuilder(
- builder: (lightDynamic, darkDynamic) {
- _updateSystemColorSchemes(lightDynamic, darkDynamic);
- return MaterialApp(
- debugShowCheckedModeBanner: false,
- navigatorKey: globalState.navigatorKey,
- localizationsDelegates: const [
- AppLocalizations.delegate,
- GlobalMaterialLocalizations.delegate,
- GlobalCupertinoLocalizations.delegate,
- GlobalWidgetsLocalizations.delegate
- ],
- builder: (_, child) {
- return AppEnvManager(
- child: _buildPlatformApp(
- _buildApp(child!),
- ),
- );
- },
- scrollBehavior: BaseScrollBehavior(),
- title: appName,
- locale: utils.getLocaleForString(locale),
- supportedLocales: AppLocalizations.delegate.supportedLocales,
- themeMode: themeProps.themeMode,
- theme: ThemeData(
- useMaterial3: true,
- pageTransitionsTheme: _pageTransitionsTheme,
- colorScheme: _getAppColorScheme(
- brightness: Brightness.light,
- primaryColor: themeProps.primaryColor,
- ),
+ return MaterialApp(
+ debugShowCheckedModeBanner: false,
+ navigatorKey: globalState.navigatorKey,
+ localizationsDelegates: const [
+ AppLocalizations.delegate,
+ GlobalMaterialLocalizations.delegate,
+ GlobalCupertinoLocalizations.delegate,
+ GlobalWidgetsLocalizations.delegate
+ ],
+ builder: (_, child) {
+ return AppEnvManager(
+ child: _buildPlatformApp(
+ _buildApp(child!),
),
- darkTheme: ThemeData(
- useMaterial3: true,
- pageTransitionsTheme: _pageTransitionsTheme,
- colorScheme: _getAppColorScheme(
- brightness: Brightness.dark,
- primaryColor: themeProps.primaryColor,
- ).toPureBlack(themeProps.pureBlack),
- ),
- home: child,
);
},
+ scrollBehavior: BaseScrollBehavior(),
+ title: appName,
+ locale: utils.getLocaleForString(locale),
+ supportedLocales: AppLocalizations.delegate.supportedLocales,
+ themeMode: themeProps.themeMode,
+ theme: ThemeData(
+ useMaterial3: true,
+ pageTransitionsTheme: _pageTransitionsTheme,
+ colorScheme: _getAppColorScheme(
+ brightness: Brightness.light,
+ primaryColor: themeProps.primaryColor,
+ ),
+ ),
+ darkTheme: ThemeData(
+ useMaterial3: true,
+ pageTransitionsTheme: _pageTransitionsTheme,
+ colorScheme: _getAppColorScheme(
+ brightness: Brightness.dark,
+ primaryColor: themeProps.primaryColor,
+ ).toPureBlack(themeProps.pureBlack),
+ ),
+ home: child,
);
},
child: const HomePage(),
diff --git a/lib/clash/interface.dart b/lib/clash/interface.dart
index b0f11ea..c0ee9de 100644
--- a/lib/clash/interface.dart
+++ b/lib/clash/interface.dart
@@ -58,6 +58,8 @@ mixin ClashInterface {
stopLog();
+ Future crash();
+
FutureOr getConnections();
FutureOr closeConnection(String id);
@@ -104,6 +106,7 @@ abstract class ClashHandlerInterface with ClashInterface {
case ActionMethod.closeConnection:
case ActionMethod.stopListener:
case ActionMethod.setState:
+ case ActionMethod.crash:
completer?.complete(result.data as bool);
return;
case ActionMethod.changeProxy:
@@ -242,6 +245,13 @@ abstract class ClashHandlerInterface with ClashInterface {
);
}
+ @override
+ Future crash() {
+ return invoke(
+ method: ActionMethod.crash,
+ );
+ }
+
@override
Future getProxies() {
return invoke(
diff --git a/lib/clash/service.dart b/lib/clash/service.dart
index 5723b41..2c1866c 100644
--- a/lib/clash/service.dart
+++ b/lib/clash/service.dart
@@ -71,9 +71,9 @@ class ClashService extends ClashHandlerInterface {
}
}, (error, stack) {
commonPrint.log(error.toString());
- if(error is SocketException){
+ if (error is SocketException) {
globalState.showNotifier(error.toString());
- globalState.appController.restartCore();
+ // globalState.appController.restartCore();
}
});
}
@@ -92,12 +92,11 @@ class ClashService extends ClashHandlerInterface {
final arg = Platform.isWindows
? "${serverSocket.port}"
: serverSocket.address.address;
- bool isSuccess = false;
if (Platform.isWindows && await system.checkIsAdmin()) {
- isSuccess = await request.startCoreByHelper(arg);
- }
- if (isSuccess) {
- return;
+ final isSuccess = await request.startCoreByHelper(arg);
+ if (isSuccess) {
+ return;
+ }
}
process = await Process.start(
appPath.corePath,
diff --git a/lib/common/common.dart b/lib/common/common.dart
index 1b95ccf..7aae94d 100644
--- a/lib/common/common.dart
+++ b/lib/common/common.dart
@@ -12,7 +12,7 @@ export 'iterable.dart';
export 'keyboard.dart';
export 'launch.dart';
export 'link.dart';
-export 'list.dart';
+export 'fixed.dart';
export 'lock.dart';
export 'measure.dart';
export 'navigation.dart';
diff --git a/lib/common/constant.dart b/lib/common/constant.dart
index 907cd7d..908cb8a 100644
--- a/lib/common/constant.dart
+++ b/lib/common/constant.dart
@@ -16,16 +16,14 @@ const browserUa =
const packageName = "com.follow.clash";
final unixSocketPath = "/tmp/FlClashSocket_${Random().nextInt(10000)}.sock";
const helperPort = 47890;
-const helperTag = "2024125";
-const baseInfoEdgeInsets = EdgeInsets.symmetric(
- vertical: 16,
- horizontal: 16,
+const maxTextScale = 1.4;
+const minTextScale = 0.8;
+final baseInfoEdgeInsets = EdgeInsets.symmetric(
+ vertical: 16.ap,
+ horizontal: 16.ap,
);
-double textScaleFactor = min(
- WidgetsBinding.instance.platformDispatcher.textScaleFactor,
- 1.2,
-);
+final defaultTextScaleFactor = WidgetsBinding.instance.platformDispatcher.textScaleFactor;
const httpTimeoutDuration = Duration(milliseconds: 5000);
const moreDuration = Duration(milliseconds: 100);
const animateDuration = Duration(milliseconds: 100);
@@ -44,7 +42,6 @@ const profilesDirectoryName = "profiles";
const localhost = "127.0.0.1";
const clashConfigKey = "clash_config";
const configKey = "config";
-const listItemPadding = EdgeInsets.symmetric(horizontal: 16);
const double dialogCommonWidth = 300;
const repository = "chen08209/FlClash";
const defaultExternalController = "127.0.0.1:9090";
@@ -60,6 +57,7 @@ final commonFilter = ImageFilter.blur(
const navigationItemListEquality = ListEquality();
const connectionListEquality = ListEquality();
const stringListEquality = ListEquality();
+const intListEquality = ListEquality();
const logListEquality = ListEquality();
const groupListEquality = ListEquality();
const externalProviderListEquality = ListEquality();
@@ -78,22 +76,24 @@ const viewModeColumnsMap = {
ViewMode.desktop: [4, 3],
};
-const defaultPrimaryColor = 0xFF795548;
+const defaultPrimaryColor = 0XFFD8C0C3;
double getWidgetHeight(num lines) {
- return max(lines * 84 * textScaleFactor + (lines - 1) * 16, 0);
+ return max(lines * 84 + (lines - 1) * 16, 0).ap;
}
+const maxLength = 150;
+
final mainIsolate = "FlClashMainIsolate";
final serviceIsolate = "FlClashServiceIsolate";
const defaultPrimaryColors = [
- defaultPrimaryColor,
+ 0xFF795548,
0xFF03A9F4,
0xFFFFFF00,
0XFFBBC9CC,
0XFFABD397,
- 0XFFD8C0C3,
+ defaultPrimaryColor,
0XFF665390,
];
diff --git a/lib/common/context.dart b/lib/common/context.dart
index 47e364d..70751b9 100644
--- a/lib/common/context.dart
+++ b/lib/common/context.dart
@@ -1,4 +1,4 @@
-import 'package:fl_clash/manager/manager.dart';
+import 'package:fl_clash/manager/message_manager.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart';
@@ -11,6 +11,36 @@ extension BuildContextExtension on BuildContext {
return findAncestorStateOfType()?.message(text);
}
+ showSnackBar(
+ String message, {
+ SnackBarAction? action,
+ }) {
+ final width = viewWidth;
+ EdgeInsets margin;
+ if (width < 600) {
+ margin = const EdgeInsets.only(
+ bottom: 16,
+ right: 16,
+ left: 16,
+ );
+ } else {
+ margin = EdgeInsets.only(
+ bottom: 16,
+ left: 16,
+ right: width - 316,
+ );
+ }
+ ScaffoldMessenger.of(this).showSnackBar(
+ SnackBar(
+ action: action,
+ content: Text(message),
+ behavior: SnackBarBehavior.floating,
+ duration: const Duration(milliseconds: 1500),
+ margin: margin,
+ ),
+ );
+ }
+
Size get appSize {
return MediaQuery.of(this).size;
}
@@ -27,10 +57,10 @@ extension BuildContextExtension on BuildContext {
T? state;
visitor(Element element) {
- if(!element.mounted){
+ if (!element.mounted) {
return;
}
- if(element is StatefulElement){
+ if (element is StatefulElement) {
if (element.state is T) {
state = element.state as T;
}
diff --git a/lib/common/fixed.dart b/lib/common/fixed.dart
new file mode 100644
index 0000000..55e8ef0
--- /dev/null
+++ b/lib/common/fixed.dart
@@ -0,0 +1,79 @@
+import 'iterable.dart';
+
+class FixedList {
+ final int maxLength;
+ final List _list;
+
+ FixedList(this.maxLength, {List? list})
+ : _list = (list ?? [])..truncate(maxLength);
+
+ add(T item) {
+ _list.add(item);
+ _list.truncate(maxLength);
+ }
+
+ clear() {
+ _list.clear();
+ }
+
+ List get list => List.unmodifiable(_list);
+
+ int get length => _list.length;
+
+ T operator [](int index) => _list[index];
+
+ FixedList copyWith() {
+ return FixedList(
+ maxLength,
+ list: _list,
+ );
+ }
+}
+
+class FixedMap {
+ int maxLength;
+ late Map _map;
+
+ FixedMap(this.maxLength, {Map? map}) {
+ _map = map ?? {};
+ }
+
+ updateCacheValue(K key, V Function() callback) {
+ final realValue = _map.updateCacheValue(
+ key,
+ callback,
+ );
+ _adjustMap();
+ return realValue;
+ }
+
+ clear() {
+ _map.clear();
+ }
+
+ updateMaxLength(int size) {
+ maxLength = size;
+ _adjustMap();
+ }
+
+ updateMap(Map map) {
+ _map = map;
+ _adjustMap();
+ }
+
+ _adjustMap() {
+ if (_map.length > maxLength) {
+ _map = Map.fromEntries(
+ map.entries.toList()..truncate(maxLength),
+ );
+ }
+ }
+
+ V? get(K key) => _map[key];
+
+ bool containsKey(K key) => _map.containsKey(key);
+
+ int get length => _map.length;
+
+ Map get map => Map.unmodifiable(_map);
+}
diff --git a/lib/common/iterable.dart b/lib/common/iterable.dart
index aac4471..9cd7033 100644
--- a/lib/common/iterable.dart
+++ b/lib/common/iterable.dart
@@ -38,6 +38,43 @@ extension IterableExt on Iterable {
count++;
}
}
+
+ Iterable takeLast({int count = 50}) {
+ if (count <= 0) return Iterable.empty();
+ return count >= length ? this : toList().skip(length - count);
+ }
+}
+
+extension ListExt on List {
+ void truncate(int maxLength) {
+ assert(maxLength > 0);
+ if (length > maxLength) {
+ removeRange(0, length - maxLength);
+ }
+ }
+
+ List intersection(List list) {
+ return where((item) => list.contains(item)).toList();
+ }
+
+ List> batch(int maxConcurrent) {
+ final batches = (length / maxConcurrent).ceil();
+ final List> res = [];
+ for (int i = 0; i < batches; i++) {
+ if (i != batches - 1) {
+ res.add(sublist(i * maxConcurrent, maxConcurrent * (i + 1)));
+ } else {
+ res.add(sublist(i * maxConcurrent, length));
+ }
+ }
+ return res;
+ }
+
+ List safeSublist(int start) {
+ if (start <= 0) return this;
+ if (start > length) return [];
+ return sublist(start);
+ }
}
extension DoubleListExt on List {
@@ -67,9 +104,9 @@ extension DoubleListExt on List {
}
extension MapExt on Map {
- getCacheValue(K key, V defaultValue) {
+ updateCacheValue(K key, V Function() callback) {
if (this[key] == null) {
- this[key] = defaultValue;
+ this[key] = callback();
}
return this[key];
}
diff --git a/lib/common/list.dart b/lib/common/list.dart
deleted file mode 100644
index ca8add7..0000000
--- a/lib/common/list.dart
+++ /dev/null
@@ -1,93 +0,0 @@
-import 'dart:collection';
-
-class FixedList {
- final int maxLength;
- final List _list;
-
- FixedList(this.maxLength, {List? list}) : _list = list ?? [];
-
- add(T item) {
- if (_list.length == maxLength) {
- _list.removeAt(0);
- }
- _list.add(item);
- }
-
- clear() {
- _list.clear();
- }
-
- List get list => List.unmodifiable(_list);
-
- int get length => _list.length;
-
- T operator [](int index) => _list[index];
-
- FixedList copyWith() {
- return FixedList(
- maxLength,
- list: _list,
- );
- }
-}
-
-class FixedMap {
- int maxSize;
- final Map _map = {};
- final Queue _queue = Queue();
-
- FixedMap(this.maxSize);
-
- put(K key, V value) {
- if (_map.length == maxSize) {
- final oldestKey = _queue.removeFirst();
- _map.remove(oldestKey);
- }
- _map[key] = value;
- _queue.add(key);
- return value;
- }
-
- clear() {
- _map.clear();
- _queue.clear();
- }
-
- updateMaxSize(int size){
- maxSize = size;
- }
-
- V? get(K key) => _map[key];
-
-
- bool containsKey(K key) => _map.containsKey(key);
-
- int get length => _map.length;
-
- Map get map => Map.unmodifiable(_map);
-}
-
-extension ListExtension on List {
- List intersection(List list) {
- return where((item) => list.contains(item)).toList();
- }
-
- List> batch(int maxConcurrent) {
- final batches = (length / maxConcurrent).ceil();
- final List> res = [];
- for (int i = 0; i < batches; i++) {
- if (i != batches - 1) {
- res.add(sublist(i * maxConcurrent, maxConcurrent * (i + 1)));
- } else {
- res.add(sublist(i * maxConcurrent, length));
- }
- }
- return res;
- }
-
- List safeSublist(int start) {
- if (start <= 0) return this;
- if (start > length) return [];
- return sublist(start);
- }
-}
diff --git a/lib/common/measure.dart b/lib/common/measure.dart
index 736fdec..fbeabf5 100644
--- a/lib/common/measure.dart
+++ b/lib/common/measure.dart
@@ -3,11 +3,13 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class Measure {
- final TextScaler _textScale;
+ final TextScaler _textScaler;
final BuildContext context;
+ final Map _measureMap;
- Measure.of(this.context)
- : _textScale = TextScaler.linear(
+ Measure.of(this.context, double textScaleFactor)
+ : _measureMap = {},
+ _textScaler = TextScaler.linear(
textScaleFactor,
);
@@ -21,7 +23,7 @@ class Measure {
style: text.style,
),
maxLines: text.maxLines,
- textScaler: _textScale,
+ textScaler: _textScaler,
textDirection: text.textDirection ?? TextDirection.ltr,
)..layout(
maxWidth: maxWidth,
@@ -29,81 +31,87 @@ class Measure {
return textPainter.size;
}
- double? _bodyMediumHeight;
- Size? _bodyLargeSize;
- double? _bodySmallHeight;
- double? _labelSmallHeight;
- double? _labelMediumHeight;
- double? _titleLargeHeight;
- double? _titleMediumHeight;
-
double get bodyMediumHeight {
- _bodyMediumHeight ??= computeTextSize(
- Text(
- "X",
- style: context.textTheme.bodyMedium,
- ),
- ).height;
- return _bodyMediumHeight!;
+ return _measureMap.updateCacheValue(
+ "bodyMediumHeight",
+ () => computeTextSize(
+ Text(
+ "X",
+ style: context.textTheme.bodyMedium,
+ ),
+ ).height,
+ );
}
- Size get bodyLargeSize {
- _bodyLargeSize ??= computeTextSize(
- Text(
- "X",
- style: context.textTheme.bodyLarge,
- ),
+ double get bodyLargeHeight {
+ return _measureMap.updateCacheValue(
+ "bodyLargeHeight",
+ () => computeTextSize(
+ Text(
+ "X",
+ style: context.textTheme.bodyLarge,
+ ),
+ ).height,
);
- return _bodyLargeSize!;
}
double get bodySmallHeight {
- _bodySmallHeight ??= computeTextSize(
- Text(
- "X",
- style: context.textTheme.bodySmall,
- ),
- ).height;
- return _bodySmallHeight!;
+ return _measureMap.updateCacheValue(
+ "bodySmallHeight",
+ () => computeTextSize(
+ Text(
+ "X",
+ style: context.textTheme.bodySmall,
+ ),
+ ).height,
+ );
}
double get labelSmallHeight {
- _labelSmallHeight ??= computeTextSize(
- Text(
- "X",
- style: context.textTheme.labelSmall,
- ),
- ).height;
- return _labelSmallHeight!;
+ return _measureMap.updateCacheValue(
+ "labelSmallHeight",
+ () => computeTextSize(
+ Text(
+ "X",
+ style: context.textTheme.labelSmall,
+ ),
+ ).height,
+ );
}
double get labelMediumHeight {
- _labelMediumHeight ??= computeTextSize(
- Text(
- "X",
- style: context.textTheme.labelMedium,
- ),
- ).height;
- return _labelMediumHeight!;
+ return _measureMap.updateCacheValue(
+ "labelMediumHeight",
+ () => computeTextSize(
+ Text(
+ "X",
+ style: context.textTheme.labelMedium,
+ ),
+ ).height,
+ );
}
double get titleLargeHeight {
- _titleLargeHeight ??= computeTextSize(
- Text(
- "X",
- style: context.textTheme.titleLarge,
- ),
- ).height;
- return _titleLargeHeight!;
+ return _measureMap.updateCacheValue(
+ "titleLargeHeight",
+ () => computeTextSize(
+ Text(
+ "X",
+ style: context.textTheme.titleLarge,
+ ),
+ ).height,
+ );
}
double get titleMediumHeight {
- _titleMediumHeight ??= computeTextSize(
- Text(
- "X",
- style: context.textTheme.titleMedium,
- ),
- ).height;
- return _titleMediumHeight!;
+ return _measureMap.updateCacheValue(
+ "titleMediumHeight",
+ () => computeTextSize(
+ Text(
+ "X",
+ style: context.textTheme.titleMedium,
+ ),
+ ).height,
+ );
}
}
diff --git a/lib/common/navigation.dart b/lib/common/navigation.dart
index efe512c..5d2ccab 100644
--- a/lib/common/navigation.dart
+++ b/lib/common/navigation.dart
@@ -14,7 +14,6 @@ class Navigation {
const NavigationItem(
icon: Icon(Icons.space_dashboard),
label: PageLabel.dashboard,
- keep: false,
fragment: DashboardFragment(
key: GlobalObjectKey(PageLabel.dashboard),
),
diff --git a/lib/common/num.dart b/lib/common/num.dart
index b9503b7..7166509 100644
--- a/lib/common/num.dart
+++ b/lib/common/num.dart
@@ -1,3 +1,4 @@
+import 'package:fl_clash/state.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -12,6 +13,10 @@ extension NumExt on num {
}
return formatted;
}
+
+ double get ap {
+ return this * (1 + (globalState.theme.textScaleFactor - 1) * 0.5);
+ }
}
extension DoubleExt on double {
diff --git a/lib/common/print.dart b/lib/common/print.dart
index 7be5625..fb9786b 100644
--- a/lib/common/print.dart
+++ b/lib/common/print.dart
@@ -1,4 +1,3 @@
-import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/cupertino.dart';
@@ -20,10 +19,7 @@ class CommonPrint {
return;
}
globalState.appController.addLog(
- Log(
- logLevel: LogLevel.info,
- payload: payload,
- ),
+ Log.app(payload),
);
}
}
diff --git a/lib/common/request.dart b/lib/common/request.dart
index c0fad05..c06135c 100644
--- a/lib/common/request.dart
+++ b/lib/common/request.dart
@@ -130,7 +130,7 @@ class Request {
if (response.statusCode != HttpStatus.ok) {
return false;
}
- return (response.data as String) == helperTag;
+ return (response.data as String) == globalState.coreSHA256;
} catch (_) {
return false;
}
diff --git a/lib/common/system.dart b/lib/common/system.dart
index e0eadf9..20f26da 100644
--- a/lib/common/system.dart
+++ b/lib/common/system.dart
@@ -55,18 +55,24 @@ class System {
}
Future authorizeCore() async {
+ if (Platform.isAndroid) {
+ return AuthorizeCode.none;
+ }
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
final isAdmin = await checkIsAdmin();
if (isAdmin) {
return AuthorizeCode.none;
}
+
if (Platform.isWindows) {
final result = await windows?.registerService();
if (result == true) {
return AuthorizeCode.success;
}
return AuthorizeCode.error;
- } else if (Platform.isMacOS) {
+ }
+
+ if (Platform.isMacOS) {
final shell = 'chown root:admin $corePath; chmod +sx $corePath';
final arguments = [
"-e",
diff --git a/lib/common/theme.dart b/lib/common/theme.dart
index 64fdbd1..97e3912 100644
--- a/lib/common/theme.dart
+++ b/lib/common/theme.dart
@@ -4,36 +4,43 @@ import 'package:flutter/material.dart';
class CommonTheme {
final BuildContext context;
final Map _colorMap;
+ final double textScaleFactor;
- CommonTheme.of(this.context) : _colorMap = {};
+ CommonTheme.of(
+ this.context,
+ this.textScaleFactor,
+ ) : _colorMap = {};
Color get darkenSecondaryContainer {
- return _colorMap.getCacheValue(
+ return _colorMap.updateCacheValue(
"darkenSecondaryContainer",
- context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.1),
+ () => context.colorScheme.secondaryContainer
+ .blendDarken(context, factor: 0.1),
);
}
Color get darkenSecondaryContainerLighter {
- return _colorMap.getCacheValue(
+ return _colorMap.updateCacheValue(
"darkenSecondaryContainerLighter",
- context.colorScheme.secondaryContainer
+ () => context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.1)
.opacity60,
);
}
Color get darken2SecondaryContainer {
- return _colorMap.getCacheValue(
+ return _colorMap.updateCacheValue(
"darken2SecondaryContainer",
- context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.2),
+ () => context.colorScheme.secondaryContainer
+ .blendDarken(context, factor: 0.2),
);
}
Color get darken3PrimaryContainer {
- return _colorMap.getCacheValue(
+ return _colorMap.updateCacheValue(
"darken3PrimaryContainer",
- context.colorScheme.primaryContainer.blendDarken(context, factor: 0.3),
+ () => context.colorScheme.primaryContainer
+ .blendDarken(context, factor: 0.3),
);
}
}
diff --git a/lib/common/tray.dart b/lib/common/tray.dart
index f2e744c..8c80aa6 100644
--- a/lib/common/tray.dart
+++ b/lib/common/tray.dart
@@ -80,7 +80,7 @@ class Tray {
);
}
menuItems.add(MenuItem.separator());
- if (!Platform.isWindows) {
+ if (Platform.isMacOS) {
for (final group in trayState.groups) {
List