Compare commits

...

2 Commits

Author SHA1 Message Date
chen08209
7cb5d40969 Support custom text scaling
Optimize the display of different text scale

Optimize windows setup experience
2025-04-23 14:56:20 +08:00
chen08209
30ee6889ab Optimize android tv experience
Optimize hyperOS freeform window

Add developer mode

Update core

Optimize more details
2025-04-22 16:05:24 +08:00
85 changed files with 3538 additions and 1103 deletions

View File

@@ -201,6 +201,7 @@ jobs:
env: env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TAG: ${{ github.ref_name }} TAG: ${{ github.ref_name }}
RUN_ID: ${{ github.run_id }}
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install requests pip install requests
@@ -211,6 +212,14 @@ jobs:
version=$(echo "${{ github.ref_name }}" | sed 's/^v//') version=$(echo "${{ github.ref_name }}" | sed 's/^v//')
sed "s|VERSION|$version|g" ./.github/release_template.md >> release.md 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 - name: Release
if: ${{ env.IS_STABLE == 'true' }} if: ${{ env.IS_STABLE == 'true' }}
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2

View File

@@ -44,6 +44,7 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" /> <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />

View File

@@ -20,6 +20,10 @@ enum class RunState {
object GlobalState { object GlobalState {
val runLock = ReentrantLock() val runLock = ReentrantLock()
const val NOTIFICATION_CHANNEL = "FlClash"
const val NOTIFICATION_ID = 1
val runState: MutableLiveData<RunState> = MutableLiveData<RunState>(RunState.STOP) val runState: MutableLiveData<RunState> = MutableLiveData<RunState>(RunState.STOP)
var flutterEngine: FlutterEngine? = null var flutterEngine: FlutterEngine? = null
private var serviceEngine: FlutterEngine? = null private var serviceEngine: FlutterEngine? = null

View File

@@ -1,6 +1,5 @@
package com.follow.clash package com.follow.clash
import com.follow.clash.core.Core
import com.follow.clash.plugins.AppPlugin import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ServicePlugin import com.follow.clash.plugins.ServicePlugin
import com.follow.clash.plugins.TilePlugin import com.follow.clash.plugins.TilePlugin

View File

@@ -27,7 +27,6 @@ import java.util.concurrent.locks.ReentrantLock
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
suspend fun Drawable.getBase64(): String { suspend fun Drawable.getBase64(): String {
val drawable = this val drawable = this
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {

View File

@@ -168,8 +168,10 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
try { try {
if (GlobalState.runState.value != RunState.START) return if (GlobalState.runState.value != RunState.START) return
val data = flutterMethodChannel.awaitResult<String>("getStartForegroundParams") val data = flutterMethodChannel.awaitResult<String>("getStartForegroundParams")
val startForegroundParams = Gson().fromJson( val startForegroundParams = if (data != null) Gson().fromJson(
data, StartForegroundParams::class.java data, StartForegroundParams::class.java
) else StartForegroundParams(
title = "", content = ""
) )
if (lastStartForegroundParams != startForegroundParams) { if (lastStartForegroundParams != startForegroundParams) {
lastStartForegroundParams = startForegroundParams lastStartForegroundParams = startForegroundParams

View File

@@ -1,6 +1,26 @@
package com.follow.clash.services 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 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 { interface BaseServiceInterface {
@@ -9,4 +29,71 @@ interface BaseServiceInterface {
fun stop() fun stop()
suspend fun startForeground(title: String, content: String) suspend fun startForeground(title: String, content: String)
}
fun Service.createFlClashNotificationBuilder(): Deferred<NotificationCompat.Builder> =
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)
}
}
Log.d("[FlClash]","startForeground===>")
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)
}
} }

View File

@@ -1,29 +1,51 @@
package com.follow.clash.services package com.follow.clash.services
import android.annotation.SuppressLint 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.app.Service
import android.content.Intent import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.os.Binder import android.os.Binder
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.follow.clash.GlobalState import com.follow.clash.GlobalState
import com.follow.clash.MainActivity
import com.follow.clash.extensions.getActionPendingIntent
import com.follow.clash.models.VpnOptions 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 { 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() private val binder = LocalBinder()
inner class LocalBinder : Binder() { inner class LocalBinder : Binder() {
@@ -38,93 +60,8 @@ class FlClashService : Service(), BaseServiceInterface {
return super.onUnbind(intent) return super.onUnbind(intent)
} }
private val CHANNEL = "FlClash" override fun onDestroy() {
stop()
private val notificationId: Int = 1 super.onDestroy()
private val notificationBuilderDeferred: Deferred<NotificationCompat.Builder> 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)
}
} }
} }

View File

@@ -1,12 +1,8 @@
package com.follow.clash.services package com.follow.clash.services
import android.annotation.SuppressLint 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.PendingIntent
import android.content.Intent import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.net.ProxyInfo import android.net.ProxyInfo
import android.net.VpnService import android.net.VpnService
import android.os.Binder import android.os.Binder
@@ -17,18 +13,13 @@ import android.os.RemoteException
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.follow.clash.GlobalState 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.getIpv4RouteAddress
import com.follow.clash.extensions.getIpv6RouteAddress import com.follow.clash.extensions.getIpv6RouteAddress
import com.follow.clash.extensions.toCIDR import com.follow.clash.extensions.toCIDR
import com.follow.clash.models.AccessControlMode import com.follow.clash.models.AccessControlMode
import com.follow.clash.models.VpnOptions import com.follow.clash.models.VpnOptions
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -128,82 +119,22 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
} }
} }
private val CHANNEL = "FlClash" private var cachedBuilder: NotificationCompat.Builder? = null
private val notificationId: Int = 1 private suspend fun notificationBuilder(): NotificationCompat.Builder {
if (cachedBuilder == null) {
private val notificationBuilderDeferred: Deferred<NotificationCompat.Builder> by lazy { cachedBuilder = createFlClashNotificationBuilder().await()
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)
}
} }
} return cachedBuilder!!
private suspend fun getNotificationBuilder(): NotificationCompat.Builder {
return notificationBuilderDeferred.await()
} }
@SuppressLint("ForegroundServiceType") @SuppressLint("ForegroundServiceType")
override suspend fun startForeground(title: String, content: String) { override suspend fun startForeground(title: String, content: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForeground(
val manager = getSystemService(NotificationManager::class.java) notificationBuilder()
var channel = manager?.getNotificationChannel(CHANNEL)
if (channel == null) {
channel =
NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
manager?.createNotificationChannel(channel)
}
}
val notification =
getNotificationBuilder()
.setContentTitle(title) .setContentTitle(title)
.setContentText(content) .setContentText(content).build()
.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 onTrimMemory(level: Int) { override fun onTrimMemory(level: Int) {

View File

@@ -385,5 +385,12 @@
"expressiveScheme": "Expressive", "expressiveScheme": "Expressive",
"contentScheme": "Content", "contentScheme": "Content",
"rainbowScheme": "Rainbow", "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"
} }

View File

@@ -385,5 +385,13 @@
"expressiveScheme": "エクスプレッシブ", "expressiveScheme": "エクスプレッシブ",
"contentScheme": "コンテンツテーマ", "contentScheme": "コンテンツテーマ",
"rainbowScheme": "レインボー", "rainbowScheme": "レインボー",
"fruitSaladScheme": "フルーツサラダ" "fruitSaladScheme": "フルーツサラダ",
"developerMode": "デベロッパーモード",
"developerModeEnableTip": "デベロッパーモードが有効になりました。",
"messageTest": "メッセージテスト",
"messageTestTip": "これはメッセージです。",
"crashTest": "クラッシュテスト",
"clearData": "データを消去",
"zoom": "ズーム",
"textScale": "テキストスケーリング"
} }

View File

@@ -385,5 +385,13 @@
"expressiveScheme": "Экспрессивные", "expressiveScheme": "Экспрессивные",
"contentScheme": "Контентная тема", "contentScheme": "Контентная тема",
"rainbowScheme": "Радужные", "rainbowScheme": "Радужные",
"fruitSaladScheme": "Фруктовый микс" "fruitSaladScheme": "Фруктовый микс",
"developerMode": "Режим разработчика",
"developerModeEnableTip": "Режим разработчика активирован.",
"messageTest": "Тестирование сообщения",
"messageTestTip": "Это сообщение.",
"crashTest": "Тест на сбои",
"clearData": "Очистить данные",
"zoom": "Масштаб",
"textScale": "Масштабирование текста"
} }

View File

@@ -385,5 +385,13 @@
"expressiveScheme": "表现力", "expressiveScheme": "表现力",
"contentScheme": "内容主题", "contentScheme": "内容主题",
"rainbowScheme": "彩虹", "rainbowScheme": "彩虹",
"fruitSaladScheme": "果缤纷" "fruitSaladScheme": "果缤纷",
"developerMode": "开发者模式",
"developerModeEnableTip": "开发者模式已启用。",
"messageTest": "消息测试",
"messageTestTip": "这是一条消息。",
"crashTest": "崩溃测试",
"clearData": "清除数据",
"zoom": "缩放",
"textScale": "文本缩放"
} }

View File

@@ -166,6 +166,9 @@ func handleAction(action *Action, result func(data interface{})) {
data := action.Data.(string) data := action.Data.(string)
handleSetState(data) handleSetState(data)
result(true) result(true)
case crashMethod:
result(true)
handleCrash()
default: default:
handle := nextHandle(action, result) handle := nextHandle(action, result)
if handle { if handle {

View File

@@ -78,7 +78,6 @@ func getRawConfigWithId(id string) *config.RawConfig {
path := getProfilePath(id) path := getProfilePath(id)
bytes, err := readFile(path) bytes, err := readFile(path)
if err != nil { if err != nil {
log.Errorln("profile is not exist")
return config.DefaultRawConfig() return config.DefaultRawConfig()
} }
prof, err := config.UnmarshalRawConfig(bytes) prof, err := config.UnmarshalRawConfig(bytes)

View File

@@ -82,6 +82,7 @@ const (
getRunTimeMethod Method = "getRunTime" getRunTimeMethod Method = "getRunTime"
getCurrentProfileNameMethod Method = "getCurrentProfileName" getCurrentProfileNameMethod Method = "getCurrentProfileName"
getProfileMethod Method = "getProfile" getProfileMethod Method = "getProfile"
crashMethod Method = "crash"
) )
type Method string type Method string

View File

@@ -7,6 +7,7 @@ replace github.com/metacubex/mihomo => ./Clash.Meta
require ( require (
github.com/metacubex/mihomo v0.0.0-00010101000000-000000000000 github.com/metacubex/mihomo v0.0.0-00010101000000-000000000000
github.com/samber/lo v1.49.1 github.com/samber/lo v1.49.1
golang.org/x/sync v0.11.0
) )
require ( require (
@@ -52,20 +53,20 @@ require (
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect
github.com/metacubex/bart v0.19.0 // indirect github.com/metacubex/bart v0.19.0 // indirect
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 // 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/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // 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/quic-go v0.49.1-0.20250212162123-c135a4412996 // indirect
github.com/metacubex/randv2 v0.2.0 // 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-20250404030904-b2cc8aab562c // indirect
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 // indirect
github.com/metacubex/sing-shadowsocks v0.2.8 // indirect github.com/metacubex/sing-shadowsocks v0.2.8 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.2 // 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-vmess v0.1.14-0.20250228002636-abc39e113b82 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 // 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/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/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
github.com/miekg/dns v1.1.63 // indirect github.com/miekg/dns v1.1.63 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // 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/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/sing v0.5.2 // indirect github.com/sagernet/sing v0.5.2 // indirect
github.com/sagernet/sing-mux v0.2.1 // 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/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/shirou/gopsutil/v4 v4.25.1 // indirect github.com/shirou/gopsutil/v4 v4.25.1 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // 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/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
golang.org/x/mod v0.20.0 // indirect golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.35.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/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.7.0 // indirect

View File

@@ -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/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 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro= 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.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc=
github.com/metacubex/chacha v0.1.1/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= 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 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= 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= 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/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 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= 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/sing-quic v0.0.0-20250404030904-b2cc8aab562c h1:OB3WmMA8YPJjE36RjD9X8xlrWGJ4orxbf2R/KAE28b0=
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629/go.mod h1:TTeIOZLdGmzc07Oedn++vWUUfkZoXLF4sEMxWuhBFr8= github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
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-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4= 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-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 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q= 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-shadowtls v0.0.0-20250412122235-0e9005731a63 h1:vy/8ZYYtWUXYnOnw/NF8ThG1W/RqM/h5rkun+OXZMH0=
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/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 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-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 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc= 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 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= 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.7.0-alpha.1 h1:oMFsPh2oTlALJ7vKXPJuqgy0YeiZ+q/LLw+ZdxZ80l4=
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/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 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= 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 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 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo=
github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE= 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 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= 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= github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=

View File

@@ -442,6 +442,10 @@ func handleSetState(params string) {
_ = json.Unmarshal([]byte(params), state.CurrentState) _ = json.Unmarshal([]byte(params), state.CurrentState)
} }
func handleCrash() {
panic("handle invoke crash")
}
func init() { func init() {
adapter.UrlTestHook = func(url string, name string, delay uint16) { adapter.UrlTestHook = func(url string, name string, delay uint16) {
delayData := &Delay{ delayData := &Delay{

View File

@@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/l10n/l10n.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 'package:flutter_riverpod/flutter_riverpod.dart';
import 'controller.dart'; import 'controller.dart';
import 'models/models.dart';
import 'pages/pages.dart'; import 'pages/pages.dart';
class Application extends ConsumerStatefulWidget { class Application extends ConsumerStatefulWidget {
@@ -27,7 +25,6 @@ class Application extends ConsumerStatefulWidget {
} }
class ApplicationState extends ConsumerState<Application> { class ApplicationState extends ConsumerState<Application> {
late ColorSchemes systemColorSchemes;
Timer? _autoUpdateGroupTaskTimer; Timer? _autoUpdateGroupTaskTimer;
Timer? _autoUpdateProfilesTaskTimer; Timer? _autoUpdateProfilesTaskTimer;
@@ -132,19 +129,6 @@ class ApplicationState extends ConsumerState<Application> {
); );
} }
_updateSystemColorSchemes(
ColorScheme? lightDynamic,
ColorScheme? darkDynamic,
) {
systemColorSchemes = ColorSchemes(
lightColorScheme: lightDynamic,
darkColorScheme: darkDynamic,
);
WidgetsBinding.instance.addPostFrameCallback((_) {
globalState.appController.updateSystemColorSchemes(systemColorSchemes);
});
}
@override @override
Widget build(context) { Widget build(context) {
return _buildPlatformState( return _buildPlatformState(
@@ -154,49 +138,44 @@ class ApplicationState extends ConsumerState<Application> {
final locale = final locale =
ref.watch(appSettingProvider.select((state) => state.locale)); ref.watch(appSettingProvider.select((state) => state.locale));
final themeProps = ref.watch(themeSettingProvider); final themeProps = ref.watch(themeSettingProvider);
return DynamicColorBuilder( return MaterialApp(
builder: (lightDynamic, darkDynamic) { debugShowCheckedModeBanner: false,
_updateSystemColorSchemes(lightDynamic, darkDynamic); navigatorKey: globalState.navigatorKey,
return MaterialApp( localizationsDelegates: const [
debugShowCheckedModeBanner: false, AppLocalizations.delegate,
navigatorKey: globalState.navigatorKey, GlobalMaterialLocalizations.delegate,
localizationsDelegates: const [ GlobalCupertinoLocalizations.delegate,
AppLocalizations.delegate, GlobalWidgetsLocalizations.delegate
GlobalMaterialLocalizations.delegate, ],
GlobalCupertinoLocalizations.delegate, builder: (_, child) {
GlobalWidgetsLocalizations.delegate return AppEnvManager(
], child: _buildPlatformApp(
builder: (_, child) { _buildApp(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,
),
), ),
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(), child: const HomePage(),

View File

@@ -58,6 +58,8 @@ mixin ClashInterface {
stopLog(); stopLog();
Future<bool> crash();
FutureOr<String> getConnections(); FutureOr<String> getConnections();
FutureOr<bool> closeConnection(String id); FutureOr<bool> closeConnection(String id);
@@ -104,6 +106,7 @@ abstract class ClashHandlerInterface with ClashInterface {
case ActionMethod.closeConnection: case ActionMethod.closeConnection:
case ActionMethod.stopListener: case ActionMethod.stopListener:
case ActionMethod.setState: case ActionMethod.setState:
case ActionMethod.crash:
completer?.complete(result.data as bool); completer?.complete(result.data as bool);
return; return;
case ActionMethod.changeProxy: case ActionMethod.changeProxy:
@@ -242,6 +245,14 @@ abstract class ClashHandlerInterface with ClashInterface {
); );
} }
@override
Future<bool> crash() {
return invoke<bool>(
method: ActionMethod.crash,
);
}
@override @override
Future<String> getProxies() { Future<String> getProxies() {
return invoke<String>( return invoke<String>(

View File

@@ -17,15 +17,12 @@ const packageName = "com.follow.clash";
final unixSocketPath = "/tmp/FlClashSocket_${Random().nextInt(10000)}.sock"; final unixSocketPath = "/tmp/FlClashSocket_${Random().nextInt(10000)}.sock";
const helperPort = 47890; const helperPort = 47890;
const helperTag = "2024125"; const helperTag = "2024125";
const baseInfoEdgeInsets = EdgeInsets.symmetric( final baseInfoEdgeInsets = EdgeInsets.symmetric(
vertical: 16, vertical: 16.ap,
horizontal: 16, horizontal: 16.ap,
); );
double textScaleFactor = min( final defaultTextScaleFactor = WidgetsBinding.instance.platformDispatcher.textScaleFactor;
WidgetsBinding.instance.platformDispatcher.textScaleFactor,
1.2,
);
const httpTimeoutDuration = Duration(milliseconds: 5000); const httpTimeoutDuration = Duration(milliseconds: 5000);
const moreDuration = Duration(milliseconds: 100); const moreDuration = Duration(milliseconds: 100);
const animateDuration = Duration(milliseconds: 100); const animateDuration = Duration(milliseconds: 100);
@@ -44,7 +41,6 @@ const profilesDirectoryName = "profiles";
const localhost = "127.0.0.1"; const localhost = "127.0.0.1";
const clashConfigKey = "clash_config"; const clashConfigKey = "clash_config";
const configKey = "config"; const configKey = "config";
const listItemPadding = EdgeInsets.symmetric(horizontal: 16);
const double dialogCommonWidth = 300; const double dialogCommonWidth = 300;
const repository = "chen08209/FlClash"; const repository = "chen08209/FlClash";
const defaultExternalController = "127.0.0.1:9090"; const defaultExternalController = "127.0.0.1:9090";
@@ -81,7 +77,7 @@ const viewModeColumnsMap = {
const defaultPrimaryColor = 0xFF795548; const defaultPrimaryColor = 0xFF795548;
double getWidgetHeight(num lines) { double getWidgetHeight(num lines) {
return max(lines * 84 * textScaleFactor + (lines - 1) * 16, 0); return max(lines * 84 + (lines - 1) * 16, 0).ap;
} }
final mainIsolate = "FlClashMainIsolate"; final mainIsolate = "FlClashMainIsolate";

View File

@@ -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:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -11,6 +11,36 @@ extension BuildContextExtension on BuildContext {
return findAncestorStateOfType<MessageManagerState>()?.message(text); return findAncestorStateOfType<MessageManagerState>()?.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 { Size get appSize {
return MediaQuery.of(this).size; return MediaQuery.of(this).size;
} }
@@ -27,10 +57,10 @@ extension BuildContextExtension on BuildContext {
T? state; T? state;
visitor(Element element) { visitor(Element element) {
if(!element.mounted){ if (!element.mounted) {
return; return;
} }
if(element is StatefulElement){ if (element is StatefulElement) {
if (element.state is T) { if (element.state is T) {
state = element.state as T; state = element.state as T;
} }

View File

@@ -3,11 +3,13 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class Measure { class Measure {
final TextScaler _textScale; final TextScaler _textScaler;
final BuildContext context; final BuildContext context;
final Map<String, dynamic> _measureMap;
Measure.of(this.context) Measure.of(this.context, double textScaleFactor)
: _textScale = TextScaler.linear( : _measureMap = {},
_textScaler = TextScaler.linear(
textScaleFactor, textScaleFactor,
); );
@@ -21,7 +23,7 @@ class Measure {
style: text.style, style: text.style,
), ),
maxLines: text.maxLines, maxLines: text.maxLines,
textScaler: _textScale, textScaler: _textScaler,
textDirection: text.textDirection ?? TextDirection.ltr, textDirection: text.textDirection ?? TextDirection.ltr,
)..layout( )..layout(
maxWidth: maxWidth, maxWidth: maxWidth,
@@ -29,81 +31,75 @@ class Measure {
return textPainter.size; return textPainter.size;
} }
double? _bodyMediumHeight;
Size? _bodyLargeSize;
double? _bodySmallHeight;
double? _labelSmallHeight;
double? _labelMediumHeight;
double? _titleLargeHeight;
double? _titleMediumHeight;
double get bodyMediumHeight { double get bodyMediumHeight {
_bodyMediumHeight ??= computeTextSize( return _measureMap.getCacheValue(
Text( "bodyMediumHeight",
"X", computeTextSize(
style: context.textTheme.bodyMedium, Text(
), "X",
).height; style: context.textTheme.bodyMedium,
return _bodyMediumHeight!; ),
} ).height,
Size get bodyLargeSize {
_bodyLargeSize ??= computeTextSize(
Text(
"X",
style: context.textTheme.bodyLarge,
),
); );
return _bodyLargeSize!;
} }
double get bodySmallHeight { double get bodySmallHeight {
_bodySmallHeight ??= computeTextSize( return _measureMap.getCacheValue(
Text( "bodySmallHeight",
"X", computeTextSize(
style: context.textTheme.bodySmall, Text(
), "X",
).height; style: context.textTheme.bodySmall,
return _bodySmallHeight!; ),
).height,
);
} }
double get labelSmallHeight { double get labelSmallHeight {
_labelSmallHeight ??= computeTextSize( return _measureMap.getCacheValue(
Text( "labelSmallHeight",
"X", computeTextSize(
style: context.textTheme.labelSmall, Text(
), "X",
).height; style: context.textTheme.labelSmall,
return _labelSmallHeight!; ),
).height,
);
} }
double get labelMediumHeight { double get labelMediumHeight {
_labelMediumHeight ??= computeTextSize( return _measureMap.getCacheValue(
Text( "labelMediumHeight",
"X", computeTextSize(
style: context.textTheme.labelMedium, Text(
), "X",
).height; style: context.textTheme.labelMedium,
return _labelMediumHeight!; ),
).height,
);
} }
double get titleLargeHeight { double get titleLargeHeight {
_titleLargeHeight ??= computeTextSize( return _measureMap.getCacheValue(
Text( "titleLargeHeight",
"X", computeTextSize(
style: context.textTheme.titleLarge, Text(
), "X",
).height; style: context.textTheme.titleLarge,
return _titleLargeHeight!; ),
).height,
);
} }
double get titleMediumHeight { double get titleMediumHeight {
_titleMediumHeight ??= computeTextSize( return _measureMap.getCacheValue(
Text( "titleMediumHeight",
"X", computeTextSize(
style: context.textTheme.titleMedium, Text(
), "X",
).height; style: context.textTheme.titleMedium,
return _titleMediumHeight!; ),
).height,
);
} }
} }

View File

@@ -1,3 +1,4 @@
import 'package:fl_clash/state.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -12,6 +13,10 @@ extension NumExt on num {
} }
return formatted; return formatted;
} }
double get ap {
return this * (1 + (globalState.theme.textScaleFactor - 1) * 0.5);
}
} }
extension DoubleExt on double { extension DoubleExt on double {

View File

@@ -1,4 +1,3 @@
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@@ -20,10 +19,7 @@ class CommonPrint {
return; return;
} }
globalState.appController.addLog( globalState.appController.addLog(
Log( Log.app(payload),
logLevel: LogLevel.info,
payload: payload,
),
); );
} }
} }

View File

@@ -4,8 +4,12 @@ import 'package:flutter/material.dart';
class CommonTheme { class CommonTheme {
final BuildContext context; final BuildContext context;
final Map<String, Color> _colorMap; final Map<String, Color> _colorMap;
final double textScaleFactor;
CommonTheme.of(this.context) : _colorMap = {}; CommonTheme.of(
this.context,
this.textScaleFactor,
) : _colorMap = {};
Color get darkenSecondaryContainer { Color get darkenSecondaryContainer {
return _colorMap.getCacheValue( return _colorMap.getCacheValue(

View File

@@ -80,7 +80,7 @@ class Tray {
); );
} }
menuItems.add(MenuItem.separator()); menuItems.add(MenuItem.separator());
if (!Platform.isWindows) { if (Platform.isMacOS) {
for (final group in trayState.groups) { for (final group in trayState.groups) {
List<MenuItem> subMenuItems = []; List<MenuItem> subMenuItems = [];
for (final proxy in group.all) { for (final proxy in group.all) {

View File

@@ -334,23 +334,22 @@ class AppController {
try { try {
await updateProfile(profile); await updateProfile(profile);
} catch (e) { } catch (e) {
_ref.read(logsProvider.notifier).addLog( commonPrint.log(e.toString());
Log(
logLevel: LogLevel.info,
payload: e.toString(),
),
);
} }
} }
} }
Future<void> updateGroups() async { Future<void> updateGroups() async {
_ref.read(groupsProvider.notifier).value = await retry( try {
task: () async { _ref.read(groupsProvider.notifier).value = await retry(
return await clashCore.getProxiesGroups(); task: () async {
}, return await clashCore.getProxiesGroups();
retryIf: (res) => res.isEmpty, },
); retryIf: (res) => res.isEmpty,
);
} catch (_) {
_ref.read(groupsProvider.notifier).value = [];
}
} }
updateProfiles() async { updateProfiles() async {
@@ -362,10 +361,6 @@ class AppController {
} }
} }
updateSystemColorSchemes(ColorSchemes colorSchemes) {
_ref.read(appSchemesProvider.notifier).value = colorSchemes;
}
savePreferences() async { savePreferences() async {
commonPrint.log("save preferences"); commonPrint.log("save preferences");
await preferences.saveConfig(globalState.config); await preferences.saveConfig(globalState.config);
@@ -410,6 +405,14 @@ class AppController {
} }
} }
Future handleClear() async {
await preferences.clearPreferences();
commonPrint.log("clear preferences");
globalState.config = Config(
themeProps: defaultThemeProps,
);
}
autoCheckUpdate() async { autoCheckUpdate() async {
if (!_ref.read(appSettingProvider).autoCheckUpdate) return; if (!_ref.read(appSettingProvider).autoCheckUpdate) return;
final res = await request.checkForUpdate(); final res = await request.checkForUpdate();

View File

@@ -91,7 +91,14 @@ enum Mode { rule, global, direct }
enum ViewMode { mobile, laptop, desktop } enum ViewMode { mobile, laptop, desktop }
enum LogLevel { debug, info, warning, error, silent } enum LogLevel {
debug,
info,
warning,
error,
silent,
app,
}
enum TransportProtocol { udp, tcp } enum TransportProtocol { udp, tcp }
@@ -262,6 +269,7 @@ enum ActionMethod {
getCountryCode, getCountryCode,
getMemory, getMemory,
getProfile, getProfile,
crash,
///Android, ///Android,
setFdMap, setFdMap,
@@ -308,6 +316,12 @@ enum DashboardWidget {
child: NetworkSpeed(), child: NetworkSpeed(),
), ),
), ),
outboundModeV2(
GridItem(
crossAxisCellCount: 8,
child: OutboundModeV2(),
),
),
outboundMode( outboundMode(
GridItem( GridItem(
crossAxisCellCount: 4, crossAxisCellCount: 4,

View File

@@ -1,7 +1,11 @@
import 'dart:async';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/list.dart'; import 'package:fl_clash/widgets/list.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@immutable @immutable
class Contributor { class Contributor {
@@ -116,33 +120,43 @@ class AboutFragment extends StatelessWidget {
title: Column( title: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Wrap( Consumer(builder: (_, ref, ___) {
spacing: 16, return _DeveloperModeDetector(
crossAxisAlignment: WrapCrossAlignment.center, child: Wrap(
children: [ spacing: 16,
Padding( crossAxisAlignment: WrapCrossAlignment.center,
padding: const EdgeInsets.all(12),
child: Image.asset(
'assets/images/icon.png',
width: 64,
height: 64,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Padding(
appName, padding: const EdgeInsets.all(12),
style: Theme.of(context).textTheme.headlineSmall, child: Image.asset(
'assets/images/icon.png',
width: 64,
height: 64,
),
), ),
Text( Column(
globalState.packageInfo.version, crossAxisAlignment: CrossAxisAlignment.start,
style: Theme.of(context).textTheme.labelLarge, children: [
Text(
appName,
style: Theme.of(context).textTheme.headlineSmall,
),
Text(
globalState.packageInfo.version,
style: Theme.of(context).textTheme.labelLarge,
)
],
) )
], ],
) ),
], onEnterDeveloperMode: () {
), ref.read(appSettingProvider.notifier).updateState(
(state) => state.copyWith(developerMode: true),
);
context.showNotifier(appLocalizations.developerModeEnableTip);
},
);
}),
const SizedBox( const SizedBox(
height: 24, height: 24,
), ),
@@ -209,3 +223,52 @@ class Avatar extends StatelessWidget {
); );
} }
} }
class _DeveloperModeDetector extends StatefulWidget {
final Widget child;
final VoidCallback onEnterDeveloperMode;
const _DeveloperModeDetector({
required this.child,
required this.onEnterDeveloperMode,
});
@override
State<_DeveloperModeDetector> createState() => _DeveloperModeDetectorState();
}
class _DeveloperModeDetectorState extends State<_DeveloperModeDetector> {
int _counter = 0;
Timer? _timer;
void _handleTap() {
_counter++;
if (_counter >= 5) {
widget.onEnterDeveloperMode();
_resetCounter();
} else {
_timer?.cancel();
_timer = Timer(Duration(seconds: 1), _resetCounter);
}
}
void _resetCounter() {
_counter = 0;
_timer?.cancel();
_timer = null;
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: widget.child,
);
}
}

View File

@@ -73,7 +73,6 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
updateRequestsThrottler(); updateRequestsThrottler();
} }
}, },
fireImmediately: true,
); );
} }
@@ -149,72 +148,77 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
_handleTryClearCache(constraints.maxWidth - 40 - (value ? 60 : 0)); _handleTryClearCache(constraints.maxWidth - 40 - (value ? 60 : 0));
return child!; return child!;
}, },
child: ValueListenableBuilder<ConnectionsState>( child: TextScaleNotification(
valueListenable: _requestsStateNotifier, child: ValueListenableBuilder<ConnectionsState>(
builder: (_, state, __) { valueListenable: _requestsStateNotifier,
final connections = state.list; builder: (_, state, __) {
if (connections.isEmpty) { final connections = state.list;
return NullStatus( if (connections.isEmpty) {
label: appLocalizations.nullRequestsDesc, return NullStatus(
); label: appLocalizations.nullRequestsDesc,
} );
final items = connections }
.map<Widget>( final items = connections
(connection) => ConnectionItem( .map<Widget>(
key: Key(connection.id), (connection) => ConnectionItem(
connection: connection, key: Key(connection.id),
onClickKeyword: (value) { connection: connection,
context.commonScaffoldState?.addKeyword(value); onClickKeyword: (value) {
}, context.commonScaffoldState?.addKeyword(value);
), },
) ),
.separated( )
const Divider( .separated(
height: 0, const Divider(
), height: 0,
) ),
.toList(); )
return Align( .toList();
alignment: Alignment.topCenter, return Align(
child: ScrollToEndBox( alignment: Alignment.topCenter,
controller: _scrollController, child: ScrollToEndBox(
cacheKey: _cacheKey,
dataSource: connections,
child: CommonScrollBar(
controller: _scrollController, controller: _scrollController,
child: CacheItemExtentListView( cacheKey: _cacheKey,
key: _key, dataSource: connections,
reverse: true, child: CommonScrollBar(
shrinkWrap: true,
physics: NextClampingScrollPhysics(),
controller: _scrollController, controller: _scrollController,
itemExtentBuilder: (index) { child: CacheItemExtentListView(
final widget = items[index]; key: _key,
if (widget.runtimeType == Divider) { reverse: true,
return 0; shrinkWrap: true,
} physics: NextClampingScrollPhysics(),
final measure = globalState.measure; controller: _scrollController,
final bodyMediumHeight = measure.bodyMediumHeight; itemExtentBuilder: (index) {
final connection = connections[(index / 2).floor()]; final widget = items[index];
final height = _calcCacheHeight(connection); if (widget.runtimeType == Divider) {
return height + bodyMediumHeight + 32; return 0;
}, }
itemBuilder: (_, index) { final measure = globalState.measure;
return items[index]; final bodyMediumHeight = measure.bodyMediumHeight;
}, final connection = connections[(index / 2).floor()];
itemCount: items.length, final height = _calcCacheHeight(connection);
keyBuilder: (int index) { return height + bodyMediumHeight + 32;
final widget = items[index]; },
if (widget.runtimeType == Divider) { itemBuilder: (_, index) {
return "divider"; return items[index];
} },
final connection = connections[(index / 2).floor()]; itemCount: items.length,
return connection.id; keyBuilder: (int index) {
}, final widget = items[index];
if (widget.runtimeType == Divider) {
return "divider";
}
final connection = connections[(index / 2).floor()];
return connection.id;
},
),
), ),
), ),
), );
); },
),
onNotification: (_) {
_key.currentState?.clearCache();
}, },
), ),
); );

View File

@@ -93,7 +93,7 @@ class _DashboardFragmentState extends ConsumerState<DashboardFragment>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final dashboardState = ref.watch(dashboardStateProvider); final dashboardState = ref.watch(dashboardStateProvider);
final columns = max(4 * ((dashboardState.viewWidth / 350).ceil()), 8); final columns = max(4 * ((dashboardState.viewWidth / 320).ceil()), 8);
return Align( return Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: SingleChildScrollView( child: SingleChildScrollView(
@@ -103,8 +103,8 @@ class _DashboardFragmentState extends ConsumerState<DashboardFragment>
child: SuperGrid( child: SuperGrid(
key: key, key: key,
crossAxisCount: columns, crossAxisCount: columns,
crossAxisSpacing: 16, crossAxisSpacing: 16.ap,
mainAxisSpacing: 16, mainAxisSpacing: 16.ap,
children: [ children: [
...dashboardState.dashboardWidgets ...dashboardState.dashboardWidgets
.where( .where(

View File

@@ -4,6 +4,7 @@ import 'dart:io';
import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/common.dart'; import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart'; import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -57,37 +58,43 @@ class _MemoryInfoState extends State<MemoryInfo> {
onPressed: () { onPressed: () {
clashCore.requestGc(); clashCore.requestGc();
}, },
child: Column( child: Container(
children: [ padding: baseInfoEdgeInsets.copyWith(
ValueListenableBuilder( top: 0,
valueListenable: _memoryInfoStateNotifier, ),
builder: (_, trafficValue, __) { child: Column(
return Padding( mainAxisSize: MainAxisSize.max,
padding: baseInfoEdgeInsets.copyWith( mainAxisAlignment: MainAxisAlignment.end,
bottom: 0, crossAxisAlignment: CrossAxisAlignment.start,
top: 12, children: [
), SizedBox(
child: Row( height: globalState.measure.bodyMediumHeight + 2,
children: [ child: ValueListenableBuilder(
Text( valueListenable: _memoryInfoStateNotifier,
trafficValue.showValue, builder: (_, trafficValue, __) {
style: return Row(
context.textTheme.bodyMedium?.toLight.adjustSize(1), mainAxisAlignment: MainAxisAlignment.start,
), children: [
SizedBox( Text(
width: 8, trafficValue.showValue,
), style: context.textTheme.bodyMedium?.toLight
Text( .adjustSize(1),
trafficValue.showUnit, ),
style: SizedBox(
context.textTheme.bodyMedium?.toLight.adjustSize(1), width: 8,
) ),
], Text(
), trafficValue.showUnit,
); style: context.textTheme.bodyMedium?.toLight
}, .adjustSize(1),
), )
], ],
);
},
),
)
],
),
), ),
), ),
); );

View File

@@ -206,7 +206,7 @@ class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
); );
}, },
icon: Icon( icon: Icon(
size: 16, size: 16.ap,
Icons.info_outline, Icons.info_outline,
color: context.colorScheme.onSurfaceVariant, color: context.colorScheme.onSurfaceVariant,
), ),

View File

@@ -17,58 +17,145 @@ class OutboundMode extends StatelessWidget {
height: height, height: height,
child: Consumer( child: Consumer(
builder: (_, ref, __) { builder: (_, ref, __) {
final mode = final mode = ref.watch(
ref.watch(patchClashConfigProvider.select((state) => state.mode)); patchClashConfigProvider.select(
return CommonCard( (state) => state.mode,
onPressed: () {},
info: Info(
label: appLocalizations.outboundMode,
iconData: Icons.call_split_sharp,
),
child: Padding(
padding: const EdgeInsets.only(
top: 12,
bottom: 16,
),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
for (final item in Mode.values)
Flexible(
child: ListItem.radio(
dense: true,
horizontalTitleGap: 4,
padding: const EdgeInsets.only(
left: 12,
right: 16,
),
delegate: RadioDelegate(
value: item,
groupValue: mode,
onChanged: (value) async {
if (value == null) {
return;
}
globalState.appController.changeMode(value);
},
),
title: Text(
Intl.message(item.name),
style: Theme.of(context)
.textTheme
.bodyMedium
?.toSoftBold,
),
),
),
],
),
), ),
); );
return Theme(
data: Theme.of(context).copyWith(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent
),
child: CommonCard(
onPressed: () {},
info: Info(
label: appLocalizations.outboundMode,
iconData: Icons.call_split_sharp,
),
child: Padding(
padding: const EdgeInsets.only(
top: 12,
bottom: 16,
),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (final item in Mode.values)
Flexible(
fit: FlexFit.tight,
child: ListItem.radio(
dense: true,
horizontalTitleGap: 4,
padding: EdgeInsets.only(
left: 12.ap,
right: 16.ap,
),
delegate: RadioDelegate(
value: item,
groupValue: mode,
onChanged: (value) async {
if (value == null) {
return;
}
globalState.appController.changeMode(value);
},
),
title: Text(
Intl.message(item.name),
style: Theme.of(context)
.textTheme
.bodyMedium
?.toSoftBold,
),
),
),
],
),
),
));
}, },
), ),
); );
} }
} }
class OutboundModeV2 extends StatelessWidget {
const OutboundModeV2({super.key});
Color _getTextColor(BuildContext context, Mode mode) {
return switch (mode) {
Mode.rule => context.colorScheme.onSecondaryContainer,
Mode.global => context.colorScheme.onPrimaryContainer,
Mode.direct => context.colorScheme.onTertiaryContainer,
};
}
@override
Widget build(BuildContext context) {
final height = getWidgetHeight(0.72);
return SizedBox(
height: height,
child: CommonCard(
padding: EdgeInsets.zero,
child: Consumer(
builder: (_, ref, __) {
final mode = ref.watch(
patchClashConfigProvider.select(
(state) => state.mode,
),
);
final thumbColor = switch (mode) {
Mode.rule => context.colorScheme.secondaryContainer,
Mode.global => globalState.theme.darken3PrimaryContainer,
Mode.direct => context.colorScheme.tertiaryContainer,
};
return Container(
constraints: BoxConstraints.expand(),
child: CommonTabBar<Mode>(
children: Map.fromEntries(
Mode.values.map(
(item) => MapEntry(
item,
Container(
clipBehavior: Clip.antiAlias,
alignment: Alignment.center,
decoration: BoxDecoration(),
height: height - 16,
child: Text(
Intl.message(item.name),
style: Theme.of(context)
.textTheme
.titleSmall
?.adjustSize(1)
.copyWith(
color: _getTextColor(
context,
mode,
),
),
),
),
),
),
),
padding: EdgeInsets.symmetric(horizontal: 8),
groupValue: mode,
onValueChanged: (value) {
if (value == null) {
return;
}
globalState.appController.changeMode(value);
},
thumbColor: thumbColor,
),
);
},
),
),
);
}
}

View File

@@ -0,0 +1,86 @@
import 'package:fl_clash/clash/core.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class DeveloperView extends ConsumerWidget {
const DeveloperView({super.key});
Widget _getDeveloperList(BuildContext context) {
return generateSectionV2(
title: appLocalizations.options,
items: [
ListItem(
leading: Icon(Icons.ac_unit),
title: Text(appLocalizations.messageTest),
onTap: () {
context.showNotifier(
appLocalizations.messageTestTip,
);
},
),
ListItem(
leading: Icon(Icons.heart_broken),
title: Text(appLocalizations.crashTest),
onTap: () {
clashCore.clashInterface.crash();
},
),
ListItem(
leading: Icon(Icons.delete_forever),
title: Text(appLocalizations.clearData),
onTap: () async {
await globalState.appController.handleClear();
},
)
],
);
}
@override
Widget build(BuildContext context, ref) {
final enable = ref.watch(
appSettingProvider.select(
(state) => state.developerMode,
),
);
return SingleChildScrollView(
padding: baseInfoEdgeInsets,
child: Column(
children: [
CommonCard(
type: CommonCardType.filled,
radius: 18,
child: ListItem.switchItem(
padding: const EdgeInsets.only(
left: 16,
right: 16,
top: 4,
bottom: 4,
),
title: Text(appLocalizations.developerMode),
delegate: SwitchDelegate(
value: enable,
onChanged: (value) {
ref.read(appSettingProvider.notifier).updateState(
(state) => state.copyWith(
developerMode: value,
),
);
},
),
),
),
SizedBox(
height: 16,
),
_getDeveloperList(context)
],
),
);
}
}

View File

@@ -11,3 +11,4 @@ export 'backup_and_recovery.dart';
export 'resources.dart'; export 'resources.dart';
export 'connection/requests.dart'; export 'connection/requests.dart';
export 'connection/connections.dart'; export 'connection/connections.dart';
export 'developer.dart';

View File

@@ -34,19 +34,6 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
_logsStateNotifier.value = _logsStateNotifier.value.copyWith( _logsStateNotifier.value = _logsStateNotifier.value.copyWith(
logs: globalState.appState.logs.list, logs: globalState.appState.logs.list,
); );
ref.listenManual(
logsProvider.select((state) => state.list),
(prev, next) {
if (prev != next) {
final isEquality = logListEquality.equals(prev, next);
if (!isEquality) {
_logs = next;
updateLogsThrottler();
}
}
},
fireImmediately: true,
);
ref.listenManual( ref.listenManual(
isCurrentPageProvider( isCurrentPageProvider(
PageLabel.logs, PageLabel.logs,
@@ -60,6 +47,18 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
}, },
fireImmediately: true, fireImmediately: true,
); );
ref.listenManual(
logsProvider.select((state) => state.list),
(prev, next) {
if (prev != next) {
final isEquality = logListEquality.equals(prev, next);
if (!isEquality) {
_logs = next;
updateLogsThrottler();
}
}
},
);
} }
@override @override
@@ -123,7 +122,7 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
final height = globalState.measure final height = globalState.measure
.computeTextSize( .computeTextSize(
Text( Text(
log.payload ?? "", log.payload,
style: globalState.appController.context.textTheme.bodyLarge, style: globalState.appController.context.textTheme.bodyLarge,
), ),
maxWidth: _currentMaxWidth, maxWidth: _currentMaxWidth,
@@ -154,8 +153,7 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
return LayoutBuilder( return LayoutBuilder(
builder: (_, constraints) { builder: (_, constraints) {
_handleTryClearCache(constraints.maxWidth - 40); _handleTryClearCache(constraints.maxWidth - 40);
return Align( return TextScaleNotification(
alignment: Alignment.topCenter,
child: ValueListenableBuilder<LogsState>( child: ValueListenableBuilder<LogsState>(
valueListenable: _logsStateNotifier, valueListenable: _logsStateNotifier,
builder: (_, state, __) { builder: (_, state, __) {
@@ -168,7 +166,7 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
final items = logs final items = logs
.map<Widget>( .map<Widget>(
(log) => LogItem( (log) => LogItem(
key: Key(log.dateTime.toString()), key: Key(log.dateTime),
log: log, log: log,
onClick: (value) { onClick: (value) {
context.commonScaffoldState?.addKeyword(value); context.commonScaffoldState?.addKeyword(value);
@@ -181,43 +179,49 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
), ),
) )
.toList(); .toList();
return ScrollToEndBox<Log>( return Align(
controller: _scrollController, alignment: Alignment.topCenter,
cacheKey: _cacheKey, child: ScrollToEndBox<Log>(
dataSource: logs,
child: CommonScrollBar(
controller: _scrollController, controller: _scrollController,
child: CacheItemExtentListView( cacheKey: _cacheKey,
key: _key, dataSource: logs,
reverse: true, child: CommonScrollBar(
shrinkWrap: true,
physics: NextClampingScrollPhysics(),
controller: _scrollController, controller: _scrollController,
itemBuilder: (_, index) { child: CacheItemExtentListView(
return items[index]; key: _key,
}, reverse: true,
itemExtentBuilder: (index) { shrinkWrap: true,
final item = items[index]; physics: NextClampingScrollPhysics(),
if (item.runtimeType == Divider) { controller: _scrollController,
return 0; itemBuilder: (_, index) {
} return items[index];
final log = logs[(index / 2).floor()]; },
return _getItemHeight(log); itemExtentBuilder: (index) {
}, final item = items[index];
itemCount: items.length, if (item.runtimeType == Divider) {
keyBuilder: (int index) { return 0;
final item = items[index]; }
if (item.runtimeType == Divider) { final log = logs[(index / 2).floor()];
return "divider"; return _getItemHeight(log);
} },
final log = logs[(index / 2).floor()]; itemCount: items.length,
return log.payload ?? ""; keyBuilder: (int index) {
}, final item = items[index];
if (item.runtimeType == Divider) {
return "divider";
}
final log = logs[(index / 2).floor()];
return log.payload;
},
),
), ),
), ),
); );
}, },
), ),
onNotification: (_) {
_key.currentState?.clearCache();
},
); );
}, },
); );
@@ -242,14 +246,14 @@ class LogItem extends StatelessWidget {
vertical: 4, vertical: 4,
), ),
title: SelectableText( title: SelectableText(
log.payload ?? '', log.payload,
style: context.textTheme.bodyLarge, style: context.textTheme.bodyLarge,
), ),
subtitle: Column( subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SelectableText( SelectableText(
"${log.dateTime}", log.dateTime,
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
color: context.colorScheme.primary, color: context.colorScheme.primary,
), ),

View File

@@ -104,8 +104,13 @@ class _URLFormDialogState extends State<URLFormDialog> {
runSpacing: 16, runSpacing: 16,
children: [ children: [
TextField( TextField(
maxLines: 5, keyboardType: TextInputType.url,
minLines: 1, minLines: 1,
maxLines: 5,
onSubmitted: (_) {
_handleAddProfileFormURL();
},
onEditingComplete: _handleAddProfileFormURL,
controller: urlController, controller: urlController,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),

View File

@@ -214,6 +214,7 @@ class _EditProfileState extends State<EditProfile> {
final items = [ final items = [
ListItem( ListItem(
title: TextFormField( title: TextFormField(
textInputAction: TextInputAction.next,
controller: labelController, controller: labelController,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
@@ -230,6 +231,8 @@ class _EditProfileState extends State<EditProfile> {
if (widget.profile.type == ProfileType.url) ...[ if (widget.profile.type == ProfileType.url) ...[
ListItem( ListItem(
title: TextFormField( title: TextFormField(
textInputAction: TextInputAction.next,
keyboardType: TextInputType.url,
controller: urlController, controller: urlController,
maxLines: 5, maxLines: 5,
minLines: 1, minLines: 1,
@@ -258,6 +261,7 @@ class _EditProfileState extends State<EditProfile> {
if (autoUpdate) if (autoUpdate)
ListItem( ListItem(
title: TextFormField( title: TextFormField(
textInputAction: TextInputAction.next,
controller: autoUpdateDurationController, controller: autoUpdateDurationController,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),

View File

@@ -228,7 +228,7 @@ class _OverrideProfileState extends State<OverrideProfile> {
message: TextSpan( message: TextSpan(
text: appLocalizations.saveTip, text: appLocalizations.saveTip,
), ),
confirmText: appLocalizations.tip, confirmText: appLocalizations.save,
); );
if (res != true) { if (res != true) {
return; return;
@@ -873,6 +873,8 @@ class _AddRuleDialogState extends State<AddRuleDialog> {
builder: (filed) { builder: (filed) {
return DropdownMenu( return DropdownMenu(
width: 200, width: 200,
enableFilter: false,
enableSearch: false,
controller: _subRuleController, controller: _subRuleController,
label: Text(appLocalizations.subRule), label: Text(appLocalizations.subRule),
menuHeight: 250, menuHeight: 250,
@@ -890,11 +892,11 @@ class _AddRuleDialogState extends State<AddRuleDialog> {
builder: (filed) { builder: (filed) {
return DropdownMenu( return DropdownMenu(
controller: _ruleTargetController, controller: _ruleTargetController,
initialSelection: filed.value,
label: Text(appLocalizations.ruleTarget), label: Text(appLocalizations.ruleTarget),
width: 200, width: 200,
menuHeight: 250, menuHeight: 250,
enableFilter: true, enableFilter: false,
enableSearch: false,
dropdownMenuEntries: _targetItems, dropdownMenuEntries: _targetItems,
errorText: filed.errorText, errorText: filed.errorText,
); );

View File

@@ -39,7 +39,19 @@ class ThemeFragment extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SingleChildScrollView(child: ThemeColorsBox()); return SingleChildScrollView(
child: Column(
children: [
_ThemeModeItem(),
_PrimaryColorItem(),
_PrueBlackItem(),
_TextScaleFactorItem(),
const SizedBox(
height: 64,
),
],
),
);
} }
} }
@@ -75,29 +87,6 @@ class ItemCard extends StatelessWidget {
} }
} }
class ThemeColorsBox extends ConsumerStatefulWidget {
const ThemeColorsBox({super.key});
@override
ConsumerState<ThemeColorsBox> createState() => _ThemeColorsBoxState();
}
class _ThemeColorsBoxState extends ConsumerState<ThemeColorsBox> {
@override
Widget build(BuildContext context) {
return Column(
children: [
_ThemeModeItem(),
_PrimaryColorItem(),
_PrueBlackItem(),
const SizedBox(
height: 64,
),
],
);
}
}
class _ThemeModeItem extends ConsumerWidget { class _ThemeModeItem extends ConsumerWidget {
const _ThemeModeItem(); const _ThemeModeItem();
@@ -309,124 +298,138 @@ class _PrimaryColorItemState extends ConsumerState<_PrimaryColorItem> {
final primaryColors = [null, ...vm3.b]; final primaryColors = [null, ...vm3.b];
final schemeVariant = vm3.c; final schemeVariant = vm3.c;
return ItemCard( return CommonPopScope(
info: Info( onPop: () {
label: appLocalizations.themeColor, if (_removablePrimaryColor != null) {
iconData: Icons.palette, setState(() {
), _removablePrimaryColor = null;
actions: genActions( });
[ return false;
if (_removablePrimaryColor == null) }
FilledButton( return true;
style: ButtonStyle( },
visualDensity: VisualDensity.compact, child: ItemCard(
), info: Info(
onPressed: _handleChangeSchemeVariant, label: appLocalizations.themeColor,
child: Text(Intl.message("${schemeVariant.name}Scheme")), iconData: Icons.palette,
),
_removablePrimaryColor != null
? FilledButton(
style: ButtonStyle(
visualDensity: VisualDensity.compact,
),
onPressed: () {
setState(() {
_removablePrimaryColor = null;
});
},
child: Text(appLocalizations.cancel),
)
: IconButton.filledTonal(
iconSize: 20,
padding: EdgeInsets.all(4),
visualDensity: VisualDensity.compact,
onPressed: _handleReset,
icon: Icon(Icons.replay),
)
],
space: 8,
),
child: Container(
margin: const EdgeInsets.only(
left: 16,
right: 16,
bottom: 16,
), ),
child: LayoutBuilder( actions: genActions(
builder: (_, constraints) { [
final columns = _calcColumns(constraints.maxWidth); if (_removablePrimaryColor == null)
final itemWidth = FilledButton(
(constraints.maxWidth - (columns - 1) * 16) / columns; style: ButtonStyle(
return Wrap( visualDensity: VisualDensity.compact,
spacing: 16, ),
runSpacing: 16, onPressed: _handleChangeSchemeVariant,
children: [ child: Text(Intl.message("${schemeVariant.name}Scheme")),
for (final color in primaryColors) ),
Container( _removablePrimaryColor != null
clipBehavior: Clip.none, ? FilledButton(
width: itemWidth, style: ButtonStyle(
height: itemWidth, visualDensity: VisualDensity.compact,
child: Stack( ),
alignment: Alignment.center, onPressed: () {
setState(() {
_removablePrimaryColor = null;
});
},
child: Text(appLocalizations.cancel),
)
: IconButton.filledTonal(
iconSize: 20,
padding: EdgeInsets.all(4),
visualDensity: VisualDensity.compact,
onPressed: _handleReset,
icon: Icon(Icons.replay),
)
],
space: 8,
),
child: Container(
margin: const EdgeInsets.only(
left: 16,
right: 16,
bottom: 16,
),
child: LayoutBuilder(
builder: (_, constraints) {
final columns = _calcColumns(constraints.maxWidth);
final itemWidth =
(constraints.maxWidth - (columns - 1) * 16) / columns;
return Wrap(
spacing: 16,
runSpacing: 16,
children: [
for (final color in primaryColors)
Container(
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ width: itemWidth,
EffectGestureDetector( height: itemWidth,
child: ColorSchemeBox( child: Stack(
isSelected: color == primaryColor, alignment: Alignment.center,
primaryColor: color != null ? Color(color) : null, clipBehavior: Clip.none,
onPressed: () { children: [
ref EffectGestureDetector(
.read(themeSettingProvider.notifier) child: ColorSchemeBox(
.updateState( isSelected: color == primaryColor,
(state) => state.copyWith( primaryColor: color != null ? Color(color) : null,
primaryColor: color, onPressed: () {
), setState(() {
); _removablePrimaryColor = null;
});
ref
.read(themeSettingProvider.notifier)
.updateState(
(state) => state.copyWith(
primaryColor: color,
),
);
},
),
onLongPress: () {
setState(() {
_removablePrimaryColor = color;
});
}, },
), ),
onLongPress: () { if (_removablePrimaryColor != null &&
setState(() { _removablePrimaryColor == color)
_removablePrimaryColor = color; Container(
}); color: Colors.white.opacity0,
}, padding: EdgeInsets.all(8),
), child: IconButton.filledTonal(
if (_removablePrimaryColor != null && onPressed: _handleDel,
_removablePrimaryColor == color) padding: EdgeInsets.all(12),
Container( iconSize: 30,
color: Colors.white.opacity0, icon: Icon(
padding: EdgeInsets.all(8), color: context.colorScheme.primary,
child: IconButton.filledTonal( Icons.delete,
onPressed: _handleDel, ),
padding: EdgeInsets.all(12),
iconSize: 30,
icon: Icon(
color: context.colorScheme.primary,
Icons.delete,
), ),
), ),
), ],
],
),
),
if (_removablePrimaryColor == null)
Container(
width: itemWidth,
height: itemWidth,
padding: EdgeInsets.all(
4,
),
child: IconButton.filledTonal(
onPressed: _handleAdd,
iconSize: 32,
icon: Icon(
color: context.colorScheme.primary,
Icons.add,
), ),
), ),
) if (_removablePrimaryColor == null)
], Container(
); width: itemWidth,
}, height: itemWidth,
padding: EdgeInsets.all(
4,
),
child: IconButton.filledTonal(
onPressed: _handleAdd,
iconSize: 32,
icon: Icon(
color: context.colorScheme.primary,
Icons.add,
),
),
)
],
);
},
),
), ),
), ),
); );
@@ -468,6 +471,77 @@ class _PrueBlackItem extends ConsumerWidget {
} }
} }
class _TextScaleFactorItem extends ConsumerWidget {
const _TextScaleFactorItem();
@override
Widget build(BuildContext context, WidgetRef ref) {
final textScale = ref.watch(
themeSettingProvider.select(
(state) => state.textScale,
),
);
final String process =
"${((textScale.scale * 100) as double).round()}%";
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: ListItem.switchItem(
leading: Icon(
Icons.text_fields,
),
horizontalTitleGap: 12,
title: Text(
appLocalizations.textScale,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurfaceVariant,
),
),
delegate: SwitchDelegate(
value: textScale.enable,
onChanged: (value) {
ref.read(themeSettingProvider.notifier).updateState(
(state) => state.copyWith.textScale(
enable: value,
),
);
},
),
),
),
DisabledMask(
status: !textScale.enable,
child: ActivateBox(
active: textScale.enable,
child: SliderTheme(
data: _SliderDefaultsM3(context),
child: Slider(
min: 0.8,
divisions: 8,
padding: EdgeInsets.symmetric(
horizontal: 16,
),
max: 1.2,
label: process,
value: textScale.scale,
onChanged: (value) {
ref.read(themeSettingProvider.notifier).updateState(
(state) => state.copyWith.textScale(
scale: value,
),
);
},
),
),
),
),
],
);
}
}
class _PaletteDialog extends StatefulWidget { class _PaletteDialog extends StatefulWidget {
const _PaletteDialog(); const _PaletteDialog();
@@ -530,3 +604,112 @@ class _PaletteDialogState extends State<_PaletteDialog> {
); );
} }
} }
class _SliderDefaultsM3 extends SliderThemeData {
_SliderDefaultsM3(this.context) : super(trackHeight: 16.0);
final BuildContext context;
late final ColorScheme _colors = Theme.of(context).colorScheme;
@override
Color? get activeTrackColor => _colors.primary;
@override
Color? get inactiveTrackColor => _colors.secondaryContainer;
@override
Color? get secondaryActiveTrackColor => _colors.primary.withOpacity(0.54);
@override
Color? get disabledActiveTrackColor => _colors.onSurface.withOpacity(0.38);
@override
Color? get disabledInactiveTrackColor => _colors.onSurface.withOpacity(0.12);
@override
Color? get disabledSecondaryActiveTrackColor =>
_colors.onSurface.withOpacity(0.38);
@override
Color? get activeTickMarkColor => _colors.onPrimary.withOpacity(1.0);
@override
Color? get inactiveTickMarkColor =>
_colors.onSecondaryContainer.withOpacity(1.0);
@override
Color? get disabledActiveTickMarkColor => _colors.onInverseSurface;
@override
Color? get disabledInactiveTickMarkColor => _colors.onSurface;
@override
Color? get thumbColor => _colors.primary;
@override
Color? get disabledThumbColor => _colors.onSurface.withOpacity(0.38);
@override
Color? get overlayColor =>
WidgetStateColor.resolveWith((Set<WidgetState> states) {
if (states.contains(WidgetState.dragged)) {
return _colors.primary.withOpacity(0.1);
}
if (states.contains(WidgetState.hovered)) {
return _colors.primary.withOpacity(0.08);
}
if (states.contains(WidgetState.focused)) {
return _colors.primary.withOpacity(0.1);
}
return Colors.transparent;
});
@override
TextStyle? get valueIndicatorTextStyle =>
Theme.of(context).textTheme.labelLarge!.copyWith(
color: _colors.onInverseSurface,
);
@override
Color? get valueIndicatorColor => _colors.inverseSurface;
@override
SliderComponentShape? get valueIndicatorShape =>
const RoundedRectSliderValueIndicatorShape();
@override
SliderComponentShape? get thumbShape => const HandleThumbShape();
@override
SliderTrackShape? get trackShape => const GappedSliderTrackShape();
@override
SliderComponentShape? get overlayShape => const RoundSliderOverlayShape();
@override
SliderTickMarkShape? get tickMarkShape =>
const RoundSliderTickMarkShape(tickMarkRadius: 4.0 / 2);
@override
WidgetStateProperty<Size?>? get thumbSize {
return WidgetStateProperty.resolveWith((Set<WidgetState> states) {
if (states.contains(WidgetState.disabled)) {
return const Size(4.0, 44.0);
}
if (states.contains(WidgetState.hovered)) {
return const Size(4.0, 44.0);
}
if (states.contains(WidgetState.focused)) {
return const Size(2.0, 44.0);
}
if (states.contains(WidgetState.pressed)) {
return const Size(2.0, 44.0);
}
return const Size(4.0, 44.0);
});
}
@override
double? get trackGap => 6.0;
}

View File

@@ -14,6 +14,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'backup_and_recovery.dart'; import 'backup_and_recovery.dart';
import 'developer.dart';
import 'theme.dart'; import 'theme.dart';
import 'package:path/path.dart' show dirname, join; import 'package:path/path.dart' show dirname, join;
@@ -54,11 +55,12 @@ class _ToolboxFragmentState extends ConsumerState<ToolsFragment> {
); );
} }
List<Widget> _getOtherList() { List<Widget> _getOtherList(bool enableDeveloperMode) {
return generateSection( return generateSection(
title: appLocalizations.other, title: appLocalizations.other,
items: [ items: [
_DisclaimerItem(), _DisclaimerItem(),
if (enableDeveloperMode) _DeveloperItem(),
_InfoItem(), _InfoItem(),
], ],
); );
@@ -82,7 +84,11 @@ class _ToolboxFragmentState extends ConsumerState<ToolsFragment> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
ref.watch(appSettingProvider.select((state) => state.locale)); final vm2 = ref.watch(
appSettingProvider.select(
(state) => VM2(a: state.locale, b: state.developerMode),
),
);
final items = [ final items = [
Consumer( Consumer(
builder: (_, ref, __) { builder: (_, ref, __) {
@@ -99,7 +105,7 @@ class _ToolboxFragmentState extends ConsumerState<ToolsFragment> {
}, },
), ),
..._getSettingList(), ..._getSettingList(),
..._getOtherList(), ..._getOtherList(vm2.b),
]; ];
return ListView.builder( return ListView.builder(
itemCount: items.length, itemCount: items.length,
@@ -297,3 +303,19 @@ class _InfoItem extends StatelessWidget {
); );
} }
} }
class _DeveloperItem extends StatelessWidget {
const _DeveloperItem();
@override
Widget build(BuildContext context) {
return ListItem.open(
leading: const Icon(Icons.developer_mode),
title: Text(appLocalizations.developerMode),
delegate: OpenDelegate(
title: appLocalizations.developerMode,
widget: const DeveloperView(),
),
);
}
}

View File

@@ -146,6 +146,7 @@ class MessageLookup extends MessageLookupByLibrary {
"The current application is already the latest version", "The current application is already the latest version",
), ),
"checking": MessageLookupByLibrary.simpleMessage("Checking..."), "checking": MessageLookupByLibrary.simpleMessage("Checking..."),
"clearData": MessageLookupByLibrary.simpleMessage("Clear Data"),
"clipboardExport": MessageLookupByLibrary.simpleMessage("Export clipboard"), "clipboardExport": MessageLookupByLibrary.simpleMessage("Export clipboard"),
"clipboardImport": MessageLookupByLibrary.simpleMessage("Clipboard import"), "clipboardImport": MessageLookupByLibrary.simpleMessage("Clipboard import"),
"colorExists": MessageLookupByLibrary.simpleMessage( "colorExists": MessageLookupByLibrary.simpleMessage(
@@ -177,6 +178,7 @@ class MessageLookup extends MessageLookupByLibrary {
"core": MessageLookupByLibrary.simpleMessage("Core"), "core": MessageLookupByLibrary.simpleMessage("Core"),
"coreInfo": MessageLookupByLibrary.simpleMessage("Core info"), "coreInfo": MessageLookupByLibrary.simpleMessage("Core info"),
"country": MessageLookupByLibrary.simpleMessage("Country"), "country": MessageLookupByLibrary.simpleMessage("Country"),
"crashTest": MessageLookupByLibrary.simpleMessage("Crash test"),
"create": MessageLookupByLibrary.simpleMessage("Create"), "create": MessageLookupByLibrary.simpleMessage("Create"),
"cut": MessageLookupByLibrary.simpleMessage("Cut"), "cut": MessageLookupByLibrary.simpleMessage("Cut"),
"dark": MessageLookupByLibrary.simpleMessage("Dark"), "dark": MessageLookupByLibrary.simpleMessage("Dark"),
@@ -208,6 +210,10 @@ class MessageLookup extends MessageLookupByLibrary {
"detectionTip": MessageLookupByLibrary.simpleMessage( "detectionTip": MessageLookupByLibrary.simpleMessage(
"Relying on third-party api is for reference only", "Relying on third-party api is for reference only",
), ),
"developerMode": MessageLookupByLibrary.simpleMessage("Developer mode"),
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage(
"Developer mode is enabled.",
),
"direct": MessageLookupByLibrary.simpleMessage("Direct"), "direct": MessageLookupByLibrary.simpleMessage("Direct"),
"disclaimer": MessageLookupByLibrary.simpleMessage("Disclaimer"), "disclaimer": MessageLookupByLibrary.simpleMessage("Disclaimer"),
"disclaimerDesc": MessageLookupByLibrary.simpleMessage( "disclaimerDesc": MessageLookupByLibrary.simpleMessage(
@@ -362,6 +368,10 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"loose": MessageLookupByLibrary.simpleMessage("Loose"), "loose": MessageLookupByLibrary.simpleMessage("Loose"),
"memoryInfo": MessageLookupByLibrary.simpleMessage("Memory info"), "memoryInfo": MessageLookupByLibrary.simpleMessage("Memory info"),
"messageTest": MessageLookupByLibrary.simpleMessage("Message test"),
"messageTestTip": MessageLookupByLibrary.simpleMessage(
"This is a message.",
),
"min": MessageLookupByLibrary.simpleMessage("Min"), "min": MessageLookupByLibrary.simpleMessage("Min"),
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("Minimize on exit"), "minimizeOnExit": MessageLookupByLibrary.simpleMessage("Minimize on exit"),
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage( "minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
@@ -627,6 +637,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Enabling it will allow TCP concurrency", "Enabling it will allow TCP concurrency",
), ),
"testUrl": MessageLookupByLibrary.simpleMessage("Test url"), "testUrl": MessageLookupByLibrary.simpleMessage("Test url"),
"textScale": MessageLookupByLibrary.simpleMessage("Text Scaling"),
"theme": MessageLookupByLibrary.simpleMessage("Theme"), "theme": MessageLookupByLibrary.simpleMessage("Theme"),
"themeColor": MessageLookupByLibrary.simpleMessage("Theme color"), "themeColor": MessageLookupByLibrary.simpleMessage("Theme color"),
"themeDesc": MessageLookupByLibrary.simpleMessage( "themeDesc": MessageLookupByLibrary.simpleMessage(

View File

@@ -104,6 +104,7 @@ class MessageLookup extends MessageLookupByLibrary {
"checkUpdate": MessageLookupByLibrary.simpleMessage("更新を確認"), "checkUpdate": MessageLookupByLibrary.simpleMessage("更新を確認"),
"checkUpdateError": MessageLookupByLibrary.simpleMessage("アプリは最新版です"), "checkUpdateError": MessageLookupByLibrary.simpleMessage("アプリは最新版です"),
"checking": MessageLookupByLibrary.simpleMessage("確認中..."), "checking": MessageLookupByLibrary.simpleMessage("確認中..."),
"clearData": MessageLookupByLibrary.simpleMessage("データを消去"),
"clipboardExport": MessageLookupByLibrary.simpleMessage("クリップボードにエクスポート"), "clipboardExport": MessageLookupByLibrary.simpleMessage("クリップボードにエクスポート"),
"clipboardImport": MessageLookupByLibrary.simpleMessage("クリップボードからインポート"), "clipboardImport": MessageLookupByLibrary.simpleMessage("クリップボードからインポート"),
"colorExists": MessageLookupByLibrary.simpleMessage("この色は既に存在します"), "colorExists": MessageLookupByLibrary.simpleMessage("この色は既に存在します"),
@@ -127,6 +128,7 @@ class MessageLookup extends MessageLookupByLibrary {
"core": MessageLookupByLibrary.simpleMessage("コア"), "core": MessageLookupByLibrary.simpleMessage("コア"),
"coreInfo": MessageLookupByLibrary.simpleMessage("コア情報"), "coreInfo": MessageLookupByLibrary.simpleMessage("コア情報"),
"country": MessageLookupByLibrary.simpleMessage(""), "country": MessageLookupByLibrary.simpleMessage(""),
"crashTest": MessageLookupByLibrary.simpleMessage("クラッシュテスト"),
"create": MessageLookupByLibrary.simpleMessage("作成"), "create": MessageLookupByLibrary.simpleMessage("作成"),
"cut": MessageLookupByLibrary.simpleMessage("切り取り"), "cut": MessageLookupByLibrary.simpleMessage("切り取り"),
"dark": MessageLookupByLibrary.simpleMessage("ダーク"), "dark": MessageLookupByLibrary.simpleMessage("ダーク"),
@@ -150,6 +152,10 @@ class MessageLookup extends MessageLookupByLibrary {
"ClashMetaベースのマルチプラットフォームプロキシクライアント。シンプルで使いやすく、オープンソースで広告なし。", "ClashMetaベースのマルチプラットフォームプロキシクライアント。シンプルで使いやすく、オープンソースで広告なし。",
), ),
"detectionTip": MessageLookupByLibrary.simpleMessage("サードパーティAPIに依存参考値"), "detectionTip": MessageLookupByLibrary.simpleMessage("サードパーティAPIに依存参考値"),
"developerMode": MessageLookupByLibrary.simpleMessage("デベロッパーモード"),
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage(
"デベロッパーモードが有効になりました。",
),
"direct": MessageLookupByLibrary.simpleMessage("ダイレクト"), "direct": MessageLookupByLibrary.simpleMessage("ダイレクト"),
"disclaimer": MessageLookupByLibrary.simpleMessage("免責事項"), "disclaimer": MessageLookupByLibrary.simpleMessage("免責事項"),
"disclaimerDesc": MessageLookupByLibrary.simpleMessage( "disclaimerDesc": MessageLookupByLibrary.simpleMessage(
@@ -258,6 +264,8 @@ class MessageLookup extends MessageLookupByLibrary {
"loopbackDesc": MessageLookupByLibrary.simpleMessage("UWPループバック解除用"), "loopbackDesc": MessageLookupByLibrary.simpleMessage("UWPループバック解除用"),
"loose": MessageLookupByLibrary.simpleMessage(""), "loose": MessageLookupByLibrary.simpleMessage(""),
"memoryInfo": MessageLookupByLibrary.simpleMessage("メモリ情報"), "memoryInfo": MessageLookupByLibrary.simpleMessage("メモリ情報"),
"messageTest": MessageLookupByLibrary.simpleMessage("メッセージテスト"),
"messageTestTip": MessageLookupByLibrary.simpleMessage("これはメッセージです。"),
"min": MessageLookupByLibrary.simpleMessage("最小化"), "min": MessageLookupByLibrary.simpleMessage("最小化"),
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("終了時に最小化"), "minimizeOnExit": MessageLookupByLibrary.simpleMessage("終了時に最小化"),
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage( "minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
@@ -463,6 +471,7 @@ class MessageLookup extends MessageLookupByLibrary {
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP並列処理"), "tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP並列処理"),
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("TCP並列処理を許可"), "tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("TCP並列処理を許可"),
"testUrl": MessageLookupByLibrary.simpleMessage("URLテスト"), "testUrl": MessageLookupByLibrary.simpleMessage("URLテスト"),
"textScale": MessageLookupByLibrary.simpleMessage("テキストスケーリング"),
"theme": MessageLookupByLibrary.simpleMessage("テーマ"), "theme": MessageLookupByLibrary.simpleMessage("テーマ"),
"themeColor": MessageLookupByLibrary.simpleMessage("テーマカラー"), "themeColor": MessageLookupByLibrary.simpleMessage("テーマカラー"),
"themeDesc": MessageLookupByLibrary.simpleMessage("ダークモードの設定、色の調整"), "themeDesc": MessageLookupByLibrary.simpleMessage("ダークモードの設定、色の調整"),

View File

@@ -148,6 +148,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Текущее приложение уже является последней версией", "Текущее приложение уже является последней версией",
), ),
"checking": MessageLookupByLibrary.simpleMessage("Проверка..."), "checking": MessageLookupByLibrary.simpleMessage("Проверка..."),
"clearData": MessageLookupByLibrary.simpleMessage("Очистить данные"),
"clipboardExport": MessageLookupByLibrary.simpleMessage( "clipboardExport": MessageLookupByLibrary.simpleMessage(
"Экспорт в буфер обмена", "Экспорт в буфер обмена",
), ),
@@ -183,6 +184,7 @@ class MessageLookup extends MessageLookupByLibrary {
"core": MessageLookupByLibrary.simpleMessage("Ядро"), "core": MessageLookupByLibrary.simpleMessage("Ядро"),
"coreInfo": MessageLookupByLibrary.simpleMessage("Информация о ядре"), "coreInfo": MessageLookupByLibrary.simpleMessage("Информация о ядре"),
"country": MessageLookupByLibrary.simpleMessage("Страна"), "country": MessageLookupByLibrary.simpleMessage("Страна"),
"crashTest": MessageLookupByLibrary.simpleMessage("Тест на сбои"),
"create": MessageLookupByLibrary.simpleMessage("Создать"), "create": MessageLookupByLibrary.simpleMessage("Создать"),
"cut": MessageLookupByLibrary.simpleMessage("Вырезать"), "cut": MessageLookupByLibrary.simpleMessage("Вырезать"),
"dark": MessageLookupByLibrary.simpleMessage("Темный"), "dark": MessageLookupByLibrary.simpleMessage("Темный"),
@@ -216,6 +218,10 @@ class MessageLookup extends MessageLookupByLibrary {
"detectionTip": MessageLookupByLibrary.simpleMessage( "detectionTip": MessageLookupByLibrary.simpleMessage(
"Опирается на сторонний API, только для справки", "Опирается на сторонний API, только для справки",
), ),
"developerMode": MessageLookupByLibrary.simpleMessage("Режим разработчика"),
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage(
"Режим разработчика активирован.",
),
"direct": MessageLookupByLibrary.simpleMessage("Прямой"), "direct": MessageLookupByLibrary.simpleMessage("Прямой"),
"disclaimer": MessageLookupByLibrary.simpleMessage( "disclaimer": MessageLookupByLibrary.simpleMessage(
"Отказ от ответственности", "Отказ от ответственности",
@@ -386,6 +392,10 @@ class MessageLookup extends MessageLookupByLibrary {
), ),
"loose": MessageLookupByLibrary.simpleMessage("Свободный"), "loose": MessageLookupByLibrary.simpleMessage("Свободный"),
"memoryInfo": MessageLookupByLibrary.simpleMessage("Информация о памяти"), "memoryInfo": MessageLookupByLibrary.simpleMessage("Информация о памяти"),
"messageTest": MessageLookupByLibrary.simpleMessage(
"Тестирование сообщения",
),
"messageTestTip": MessageLookupByLibrary.simpleMessage("Это сообщение."),
"min": MessageLookupByLibrary.simpleMessage("Мин"), "min": MessageLookupByLibrary.simpleMessage("Мин"),
"minimizeOnExit": MessageLookupByLibrary.simpleMessage( "minimizeOnExit": MessageLookupByLibrary.simpleMessage(
"Свернуть при выходе", "Свернуть при выходе",
@@ -665,6 +675,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Включение позволит использовать параллелизм TCP", "Включение позволит использовать параллелизм TCP",
), ),
"testUrl": MessageLookupByLibrary.simpleMessage("Тест URL"), "testUrl": MessageLookupByLibrary.simpleMessage("Тест URL"),
"textScale": MessageLookupByLibrary.simpleMessage("Масштабирование текста"),
"theme": MessageLookupByLibrary.simpleMessage("Тема"), "theme": MessageLookupByLibrary.simpleMessage("Тема"),
"themeColor": MessageLookupByLibrary.simpleMessage("Цвет темы"), "themeColor": MessageLookupByLibrary.simpleMessage("Цвет темы"),
"themeDesc": MessageLookupByLibrary.simpleMessage( "themeDesc": MessageLookupByLibrary.simpleMessage(

View File

@@ -94,6 +94,7 @@ class MessageLookup extends MessageLookupByLibrary {
"checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"), "checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"),
"checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"), "checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"),
"checking": MessageLookupByLibrary.simpleMessage("检测中..."), "checking": MessageLookupByLibrary.simpleMessage("检测中..."),
"clearData": MessageLookupByLibrary.simpleMessage("清除数据"),
"clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"), "clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"),
"clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"), "clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"),
"colorExists": MessageLookupByLibrary.simpleMessage("该颜色已存在"), "colorExists": MessageLookupByLibrary.simpleMessage("该颜色已存在"),
@@ -117,6 +118,7 @@ class MessageLookup extends MessageLookupByLibrary {
"core": MessageLookupByLibrary.simpleMessage("内核"), "core": MessageLookupByLibrary.simpleMessage("内核"),
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"), "coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
"country": MessageLookupByLibrary.simpleMessage("区域"), "country": MessageLookupByLibrary.simpleMessage("区域"),
"crashTest": MessageLookupByLibrary.simpleMessage("崩溃测试"),
"create": MessageLookupByLibrary.simpleMessage("创建"), "create": MessageLookupByLibrary.simpleMessage("创建"),
"cut": MessageLookupByLibrary.simpleMessage("剪切"), "cut": MessageLookupByLibrary.simpleMessage("剪切"),
"dark": MessageLookupByLibrary.simpleMessage("深色"), "dark": MessageLookupByLibrary.simpleMessage("深色"),
@@ -136,6 +138,8 @@ class MessageLookup extends MessageLookupByLibrary {
"基于ClashMeta的多平台代理客户端简单易用开源无广告。", "基于ClashMeta的多平台代理客户端简单易用开源无广告。",
), ),
"detectionTip": MessageLookupByLibrary.simpleMessage("依赖第三方api仅供参考"), "detectionTip": MessageLookupByLibrary.simpleMessage("依赖第三方api仅供参考"),
"developerMode": MessageLookupByLibrary.simpleMessage("开发者模式"),
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage("开发者模式已启用。"),
"direct": MessageLookupByLibrary.simpleMessage("直连"), "direct": MessageLookupByLibrary.simpleMessage("直连"),
"disclaimer": MessageLookupByLibrary.simpleMessage("免责声明"), "disclaimer": MessageLookupByLibrary.simpleMessage("免责声明"),
"disclaimerDesc": MessageLookupByLibrary.simpleMessage( "disclaimerDesc": MessageLookupByLibrary.simpleMessage(
@@ -232,6 +236,8 @@ class MessageLookup extends MessageLookupByLibrary {
"loopbackDesc": MessageLookupByLibrary.simpleMessage("用于UWP回环解锁"), "loopbackDesc": MessageLookupByLibrary.simpleMessage("用于UWP回环解锁"),
"loose": MessageLookupByLibrary.simpleMessage("宽松"), "loose": MessageLookupByLibrary.simpleMessage("宽松"),
"memoryInfo": MessageLookupByLibrary.simpleMessage("内存信息"), "memoryInfo": MessageLookupByLibrary.simpleMessage("内存信息"),
"messageTest": MessageLookupByLibrary.simpleMessage("消息测试"),
"messageTestTip": MessageLookupByLibrary.simpleMessage("这是一条消息。"),
"min": MessageLookupByLibrary.simpleMessage("最小"), "min": MessageLookupByLibrary.simpleMessage("最小"),
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"), "minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"), "minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
@@ -407,6 +413,7 @@ class MessageLookup extends MessageLookupByLibrary {
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"), "tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"),
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许TCP并发"), "tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许TCP并发"),
"testUrl": MessageLookupByLibrary.simpleMessage("测速链接"), "testUrl": MessageLookupByLibrary.simpleMessage("测速链接"),
"textScale": MessageLookupByLibrary.simpleMessage("文本缩放"),
"theme": MessageLookupByLibrary.simpleMessage("主题"), "theme": MessageLookupByLibrary.simpleMessage("主题"),
"themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"), "themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"),
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"), "themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),

View File

@@ -3004,6 +3004,61 @@ class AppLocalizations {
args: [], args: [],
); );
} }
/// `Developer mode`
String get developerMode {
return Intl.message(
'Developer mode',
name: 'developerMode',
desc: '',
args: [],
);
}
/// `Developer mode is enabled.`
String get developerModeEnableTip {
return Intl.message(
'Developer mode is enabled.',
name: 'developerModeEnableTip',
desc: '',
args: [],
);
}
/// `Message test`
String get messageTest {
return Intl.message(
'Message test',
name: 'messageTest',
desc: '',
args: [],
);
}
/// `This is a message.`
String get messageTestTip {
return Intl.message(
'This is a message.',
name: 'messageTestTip',
desc: '',
args: [],
);
}
/// `Crash test`
String get crashTest {
return Intl.message('Crash test', name: 'crashTest', desc: '', args: []);
}
/// `Clear Data`
String get clearData {
return Intl.message('Clear Data', name: 'clearData', desc: '', args: []);
}
/// `Text Scaling`
String get textScale {
return Intl.message('Text Scaling', name: 'textScale', desc: '', args: []);
}
} }
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> { class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -73,7 +73,7 @@ class _ClashContainerState extends ConsumerState<ClashManager>
void onLog(Log log) { void onLog(Log log) {
ref.watch(logsProvider.notifier).addLog(log); ref.watch(logsProvider.notifier).addLog(log);
if (log.logLevel == LogLevel.error) { if (log.logLevel == LogLevel.error) {
globalState.showNotifier(log.payload ?? ''); globalState.showNotifier(log.payload);
} }
super.onLog(log); super.onLog(log);
} }

View File

@@ -21,7 +21,7 @@ class MessageManager extends StatefulWidget {
class MessageManagerState extends State<MessageManager> { class MessageManagerState extends State<MessageManager> {
final _messagesNotifier = ValueNotifier<List<CommonMessage>>([]); final _messagesNotifier = ValueNotifier<List<CommonMessage>>([]);
final List<CommonMessage> _bufferMessages = []; final List<CommonMessage> _bufferMessages = [];
Completer<bool>? _messageIngCompleter; bool _pushing = false;
@override @override
void initState() { void initState() {
@@ -40,26 +40,27 @@ class MessageManagerState extends State<MessageManager> {
text: text, text: text,
); );
_bufferMessages.add(commonMessage); _bufferMessages.add(commonMessage);
_showMessage(); await _showMessage();
} }
_showMessage() async { _showMessage() async {
if (_messageIngCompleter?.isCompleted == false) { if (_pushing == true) {
return; return;
} }
_pushing = true;
while (_bufferMessages.isNotEmpty) { while (_bufferMessages.isNotEmpty) {
final commonMessage = _bufferMessages.removeAt(0); final commonMessage = _bufferMessages.removeAt(0);
_messagesNotifier.value = List.from(_messagesNotifier.value) _messagesNotifier.value = List.from(_messagesNotifier.value)
..add( ..add(
commonMessage, commonMessage,
); );
_messageIngCompleter = Completer();
await Future.delayed(Duration(seconds: 1)); await Future.delayed(Duration(seconds: 1));
Future.delayed(commonMessage.duration, () { Future.delayed(commonMessage.duration, () {
_handleRemove(commonMessage); _handleRemove(commonMessage);
}); });
_messageIngCompleter?.complete(true); if (_bufferMessages.isEmpty) {
_pushing = false;
}
} }
} }
@@ -77,41 +78,41 @@ class MessageManagerState extends State<MessageManager> {
valueListenable: _messagesNotifier, valueListenable: _messagesNotifier,
builder: (_, messages, __) { builder: (_, messages, __) {
return FadeThroughBox( return FadeThroughBox(
margin: EdgeInsets.only(
top: kToolbarHeight + 8,
left: 12,
right: 12,
),
alignment: Alignment.topRight, alignment: Alignment.topRight,
child: messages.isEmpty child: messages.isEmpty
? SizedBox() ? SizedBox()
: LayoutBuilder( : LayoutBuilder(
key: Key(messages.last.id), key: Key(messages.last.id),
builder: (_, constraints) { builder: (_, constraints) {
return Card( return Card(
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(12.0), Radius.circular(12.0),
), ),
),
elevation: 10,
margin: EdgeInsets.only(
top: kToolbarHeight + 8,
left: 12,
right: 12,
),
color: context.colorScheme.surfaceContainerHigh,
child: Container(
width: min(
constraints.maxWidth,
400,
),
padding: EdgeInsets.symmetric(
horizontal: 12,
vertical: 16,
),
child: Text(
messages.last.text,
),
),
);
},
), ),
elevation: 10,
color: context.colorScheme.surfaceContainerHigh,
child: Container(
width: min(
constraints.maxWidth,
500,
),
padding: EdgeInsets.symmetric(
horizontal: 12,
vertical: 16,
),
child: Text(
messages.last.text,
),
),
);
},
),
); );
}, },
), ),

View File

@@ -1,10 +1,13 @@
import 'dart:math';
import 'package:fl_clash/common/constant.dart'; import 'package:fl_clash/common/constant.dart';
import 'package:fl_clash/common/measure.dart'; import 'package:fl_clash/common/measure.dart';
import 'package:fl_clash/common/theme.dart'; import 'package:fl_clash/common/theme.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class ThemeManager extends StatelessWidget { class ThemeManager extends ConsumerWidget {
final Widget child; final Widget child;
const ThemeManager({ const ThemeManager({
@@ -13,14 +16,30 @@ class ThemeManager extends StatelessWidget {
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, ref) {
globalState.measure = Measure.of(context); final textScale = ref.read(
globalState.theme = CommonTheme.of(context); themeSettingProvider.select((state) => state.textScale),
);
final double textScaleFactor = max(
min(
textScale.enable ? textScale.scale : defaultTextScaleFactor,
1.2,
),
0.8,
);
globalState.measure = Measure.of(context, textScaleFactor);
globalState.theme = CommonTheme.of(context, textScaleFactor);
final padding = MediaQuery.of(context).padding;
final height = MediaQuery.of(context).size.height;
return MediaQuery( return MediaQuery(
data: MediaQuery.of(context).copyWith( data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear( textScaler: TextScaler.linear(
textScaleFactor, textScaleFactor,
), ),
padding: padding.copyWith(
top: padding.top > height * 0.3 ? 0.0 : padding.top,
),
), ),
child: LayoutBuilder( child: LayoutBuilder(
builder: (_, container) { builder: (_, container) {

View File

@@ -16,7 +16,6 @@ class AppState with _$AppState {
@Default(false) bool isInit, @Default(false) bool isInit,
@Default(PageLabel.dashboard) PageLabel pageLabel, @Default(PageLabel.dashboard) PageLabel pageLabel,
@Default([]) List<Package> packages, @Default([]) List<Package> packages,
@Default(ColorSchemes()) ColorSchemes colorSchemes,
@Default(0) int sortNum, @Default(0) int sortNum,
required Size viewSize, required Size viewSize,
@Default({}) DelayMap delayMap, @Default({}) DelayMap delayMap,

View File

@@ -84,33 +84,33 @@ extension ConnectionExt on Connection {
} }
} }
@JsonSerializable() String _logDateTime(_) {
class Log { return DateTime.now().toString();
@JsonKey(name: "LogLevel") }
LogLevel logLevel;
@JsonKey(name: "Payload")
String? payload;
DateTime _dateTime;
Log({ // String _logId(_) {
required this.logLevel, // return utils.id;
this.payload, // }
}) : _dateTime = DateTime.now();
DateTime get dateTime => _dateTime; @freezed
class Log with _$Log {
const factory Log({
@JsonKey(name: "LogLevel") @Default(LogLevel.app) LogLevel logLevel,
@JsonKey(name: "Payload") @Default("") String payload,
@JsonKey(fromJson: _logDateTime) required String dateTime,
}) = _Log;
factory Log.fromJson(Map<String, dynamic> json) { factory Log.app(
return _$LogFromJson(json); String payload,
) {
return Log(
payload: payload,
dateTime: _logDateTime(null),
// id: _logId(null),
);
} }
Map<String, dynamic> toJson() { factory Log.fromJson(Map<String, Object?> json) => _$LogFromJson(json);
return _$LogToJson(this);
}
@override
String toString() {
return 'Log{logLevel: $logLevel, payload: $payload, dateTime: $dateTime}';
}
} }
@freezed @freezed
@@ -127,11 +127,10 @@ extension LogsStateExt on LogsState {
final lowQuery = query.toLowerCase(); final lowQuery = query.toLowerCase();
return logs.where( return logs.where(
(log) { (log) {
final payload = log.payload?.toLowerCase(); final payload = log.payload.toLowerCase();
final logLevelName = log.logLevel.name; final logLevelName = log.logLevel.name;
return {logLevelName}.containsAll(keywords) && return {logLevelName}.containsAll(keywords) &&
((payload?.contains(lowQuery) ?? false) || ((payload.contains(lowQuery)) || logLevelName.contains(lowQuery));
logLevelName.contains(lowQuery));
}, },
).toList(); ).toList();
} }

View File

@@ -37,7 +37,9 @@ const defaultNetworkProps = NetworkProps();
const defaultProxiesStyle = ProxiesStyle(); const defaultProxiesStyle = ProxiesStyle();
const defaultWindowProps = WindowProps(); const defaultWindowProps = WindowProps();
const defaultAccessControl = AccessControl(); const defaultAccessControl = AccessControl();
const defaultThemeProps = ThemeProps(); final defaultThemeProps = ThemeProps(
primaryColor: defaultPrimaryColor,
);
const List<DashboardWidget> defaultDashboardWidgets = [ const List<DashboardWidget> defaultDashboardWidgets = [
DashboardWidget.networkSpeed, DashboardWidget.networkSpeed,
@@ -82,6 +84,7 @@ class AppSettingProps with _$AppSettingProps {
@Default(false) bool disclaimerAccepted, @Default(false) bool disclaimerAccepted,
@Default(true) bool minimizeOnExit, @Default(true) bool minimizeOnExit,
@Default(false) bool hidden, @Default(false) bool hidden,
@Default(false) bool developerMode,
}) = _AppSettingProps; }) = _AppSettingProps;
factory AppSettingProps.fromJson(Map<String, Object?> json) => factory AppSettingProps.fromJson(Map<String, Object?> json) =>
@@ -170,18 +173,41 @@ class ProxiesStyle with _$ProxiesStyle {
json == null ? defaultProxiesStyle : _$ProxiesStyleFromJson(json); json == null ? defaultProxiesStyle : _$ProxiesStyleFromJson(json);
} }
@freezed
class TextScale with _$TextScale {
const factory TextScale({
@Default(false) enable,
@Default(1.0) scale,
}) = _TextScale;
factory TextScale.fromJson(Map<String, Object?> json) =>
_$TextScaleFromJson(json);
}
@freezed @freezed
class ThemeProps with _$ThemeProps { class ThemeProps with _$ThemeProps {
const factory ThemeProps({ const factory ThemeProps({
@Default(defaultPrimaryColor) int? primaryColor, int? primaryColor,
@Default(defaultPrimaryColors) List<int> primaryColors, @Default(defaultPrimaryColors) List<int> primaryColors,
@Default(ThemeMode.dark) ThemeMode themeMode, @Default(ThemeMode.dark) ThemeMode themeMode,
@Default(DynamicSchemeVariant.tonalSpot) DynamicSchemeVariant schemeVariant, @Default(DynamicSchemeVariant.tonalSpot) DynamicSchemeVariant schemeVariant,
@Default(false) bool pureBlack, @Default(false) bool pureBlack,
@Default(TextScale()) TextScale textScale,
}) = _ThemeProps; }) = _ThemeProps;
factory ThemeProps.fromJson(Map<String, Object?>? json) => factory ThemeProps.fromJson(Map<String, Object?> json) =>
json == null ? defaultThemeProps : _$ThemePropsFromJson(json); _$ThemePropsFromJson(json);
factory ThemeProps.safeFromJson(Map<String, Object?>? json) {
if (json == null) {
return defaultThemeProps;
}
try {
return ThemeProps.fromJson(json);
} catch (_) {
return defaultThemeProps;
}
}
} }
@freezed @freezed
@@ -197,7 +223,7 @@ class Config with _$Config {
DAV? dav, DAV? dav,
@Default(defaultNetworkProps) NetworkProps networkProps, @Default(defaultNetworkProps) NetworkProps networkProps,
@Default(defaultVpnProps) VpnProps vpnProps, @Default(defaultVpnProps) VpnProps vpnProps,
@Default(defaultThemeProps) ThemeProps themeProps, @JsonKey(fromJson: ThemeProps.safeFromJson) required ThemeProps themeProps,
@Default(defaultProxiesStyle) ProxiesStyle proxiesStyle, @Default(defaultProxiesStyle) ProxiesStyle proxiesStyle,
@Default(defaultWindowProps) WindowProps windowProps, @Default(defaultWindowProps) WindowProps windowProps,
@Default(defaultClashConfig) ClashConfig patchClashConfig, @Default(defaultClashConfig) ClashConfig patchClashConfig,

View File

@@ -19,7 +19,6 @@ mixin _$AppState {
bool get isInit => throw _privateConstructorUsedError; bool get isInit => throw _privateConstructorUsedError;
PageLabel get pageLabel => throw _privateConstructorUsedError; PageLabel get pageLabel => throw _privateConstructorUsedError;
List<Package> get packages => throw _privateConstructorUsedError; List<Package> get packages => throw _privateConstructorUsedError;
ColorSchemes get colorSchemes => throw _privateConstructorUsedError;
int get sortNum => throw _privateConstructorUsedError; int get sortNum => throw _privateConstructorUsedError;
Size get viewSize => throw _privateConstructorUsedError; Size get viewSize => throw _privateConstructorUsedError;
Map<String, Map<String, int?>> get delayMap => Map<String, Map<String, int?>> get delayMap =>
@@ -53,7 +52,6 @@ abstract class $AppStateCopyWith<$Res> {
{bool isInit, {bool isInit,
PageLabel pageLabel, PageLabel pageLabel,
List<Package> packages, List<Package> packages,
ColorSchemes colorSchemes,
int sortNum, int sortNum,
Size viewSize, Size viewSize,
Map<String, Map<String, int?>> delayMap, Map<String, Map<String, int?>> delayMap,
@@ -69,8 +67,6 @@ abstract class $AppStateCopyWith<$Res> {
FixedList<Traffic> traffics, FixedList<Traffic> traffics,
Traffic totalTraffic, Traffic totalTraffic,
bool needApply}); bool needApply});
$ColorSchemesCopyWith<$Res> get colorSchemes;
} }
/// @nodoc /// @nodoc
@@ -91,7 +87,6 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
Object? isInit = null, Object? isInit = null,
Object? pageLabel = null, Object? pageLabel = null,
Object? packages = null, Object? packages = null,
Object? colorSchemes = null,
Object? sortNum = null, Object? sortNum = null,
Object? viewSize = null, Object? viewSize = null,
Object? delayMap = null, Object? delayMap = null,
@@ -121,10 +116,6 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
? _value.packages ? _value.packages
: packages // ignore: cast_nullable_to_non_nullable : packages // ignore: cast_nullable_to_non_nullable
as List<Package>, as List<Package>,
colorSchemes: null == colorSchemes
? _value.colorSchemes
: colorSchemes // ignore: cast_nullable_to_non_nullable
as ColorSchemes,
sortNum: null == sortNum sortNum: null == sortNum
? _value.sortNum ? _value.sortNum
: sortNum // ignore: cast_nullable_to_non_nullable : sortNum // ignore: cast_nullable_to_non_nullable
@@ -187,16 +178,6 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
as bool, as bool,
) as $Val); ) as $Val);
} }
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ColorSchemesCopyWith<$Res> get colorSchemes {
return $ColorSchemesCopyWith<$Res>(_value.colorSchemes, (value) {
return _then(_value.copyWith(colorSchemes: value) as $Val);
});
}
} }
/// @nodoc /// @nodoc
@@ -211,7 +192,6 @@ abstract class _$$AppStateImplCopyWith<$Res>
{bool isInit, {bool isInit,
PageLabel pageLabel, PageLabel pageLabel,
List<Package> packages, List<Package> packages,
ColorSchemes colorSchemes,
int sortNum, int sortNum,
Size viewSize, Size viewSize,
Map<String, Map<String, int?>> delayMap, Map<String, Map<String, int?>> delayMap,
@@ -227,9 +207,6 @@ abstract class _$$AppStateImplCopyWith<$Res>
FixedList<Traffic> traffics, FixedList<Traffic> traffics,
Traffic totalTraffic, Traffic totalTraffic,
bool needApply}); bool needApply});
@override
$ColorSchemesCopyWith<$Res> get colorSchemes;
} }
/// @nodoc /// @nodoc
@@ -248,7 +225,6 @@ class __$$AppStateImplCopyWithImpl<$Res>
Object? isInit = null, Object? isInit = null,
Object? pageLabel = null, Object? pageLabel = null,
Object? packages = null, Object? packages = null,
Object? colorSchemes = null,
Object? sortNum = null, Object? sortNum = null,
Object? viewSize = null, Object? viewSize = null,
Object? delayMap = null, Object? delayMap = null,
@@ -278,10 +254,6 @@ class __$$AppStateImplCopyWithImpl<$Res>
? _value._packages ? _value._packages
: packages // ignore: cast_nullable_to_non_nullable : packages // ignore: cast_nullable_to_non_nullable
as List<Package>, as List<Package>,
colorSchemes: null == colorSchemes
? _value.colorSchemes
: colorSchemes // ignore: cast_nullable_to_non_nullable
as ColorSchemes,
sortNum: null == sortNum sortNum: null == sortNum
? _value.sortNum ? _value.sortNum
: sortNum // ignore: cast_nullable_to_non_nullable : sortNum // ignore: cast_nullable_to_non_nullable
@@ -353,7 +325,6 @@ class _$AppStateImpl implements _AppState {
{this.isInit = false, {this.isInit = false,
this.pageLabel = PageLabel.dashboard, this.pageLabel = PageLabel.dashboard,
final List<Package> packages = const [], final List<Package> packages = const [],
this.colorSchemes = const ColorSchemes(),
this.sortNum = 0, this.sortNum = 0,
required this.viewSize, required this.viewSize,
final Map<String, Map<String, int?>> delayMap = const {}, final Map<String, Map<String, int?>> delayMap = const {},
@@ -389,9 +360,6 @@ class _$AppStateImpl implements _AppState {
return EqualUnmodifiableListView(_packages); return EqualUnmodifiableListView(_packages);
} }
@override
@JsonKey()
final ColorSchemes colorSchemes;
@override @override
@JsonKey() @JsonKey()
final int sortNum; final int sortNum;
@@ -449,7 +417,7 @@ class _$AppStateImpl implements _AppState {
@override @override
String toString() { String toString() {
return 'AppState(isInit: $isInit, pageLabel: $pageLabel, packages: $packages, colorSchemes: $colorSchemes, sortNum: $sortNum, viewSize: $viewSize, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, needApply: $needApply)'; return 'AppState(isInit: $isInit, pageLabel: $pageLabel, packages: $packages, sortNum: $sortNum, viewSize: $viewSize, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic, needApply: $needApply)';
} }
@override @override
@@ -461,8 +429,6 @@ class _$AppStateImpl implements _AppState {
(identical(other.pageLabel, pageLabel) || (identical(other.pageLabel, pageLabel) ||
other.pageLabel == pageLabel) && other.pageLabel == pageLabel) &&
const DeepCollectionEquality().equals(other._packages, _packages) && const DeepCollectionEquality().equals(other._packages, _packages) &&
(identical(other.colorSchemes, colorSchemes) ||
other.colorSchemes == colorSchemes) &&
(identical(other.sortNum, sortNum) || other.sortNum == sortNum) && (identical(other.sortNum, sortNum) || other.sortNum == sortNum) &&
(identical(other.viewSize, viewSize) || (identical(other.viewSize, viewSize) ||
other.viewSize == viewSize) && other.viewSize == viewSize) &&
@@ -489,28 +455,26 @@ class _$AppStateImpl implements _AppState {
} }
@override @override
int get hashCode => Object.hashAll([ int get hashCode => Object.hash(
runtimeType, runtimeType,
isInit, isInit,
pageLabel, pageLabel,
const DeepCollectionEquality().hash(_packages), const DeepCollectionEquality().hash(_packages),
colorSchemes, sortNum,
sortNum, viewSize,
viewSize, const DeepCollectionEquality().hash(_delayMap),
const DeepCollectionEquality().hash(_delayMap), const DeepCollectionEquality().hash(_groups),
const DeepCollectionEquality().hash(_groups), checkIpNum,
checkIpNum, brightness,
brightness, runTime,
runTime, const DeepCollectionEquality().hash(_providers),
const DeepCollectionEquality().hash(_providers), localIp,
localIp, requests,
requests, version,
version, logs,
logs, traffics,
traffics, totalTraffic,
totalTraffic, needApply);
needApply
]);
/// Create a copy of AppState /// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -526,7 +490,6 @@ abstract class _AppState implements AppState {
{final bool isInit, {final bool isInit,
final PageLabel pageLabel, final PageLabel pageLabel,
final List<Package> packages, final List<Package> packages,
final ColorSchemes colorSchemes,
final int sortNum, final int sortNum,
required final Size viewSize, required final Size viewSize,
final Map<String, Map<String, int?>> delayMap, final Map<String, Map<String, int?>> delayMap,
@@ -550,8 +513,6 @@ abstract class _AppState implements AppState {
@override @override
List<Package> get packages; List<Package> get packages;
@override @override
ColorSchemes get colorSchemes;
@override
int get sortNum; int get sortNum;
@override @override
Size get viewSize; Size get viewSize;

View File

@@ -342,6 +342,7 @@ const _$LogLevelEnumMap = {
LogLevel.warning: 'warning', LogLevel.warning: 'warning',
LogLevel.error: 'error', LogLevel.error: 'error',
LogLevel.silent: 'silent', LogLevel.silent: 'silent',
LogLevel.app: 'app',
}; };
const _$FindProcessModeEnumMap = { const _$FindProcessModeEnumMap = {

View File

@@ -1092,6 +1092,203 @@ abstract class _Connection implements Connection {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
Log _$LogFromJson(Map<String, dynamic> json) {
return _Log.fromJson(json);
}
/// @nodoc
mixin _$Log {
@JsonKey(name: "LogLevel")
LogLevel get logLevel => throw _privateConstructorUsedError;
@JsonKey(name: "Payload")
String get payload => throw _privateConstructorUsedError;
@JsonKey(fromJson: _logDateTime)
String get dateTime => throw _privateConstructorUsedError;
/// Serializes this Log to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of Log
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$LogCopyWith<Log> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $LogCopyWith<$Res> {
factory $LogCopyWith(Log value, $Res Function(Log) then) =
_$LogCopyWithImpl<$Res, Log>;
@useResult
$Res call(
{@JsonKey(name: "LogLevel") LogLevel logLevel,
@JsonKey(name: "Payload") String payload,
@JsonKey(fromJson: _logDateTime) String dateTime});
}
/// @nodoc
class _$LogCopyWithImpl<$Res, $Val extends Log> implements $LogCopyWith<$Res> {
_$LogCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Log
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? logLevel = null,
Object? payload = null,
Object? dateTime = null,
}) {
return _then(_value.copyWith(
logLevel: null == logLevel
? _value.logLevel
: logLevel // ignore: cast_nullable_to_non_nullable
as LogLevel,
payload: null == payload
? _value.payload
: payload // ignore: cast_nullable_to_non_nullable
as String,
dateTime: null == dateTime
? _value.dateTime
: dateTime // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$LogImplCopyWith<$Res> implements $LogCopyWith<$Res> {
factory _$$LogImplCopyWith(_$LogImpl value, $Res Function(_$LogImpl) then) =
__$$LogImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{@JsonKey(name: "LogLevel") LogLevel logLevel,
@JsonKey(name: "Payload") String payload,
@JsonKey(fromJson: _logDateTime) String dateTime});
}
/// @nodoc
class __$$LogImplCopyWithImpl<$Res> extends _$LogCopyWithImpl<$Res, _$LogImpl>
implements _$$LogImplCopyWith<$Res> {
__$$LogImplCopyWithImpl(_$LogImpl _value, $Res Function(_$LogImpl) _then)
: super(_value, _then);
/// Create a copy of Log
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? logLevel = null,
Object? payload = null,
Object? dateTime = null,
}) {
return _then(_$LogImpl(
logLevel: null == logLevel
? _value.logLevel
: logLevel // ignore: cast_nullable_to_non_nullable
as LogLevel,
payload: null == payload
? _value.payload
: payload // ignore: cast_nullable_to_non_nullable
as String,
dateTime: null == dateTime
? _value.dateTime
: dateTime // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$LogImpl implements _Log {
const _$LogImpl(
{@JsonKey(name: "LogLevel") this.logLevel = LogLevel.app,
@JsonKey(name: "Payload") this.payload = "",
@JsonKey(fromJson: _logDateTime) required this.dateTime});
factory _$LogImpl.fromJson(Map<String, dynamic> json) =>
_$$LogImplFromJson(json);
@override
@JsonKey(name: "LogLevel")
final LogLevel logLevel;
@override
@JsonKey(name: "Payload")
final String payload;
@override
@JsonKey(fromJson: _logDateTime)
final String dateTime;
@override
String toString() {
return 'Log(logLevel: $logLevel, payload: $payload, dateTime: $dateTime)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LogImpl &&
(identical(other.logLevel, logLevel) ||
other.logLevel == logLevel) &&
(identical(other.payload, payload) || other.payload == payload) &&
(identical(other.dateTime, dateTime) ||
other.dateTime == dateTime));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, logLevel, payload, dateTime);
/// Create a copy of Log
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$LogImplCopyWith<_$LogImpl> get copyWith =>
__$$LogImplCopyWithImpl<_$LogImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$LogImplToJson(
this,
);
}
}
abstract class _Log implements Log {
const factory _Log(
{@JsonKey(name: "LogLevel") final LogLevel logLevel,
@JsonKey(name: "Payload") final String payload,
@JsonKey(fromJson: _logDateTime) required final String dateTime}) =
_$LogImpl;
factory _Log.fromJson(Map<String, dynamic> json) = _$LogImpl.fromJson;
@override
@JsonKey(name: "LogLevel")
LogLevel get logLevel;
@override
@JsonKey(name: "Payload")
String get payload;
@override
@JsonKey(fromJson: _logDateTime)
String get dateTime;
/// Create a copy of Log
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$LogImplCopyWith<_$LogImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc /// @nodoc
mixin _$LogsState { mixin _$LogsState {
List<Log> get logs => throw _privateConstructorUsedError; List<Log> get logs => throw _privateConstructorUsedError;

View File

@@ -6,24 +6,6 @@ part of '../common.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
Log _$LogFromJson(Map<String, dynamic> json) => Log(
logLevel: $enumDecode(_$LogLevelEnumMap, json['LogLevel']),
payload: json['Payload'] as String?,
);
Map<String, dynamic> _$LogToJson(Log instance) => <String, dynamic>{
'LogLevel': _$LogLevelEnumMap[instance.logLevel]!,
'Payload': instance.payload,
};
const _$LogLevelEnumMap = {
LogLevel.debug: 'debug',
LogLevel.info: 'info',
LogLevel.warning: 'warning',
LogLevel.error: 'error',
LogLevel.silent: 'silent',
};
_$PackageImpl _$$PackageImplFromJson(Map<String, dynamic> json) => _$PackageImpl _$$PackageImplFromJson(Map<String, dynamic> json) =>
_$PackageImpl( _$PackageImpl(
packageName: json['packageName'] as String, packageName: json['packageName'] as String,
@@ -87,6 +69,28 @@ Map<String, dynamic> _$$ConnectionImplToJson(_$ConnectionImpl instance) =>
'chains': instance.chains, 'chains': instance.chains,
}; };
_$LogImpl _$$LogImplFromJson(Map<String, dynamic> json) => _$LogImpl(
logLevel: $enumDecodeNullable(_$LogLevelEnumMap, json['LogLevel']) ??
LogLevel.app,
payload: json['Payload'] as String? ?? "",
dateTime: _logDateTime(json['dateTime']),
);
Map<String, dynamic> _$$LogImplToJson(_$LogImpl instance) => <String, dynamic>{
'LogLevel': _$LogLevelEnumMap[instance.logLevel]!,
'Payload': instance.payload,
'dateTime': instance.dateTime,
};
const _$LogLevelEnumMap = {
LogLevel.debug: 'debug',
LogLevel.info: 'info',
LogLevel.warning: 'warning',
LogLevel.error: 'error',
LogLevel.silent: 'silent',
LogLevel.app: 'app',
};
_$DAVImpl _$$DAVImplFromJson(Map<String, dynamic> json) => _$DAVImpl( _$DAVImpl _$$DAVImplFromJson(Map<String, dynamic> json) => _$DAVImpl(
uri: json['uri'] as String, uri: json['uri'] as String,
user: json['user'] as String, user: json['user'] as String,

View File

@@ -37,6 +37,7 @@ mixin _$AppSettingProps {
bool get disclaimerAccepted => throw _privateConstructorUsedError; bool get disclaimerAccepted => throw _privateConstructorUsedError;
bool get minimizeOnExit => throw _privateConstructorUsedError; bool get minimizeOnExit => throw _privateConstructorUsedError;
bool get hidden => throw _privateConstructorUsedError; bool get hidden => throw _privateConstructorUsedError;
bool get developerMode => throw _privateConstructorUsedError;
/// Serializes this AppSettingProps to a JSON map. /// Serializes this AppSettingProps to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -70,7 +71,8 @@ abstract class $AppSettingPropsCopyWith<$Res> {
bool showLabel, bool showLabel,
bool disclaimerAccepted, bool disclaimerAccepted,
bool minimizeOnExit, bool minimizeOnExit,
bool hidden}); bool hidden,
bool developerMode});
} }
/// @nodoc /// @nodoc
@@ -103,6 +105,7 @@ class _$AppSettingPropsCopyWithImpl<$Res, $Val extends AppSettingProps>
Object? disclaimerAccepted = null, Object? disclaimerAccepted = null,
Object? minimizeOnExit = null, Object? minimizeOnExit = null,
Object? hidden = null, Object? hidden = null,
Object? developerMode = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
locale: freezed == locale locale: freezed == locale
@@ -165,6 +168,10 @@ class _$AppSettingPropsCopyWithImpl<$Res, $Val extends AppSettingProps>
? _value.hidden ? _value.hidden
: hidden // ignore: cast_nullable_to_non_nullable : hidden // ignore: cast_nullable_to_non_nullable
as bool, as bool,
developerMode: null == developerMode
? _value.developerMode
: developerMode // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val); ) as $Val);
} }
} }
@@ -193,7 +200,8 @@ abstract class _$$AppSettingPropsImplCopyWith<$Res>
bool showLabel, bool showLabel,
bool disclaimerAccepted, bool disclaimerAccepted,
bool minimizeOnExit, bool minimizeOnExit,
bool hidden}); bool hidden,
bool developerMode});
} }
/// @nodoc /// @nodoc
@@ -224,6 +232,7 @@ class __$$AppSettingPropsImplCopyWithImpl<$Res>
Object? disclaimerAccepted = null, Object? disclaimerAccepted = null,
Object? minimizeOnExit = null, Object? minimizeOnExit = null,
Object? hidden = null, Object? hidden = null,
Object? developerMode = null,
}) { }) {
return _then(_$AppSettingPropsImpl( return _then(_$AppSettingPropsImpl(
locale: freezed == locale locale: freezed == locale
@@ -286,6 +295,10 @@ class __$$AppSettingPropsImplCopyWithImpl<$Res>
? _value.hidden ? _value.hidden
: hidden // ignore: cast_nullable_to_non_nullable : hidden // ignore: cast_nullable_to_non_nullable
as bool, as bool,
developerMode: null == developerMode
? _value.developerMode
: developerMode // ignore: cast_nullable_to_non_nullable
as bool,
)); ));
} }
} }
@@ -309,7 +322,8 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
this.showLabel = false, this.showLabel = false,
this.disclaimerAccepted = false, this.disclaimerAccepted = false,
this.minimizeOnExit = true, this.minimizeOnExit = true,
this.hidden = false}) this.hidden = false,
this.developerMode = false})
: _dashboardWidgets = dashboardWidgets; : _dashboardWidgets = dashboardWidgets;
factory _$AppSettingPropsImpl.fromJson(Map<String, dynamic> json) => factory _$AppSettingPropsImpl.fromJson(Map<String, dynamic> json) =>
@@ -366,10 +380,13 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
@override @override
@JsonKey() @JsonKey()
final bool hidden; final bool hidden;
@override
@JsonKey()
final bool developerMode;
@override @override
String toString() { String toString() {
return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, minimizeOnExit: $minimizeOnExit, hidden: $hidden)'; return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode)';
} }
@override @override
@@ -402,7 +419,9 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
other.disclaimerAccepted == disclaimerAccepted) && other.disclaimerAccepted == disclaimerAccepted) &&
(identical(other.minimizeOnExit, minimizeOnExit) || (identical(other.minimizeOnExit, minimizeOnExit) ||
other.minimizeOnExit == minimizeOnExit) && other.minimizeOnExit == minimizeOnExit) &&
(identical(other.hidden, hidden) || other.hidden == hidden)); (identical(other.hidden, hidden) || other.hidden == hidden) &&
(identical(other.developerMode, developerMode) ||
other.developerMode == developerMode));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -423,7 +442,8 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
showLabel, showLabel,
disclaimerAccepted, disclaimerAccepted,
minimizeOnExit, minimizeOnExit,
hidden); hidden,
developerMode);
/// Create a copy of AppSettingProps /// Create a copy of AppSettingProps
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -459,7 +479,8 @@ abstract class _AppSettingProps implements AppSettingProps {
final bool showLabel, final bool showLabel,
final bool disclaimerAccepted, final bool disclaimerAccepted,
final bool minimizeOnExit, final bool minimizeOnExit,
final bool hidden}) = _$AppSettingPropsImpl; final bool hidden,
final bool developerMode}) = _$AppSettingPropsImpl;
factory _AppSettingProps.fromJson(Map<String, dynamic> json) = factory _AppSettingProps.fromJson(Map<String, dynamic> json) =
_$AppSettingPropsImpl.fromJson; _$AppSettingPropsImpl.fromJson;
@@ -495,6 +516,8 @@ abstract class _AppSettingProps implements AppSettingProps {
bool get minimizeOnExit; bool get minimizeOnExit;
@override @override
bool get hidden; bool get hidden;
@override
bool get developerMode;
/// Create a copy of AppSettingProps /// Create a copy of AppSettingProps
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -1717,6 +1740,170 @@ abstract class _ProxiesStyle implements ProxiesStyle {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
TextScale _$TextScaleFromJson(Map<String, dynamic> json) {
return _TextScale.fromJson(json);
}
/// @nodoc
mixin _$TextScale {
dynamic get enable => throw _privateConstructorUsedError;
dynamic get scale => throw _privateConstructorUsedError;
/// Serializes this TextScale to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of TextScale
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$TextScaleCopyWith<TextScale> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TextScaleCopyWith<$Res> {
factory $TextScaleCopyWith(TextScale value, $Res Function(TextScale) then) =
_$TextScaleCopyWithImpl<$Res, TextScale>;
@useResult
$Res call({dynamic enable, dynamic scale});
}
/// @nodoc
class _$TextScaleCopyWithImpl<$Res, $Val extends TextScale>
implements $TextScaleCopyWith<$Res> {
_$TextScaleCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of TextScale
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? enable = freezed,
Object? scale = freezed,
}) {
return _then(_value.copyWith(
enable: freezed == enable
? _value.enable
: enable // ignore: cast_nullable_to_non_nullable
as dynamic,
scale: freezed == scale
? _value.scale
: scale // ignore: cast_nullable_to_non_nullable
as dynamic,
) as $Val);
}
}
/// @nodoc
abstract class _$$TextScaleImplCopyWith<$Res>
implements $TextScaleCopyWith<$Res> {
factory _$$TextScaleImplCopyWith(
_$TextScaleImpl value, $Res Function(_$TextScaleImpl) then) =
__$$TextScaleImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({dynamic enable, dynamic scale});
}
/// @nodoc
class __$$TextScaleImplCopyWithImpl<$Res>
extends _$TextScaleCopyWithImpl<$Res, _$TextScaleImpl>
implements _$$TextScaleImplCopyWith<$Res> {
__$$TextScaleImplCopyWithImpl(
_$TextScaleImpl _value, $Res Function(_$TextScaleImpl) _then)
: super(_value, _then);
/// Create a copy of TextScale
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? enable = freezed,
Object? scale = freezed,
}) {
return _then(_$TextScaleImpl(
enable: freezed == enable ? _value.enable! : enable,
scale: freezed == scale ? _value.scale! : scale,
));
}
}
/// @nodoc
@JsonSerializable()
class _$TextScaleImpl implements _TextScale {
const _$TextScaleImpl({this.enable = false, this.scale = 1.0});
factory _$TextScaleImpl.fromJson(Map<String, dynamic> json) =>
_$$TextScaleImplFromJson(json);
@override
@JsonKey()
final dynamic enable;
@override
@JsonKey()
final dynamic scale;
@override
String toString() {
return 'TextScale(enable: $enable, scale: $scale)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TextScaleImpl &&
const DeepCollectionEquality().equals(other.enable, enable) &&
const DeepCollectionEquality().equals(other.scale, scale));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(enable),
const DeepCollectionEquality().hash(scale));
/// Create a copy of TextScale
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$TextScaleImplCopyWith<_$TextScaleImpl> get copyWith =>
__$$TextScaleImplCopyWithImpl<_$TextScaleImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$TextScaleImplToJson(
this,
);
}
}
abstract class _TextScale implements TextScale {
const factory _TextScale({final dynamic enable, final dynamic scale}) =
_$TextScaleImpl;
factory _TextScale.fromJson(Map<String, dynamic> json) =
_$TextScaleImpl.fromJson;
@override
dynamic get enable;
@override
dynamic get scale;
/// Create a copy of TextScale
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$TextScaleImplCopyWith<_$TextScaleImpl> get copyWith =>
throw _privateConstructorUsedError;
}
ThemeProps _$ThemePropsFromJson(Map<String, dynamic> json) { ThemeProps _$ThemePropsFromJson(Map<String, dynamic> json) {
return _ThemeProps.fromJson(json); return _ThemeProps.fromJson(json);
} }
@@ -1728,6 +1915,7 @@ mixin _$ThemeProps {
ThemeMode get themeMode => throw _privateConstructorUsedError; ThemeMode get themeMode => throw _privateConstructorUsedError;
DynamicSchemeVariant get schemeVariant => throw _privateConstructorUsedError; DynamicSchemeVariant get schemeVariant => throw _privateConstructorUsedError;
bool get pureBlack => throw _privateConstructorUsedError; bool get pureBlack => throw _privateConstructorUsedError;
TextScale get textScale => throw _privateConstructorUsedError;
/// Serializes this ThemeProps to a JSON map. /// Serializes this ThemeProps to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -1750,7 +1938,10 @@ abstract class $ThemePropsCopyWith<$Res> {
List<int> primaryColors, List<int> primaryColors,
ThemeMode themeMode, ThemeMode themeMode,
DynamicSchemeVariant schemeVariant, DynamicSchemeVariant schemeVariant,
bool pureBlack}); bool pureBlack,
TextScale textScale});
$TextScaleCopyWith<$Res> get textScale;
} }
/// @nodoc /// @nodoc
@@ -1773,6 +1964,7 @@ class _$ThemePropsCopyWithImpl<$Res, $Val extends ThemeProps>
Object? themeMode = null, Object? themeMode = null,
Object? schemeVariant = null, Object? schemeVariant = null,
Object? pureBlack = null, Object? pureBlack = null,
Object? textScale = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
primaryColor: freezed == primaryColor primaryColor: freezed == primaryColor
@@ -1795,8 +1987,22 @@ class _$ThemePropsCopyWithImpl<$Res, $Val extends ThemeProps>
? _value.pureBlack ? _value.pureBlack
: pureBlack // ignore: cast_nullable_to_non_nullable : pureBlack // ignore: cast_nullable_to_non_nullable
as bool, as bool,
textScale: null == textScale
? _value.textScale
: textScale // ignore: cast_nullable_to_non_nullable
as TextScale,
) as $Val); ) as $Val);
} }
/// Create a copy of ThemeProps
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$TextScaleCopyWith<$Res> get textScale {
return $TextScaleCopyWith<$Res>(_value.textScale, (value) {
return _then(_value.copyWith(textScale: value) as $Val);
});
}
} }
/// @nodoc /// @nodoc
@@ -1812,7 +2018,11 @@ abstract class _$$ThemePropsImplCopyWith<$Res>
List<int> primaryColors, List<int> primaryColors,
ThemeMode themeMode, ThemeMode themeMode,
DynamicSchemeVariant schemeVariant, DynamicSchemeVariant schemeVariant,
bool pureBlack}); bool pureBlack,
TextScale textScale});
@override
$TextScaleCopyWith<$Res> get textScale;
} }
/// @nodoc /// @nodoc
@@ -1833,6 +2043,7 @@ class __$$ThemePropsImplCopyWithImpl<$Res>
Object? themeMode = null, Object? themeMode = null,
Object? schemeVariant = null, Object? schemeVariant = null,
Object? pureBlack = null, Object? pureBlack = null,
Object? textScale = null,
}) { }) {
return _then(_$ThemePropsImpl( return _then(_$ThemePropsImpl(
primaryColor: freezed == primaryColor primaryColor: freezed == primaryColor
@@ -1855,6 +2066,10 @@ class __$$ThemePropsImplCopyWithImpl<$Res>
? _value.pureBlack ? _value.pureBlack
: pureBlack // ignore: cast_nullable_to_non_nullable : pureBlack // ignore: cast_nullable_to_non_nullable
as bool, as bool,
textScale: null == textScale
? _value.textScale
: textScale // ignore: cast_nullable_to_non_nullable
as TextScale,
)); ));
} }
} }
@@ -1863,18 +2078,18 @@ class __$$ThemePropsImplCopyWithImpl<$Res>
@JsonSerializable() @JsonSerializable()
class _$ThemePropsImpl implements _ThemeProps { class _$ThemePropsImpl implements _ThemeProps {
const _$ThemePropsImpl( const _$ThemePropsImpl(
{this.primaryColor = defaultPrimaryColor, {this.primaryColor,
final List<int> primaryColors = defaultPrimaryColors, final List<int> primaryColors = defaultPrimaryColors,
this.themeMode = ThemeMode.dark, this.themeMode = ThemeMode.dark,
this.schemeVariant = DynamicSchemeVariant.tonalSpot, this.schemeVariant = DynamicSchemeVariant.tonalSpot,
this.pureBlack = false}) this.pureBlack = false,
this.textScale = const TextScale()})
: _primaryColors = primaryColors; : _primaryColors = primaryColors;
factory _$ThemePropsImpl.fromJson(Map<String, dynamic> json) => factory _$ThemePropsImpl.fromJson(Map<String, dynamic> json) =>
_$$ThemePropsImplFromJson(json); _$$ThemePropsImplFromJson(json);
@override @override
@JsonKey()
final int? primaryColor; final int? primaryColor;
final List<int> _primaryColors; final List<int> _primaryColors;
@override @override
@@ -1894,10 +2109,13 @@ class _$ThemePropsImpl implements _ThemeProps {
@override @override
@JsonKey() @JsonKey()
final bool pureBlack; final bool pureBlack;
@override
@JsonKey()
final TextScale textScale;
@override @override
String toString() { String toString() {
return 'ThemeProps(primaryColor: $primaryColor, primaryColors: $primaryColors, themeMode: $themeMode, schemeVariant: $schemeVariant, pureBlack: $pureBlack)'; return 'ThemeProps(primaryColor: $primaryColor, primaryColors: $primaryColors, themeMode: $themeMode, schemeVariant: $schemeVariant, pureBlack: $pureBlack, textScale: $textScale)';
} }
@override @override
@@ -1914,7 +2132,9 @@ class _$ThemePropsImpl implements _ThemeProps {
(identical(other.schemeVariant, schemeVariant) || (identical(other.schemeVariant, schemeVariant) ||
other.schemeVariant == schemeVariant) && other.schemeVariant == schemeVariant) &&
(identical(other.pureBlack, pureBlack) || (identical(other.pureBlack, pureBlack) ||
other.pureBlack == pureBlack)); other.pureBlack == pureBlack) &&
(identical(other.textScale, textScale) ||
other.textScale == textScale));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -1925,7 +2145,8 @@ class _$ThemePropsImpl implements _ThemeProps {
const DeepCollectionEquality().hash(_primaryColors), const DeepCollectionEquality().hash(_primaryColors),
themeMode, themeMode,
schemeVariant, schemeVariant,
pureBlack); pureBlack,
textScale);
/// Create a copy of ThemeProps /// Create a copy of ThemeProps
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -1949,7 +2170,8 @@ abstract class _ThemeProps implements ThemeProps {
final List<int> primaryColors, final List<int> primaryColors,
final ThemeMode themeMode, final ThemeMode themeMode,
final DynamicSchemeVariant schemeVariant, final DynamicSchemeVariant schemeVariant,
final bool pureBlack}) = _$ThemePropsImpl; final bool pureBlack,
final TextScale textScale}) = _$ThemePropsImpl;
factory _ThemeProps.fromJson(Map<String, dynamic> json) = factory _ThemeProps.fromJson(Map<String, dynamic> json) =
_$ThemePropsImpl.fromJson; _$ThemePropsImpl.fromJson;
@@ -1964,6 +2186,8 @@ abstract class _ThemeProps implements ThemeProps {
DynamicSchemeVariant get schemeVariant; DynamicSchemeVariant get schemeVariant;
@override @override
bool get pureBlack; bool get pureBlack;
@override
TextScale get textScale;
/// Create a copy of ThemeProps /// Create a copy of ThemeProps
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -1988,6 +2212,7 @@ mixin _$Config {
DAV? get dav => throw _privateConstructorUsedError; DAV? get dav => throw _privateConstructorUsedError;
NetworkProps get networkProps => throw _privateConstructorUsedError; NetworkProps get networkProps => throw _privateConstructorUsedError;
VpnProps get vpnProps => throw _privateConstructorUsedError; VpnProps get vpnProps => throw _privateConstructorUsedError;
@JsonKey(fromJson: ThemeProps.safeFromJson)
ThemeProps get themeProps => throw _privateConstructorUsedError; ThemeProps get themeProps => throw _privateConstructorUsedError;
ProxiesStyle get proxiesStyle => throw _privateConstructorUsedError; ProxiesStyle get proxiesStyle => throw _privateConstructorUsedError;
WindowProps get windowProps => throw _privateConstructorUsedError; WindowProps get windowProps => throw _privateConstructorUsedError;
@@ -2017,7 +2242,7 @@ abstract class $ConfigCopyWith<$Res> {
DAV? dav, DAV? dav,
NetworkProps networkProps, NetworkProps networkProps,
VpnProps vpnProps, VpnProps vpnProps,
ThemeProps themeProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps,
ProxiesStyle proxiesStyle, ProxiesStyle proxiesStyle,
WindowProps windowProps, WindowProps windowProps,
ClashConfig patchClashConfig}); ClashConfig patchClashConfig});
@@ -2214,7 +2439,7 @@ abstract class _$$ConfigImplCopyWith<$Res> implements $ConfigCopyWith<$Res> {
DAV? dav, DAV? dav,
NetworkProps networkProps, NetworkProps networkProps,
VpnProps vpnProps, VpnProps vpnProps,
ThemeProps themeProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps,
ProxiesStyle proxiesStyle, ProxiesStyle proxiesStyle,
WindowProps windowProps, WindowProps windowProps,
ClashConfig patchClashConfig}); ClashConfig patchClashConfig});
@@ -2329,7 +2554,7 @@ class _$ConfigImpl implements _Config {
this.dav, this.dav,
this.networkProps = defaultNetworkProps, this.networkProps = defaultNetworkProps,
this.vpnProps = defaultVpnProps, this.vpnProps = defaultVpnProps,
this.themeProps = defaultThemeProps, @JsonKey(fromJson: ThemeProps.safeFromJson) required this.themeProps,
this.proxiesStyle = defaultProxiesStyle, this.proxiesStyle = defaultProxiesStyle,
this.windowProps = defaultWindowProps, this.windowProps = defaultWindowProps,
this.patchClashConfig = defaultClashConfig}) this.patchClashConfig = defaultClashConfig})
@@ -2374,7 +2599,7 @@ class _$ConfigImpl implements _Config {
@JsonKey() @JsonKey()
final VpnProps vpnProps; final VpnProps vpnProps;
@override @override
@JsonKey() @JsonKey(fromJson: ThemeProps.safeFromJson)
final ThemeProps themeProps; final ThemeProps themeProps;
@override @override
@JsonKey() @JsonKey()
@@ -2464,7 +2689,8 @@ abstract class _Config implements Config {
final DAV? dav, final DAV? dav,
final NetworkProps networkProps, final NetworkProps networkProps,
final VpnProps vpnProps, final VpnProps vpnProps,
final ThemeProps themeProps, @JsonKey(fromJson: ThemeProps.safeFromJson)
required final ThemeProps themeProps,
final ProxiesStyle proxiesStyle, final ProxiesStyle proxiesStyle,
final WindowProps windowProps, final WindowProps windowProps,
final ClashConfig patchClashConfig}) = _$ConfigImpl; final ClashConfig patchClashConfig}) = _$ConfigImpl;
@@ -2489,6 +2715,7 @@ abstract class _Config implements Config {
@override @override
VpnProps get vpnProps; VpnProps get vpnProps;
@override @override
@JsonKey(fromJson: ThemeProps.safeFromJson)
ThemeProps get themeProps; ThemeProps get themeProps;
@override @override
ProxiesStyle get proxiesStyle; ProxiesStyle get proxiesStyle;

View File

@@ -26,6 +26,7 @@ _$AppSettingPropsImpl _$$AppSettingPropsImplFromJson(
disclaimerAccepted: json['disclaimerAccepted'] as bool? ?? false, disclaimerAccepted: json['disclaimerAccepted'] as bool? ?? false,
minimizeOnExit: json['minimizeOnExit'] as bool? ?? true, minimizeOnExit: json['minimizeOnExit'] as bool? ?? true,
hidden: json['hidden'] as bool? ?? false, hidden: json['hidden'] as bool? ?? false,
developerMode: json['developerMode'] as bool? ?? false,
); );
Map<String, dynamic> _$$AppSettingPropsImplToJson( Map<String, dynamic> _$$AppSettingPropsImplToJson(
@@ -48,10 +49,12 @@ Map<String, dynamic> _$$AppSettingPropsImplToJson(
'disclaimerAccepted': instance.disclaimerAccepted, 'disclaimerAccepted': instance.disclaimerAccepted,
'minimizeOnExit': instance.minimizeOnExit, 'minimizeOnExit': instance.minimizeOnExit,
'hidden': instance.hidden, 'hidden': instance.hidden,
'developerMode': instance.developerMode,
}; };
const _$DashboardWidgetEnumMap = { const _$DashboardWidgetEnumMap = {
DashboardWidget.networkSpeed: 'networkSpeed', DashboardWidget.networkSpeed: 'networkSpeed',
DashboardWidget.outboundModeV2: 'outboundModeV2',
DashboardWidget.outboundMode: 'outboundMode', DashboardWidget.outboundMode: 'outboundMode',
DashboardWidget.trafficUsage: 'trafficUsage', DashboardWidget.trafficUsage: 'trafficUsage',
DashboardWidget.networkDetection: 'networkDetection', DashboardWidget.networkDetection: 'networkDetection',
@@ -219,10 +222,21 @@ const _$ProxyCardTypeEnumMap = {
ProxyCardType.min: 'min', ProxyCardType.min: 'min',
}; };
_$TextScaleImpl _$$TextScaleImplFromJson(Map<String, dynamic> json) =>
_$TextScaleImpl(
enable: json['enable'] ?? false,
scale: json['scale'] ?? 1.0,
);
Map<String, dynamic> _$$TextScaleImplToJson(_$TextScaleImpl instance) =>
<String, dynamic>{
'enable': instance.enable,
'scale': instance.scale,
};
_$ThemePropsImpl _$$ThemePropsImplFromJson(Map<String, dynamic> json) => _$ThemePropsImpl _$$ThemePropsImplFromJson(Map<String, dynamic> json) =>
_$ThemePropsImpl( _$ThemePropsImpl(
primaryColor: primaryColor: (json['primaryColor'] as num?)?.toInt(),
(json['primaryColor'] as num?)?.toInt() ?? defaultPrimaryColor,
primaryColors: (json['primaryColors'] as List<dynamic>?) primaryColors: (json['primaryColors'] as List<dynamic>?)
?.map((e) => (e as num).toInt()) ?.map((e) => (e as num).toInt())
.toList() ?? .toList() ??
@@ -233,6 +247,9 @@ _$ThemePropsImpl _$$ThemePropsImplFromJson(Map<String, dynamic> json) =>
_$DynamicSchemeVariantEnumMap, json['schemeVariant']) ?? _$DynamicSchemeVariantEnumMap, json['schemeVariant']) ??
DynamicSchemeVariant.tonalSpot, DynamicSchemeVariant.tonalSpot,
pureBlack: json['pureBlack'] as bool? ?? false, pureBlack: json['pureBlack'] as bool? ?? false,
textScale: json['textScale'] == null
? const TextScale()
: TextScale.fromJson(json['textScale'] as Map<String, dynamic>),
); );
Map<String, dynamic> _$$ThemePropsImplToJson(_$ThemePropsImpl instance) => Map<String, dynamic> _$$ThemePropsImplToJson(_$ThemePropsImpl instance) =>
@@ -242,6 +259,7 @@ Map<String, dynamic> _$$ThemePropsImplToJson(_$ThemePropsImpl instance) =>
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!, 'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
'schemeVariant': _$DynamicSchemeVariantEnumMap[instance.schemeVariant]!, 'schemeVariant': _$DynamicSchemeVariantEnumMap[instance.schemeVariant]!,
'pureBlack': instance.pureBlack, 'pureBlack': instance.pureBlack,
'textScale': instance.textScale,
}; };
const _$ThemeModeEnumMap = { const _$ThemeModeEnumMap = {
@@ -287,9 +305,8 @@ _$ConfigImpl _$$ConfigImplFromJson(Map<String, dynamic> json) => _$ConfigImpl(
vpnProps: json['vpnProps'] == null vpnProps: json['vpnProps'] == null
? defaultVpnProps ? defaultVpnProps
: VpnProps.fromJson(json['vpnProps'] as Map<String, dynamic>?), : VpnProps.fromJson(json['vpnProps'] as Map<String, dynamic>?),
themeProps: json['themeProps'] == null themeProps:
? defaultThemeProps ThemeProps.safeFromJson(json['themeProps'] as Map<String, Object?>?),
: ThemeProps.fromJson(json['themeProps'] as Map<String, dynamic>?),
proxiesStyle: json['proxiesStyle'] == null proxiesStyle: json['proxiesStyle'] == null
? defaultProxiesStyle ? defaultProxiesStyle
: ProxiesStyle.fromJson( : ProxiesStyle.fromJson(

View File

@@ -345,6 +345,7 @@ const _$ActionMethodEnumMap = {
ActionMethod.getCountryCode: 'getCountryCode', ActionMethod.getCountryCode: 'getCountryCode',
ActionMethod.getMemory: 'getMemory', ActionMethod.getMemory: 'getMemory',
ActionMethod.getProfile: 'getProfile', ActionMethod.getProfile: 'getProfile',
ActionMethod.crash: 'crash',
ActionMethod.setFdMap: 'setFdMap', ActionMethod.setFdMap: 'setFdMap',
ActionMethod.setProcessMap: 'setProcessMap', ActionMethod.setProcessMap: 'setProcessMap',
ActionMethod.setState: 'setState', ActionMethod.setState: 'setState',

View File

@@ -129,6 +129,16 @@ class _HomePageViewState extends ConsumerState<_HomePageView> {
controller: _pageController, controller: _pageController,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: navigationItems.length, itemCount: navigationItems.length,
// onPageChanged: (index) {
// debouncer.call(DebounceTag.pageChange, () {
// WidgetsBinding.instance.addPostFrameCallback((_) {
// if (_pageIndex != index) {
// final pageLabel = navigationItems[index].label;
// _toPage(pageLabel, true);
// }
// });
// });
// },
itemBuilder: (_, index) { itemBuilder: (_, index) {
final navigationItem = navigationItems[index]; final navigationItem = navigationItems[index];
return KeepScope( return KeepScope(

View File

@@ -252,21 +252,6 @@ class CurrentPageLabel extends _$CurrentPageLabel
} }
} }
@riverpod
class AppSchemes extends _$AppSchemes with AutoDisposeNotifierMixin {
@override
ColorSchemes build() {
return globalState.appState.colorSchemes;
}
@override
onUpdate(value) {
globalState.appState = globalState.appState.copyWith(
colorSchemes: value,
);
}
}
@riverpod @riverpod
class SortNum extends _$SortNum with AutoDisposeNotifierMixin { class SortNum extends _$SortNum with AutoDisposeNotifierMixin {
@override @override

View File

@@ -247,21 +247,6 @@ final currentPageLabelProvider =
); );
typedef _$CurrentPageLabel = AutoDisposeNotifier<PageLabel>; typedef _$CurrentPageLabel = AutoDisposeNotifier<PageLabel>;
String _$appSchemesHash() => r'748f48f23539a879a92f318a21e1266b1df56aae';
/// See also [AppSchemes].
@ProviderFor(AppSchemes)
final appSchemesProvider =
AutoDisposeNotifierProvider<AppSchemes, ColorSchemes>.internal(
AppSchemes.new,
name: r'appSchemesProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$appSchemesHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AppSchemes = AutoDisposeNotifier<ColorSchemes>;
String _$sortNumHash() => r'0f85ebbc77124020eaccf988c6ac9d86a7f34d7e'; String _$sortNumHash() => r'0f85ebbc77124020eaccf988c6ac9d86a7f34d7e';
/// See also [SortNum]. /// See also [SortNum].

View File

@@ -1765,7 +1765,7 @@ class _GetProfileOverrideDataProviderElement
String get profileId => (origin as GetProfileOverrideDataProvider).profileId; String get profileId => (origin as GetProfileOverrideDataProvider).profileId;
} }
String _$genColorSchemeHash() => r'a27ccae9b5c11d47cd46804f42f8e9dc7946a6c2'; String _$genColorSchemeHash() => r'b18f15c938a8132ee4ed02cdfc02f3b9f01724e2';
/// See also [genColorScheme]. /// See also [genColorScheme].
@ProviderFor(genColorScheme) @ProviderFor(genColorScheme)
@@ -1780,12 +1780,12 @@ class GenColorSchemeFamily extends Family<ColorScheme> {
GenColorSchemeProvider call( GenColorSchemeProvider call(
Brightness brightness, { Brightness brightness, {
Color? color, Color? color,
bool isOverride = false, bool ignoreConfig = false,
}) { }) {
return GenColorSchemeProvider( return GenColorSchemeProvider(
brightness, brightness,
color: color, color: color,
isOverride: isOverride, ignoreConfig: ignoreConfig,
); );
} }
@@ -1796,7 +1796,7 @@ class GenColorSchemeFamily extends Family<ColorScheme> {
return call( return call(
provider.brightness, provider.brightness,
color: provider.color, color: provider.color,
isOverride: provider.isOverride, ignoreConfig: provider.ignoreConfig,
); );
} }
@@ -1821,13 +1821,13 @@ class GenColorSchemeProvider extends AutoDisposeProvider<ColorScheme> {
GenColorSchemeProvider( GenColorSchemeProvider(
Brightness brightness, { Brightness brightness, {
Color? color, Color? color,
bool isOverride = false, bool ignoreConfig = false,
}) : this._internal( }) : this._internal(
(ref) => genColorScheme( (ref) => genColorScheme(
ref as GenColorSchemeRef, ref as GenColorSchemeRef,
brightness, brightness,
color: color, color: color,
isOverride: isOverride, ignoreConfig: ignoreConfig,
), ),
from: genColorSchemeProvider, from: genColorSchemeProvider,
name: r'genColorSchemeProvider', name: r'genColorSchemeProvider',
@@ -1840,7 +1840,7 @@ class GenColorSchemeProvider extends AutoDisposeProvider<ColorScheme> {
GenColorSchemeFamily._allTransitiveDependencies, GenColorSchemeFamily._allTransitiveDependencies,
brightness: brightness, brightness: brightness,
color: color, color: color,
isOverride: isOverride, ignoreConfig: ignoreConfig,
); );
GenColorSchemeProvider._internal( GenColorSchemeProvider._internal(
@@ -1852,12 +1852,12 @@ class GenColorSchemeProvider extends AutoDisposeProvider<ColorScheme> {
required super.from, required super.from,
required this.brightness, required this.brightness,
required this.color, required this.color,
required this.isOverride, required this.ignoreConfig,
}) : super.internal(); }) : super.internal();
final Brightness brightness; final Brightness brightness;
final Color? color; final Color? color;
final bool isOverride; final bool ignoreConfig;
@override @override
Override overrideWith( Override overrideWith(
@@ -1874,7 +1874,7 @@ class GenColorSchemeProvider extends AutoDisposeProvider<ColorScheme> {
debugGetCreateSourceHash: null, debugGetCreateSourceHash: null,
brightness: brightness, brightness: brightness,
color: color, color: color,
isOverride: isOverride, ignoreConfig: ignoreConfig,
), ),
); );
} }
@@ -1889,7 +1889,7 @@ class GenColorSchemeProvider extends AutoDisposeProvider<ColorScheme> {
return other is GenColorSchemeProvider && return other is GenColorSchemeProvider &&
other.brightness == brightness && other.brightness == brightness &&
other.color == color && other.color == color &&
other.isOverride == isOverride; other.ignoreConfig == ignoreConfig;
} }
@override @override
@@ -1897,7 +1897,7 @@ class GenColorSchemeProvider extends AutoDisposeProvider<ColorScheme> {
var hash = _SystemHash.combine(0, runtimeType.hashCode); var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, brightness.hashCode); hash = _SystemHash.combine(hash, brightness.hashCode);
hash = _SystemHash.combine(hash, color.hashCode); hash = _SystemHash.combine(hash, color.hashCode);
hash = _SystemHash.combine(hash, isOverride.hashCode); hash = _SystemHash.combine(hash, ignoreConfig.hashCode);
return _SystemHash.finish(hash); return _SystemHash.finish(hash);
} }
@@ -1912,8 +1912,8 @@ mixin GenColorSchemeRef on AutoDisposeProviderRef<ColorScheme> {
/// The parameter `color` of this provider. /// The parameter `color` of this provider.
Color? get color; Color? get color;
/// The parameter `isOverride` of this provider. /// The parameter `ignoreConfig` of this provider.
bool get isOverride; bool get ignoreConfig;
} }
class _GenColorSchemeProviderElement class _GenColorSchemeProviderElement
@@ -1925,7 +1925,7 @@ class _GenColorSchemeProviderElement
@override @override
Color? get color => (origin as GenColorSchemeProvider).color; Color? get color => (origin as GenColorSchemeProvider).color;
@override @override
bool get isOverride => (origin as GenColorSchemeProvider).isOverride; bool get ignoreConfig => (origin as GenColorSchemeProvider).ignoreConfig;
} }
String _$profileOverrideStateHash() => String _$profileOverrideStateHash() =>

View File

@@ -1,6 +1,8 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -511,7 +513,7 @@ ColorScheme genColorScheme(
Ref ref, Ref ref,
Brightness brightness, { Brightness brightness, {
Color? color, Color? color,
bool isOverride = false, bool ignoreConfig = false,
}) { }) {
final vm2 = ref.watch( final vm2 = ref.watch(
themeSettingProvider.select( themeSettingProvider.select(
@@ -521,11 +523,17 @@ ColorScheme genColorScheme(
), ),
), ),
); );
if (color == null && (isOverride == true || vm2.a == null)) { if (color == null && (ignoreConfig == true || vm2.a == null)) {
final colorSchemes = ref.watch(appSchemesProvider); // if (globalState.corePalette != null) {
return colorSchemes.getColorSchemeForBrightness( // return globalState.corePalette!.toColorScheme(brightness: brightness);
brightness, // }
vm2.b, return ColorScheme.fromSeed(
seedColor: globalState.corePalette
?.toColorScheme(brightness: brightness)
.primary ??
globalState.accentColor,
brightness: brightness,
dynamicSchemeVariant: vm2.b,
); );
} }
return ColorScheme.fromSeed( return ColorScheme.fromSeed(

View File

@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:animations/animations.dart'; import 'package:animations/animations.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/theme.dart'; import 'package:fl_clash/common/theme.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
@@ -9,6 +10,7 @@ import 'package:fl_clash/plugins/service.dart';
import 'package:fl_clash/widgets/dialog.dart'; import 'package:fl_clash/widgets/dialog.dart';
import 'package:fl_clash/widgets/scaffold.dart'; import 'package:fl_clash/widgets/scaffold.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:material_color_utilities/palettes/core_palette.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@@ -21,6 +23,7 @@ typedef UpdateTasks = List<FutureOr Function()>;
class GlobalState { class GlobalState {
static GlobalState? _instance; static GlobalState? _instance;
Map<Key, double> cacheScrollPosition = {}; Map<Key, double> cacheScrollPosition = {};
Map<Key, FixedMap<String, double>> cacheHeightMap = {};
bool isService = false; bool isService = false;
Timer? timer; Timer? timer;
Timer? groupsUpdateTimer; Timer? groupsUpdateTimer;
@@ -31,6 +34,8 @@ class GlobalState {
Function? updateCurrentDelayDebounce; Function? updateCurrentDelayDebounce;
late Measure measure; late Measure measure;
late CommonTheme theme; late CommonTheme theme;
late Color accentColor;
CorePalette? corePalette;
DateTime? startTime; DateTime? startTime;
UpdateTasks tasks = []; UpdateTasks tasks = [];
final navigatorKey = GlobalKey<NavigatorState>(); final navigatorKey = GlobalKey<NavigatorState>();
@@ -55,9 +60,18 @@ class GlobalState {
traffics: FixedList(30), traffics: FixedList(30),
totalTraffic: Traffic(), totalTraffic: Traffic(),
); );
await _initDynamicColor();
await init(); await init();
} }
_initDynamicColor() async {
try {
corePalette = await DynamicColorPlugin.getCorePalette();
accentColor = await DynamicColorPlugin.getAccentColor() ??
Color(defaultPrimaryColor);
} catch (_) {}
}
init() async { init() async {
packageInfo = await PackageInfo.fromPlatform(); packageInfo = await PackageInfo.fromPlatform();
config = await preferences.getConfig() ?? config = await preferences.getConfig() ??

View File

@@ -103,7 +103,7 @@ class PrimaryColorBox extends ConsumerWidget {
genColorSchemeProvider( genColorSchemeProvider(
themeData.brightness, themeData.brightness,
color: primaryColor, color: primaryColor,
isOverride: true, ignoreConfig: true,
), ),
); );
return Theme( return Theme(

View File

@@ -0,0 +1,73 @@
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class CommonSafeArea extends StatelessWidget {
const CommonSafeArea({
super.key,
this.left = true,
this.top = true,
this.right = true,
this.bottom = true,
this.minimum = EdgeInsets.zero,
this.maintainBottomViewPadding = false,
required this.child,
});
final bool left;
final bool top;
final bool right;
final bool bottom;
final EdgeInsets minimum;
final bool maintainBottomViewPadding;
final Widget child;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
EdgeInsets padding = MediaQuery.paddingOf(context);
final height = MediaQuery.of(context).size.height;
if (maintainBottomViewPadding) {
padding = padding.copyWith(
bottom: MediaQuery.viewPaddingOf(context).bottom,
);
}
final double realPaddingTop = padding.top > height * 0.5 ? 0 : padding.top;
return Padding(
padding: EdgeInsets.only(
left: math.max(left ? padding.left : 0.0, minimum.left),
top: math.max(top ? realPaddingTop : 0.0, minimum.top),
right: math.max(right ? padding.right : 0.0, minimum.right),
bottom: math.max(bottom ? padding.bottom : 0.0, minimum.bottom),
),
child: MediaQuery.removePadding(
context: context,
removeLeft: left,
removeTop: top,
removeRight: right,
removeBottom: bottom,
child: child,
),
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
.add(FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
properties
.add(FlagProperty('top', value: top, ifTrue: 'avoid top padding'));
properties.add(
FlagProperty('right', value: right, ifTrue: 'avoid right padding'));
properties.add(
FlagProperty('bottom', value: bottom, ifTrue: 'avoid bottom padding'));
}
}

View File

@@ -137,7 +137,7 @@ class DonutChartPainter extends CustomPainter {
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2); final center = Offset(size.width / 2, size.height / 2);
const strokeWidth = 10.0; final strokeWidth = 10.0.ap;
final radius = min(size.width / 2, size.height / 2) - strokeWidth / 2; final radius = min(size.width / 2, size.height / 2) - strokeWidth / 2;
final gapAngle = 2 * asin(strokeWidth * 1 / (2 * radius)) * 1.2; final gapAngle = 2 * asin(strokeWidth * 1 / (2 * radius)) * 1.2;

View File

@@ -36,11 +36,13 @@ class FadeBox extends StatelessWidget {
class FadeThroughBox extends StatelessWidget { class FadeThroughBox extends StatelessWidget {
final Widget child; final Widget child;
final Alignment? alignment; final Alignment? alignment;
final EdgeInsets? margin;
const FadeThroughBox({ const FadeThroughBox({
super.key, super.key,
required this.child, required this.child,
this.alignment, this.alignment,
this.margin
}); });
@override @override
@@ -52,6 +54,7 @@ class FadeThroughBox extends StatelessWidget {
secondaryAnimation, secondaryAnimation,
) { ) {
return Container( return Container(
margin: margin,
alignment: alignment ?? Alignment.centerLeft, alignment: alignment ?? Alignment.centerLeft,
child: FadeThroughTransition( child: FadeThroughTransition(
animation: animation, animation: animation,

View File

@@ -62,6 +62,22 @@ class OpenDelegate extends Delegate {
}); });
} }
class NextDelegate extends Delegate {
final Widget widget;
final String title;
final double? maxWidth;
final Widget? action;
final bool blur;
const NextDelegate({
required this.title,
required this.widget,
this.maxWidth,
this.action,
this.blur = true,
});
}
class OptionsDelegate<T> extends Delegate { class OptionsDelegate<T> extends Delegate {
final List<T> options; final List<T> options;
final String title; final String title;
@@ -138,6 +154,21 @@ class ListItem<T> extends StatelessWidget {
this.tileTitleAlignment = ListTileTitleAlignment.center, this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : onTap = null; }) : onTap = null;
const ListItem.next({
super.key,
required this.title,
this.subtitle,
this.leading,
this.padding = const EdgeInsets.symmetric(horizontal: 16),
this.trailing,
required NextDelegate this.delegate,
this.horizontalTitleGap,
this.dense,
this.titleTextStyle,
this.subtitleTextStyle,
this.tileTitleAlignment = ListTileTitleAlignment.center,
}) : onTap = null;
const ListItem.options({ const ListItem.options({
super.key, super.key,
required this.title, required this.title,
@@ -285,6 +316,34 @@ class ListItem<T> extends StatelessWidget {
}, },
); );
} }
if (delegate is NextDelegate) {
final nextDelegate = delegate as NextDelegate;
final child = SafeArea(
child: nextDelegate.widget,
);
return _buildListTile(
onTap: () {
showExtend(
context,
props: ExtendProps(
blur: nextDelegate.blur,
maxWidth: nextDelegate.maxWidth,
),
builder: (_, type) {
return AdaptiveSheetScaffold(
actions: [
if (nextDelegate.action != null) nextDelegate.action!,
],
type: type,
body: child,
title: nextDelegate.title,
);
},
);
},
);
}
if (delegate is OptionsDelegate) { if (delegate is OptionsDelegate) {
final optionsDelegate = delegate as OptionsDelegate<T>; final optionsDelegate = delegate as OptionsDelegate<T>;
return _buildListTile( return _buildListTile(
@@ -353,14 +412,11 @@ class ListItem<T> extends StatelessWidget {
radioDelegate.onChanged!(radioDelegate.value); radioDelegate.onChanged!(radioDelegate.value);
} }
}, },
leading: SizedBox( leading: Radio<T>(
width: 32, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
height: 32, value: radioDelegate.value,
child: Radio<T>( groupValue: radioDelegate.groupValue,
value: radioDelegate.value, onChanged: radioDelegate.onChanged,
groupValue: radioDelegate.groupValue,
onChanged: radioDelegate.onChanged,
),
), ),
trailing: trailing, trailing: trailing,
); );
@@ -466,6 +522,32 @@ List<Widget> generateSection({
]; ];
} }
Widget generateSectionV2({
String? title,
required Iterable<Widget> items,
List<Widget>? actions,
bool separated = true,
}) {
return Column(
children: [
if (items.isNotEmpty && title != null)
ListHeader(
title: title,
actions: actions,
),
CommonCard(
radius: 18,
type: CommonCardType.filled,
child: Column(
children: [
...items,
],
),
)
],
);
}
List<Widget> generateInfoSection({ List<Widget> generateInfoSection({
required Info info, required Info info,
required Iterable<Widget> items, required Iterable<Widget> items,
@@ -497,4 +579,4 @@ Widget generateListView(List<Widget> items) {
bottom: 16, bottom: 16,
), ),
); );
} }

View File

@@ -0,0 +1,33 @@
import 'package:fl_clash/models/config.dart';
import 'package:fl_clash/providers/config.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class TextScaleNotification extends StatelessWidget {
final Widget child;
final Function(TextScale textScale) onNotification;
const TextScaleNotification({
super.key,
required this.child,
required this.onNotification,
});
@override
Widget build(BuildContext context) {
return Consumer(
builder: (_, ref, __) {
ref.listen(
themeSettingProvider.select((state) => state.textScale),
(prev, next) {
if (prev != next) {
onNotification(next);
}
},
);
return child;
},
child: child,
);
}
}

View File

@@ -125,25 +125,25 @@ class CommonScaffoldState extends State<CommonScaffold> {
} }
} }
ThemeData _appBarTheme(BuildContext context) { Widget _buildSearchingAppBarTheme(Widget child) {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme; final ColorScheme colorScheme = theme.colorScheme;
return theme.copyWith( return Theme(
appBarTheme: AppBarTheme( data: theme.copyWith(
systemOverlayStyle: colorScheme.brightness == Brightness.dark appBarTheme: theme.appBarTheme.copyWith(
? SystemUiOverlayStyle.light backgroundColor: colorScheme.brightness == Brightness.dark
: SystemUiOverlayStyle.dark, ? Colors.grey[900]
backgroundColor: colorScheme.brightness == Brightness.dark : Colors.white,
? Colors.grey[900] iconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
: Colors.white, titleTextStyle: theme.textTheme.titleLarge,
iconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey), toolbarTextStyle: theme.textTheme.bodyMedium,
titleTextStyle: theme.textTheme.titleLarge, ),
toolbarTextStyle: theme.textTheme.bodyMedium, inputDecorationTheme: InputDecorationTheme(
), hintStyle: theme.inputDecorationTheme.hintStyle,
inputDecorationTheme: InputDecorationTheme( border: InputBorder.none,
hintStyle: theme.inputDecorationTheme.hintStyle, ),
border: InputBorder.none,
), ),
child: child,
); );
} }
@@ -318,72 +318,66 @@ class CommonScaffoldState extends State<CommonScaffold> {
child: appBar, child: appBar,
); );
} }
return _isSearch return _isSearch ? _buildSearchingAppBarTheme(appBar) : appBar;
? Theme(
data: _appBarTheme(context),
child: CommonPopScope(
onPop: () {
if (_isSearch) {
_handleExitSearching();
return false;
}
return true;
},
child: appBar,
),
)
: appBar;
} }
PreferredSizeWidget _buildAppBar() { PreferredSizeWidget _buildAppBar() {
return PreferredSize( return PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight), preferredSize: const Size.fromHeight(kToolbarHeight),
child: Stack( child: Theme(
alignment: Alignment.bottomCenter, data: Theme.of(context).copyWith(
children: [ appBarTheme: AppBarTheme(
ValueListenableBuilder<AppBarState>( systemOverlayStyle: SystemUiOverlayStyle(
valueListenable: _appBarState, statusBarColor: Colors.transparent,
builder: (_, state, __) { statusBarIconBrightness:
return _buildAppBarWrap( Theme.of(context).brightness == Brightness.dark
AppBar( ? Brightness.light
centerTitle: widget.centerTitle ?? false, : Brightness.dark,
systemOverlayStyle: SystemUiOverlayStyle( systemNavigationBarIconBrightness:
statusBarColor: Colors.transparent, Theme.of(context).brightness == Brightness.dark
statusBarIconBrightness: ? Brightness.light
Theme.of(context).brightness == Brightness.dark : Brightness.dark,
? Brightness.light systemNavigationBarColor: widget.bottomNavigationBar != null
: Brightness.dark, ? context.colorScheme.surfaceContainer
systemNavigationBarIconBrightness: : context.colorScheme.surface,
Theme.of(context).brightness == Brightness.dark systemNavigationBarDividerColor: Colors.transparent,
? Brightness.light ),
: Brightness.dark, ),
systemNavigationBarColor: widget.bottomNavigationBar != null ),
? context.colorScheme.surfaceContainer child: widget.appBar ??
: context.colorScheme.surface, Stack(
systemNavigationBarDividerColor: Colors.transparent, alignment: Alignment.bottomCenter,
), children: [
automaticallyImplyLeading: widget.automaticallyImplyLeading, ValueListenableBuilder<AppBarState>(
leading: _buildLeading(), valueListenable: _appBarState,
title: _buildTitle(state.searchState), builder: (_, state, __) {
actions: _buildActions( return _buildAppBarWrap(
state.searchState != null, AppBar(
state.actions.isNotEmpty centerTitle: widget.centerTitle ?? false,
? state.actions automaticallyImplyLeading:
: widget.actions ?? [], widget.automaticallyImplyLeading,
), leading: _buildLeading(),
title: _buildTitle(state.searchState),
actions: _buildActions(
state.searchState != null,
state.actions.isNotEmpty
? state.actions
: widget.actions ?? [],
),
),
);
},
), ),
); ValueListenableBuilder(
}, valueListenable: _loading,
), builder: (_, value, __) {
ValueListenableBuilder( return value == true
valueListenable: _loading, ? const LinearProgressIndicator()
builder: (_, value, __) { : Container();
return value == true },
? const LinearProgressIndicator() ),
: Container(); ],
}, ),
),
],
), ),
); );
} }
@@ -391,56 +385,62 @@ class CommonScaffoldState extends State<CommonScaffold> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(widget.appBar != null || widget.title != null); assert(widget.appBar != null || widget.title != null);
final body = Column( final body = SafeArea(
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
ValueListenableBuilder( children: [
valueListenable: _keywordsNotifier, ValueListenableBuilder(
builder: (_, keywords, __) { valueListenable: _keywordsNotifier,
WidgetsBinding.instance.addPostFrameCallback((_) { builder: (_, keywords, __) {
if (_onKeywordsUpdate != null) { WidgetsBinding.instance.addPostFrameCallback((_) {
_onKeywordsUpdate!(keywords); if (_onKeywordsUpdate != null) {
_onKeywordsUpdate!(keywords);
}
});
if (keywords.isEmpty) {
return SizedBox();
} }
}); return Padding(
if (keywords.isEmpty) { padding: const EdgeInsets.symmetric(
return SizedBox(); horizontal: 16,
} vertical: 16,
return Padding( ),
padding: const EdgeInsets.symmetric( child: Wrap(
horizontal: 16, runSpacing: 8,
vertical: 16, spacing: 8,
), children: [
child: Wrap( for (final keyword in keywords)
runSpacing: 8, CommonChip(
spacing: 8, label: keyword,
children: [ type: ChipType.delete,
for (final keyword in keywords) onPressed: () {
CommonChip( _deleteKeyword(keyword);
label: keyword, },
type: ChipType.delete, ),
onPressed: () { ],
_deleteKeyword(keyword); ),
}, );
), },
], ),
), Expanded(
); child: widget.body,
}, ),
), ],
Expanded( ),
child: widget.body,
),
],
); );
final scaffold = Scaffold( final scaffold = Scaffold(
appBar: widget.appBar ?? _buildAppBar(), appBar: _buildAppBar(),
body: body, body: body,
backgroundColor: widget.backgroundColor, backgroundColor: widget.backgroundColor,
floatingActionButton: ValueListenableBuilder<Widget?>( floatingActionButton: ValueListenableBuilder<Widget?>(
valueListenable: _floatingActionButton, valueListenable: _floatingActionButton,
builder: (_, value, __) { builder: (_, value, __) {
return FadeScaleBox( return IntrinsicWidth(
child: value ?? SizedBox(), child: IntrinsicHeight(
child: FadeScaleBox(
child: value ?? SizedBox(),
),
),
); );
}, },
), ),

View File

@@ -369,6 +369,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
} }
_handleDelete(int index) async { _handleDelete(int index) async {
await _transformCompleter?.future;
_preTransformState(); _preTransformState();
final indexWhere = _tempIndexList.indexWhere((i) => i == index); final indexWhere = _tempIndexList.indexWhere((i) => i == index);
_tempIndexList.removeAt(indexWhere); _tempIndexList.removeAt(indexWhere);
@@ -484,9 +485,24 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
Widget _draggableWrap({ Widget _draggableWrap({
required Widget childWhenDragging, required Widget childWhenDragging,
required Widget feedback, required Widget feedback,
required Widget target, required Widget item,
required int index, required int index,
}) { }) {
final target = DragTarget<int>(
builder: (_, __, ___) {
return AbsorbPointer(
child: item,
);
},
onWillAcceptWithDetails: (_) {
debouncer.call(
DebounceTag.handleWill,
_handleWill,
args: [index],
);
return false;
},
);
final shakeTarget = ValueListenableBuilder( final shakeTarget = ValueListenableBuilder(
valueListenable: _dragIndexNotifier, valueListenable: _dragIndexNotifier,
builder: (_, dragIndex, child) { builder: (_, dragIndex, child) {
@@ -539,7 +555,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
valueListenable: isEditNotifier, valueListenable: isEditNotifier,
builder: (_, isEdit, child) { builder: (_, isEdit, child) {
if (!isEdit) { if (!isEdit) {
return target; return item;
} }
return child!; return child!;
}, },
@@ -558,12 +574,10 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
_itemContexts[index] = context; _itemContexts[index] = context;
final childWhenDragging = ActivateBox( final childWhenDragging = ActivateBox(
child: Opacity( child: Opacity(
opacity: 0.3, opacity: 0.6,
child: _sizeBoxWrap( child: _sizeBoxWrap(
CommonCard( CommonCard(
child: Container( child: child,
color: context.colorScheme.secondaryContainer,
),
), ),
index, index,
), ),
@@ -580,25 +594,11 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
index, index,
), ),
); );
final target = DragTarget<int>(
builder: (_, __, ___) {
return child;
},
onWillAcceptWithDetails: (_) {
debouncer.call(
DebounceTag.handleWill,
_handleWill,
args: [index],
);
return false;
},
);
return _wrapTransform( return _wrapTransform(
_draggableWrap( _draggableWrap(
childWhenDragging: childWhenDragging, childWhenDragging: childWhenDragging,
feedback: feedback, feedback: feedback,
target: target, item: child,
index: index, index: index,
), ),
index, index,
@@ -666,8 +666,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
crossAxisSpacing: widget.crossAxisSpacing, crossAxisSpacing: widget.crossAxisSpacing,
mainAxisSpacing: widget.mainAxisSpacing, mainAxisSpacing: widget.mainAxisSpacing,
children: [ children: [
for (int i = 0; i < children.length; i++) for (int i = 0; i < children.length; i++) _builderItem(i),
_builderItem(i),
], ],
); );
}, },

1100
lib/widgets/tab.dart Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -84,6 +84,7 @@ class EmojiText extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return RichText( return RichText(
textScaler: MediaQuery.of(context).textScaler,
maxLines: maxLines, maxLines: maxLines,
overflow: overflow ?? TextOverflow.clip, overflow: overflow ?? TextOverflow.clip,
text: TextSpan( text: TextSpan(

View File

@@ -30,3 +30,6 @@ export 'scroll.dart';
export 'dialog.dart'; export 'dialog.dart';
export 'effect.dart'; export 'effect.dart';
export 'palette.dart'; export 'palette.dart';
export 'tab.dart';
export 'container.dart';
export 'notification.dart';

View File

@@ -843,7 +843,7 @@ packages:
source: hosted source: hosted
version: "0.12.17" version: "0.12.17"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: "direct main"
description: description:
name: material_color_utilities name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec

View File

@@ -1,7 +1,7 @@
name: fl_clash name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free. description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none' publish_to: 'none'
version: 0.8.82+202504182 version: 0.8.83+202504231
environment: environment:
sdk: '>=3.1.0 <4.0.0' sdk: '>=3.1.0 <4.0.0'
@@ -53,6 +53,7 @@ dependencies:
flutter_riverpod: ^2.6.1 flutter_riverpod: ^2.6.1
riverpod_annotation: ^2.6.1 riverpod_annotation: ^2.6.1
riverpod: ^2.6.1 riverpod: ^2.6.1
material_color_utilities: ^0.11.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
@@ -92,5 +93,5 @@ ffigen:
flutter_intl: flutter_intl:
enabled: true enabled: true
class_name: AppLocalizations class_name: AppLocalizations
arb_dir: lib/l10n/arb arb_dir: arb
output_dir: lib/l10n output_dir: lib/l10n

View File

@@ -4,6 +4,7 @@ import requests
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN") TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
TAG = os.getenv("TAG") TAG = os.getenv("TAG")
RUN_ID = os.getenv("RUN_ID")
IS_STABLE = "-" not in TAG IS_STABLE = "-" not in TAG
@@ -45,7 +46,8 @@ if TAG:
if IS_STABLE: if IS_STABLE:
text += f"\nhttps://github.com/chen08209/FlClash/releases/tag/{TAG}\n" text += f"\nhttps://github.com/chen08209/FlClash/releases/tag/{TAG}\n"
else:
text += f"\nhttps://github.com/chen08209/FlClash/actions/runs/{RUN_ID}\n"
if os.path.exists(release): if os.path.exists(release):
text += "\n" text += "\n"

View File

@@ -0,0 +1,83 @@
[Setup]
AppId={{APP_ID}}
AppVersion={{APP_VERSION}}
AppName={{DISPLAY_NAME}}
AppPublisher={{PUBLISHER_NAME}}
AppPublisherURL={{PUBLISHER_URL}}
AppSupportURL={{PUBLISHER_URL}}
AppUpdatesURL={{PUBLISHER_URL}}
DefaultDirName={{INSTALL_DIR_NAME}}
DisableProgramGroupPage=yes
OutputDir=.
OutputBaseFilename={{OUTPUT_BASE_FILENAME}}
Compression=lzma
SolidCompression=yes
SetupIconFile={{SETUP_ICON_FILE}}
WizardStyle=modern
PrivilegesRequired={{PRIVILEGES_REQUIRED}}
ArchitecturesAllowed={{ARCH}}
ArchitecturesInstallIn64BitMode={{ARCH}}
[Code]
procedure KillProcesses;
var
Processes: TArrayOfString;
i: Integer;
ResultCode: Integer;
begin
Processes := ['FlClash.exe', 'FlClashCore.exe', 'FlClashHelperService.exe'];
for i := 0 to GetArrayLength(Processes)-1 do
begin
Exec('taskkill', '/f /im ' + Processes[i], '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
end;
function InitializeSetup(): Boolean;
begin
KillProcesses;
Result := True;
end;
[Languages]
{% for locale in LOCALES %}
{% if locale.lang == 'en' %}Name: "english"; MessagesFile: "compiler:Default.isl"{% endif %}
{% if locale.lang == 'hy' %}Name: "armenian"; MessagesFile: "compiler:Languages\\Armenian.isl"{% endif %}
{% if locale.lang == 'bg' %}Name: "bulgarian"; MessagesFile: "compiler:Languages\\Bulgarian.isl"{% endif %}
{% if locale.lang == 'ca' %}Name: "catalan"; MessagesFile: "compiler:Languages\\Catalan.isl"{% endif %}
{% if locale.lang == 'zh' %}
Name: "chineseSimplified"; MessagesFile: {% if locale.file %}{{ locale.file }}{% else %}"compiler:Languages\\ChineseSimplified.isl"{% endif %}
{% endif %}
{% if locale.lang == 'co' %}Name: "corsican"; MessagesFile: "compiler:Languages\\Corsican.isl"{% endif %}
{% if locale.lang == 'cs' %}Name: "czech"; MessagesFile: "compiler:Languages\\Czech.isl"{% endif %}
{% if locale.lang == 'da' %}Name: "danish"; MessagesFile: "compiler:Languages\\Danish.isl"{% endif %}
{% if locale.lang == 'nl' %}Name: "dutch"; MessagesFile: "compiler:Languages\\Dutch.isl"{% endif %}
{% if locale.lang == 'fi' %}Name: "finnish"; MessagesFile: "compiler:Languages\\Finnish.isl"{% endif %}
{% if locale.lang == 'fr' %}Name: "french"; MessagesFile: "compiler:Languages\\French.isl"{% endif %}
{% if locale.lang == 'de' %}Name: "german"; MessagesFile: "compiler:Languages\\German.isl"{% endif %}
{% if locale.lang == 'he' %}Name: "hebrew"; MessagesFile: "compiler:Languages\\Hebrew.isl"{% endif %}
{% if locale.lang == 'is' %}Name: "icelandic"; MessagesFile: "compiler:Languages\\Icelandic.isl"{% endif %}
{% if locale.lang == 'it' %}Name: "italian"; MessagesFile: "compiler:Languages\\Italian.isl"{% endif %}
{% if locale.lang == 'ja' %}Name: "japanese"; MessagesFile: "compiler:Languages\\Japanese.isl"{% endif %}
{% if locale.lang == 'no' %}Name: "norwegian"; MessagesFile: "compiler:Languages\\Norwegian.isl"{% endif %}
{% if locale.lang == 'pl' %}Name: "polish"; MessagesFile: "compiler:Languages\\Polish.isl"{% endif %}
{% if locale.lang == 'pt' %}Name: "portuguese"; MessagesFile: "compiler:Languages\\Portuguese.isl"{% endif %}
{% if locale.lang == 'ru' %}Name: "russian"; MessagesFile: "compiler:Languages\\Russian.isl"{% endif %}
{% if locale.lang == 'sk' %}Name: "slovak"; MessagesFile: "compiler:Languages\\Slovak.isl"{% endif %}
{% if locale.lang == 'sl' %}Name: "slovenian"; MessagesFile: "compiler:Languages\\Slovenian.isl"{% endif %}
{% if locale.lang == 'es' %}Name: "spanish"; MessagesFile: "compiler:Languages\\Spanish.isl"{% endif %}
{% if locale.lang == 'tr' %}Name: "turkish"; MessagesFile: "compiler:Languages\\Turkish.isl"{% endif %}
{% if locale.lang == 'uk' %}Name: "ukrainian"; MessagesFile: "compiler:Languages\\Ukrainian.isl"{% endif %}
{% endfor %}
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: {% if CREATE_DESKTOP_ICON != true %}unchecked{% else %}checkedonce{% endif %}
[Files]
Source: "{{SOURCE_DIR}}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{autoprograms}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}"
Name: "{autodesktop}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}"; Tasks: desktopicon
[Run]
Filename: "{app}\\{{EXECUTABLE_NAME}}"; Description: "{cm:LaunchProgram,{{DISPLAY_NAME}}}"; Flags: {% if PRIVILEGES_REQUIRED == 'admin' %}runascurrentuser{% endif %} nowait postinstall skipifsilent

View File

@@ -1,3 +1,4 @@
script_template: inno_setup.iss
app_id: 728B3532-C74B-4870-9068-BE70FE12A3E6 app_id: 728B3532-C74B-4870-9068-BE70FE12A3E6
app_name: FlClash app_name: FlClash
publisher: chen08209 publisher: chen08209
@@ -9,4 +10,5 @@ setup_icon_file: ..\windows\runner\resources\app_icon.ico
locales: locales:
- lang: zh - lang: zh
file: ..\windows\packaging\exe\ChineseSimplified.isl file: ..\windows\packaging\exe\ChineseSimplified.isl
- lang: en - lang: en
privileges_required: admin