Compare commits
2 Commits
v0.8.91
...
v0.8.83-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cb5d40969 | ||
|
|
30ee6889ab |
9
.github/workflows/build.yaml
vendored
9
.github/workflows/build.yaml
vendored
@@ -201,6 +201,7 @@ jobs:
|
||||
env:
|
||||
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
@@ -211,6 +212,14 @@ jobs:
|
||||
version=$(echo "${{ github.ref_name }}" | sed 's/^v//')
|
||||
sed "s|VERSION|$version|g" ./.github/release_template.md >> release.md
|
||||
|
||||
- name: Generate sha256
|
||||
if: env.IS_STABLE == 'true'
|
||||
run: |
|
||||
cd ./dist
|
||||
for file in $(find . -type f -not -name "*.sha256"); do
|
||||
sha256sum "$file" > "${file}.sha256"
|
||||
done
|
||||
|
||||
- name: Release
|
||||
if: ${{ env.IS_STABLE == 'true' }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||
|
||||
@@ -20,6 +20,10 @@ enum class RunState {
|
||||
object GlobalState {
|
||||
val runLock = ReentrantLock()
|
||||
|
||||
const val NOTIFICATION_CHANNEL = "FlClash"
|
||||
|
||||
const val NOTIFICATION_ID = 1
|
||||
|
||||
val runState: MutableLiveData<RunState> = MutableLiveData<RunState>(RunState.STOP)
|
||||
var flutterEngine: FlutterEngine? = null
|
||||
private var serviceEngine: FlutterEngine? = null
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.follow.clash
|
||||
|
||||
import com.follow.clash.core.Core
|
||||
import com.follow.clash.plugins.AppPlugin
|
||||
import com.follow.clash.plugins.ServicePlugin
|
||||
import com.follow.clash.plugins.TilePlugin
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
suspend fun Drawable.getBase64(): String {
|
||||
val drawable = this
|
||||
return withContext(Dispatchers.IO) {
|
||||
|
||||
@@ -168,8 +168,10 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||
try {
|
||||
if (GlobalState.runState.value != RunState.START) return
|
||||
val data = flutterMethodChannel.awaitResult<String>("getStartForegroundParams")
|
||||
val startForegroundParams = Gson().fromJson(
|
||||
val startForegroundParams = if (data != null) Gson().fromJson(
|
||||
data, StartForegroundParams::class.java
|
||||
) else StartForegroundParams(
|
||||
title = "", content = ""
|
||||
)
|
||||
if (lastStartForegroundParams != startForegroundParams) {
|
||||
lastStartForegroundParams = startForegroundParams
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
package com.follow.clash.services
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.follow.clash.GlobalState
|
||||
import com.follow.clash.MainActivity
|
||||
import com.follow.clash.R
|
||||
import com.follow.clash.extensions.getActionPendingIntent
|
||||
import com.follow.clash.models.VpnOptions
|
||||
import io.flutter.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
interface BaseServiceInterface {
|
||||
|
||||
@@ -9,4 +29,71 @@ interface BaseServiceInterface {
|
||||
fun stop()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,51 @@
|
||||
package com.follow.clash.services
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.follow.clash.GlobalState
|
||||
import com.follow.clash.MainActivity
|
||||
import com.follow.clash.extensions.getActionPendingIntent
|
||||
import com.follow.clash.models.VpnOptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
|
||||
class FlClashService : Service(), BaseServiceInterface {
|
||||
|
||||
override fun start(options: VpnOptions) = 0
|
||||
|
||||
override fun stop() {
|
||||
stopSelf()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
}
|
||||
|
||||
private var cachedBuilder: NotificationCompat.Builder? = null
|
||||
|
||||
private suspend fun notificationBuilder(): NotificationCompat.Builder {
|
||||
if (cachedBuilder == null) {
|
||||
cachedBuilder = createFlClashNotificationBuilder().await()
|
||||
}
|
||||
return cachedBuilder!!
|
||||
}
|
||||
|
||||
@SuppressLint("ForegroundServiceType")
|
||||
override suspend fun startForeground(title: String, content: String) {
|
||||
startForeground(
|
||||
notificationBuilder()
|
||||
.setContentTitle(title)
|
||||
.setContentText(content).build()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
GlobalState.getCurrentVPNPlugin()?.requestGc()
|
||||
}
|
||||
|
||||
|
||||
private val binder = LocalBinder()
|
||||
|
||||
inner class LocalBinder : Binder() {
|
||||
@@ -38,93 +60,8 @@ class FlClashService : Service(), BaseServiceInterface {
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
private val CHANNEL = "FlClash"
|
||||
|
||||
private val notificationId: Int = 1
|
||||
|
||||
private val notificationBuilderDeferred: Deferred<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)
|
||||
}
|
||||
override fun onDestroy() {
|
||||
stop()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
package com.follow.clash.services
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
import android.net.ProxyInfo
|
||||
import android.net.VpnService
|
||||
import android.os.Binder
|
||||
@@ -17,18 +13,13 @@ import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.follow.clash.GlobalState
|
||||
import com.follow.clash.MainActivity
|
||||
import com.follow.clash.R
|
||||
import com.follow.clash.extensions.getActionPendingIntent
|
||||
import com.follow.clash.extensions.getIpv4RouteAddress
|
||||
import com.follow.clash.extensions.getIpv6RouteAddress
|
||||
import com.follow.clash.extensions.toCIDR
|
||||
import com.follow.clash.models.AccessControlMode
|
||||
import com.follow.clash.models.VpnOptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@@ -128,82 +119,22 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private val CHANNEL = "FlClash"
|
||||
private var cachedBuilder: NotificationCompat.Builder? = null
|
||||
|
||||
private val notificationId: Int = 1
|
||||
|
||||
private val notificationBuilderDeferred: Deferred<NotificationCompat.Builder> by lazy {
|
||||
CoroutineScope(Dispatchers.Main).async {
|
||||
val stopText = GlobalState.getText("stop")
|
||||
val intent = Intent(this@FlClashVpnService, MainActivity::class.java)
|
||||
|
||||
val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
|
||||
PendingIntent.getActivity(
|
||||
this@FlClashVpnService,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
} else {
|
||||
PendingIntent.getActivity(
|
||||
this@FlClashVpnService,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
}
|
||||
|
||||
with(NotificationCompat.Builder(this@FlClashVpnService, CHANNEL)) {
|
||||
setSmallIcon(R.drawable.ic_stat_name)
|
||||
setContentTitle("FlClash")
|
||||
setContentIntent(pendingIntent)
|
||||
setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
priority = NotificationCompat.PRIORITY_MIN
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
|
||||
}
|
||||
setOngoing(true)
|
||||
addAction(
|
||||
0,
|
||||
stopText,
|
||||
getActionPendingIntent("STOP")
|
||||
)
|
||||
setShowWhen(false)
|
||||
setOnlyAlertOnce(true)
|
||||
setAutoCancel(true)
|
||||
}
|
||||
private suspend fun notificationBuilder(): NotificationCompat.Builder {
|
||||
if (cachedBuilder == null) {
|
||||
cachedBuilder = createFlClashNotificationBuilder().await()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getNotificationBuilder(): NotificationCompat.Builder {
|
||||
return notificationBuilderDeferred.await()
|
||||
return cachedBuilder!!
|
||||
}
|
||||
|
||||
@SuppressLint("ForegroundServiceType")
|
||||
override suspend fun startForeground(title: String, content: String) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val manager = getSystemService(NotificationManager::class.java)
|
||||
var channel = manager?.getNotificationChannel(CHANNEL)
|
||||
if (channel == null) {
|
||||
channel =
|
||||
NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
|
||||
manager?.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
val notification =
|
||||
getNotificationBuilder()
|
||||
startForeground(
|
||||
notificationBuilder()
|
||||
.setContentTitle(title)
|
||||
.setContentText(content)
|
||||
.build()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
try {
|
||||
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
||||
} catch (_: Exception) {
|
||||
startForeground(notificationId, notification)
|
||||
}
|
||||
} else {
|
||||
startForeground(notificationId, notification)
|
||||
}
|
||||
.setContentText(content).build()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
|
||||
@@ -385,5 +385,12 @@
|
||||
"expressiveScheme": "Expressive",
|
||||
"contentScheme": "Content",
|
||||
"rainbowScheme": "Rainbow",
|
||||
"fruitSaladScheme": "FruitSalad"
|
||||
"fruitSaladScheme": "FruitSalad",
|
||||
"developerMode": "Developer mode",
|
||||
"developerModeEnableTip": "Developer mode is enabled.",
|
||||
"messageTest": "Message test",
|
||||
"messageTestTip": "This is a message.",
|
||||
"crashTest": "Crash test",
|
||||
"clearData": "Clear Data",
|
||||
"textScale": "Text Scaling"
|
||||
}
|
||||
@@ -385,5 +385,13 @@
|
||||
"expressiveScheme": "エクスプレッシブ",
|
||||
"contentScheme": "コンテンツテーマ",
|
||||
"rainbowScheme": "レインボー",
|
||||
"fruitSaladScheme": "フルーツサラダ"
|
||||
"fruitSaladScheme": "フルーツサラダ",
|
||||
"developerMode": "デベロッパーモード",
|
||||
"developerModeEnableTip": "デベロッパーモードが有効になりました。",
|
||||
"messageTest": "メッセージテスト",
|
||||
"messageTestTip": "これはメッセージです。",
|
||||
"crashTest": "クラッシュテスト",
|
||||
"clearData": "データを消去",
|
||||
"zoom": "ズーム",
|
||||
"textScale": "テキストスケーリング"
|
||||
}
|
||||
@@ -385,5 +385,13 @@
|
||||
"expressiveScheme": "Экспрессивные",
|
||||
"contentScheme": "Контентная тема",
|
||||
"rainbowScheme": "Радужные",
|
||||
"fruitSaladScheme": "Фруктовый микс"
|
||||
"fruitSaladScheme": "Фруктовый микс",
|
||||
"developerMode": "Режим разработчика",
|
||||
"developerModeEnableTip": "Режим разработчика активирован.",
|
||||
"messageTest": "Тестирование сообщения",
|
||||
"messageTestTip": "Это сообщение.",
|
||||
"crashTest": "Тест на сбои",
|
||||
"clearData": "Очистить данные",
|
||||
"zoom": "Масштаб",
|
||||
"textScale": "Масштабирование текста"
|
||||
}
|
||||
@@ -385,5 +385,13 @@
|
||||
"expressiveScheme": "表现力",
|
||||
"contentScheme": "内容主题",
|
||||
"rainbowScheme": "彩虹",
|
||||
"fruitSaladScheme": "果缤纷"
|
||||
"fruitSaladScheme": "果缤纷",
|
||||
"developerMode": "开发者模式",
|
||||
"developerModeEnableTip": "开发者模式已启用。",
|
||||
"messageTest": "消息测试",
|
||||
"messageTestTip": "这是一条消息。",
|
||||
"crashTest": "崩溃测试",
|
||||
"clearData": "清除数据",
|
||||
"zoom": "缩放",
|
||||
"textScale": "文本缩放"
|
||||
}
|
||||
Submodule core/Clash.Meta updated: f19dad529f...88a1848dfb
@@ -166,6 +166,9 @@ func handleAction(action *Action, result func(data interface{})) {
|
||||
data := action.Data.(string)
|
||||
handleSetState(data)
|
||||
result(true)
|
||||
case crashMethod:
|
||||
result(true)
|
||||
handleCrash()
|
||||
default:
|
||||
handle := nextHandle(action, result)
|
||||
if handle {
|
||||
|
||||
@@ -78,7 +78,6 @@ func getRawConfigWithId(id string) *config.RawConfig {
|
||||
path := getProfilePath(id)
|
||||
bytes, err := readFile(path)
|
||||
if err != nil {
|
||||
log.Errorln("profile is not exist")
|
||||
return config.DefaultRawConfig()
|
||||
}
|
||||
prof, err := config.UnmarshalRawConfig(bytes)
|
||||
|
||||
@@ -82,6 +82,7 @@ const (
|
||||
getRunTimeMethod Method = "getRunTime"
|
||||
getCurrentProfileNameMethod Method = "getCurrentProfileName"
|
||||
getProfileMethod Method = "getProfile"
|
||||
crashMethod Method = "crash"
|
||||
)
|
||||
|
||||
type Method string
|
||||
|
||||
13
core/go.mod
13
core/go.mod
@@ -7,6 +7,7 @@ replace github.com/metacubex/mihomo => ./Clash.Meta
|
||||
require (
|
||||
github.com/metacubex/mihomo v0.0.0-00010101000000-000000000000
|
||||
github.com/samber/lo v1.49.1
|
||||
golang.org/x/sync v0.11.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -52,20 +53,20 @@ require (
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect
|
||||
github.com/metacubex/bart v0.19.0 // indirect
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 // indirect
|
||||
github.com/metacubex/chacha v0.1.1 // indirect
|
||||
github.com/metacubex/chacha v0.1.2 // indirect
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect
|
||||
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 // indirect
|
||||
github.com/metacubex/randv2 v0.2.0 // indirect
|
||||
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 // indirect
|
||||
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 // indirect
|
||||
github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c // indirect
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8 // indirect
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 // indirect
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5 // indirect
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 // indirect
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 // indirect
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect
|
||||
github.com/metacubex/utls v1.6.8-alpha.4 // indirect
|
||||
github.com/metacubex/utls v1.7.0-alpha.1 // indirect
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
|
||||
github.com/miekg/dns v1.1.63 // indirect
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
|
||||
@@ -84,7 +85,6 @@ require (
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||
github.com/sagernet/sing v0.5.2 // indirect
|
||||
github.com/sagernet/sing-mux v0.2.1 // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.1.5 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
||||
@@ -107,7 +107,6 @@ require (
|
||||
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
|
||||
22
core/go.sum
22
core/go.sum
@@ -101,8 +101,8 @@ github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY
|
||||
github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
||||
github.com/metacubex/chacha v0.1.1 h1:OHIv11Nd9CISAIzegpjfupIoZp9DYm6uQw41RxvmU/c=
|
||||
github.com/metacubex/chacha v0.1.1/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc=
|
||||
github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
|
||||
@@ -111,24 +111,24 @@ github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 h1:B+AP/Pj2/j
|
||||
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996/go.mod h1:ExVjGyEwTUjCFqx+5uxgV7MOoA3fZI+th4D40H35xmY=
|
||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 h1:aHsYiTvubfgMa3JMTDY//hDXVvFWrHg6ARckR52ttZs=
|
||||
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629/go.mod h1:TTeIOZLdGmzc07Oedn++vWUUfkZoXLF4sEMxWuhBFr8=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 h1:UkPoRAnoBQMn7IK5qpoIV3OejU15q+rqel3NrbSCFKA=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c h1:OB3WmMA8YPJjE36RjD9X8xlrWGJ4orxbf2R/KAE28b0=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 h1:B211C+i/I8CWf4I/BaAV0mmkEHrDBJ0XR9EWxjPbFEg=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63 h1:vy/8ZYYtWUXYnOnw/NF8ThG1W/RqM/h5rkun+OXZMH0=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63/go.mod h1:eDZ2JpkSkewGmUlCoLSn2MRFn1D0jKPIys/6aogFx7U=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5 h1:hcsz5e5lqhBxn3iQQDIF60FLZ8PQT542GTQZ+1wcIGo=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.6.8-alpha.4 h1:5EvsCHxDNneaOtAyc8CztoNSpmonLvkvuGs01lIeeEI=
|
||||
github.com/metacubex/utls v1.6.8-alpha.4/go.mod h1:MEZ5WO/VLKYs/s/dOzEK/mlXOQxc04ESeLzRgjmLYtk=
|
||||
github.com/metacubex/utls v1.7.0-alpha.1 h1:oMFsPh2oTlALJ7vKXPJuqgy0YeiZ+q/LLw+ZdxZ80l4=
|
||||
github.com/metacubex/utls v1.7.0-alpha.1/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
@@ -174,8 +174,6 @@ github.com/sagernet/sing v0.5.2 h1:2OZQJNKGtji/66QLxbf/T/dqtK/3+fF/zuHH9tsGK7M=
|
||||
github.com/sagernet/sing v0.5.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo=
|
||||
github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE=
|
||||
github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo=
|
||||
github.com/sagernet/sing-shadowtls v0.1.5/go.mod h1:tvrDPTGLrSM46Wnf7mSr+L8NHvgvF8M4YnJF790rZX4=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
|
||||
@@ -442,6 +442,10 @@ func handleSetState(params string) {
|
||||
_ = json.Unmarshal([]byte(params), state.CurrentState)
|
||||
}
|
||||
|
||||
func handleCrash() {
|
||||
panic("handle invoke crash")
|
||||
}
|
||||
|
||||
func init() {
|
||||
adapter.UrlTestHook = func(url string, name string, delay uint16) {
|
||||
delayData := &Delay{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/l10n/l10n.dart';
|
||||
@@ -14,7 +13,6 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
import 'models/models.dart';
|
||||
import 'pages/pages.dart';
|
||||
|
||||
class Application extends ConsumerStatefulWidget {
|
||||
@@ -27,7 +25,6 @@ class Application extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class ApplicationState extends ConsumerState<Application> {
|
||||
late ColorSchemes systemColorSchemes;
|
||||
Timer? _autoUpdateGroupTaskTimer;
|
||||
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
|
||||
Widget build(context) {
|
||||
return _buildPlatformState(
|
||||
@@ -154,49 +138,44 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
final locale =
|
||||
ref.watch(appSettingProvider.select((state) => state.locale));
|
||||
final themeProps = ref.watch(themeSettingProvider);
|
||||
return DynamicColorBuilder(
|
||||
builder: (lightDynamic, darkDynamic) {
|
||||
_updateSystemColorSchemes(lightDynamic, darkDynamic);
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
navigatorKey: globalState.navigatorKey,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate
|
||||
],
|
||||
builder: (_, child) {
|
||||
return AppEnvManager(
|
||||
child: _buildPlatformApp(
|
||||
_buildApp(child!),
|
||||
),
|
||||
);
|
||||
},
|
||||
scrollBehavior: BaseScrollBehavior(),
|
||||
title: appName,
|
||||
locale: utils.getLocaleForString(locale),
|
||||
supportedLocales: AppLocalizations.delegate.supportedLocales,
|
||||
themeMode: themeProps.themeMode,
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
colorScheme: _getAppColorScheme(
|
||||
brightness: Brightness.light,
|
||||
primaryColor: themeProps.primaryColor,
|
||||
),
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
navigatorKey: globalState.navigatorKey,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate
|
||||
],
|
||||
builder: (_, child) {
|
||||
return AppEnvManager(
|
||||
child: _buildPlatformApp(
|
||||
_buildApp(child!),
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
colorScheme: _getAppColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
primaryColor: themeProps.primaryColor,
|
||||
).toPureBlack(themeProps.pureBlack),
|
||||
),
|
||||
home: child,
|
||||
);
|
||||
},
|
||||
scrollBehavior: BaseScrollBehavior(),
|
||||
title: appName,
|
||||
locale: utils.getLocaleForString(locale),
|
||||
supportedLocales: AppLocalizations.delegate.supportedLocales,
|
||||
themeMode: themeProps.themeMode,
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
colorScheme: _getAppColorScheme(
|
||||
brightness: Brightness.light,
|
||||
primaryColor: themeProps.primaryColor,
|
||||
),
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
colorScheme: _getAppColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
primaryColor: themeProps.primaryColor,
|
||||
).toPureBlack(themeProps.pureBlack),
|
||||
),
|
||||
home: child,
|
||||
);
|
||||
},
|
||||
child: const HomePage(),
|
||||
|
||||
@@ -58,6 +58,8 @@ mixin ClashInterface {
|
||||
|
||||
stopLog();
|
||||
|
||||
Future<bool> crash();
|
||||
|
||||
FutureOr<String> getConnections();
|
||||
|
||||
FutureOr<bool> closeConnection(String id);
|
||||
@@ -104,6 +106,7 @@ abstract class ClashHandlerInterface with ClashInterface {
|
||||
case ActionMethod.closeConnection:
|
||||
case ActionMethod.stopListener:
|
||||
case ActionMethod.setState:
|
||||
case ActionMethod.crash:
|
||||
completer?.complete(result.data as bool);
|
||||
return;
|
||||
case ActionMethod.changeProxy:
|
||||
@@ -242,6 +245,14 @@ abstract class ClashHandlerInterface with ClashInterface {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<bool> crash() {
|
||||
return invoke<bool>(
|
||||
method: ActionMethod.crash,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getProxies() {
|
||||
return invoke<String>(
|
||||
|
||||
@@ -17,15 +17,12 @@ const packageName = "com.follow.clash";
|
||||
final unixSocketPath = "/tmp/FlClashSocket_${Random().nextInt(10000)}.sock";
|
||||
const helperPort = 47890;
|
||||
const helperTag = "2024125";
|
||||
const baseInfoEdgeInsets = EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
horizontal: 16,
|
||||
final baseInfoEdgeInsets = EdgeInsets.symmetric(
|
||||
vertical: 16.ap,
|
||||
horizontal: 16.ap,
|
||||
);
|
||||
|
||||
double textScaleFactor = min(
|
||||
WidgetsBinding.instance.platformDispatcher.textScaleFactor,
|
||||
1.2,
|
||||
);
|
||||
final defaultTextScaleFactor = WidgetsBinding.instance.platformDispatcher.textScaleFactor;
|
||||
const httpTimeoutDuration = Duration(milliseconds: 5000);
|
||||
const moreDuration = Duration(milliseconds: 100);
|
||||
const animateDuration = Duration(milliseconds: 100);
|
||||
@@ -44,7 +41,6 @@ const profilesDirectoryName = "profiles";
|
||||
const localhost = "127.0.0.1";
|
||||
const clashConfigKey = "clash_config";
|
||||
const configKey = "config";
|
||||
const listItemPadding = EdgeInsets.symmetric(horizontal: 16);
|
||||
const double dialogCommonWidth = 300;
|
||||
const repository = "chen08209/FlClash";
|
||||
const defaultExternalController = "127.0.0.1:9090";
|
||||
@@ -81,7 +77,7 @@ const viewModeColumnsMap = {
|
||||
const defaultPrimaryColor = 0xFF795548;
|
||||
|
||||
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";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:fl_clash/manager/manager.dart';
|
||||
import 'package:fl_clash/manager/message_manager.dart';
|
||||
import 'package:fl_clash/widgets/scaffold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -11,6 +11,36 @@ extension BuildContextExtension on BuildContext {
|
||||
return findAncestorStateOfType<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 {
|
||||
return MediaQuery.of(this).size;
|
||||
}
|
||||
@@ -27,10 +57,10 @@ extension BuildContextExtension on BuildContext {
|
||||
T? state;
|
||||
|
||||
visitor(Element element) {
|
||||
if(!element.mounted){
|
||||
if (!element.mounted) {
|
||||
return;
|
||||
}
|
||||
if(element is StatefulElement){
|
||||
if (element is StatefulElement) {
|
||||
if (element.state is T) {
|
||||
state = element.state as T;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Measure {
|
||||
final TextScaler _textScale;
|
||||
final TextScaler _textScaler;
|
||||
final BuildContext context;
|
||||
final Map<String, dynamic> _measureMap;
|
||||
|
||||
Measure.of(this.context)
|
||||
: _textScale = TextScaler.linear(
|
||||
Measure.of(this.context, double textScaleFactor)
|
||||
: _measureMap = {},
|
||||
_textScaler = TextScaler.linear(
|
||||
textScaleFactor,
|
||||
);
|
||||
|
||||
@@ -21,7 +23,7 @@ class Measure {
|
||||
style: text.style,
|
||||
),
|
||||
maxLines: text.maxLines,
|
||||
textScaler: _textScale,
|
||||
textScaler: _textScaler,
|
||||
textDirection: text.textDirection ?? TextDirection.ltr,
|
||||
)..layout(
|
||||
maxWidth: maxWidth,
|
||||
@@ -29,81 +31,75 @@ class Measure {
|
||||
return textPainter.size;
|
||||
}
|
||||
|
||||
double? _bodyMediumHeight;
|
||||
Size? _bodyLargeSize;
|
||||
double? _bodySmallHeight;
|
||||
double? _labelSmallHeight;
|
||||
double? _labelMediumHeight;
|
||||
double? _titleLargeHeight;
|
||||
double? _titleMediumHeight;
|
||||
|
||||
double get bodyMediumHeight {
|
||||
_bodyMediumHeight ??= computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
).height;
|
||||
return _bodyMediumHeight!;
|
||||
}
|
||||
|
||||
Size get bodyLargeSize {
|
||||
_bodyLargeSize ??= computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.bodyLarge,
|
||||
),
|
||||
return _measureMap.getCacheValue(
|
||||
"bodyMediumHeight",
|
||||
computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
).height,
|
||||
);
|
||||
return _bodyLargeSize!;
|
||||
}
|
||||
|
||||
double get bodySmallHeight {
|
||||
_bodySmallHeight ??= computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.bodySmall,
|
||||
),
|
||||
).height;
|
||||
return _bodySmallHeight!;
|
||||
return _measureMap.getCacheValue(
|
||||
"bodySmallHeight",
|
||||
computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.bodySmall,
|
||||
),
|
||||
).height,
|
||||
);
|
||||
}
|
||||
|
||||
double get labelSmallHeight {
|
||||
_labelSmallHeight ??= computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.labelSmall,
|
||||
),
|
||||
).height;
|
||||
return _labelSmallHeight!;
|
||||
return _measureMap.getCacheValue(
|
||||
"labelSmallHeight",
|
||||
computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.labelSmall,
|
||||
),
|
||||
).height,
|
||||
);
|
||||
}
|
||||
|
||||
double get labelMediumHeight {
|
||||
_labelMediumHeight ??= computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.labelMedium,
|
||||
),
|
||||
).height;
|
||||
return _labelMediumHeight!;
|
||||
return _measureMap.getCacheValue(
|
||||
"labelMediumHeight",
|
||||
computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.labelMedium,
|
||||
),
|
||||
).height,
|
||||
);
|
||||
}
|
||||
|
||||
double get titleLargeHeight {
|
||||
_titleLargeHeight ??= computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.titleLarge,
|
||||
),
|
||||
).height;
|
||||
return _titleLargeHeight!;
|
||||
return _measureMap.getCacheValue(
|
||||
"titleLargeHeight",
|
||||
computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.titleLarge,
|
||||
),
|
||||
).height,
|
||||
);
|
||||
}
|
||||
|
||||
double get titleMediumHeight {
|
||||
_titleMediumHeight ??= computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.titleMedium,
|
||||
),
|
||||
).height;
|
||||
return _titleMediumHeight!;
|
||||
return _measureMap.getCacheValue(
|
||||
"titleMediumHeight",
|
||||
computeTextSize(
|
||||
Text(
|
||||
"X",
|
||||
style: context.textTheme.titleMedium,
|
||||
),
|
||||
).height,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -12,6 +13,10 @@ extension NumExt on num {
|
||||
}
|
||||
return formatted;
|
||||
}
|
||||
|
||||
double get ap {
|
||||
return this * (1 + (globalState.theme.textScaleFactor - 1) * 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
extension DoubleExt on double {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -20,10 +19,7 @@ class CommonPrint {
|
||||
return;
|
||||
}
|
||||
globalState.appController.addLog(
|
||||
Log(
|
||||
logLevel: LogLevel.info,
|
||||
payload: payload,
|
||||
),
|
||||
Log.app(payload),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,12 @@ import 'package:flutter/material.dart';
|
||||
class CommonTheme {
|
||||
final BuildContext context;
|
||||
final Map<String, Color> _colorMap;
|
||||
final double textScaleFactor;
|
||||
|
||||
CommonTheme.of(this.context) : _colorMap = {};
|
||||
CommonTheme.of(
|
||||
this.context,
|
||||
this.textScaleFactor,
|
||||
) : _colorMap = {};
|
||||
|
||||
Color get darkenSecondaryContainer {
|
||||
return _colorMap.getCacheValue(
|
||||
|
||||
@@ -80,7 +80,7 @@ class Tray {
|
||||
);
|
||||
}
|
||||
menuItems.add(MenuItem.separator());
|
||||
if (!Platform.isWindows) {
|
||||
if (Platform.isMacOS) {
|
||||
for (final group in trayState.groups) {
|
||||
List<MenuItem> subMenuItems = [];
|
||||
for (final proxy in group.all) {
|
||||
|
||||
@@ -334,23 +334,22 @@ class AppController {
|
||||
try {
|
||||
await updateProfile(profile);
|
||||
} catch (e) {
|
||||
_ref.read(logsProvider.notifier).addLog(
|
||||
Log(
|
||||
logLevel: LogLevel.info,
|
||||
payload: e.toString(),
|
||||
),
|
||||
);
|
||||
commonPrint.log(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateGroups() async {
|
||||
_ref.read(groupsProvider.notifier).value = await retry(
|
||||
task: () async {
|
||||
return await clashCore.getProxiesGroups();
|
||||
},
|
||||
retryIf: (res) => res.isEmpty,
|
||||
);
|
||||
try {
|
||||
_ref.read(groupsProvider.notifier).value = await retry(
|
||||
task: () async {
|
||||
return await clashCore.getProxiesGroups();
|
||||
},
|
||||
retryIf: (res) => res.isEmpty,
|
||||
);
|
||||
} catch (_) {
|
||||
_ref.read(groupsProvider.notifier).value = [];
|
||||
}
|
||||
}
|
||||
|
||||
updateProfiles() async {
|
||||
@@ -362,10 +361,6 @@ class AppController {
|
||||
}
|
||||
}
|
||||
|
||||
updateSystemColorSchemes(ColorSchemes colorSchemes) {
|
||||
_ref.read(appSchemesProvider.notifier).value = colorSchemes;
|
||||
}
|
||||
|
||||
savePreferences() async {
|
||||
commonPrint.log("save preferences");
|
||||
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 {
|
||||
if (!_ref.read(appSettingProvider).autoCheckUpdate) return;
|
||||
final res = await request.checkForUpdate();
|
||||
|
||||
@@ -91,7 +91,14 @@ enum Mode { rule, global, direct }
|
||||
|
||||
enum ViewMode { mobile, laptop, desktop }
|
||||
|
||||
enum LogLevel { debug, info, warning, error, silent }
|
||||
enum LogLevel {
|
||||
debug,
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
silent,
|
||||
app,
|
||||
}
|
||||
|
||||
enum TransportProtocol { udp, tcp }
|
||||
|
||||
@@ -262,6 +269,7 @@ enum ActionMethod {
|
||||
getCountryCode,
|
||||
getMemory,
|
||||
getProfile,
|
||||
crash,
|
||||
|
||||
///Android,
|
||||
setFdMap,
|
||||
@@ -308,6 +316,12 @@ enum DashboardWidget {
|
||||
child: NetworkSpeed(),
|
||||
),
|
||||
),
|
||||
outboundModeV2(
|
||||
GridItem(
|
||||
crossAxisCellCount: 8,
|
||||
child: OutboundModeV2(),
|
||||
),
|
||||
),
|
||||
outboundMode(
|
||||
GridItem(
|
||||
crossAxisCellCount: 4,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/providers/config.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/list.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
@immutable
|
||||
class Contributor {
|
||||
@@ -116,33 +120,43 @@ class AboutFragment extends StatelessWidget {
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 16,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Image.asset(
|
||||
'assets/images/icon.png',
|
||||
width: 64,
|
||||
height: 64,
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
Consumer(builder: (_, ref, ___) {
|
||||
return _DeveloperModeDetector(
|
||||
child: Wrap(
|
||||
spacing: 16,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
appName,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Image.asset(
|
||||
'assets/images/icon.png',
|
||||
width: 64,
|
||||
height: 64,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
globalState.packageInfo.version,
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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(
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,6 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
updateRequestsThrottler();
|
||||
}
|
||||
},
|
||||
fireImmediately: true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -149,72 +148,77 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
_handleTryClearCache(constraints.maxWidth - 40 - (value ? 60 : 0));
|
||||
return child!;
|
||||
},
|
||||
child: ValueListenableBuilder<ConnectionsState>(
|
||||
valueListenable: _requestsStateNotifier,
|
||||
builder: (_, state, __) {
|
||||
final connections = state.list;
|
||||
if (connections.isEmpty) {
|
||||
return NullStatus(
|
||||
label: appLocalizations.nullRequestsDesc,
|
||||
);
|
||||
}
|
||||
final items = connections
|
||||
.map<Widget>(
|
||||
(connection) => ConnectionItem(
|
||||
key: Key(connection.id),
|
||||
connection: connection,
|
||||
onClickKeyword: (value) {
|
||||
context.commonScaffoldState?.addKeyword(value);
|
||||
},
|
||||
),
|
||||
)
|
||||
.separated(
|
||||
const Divider(
|
||||
height: 0,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ScrollToEndBox(
|
||||
controller: _scrollController,
|
||||
cacheKey: _cacheKey,
|
||||
dataSource: connections,
|
||||
child: CommonScrollBar(
|
||||
child: TextScaleNotification(
|
||||
child: ValueListenableBuilder<ConnectionsState>(
|
||||
valueListenable: _requestsStateNotifier,
|
||||
builder: (_, state, __) {
|
||||
final connections = state.list;
|
||||
if (connections.isEmpty) {
|
||||
return NullStatus(
|
||||
label: appLocalizations.nullRequestsDesc,
|
||||
);
|
||||
}
|
||||
final items = connections
|
||||
.map<Widget>(
|
||||
(connection) => ConnectionItem(
|
||||
key: Key(connection.id),
|
||||
connection: connection,
|
||||
onClickKeyword: (value) {
|
||||
context.commonScaffoldState?.addKeyword(value);
|
||||
},
|
||||
),
|
||||
)
|
||||
.separated(
|
||||
const Divider(
|
||||
height: 0,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ScrollToEndBox(
|
||||
controller: _scrollController,
|
||||
child: CacheItemExtentListView(
|
||||
key: _key,
|
||||
reverse: true,
|
||||
shrinkWrap: true,
|
||||
physics: NextClampingScrollPhysics(),
|
||||
cacheKey: _cacheKey,
|
||||
dataSource: connections,
|
||||
child: CommonScrollBar(
|
||||
controller: _scrollController,
|
||||
itemExtentBuilder: (index) {
|
||||
final widget = items[index];
|
||||
if (widget.runtimeType == Divider) {
|
||||
return 0;
|
||||
}
|
||||
final measure = globalState.measure;
|
||||
final bodyMediumHeight = measure.bodyMediumHeight;
|
||||
final connection = connections[(index / 2).floor()];
|
||||
final height = _calcCacheHeight(connection);
|
||||
return height + bodyMediumHeight + 32;
|
||||
},
|
||||
itemBuilder: (_, index) {
|
||||
return items[index];
|
||||
},
|
||||
itemCount: items.length,
|
||||
keyBuilder: (int index) {
|
||||
final widget = items[index];
|
||||
if (widget.runtimeType == Divider) {
|
||||
return "divider";
|
||||
}
|
||||
final connection = connections[(index / 2).floor()];
|
||||
return connection.id;
|
||||
},
|
||||
child: CacheItemExtentListView(
|
||||
key: _key,
|
||||
reverse: true,
|
||||
shrinkWrap: true,
|
||||
physics: NextClampingScrollPhysics(),
|
||||
controller: _scrollController,
|
||||
itemExtentBuilder: (index) {
|
||||
final widget = items[index];
|
||||
if (widget.runtimeType == Divider) {
|
||||
return 0;
|
||||
}
|
||||
final measure = globalState.measure;
|
||||
final bodyMediumHeight = measure.bodyMediumHeight;
|
||||
final connection = connections[(index / 2).floor()];
|
||||
final height = _calcCacheHeight(connection);
|
||||
return height + bodyMediumHeight + 32;
|
||||
},
|
||||
itemBuilder: (_, index) {
|
||||
return items[index];
|
||||
},
|
||||
itemCount: items.length,
|
||||
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();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -93,7 +93,7 @@ class _DashboardFragmentState extends ConsumerState<DashboardFragment>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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(
|
||||
alignment: Alignment.topCenter,
|
||||
child: SingleChildScrollView(
|
||||
@@ -103,8 +103,8 @@ class _DashboardFragmentState extends ConsumerState<DashboardFragment>
|
||||
child: SuperGrid(
|
||||
key: key,
|
||||
crossAxisCount: columns,
|
||||
crossAxisSpacing: 16,
|
||||
mainAxisSpacing: 16,
|
||||
crossAxisSpacing: 16.ap,
|
||||
mainAxisSpacing: 16.ap,
|
||||
children: [
|
||||
...dashboardState.dashboardWidgets
|
||||
.where(
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/common.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -57,37 +58,43 @@ class _MemoryInfoState extends State<MemoryInfo> {
|
||||
onPressed: () {
|
||||
clashCore.requestGc();
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _memoryInfoStateNotifier,
|
||||
builder: (_, trafficValue, __) {
|
||||
return Padding(
|
||||
padding: baseInfoEdgeInsets.copyWith(
|
||||
bottom: 0,
|
||||
top: 12,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
trafficValue.showValue,
|
||||
style:
|
||||
context.textTheme.bodyMedium?.toLight.adjustSize(1),
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Text(
|
||||
trafficValue.showUnit,
|
||||
style:
|
||||
context.textTheme.bodyMedium?.toLight.adjustSize(1),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
child: Container(
|
||||
padding: baseInfoEdgeInsets.copyWith(
|
||||
top: 0,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: globalState.measure.bodyMediumHeight + 2,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: _memoryInfoStateNotifier,
|
||||
builder: (_, trafficValue, __) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
trafficValue.showValue,
|
||||
style: context.textTheme.bodyMedium?.toLight
|
||||
.adjustSize(1),
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Text(
|
||||
trafficValue.showUnit,
|
||||
style: context.textTheme.bodyMedium?.toLight
|
||||
.adjustSize(1),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -206,7 +206,7 @@ class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
size: 16,
|
||||
size: 16.ap,
|
||||
Icons.info_outline,
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
|
||||
@@ -17,58 +17,145 @@ class OutboundMode extends StatelessWidget {
|
||||
height: height,
|
||||
child: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
final mode =
|
||||
ref.watch(patchClashConfigProvider.select((state) => state.mode));
|
||||
return 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.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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
final mode = ref.watch(
|
||||
patchClashConfigProvider.select(
|
||||
(state) => state.mode,
|
||||
),
|
||||
);
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
86
lib/fragments/developer.dart
Normal file
86
lib/fragments/developer.dart
Normal 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)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,3 +11,4 @@ export 'backup_and_recovery.dart';
|
||||
export 'resources.dart';
|
||||
export 'connection/requests.dart';
|
||||
export 'connection/connections.dart';
|
||||
export 'developer.dart';
|
||||
|
||||
@@ -34,19 +34,6 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
|
||||
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(
|
||||
isCurrentPageProvider(
|
||||
PageLabel.logs,
|
||||
@@ -60,6 +47,18 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
},
|
||||
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
|
||||
@@ -123,7 +122,7 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
final height = globalState.measure
|
||||
.computeTextSize(
|
||||
Text(
|
||||
log.payload ?? "",
|
||||
log.payload,
|
||||
style: globalState.appController.context.textTheme.bodyLarge,
|
||||
),
|
||||
maxWidth: _currentMaxWidth,
|
||||
@@ -154,8 +153,7 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
return LayoutBuilder(
|
||||
builder: (_, constraints) {
|
||||
_handleTryClearCache(constraints.maxWidth - 40);
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
return TextScaleNotification(
|
||||
child: ValueListenableBuilder<LogsState>(
|
||||
valueListenable: _logsStateNotifier,
|
||||
builder: (_, state, __) {
|
||||
@@ -168,7 +166,7 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
final items = logs
|
||||
.map<Widget>(
|
||||
(log) => LogItem(
|
||||
key: Key(log.dateTime.toString()),
|
||||
key: Key(log.dateTime),
|
||||
log: log,
|
||||
onClick: (value) {
|
||||
context.commonScaffoldState?.addKeyword(value);
|
||||
@@ -181,43 +179,49 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
return ScrollToEndBox<Log>(
|
||||
controller: _scrollController,
|
||||
cacheKey: _cacheKey,
|
||||
dataSource: logs,
|
||||
child: CommonScrollBar(
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ScrollToEndBox<Log>(
|
||||
controller: _scrollController,
|
||||
child: CacheItemExtentListView(
|
||||
key: _key,
|
||||
reverse: true,
|
||||
shrinkWrap: true,
|
||||
physics: NextClampingScrollPhysics(),
|
||||
cacheKey: _cacheKey,
|
||||
dataSource: logs,
|
||||
child: CommonScrollBar(
|
||||
controller: _scrollController,
|
||||
itemBuilder: (_, index) {
|
||||
return items[index];
|
||||
},
|
||||
itemExtentBuilder: (index) {
|
||||
final item = items[index];
|
||||
if (item.runtimeType == Divider) {
|
||||
return 0;
|
||||
}
|
||||
final log = logs[(index / 2).floor()];
|
||||
return _getItemHeight(log);
|
||||
},
|
||||
itemCount: items.length,
|
||||
keyBuilder: (int index) {
|
||||
final item = items[index];
|
||||
if (item.runtimeType == Divider) {
|
||||
return "divider";
|
||||
}
|
||||
final log = logs[(index / 2).floor()];
|
||||
return log.payload ?? "";
|
||||
},
|
||||
child: CacheItemExtentListView(
|
||||
key: _key,
|
||||
reverse: true,
|
||||
shrinkWrap: true,
|
||||
physics: NextClampingScrollPhysics(),
|
||||
controller: _scrollController,
|
||||
itemBuilder: (_, index) {
|
||||
return items[index];
|
||||
},
|
||||
itemExtentBuilder: (index) {
|
||||
final item = items[index];
|
||||
if (item.runtimeType == Divider) {
|
||||
return 0;
|
||||
}
|
||||
final log = logs[(index / 2).floor()];
|
||||
return _getItemHeight(log);
|
||||
},
|
||||
itemCount: items.length,
|
||||
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,
|
||||
),
|
||||
title: SelectableText(
|
||||
log.payload ?? '',
|
||||
log.payload,
|
||||
style: context.textTheme.bodyLarge,
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SelectableText(
|
||||
"${log.dateTime}",
|
||||
log.dateTime,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: context.colorScheme.primary,
|
||||
),
|
||||
|
||||
@@ -104,8 +104,13 @@ class _URLFormDialogState extends State<URLFormDialog> {
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
TextField(
|
||||
maxLines: 5,
|
||||
keyboardType: TextInputType.url,
|
||||
minLines: 1,
|
||||
maxLines: 5,
|
||||
onSubmitted: (_) {
|
||||
_handleAddProfileFormURL();
|
||||
},
|
||||
onEditingComplete: _handleAddProfileFormURL,
|
||||
controller: urlController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
|
||||
@@ -214,6 +214,7 @@ class _EditProfileState extends State<EditProfile> {
|
||||
final items = [
|
||||
ListItem(
|
||||
title: TextFormField(
|
||||
textInputAction: TextInputAction.next,
|
||||
controller: labelController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
@@ -230,6 +231,8 @@ class _EditProfileState extends State<EditProfile> {
|
||||
if (widget.profile.type == ProfileType.url) ...[
|
||||
ListItem(
|
||||
title: TextFormField(
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.url,
|
||||
controller: urlController,
|
||||
maxLines: 5,
|
||||
minLines: 1,
|
||||
@@ -258,6 +261,7 @@ class _EditProfileState extends State<EditProfile> {
|
||||
if (autoUpdate)
|
||||
ListItem(
|
||||
title: TextFormField(
|
||||
textInputAction: TextInputAction.next,
|
||||
controller: autoUpdateDurationController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
|
||||
@@ -228,7 +228,7 @@ class _OverrideProfileState extends State<OverrideProfile> {
|
||||
message: TextSpan(
|
||||
text: appLocalizations.saveTip,
|
||||
),
|
||||
confirmText: appLocalizations.tip,
|
||||
confirmText: appLocalizations.save,
|
||||
);
|
||||
if (res != true) {
|
||||
return;
|
||||
@@ -873,6 +873,8 @@ class _AddRuleDialogState extends State<AddRuleDialog> {
|
||||
builder: (filed) {
|
||||
return DropdownMenu(
|
||||
width: 200,
|
||||
enableFilter: false,
|
||||
enableSearch: false,
|
||||
controller: _subRuleController,
|
||||
label: Text(appLocalizations.subRule),
|
||||
menuHeight: 250,
|
||||
@@ -890,11 +892,11 @@ class _AddRuleDialogState extends State<AddRuleDialog> {
|
||||
builder: (filed) {
|
||||
return DropdownMenu(
|
||||
controller: _ruleTargetController,
|
||||
initialSelection: filed.value,
|
||||
label: Text(appLocalizations.ruleTarget),
|
||||
width: 200,
|
||||
menuHeight: 250,
|
||||
enableFilter: true,
|
||||
enableFilter: false,
|
||||
enableSearch: false,
|
||||
dropdownMenuEntries: _targetItems,
|
||||
errorText: filed.errorText,
|
||||
);
|
||||
|
||||
@@ -39,7 +39,19 @@ class ThemeFragment extends StatelessWidget {
|
||||
|
||||
@override
|
||||
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 {
|
||||
const _ThemeModeItem();
|
||||
|
||||
@@ -309,124 +298,138 @@ class _PrimaryColorItemState extends ConsumerState<_PrimaryColorItem> {
|
||||
final primaryColors = [null, ...vm3.b];
|
||||
final schemeVariant = vm3.c;
|
||||
|
||||
return ItemCard(
|
||||
info: Info(
|
||||
label: appLocalizations.themeColor,
|
||||
iconData: Icons.palette,
|
||||
),
|
||||
actions: genActions(
|
||||
[
|
||||
if (_removablePrimaryColor == null)
|
||||
FilledButton(
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
onPressed: _handleChangeSchemeVariant,
|
||||
child: Text(Intl.message("${schemeVariant.name}Scheme")),
|
||||
),
|
||||
_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,
|
||||
return CommonPopScope(
|
||||
onPop: () {
|
||||
if (_removablePrimaryColor != null) {
|
||||
setState(() {
|
||||
_removablePrimaryColor = null;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: ItemCard(
|
||||
info: Info(
|
||||
label: appLocalizations.themeColor,
|
||||
iconData: Icons.palette,
|
||||
),
|
||||
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,
|
||||
width: itemWidth,
|
||||
height: itemWidth,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
actions: genActions(
|
||||
[
|
||||
if (_removablePrimaryColor == null)
|
||||
FilledButton(
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
onPressed: _handleChangeSchemeVariant,
|
||||
child: Text(Intl.message("${schemeVariant.name}Scheme")),
|
||||
),
|
||||
_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(
|
||||
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,
|
||||
children: [
|
||||
EffectGestureDetector(
|
||||
child: ColorSchemeBox(
|
||||
isSelected: color == primaryColor,
|
||||
primaryColor: color != null ? Color(color) : null,
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(themeSettingProvider.notifier)
|
||||
.updateState(
|
||||
(state) => state.copyWith(
|
||||
primaryColor: color,
|
||||
),
|
||||
);
|
||||
width: itemWidth,
|
||||
height: itemWidth,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
EffectGestureDetector(
|
||||
child: ColorSchemeBox(
|
||||
isSelected: color == primaryColor,
|
||||
primaryColor: color != null ? Color(color) : null,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_removablePrimaryColor = null;
|
||||
});
|
||||
ref
|
||||
.read(themeSettingProvider.notifier)
|
||||
.updateState(
|
||||
(state) => state.copyWith(
|
||||
primaryColor: color,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
onLongPress: () {
|
||||
setState(() {
|
||||
_removablePrimaryColor = color;
|
||||
});
|
||||
},
|
||||
),
|
||||
onLongPress: () {
|
||||
setState(() {
|
||||
_removablePrimaryColor = color;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_removablePrimaryColor != null &&
|
||||
_removablePrimaryColor == color)
|
||||
Container(
|
||||
color: Colors.white.opacity0,
|
||||
padding: EdgeInsets.all(8),
|
||||
child: IconButton.filledTonal(
|
||||
onPressed: _handleDel,
|
||||
padding: EdgeInsets.all(12),
|
||||
iconSize: 30,
|
||||
icon: Icon(
|
||||
color: context.colorScheme.primary,
|
||||
Icons.delete,
|
||||
if (_removablePrimaryColor != null &&
|
||||
_removablePrimaryColor == color)
|
||||
Container(
|
||||
color: Colors.white.opacity0,
|
||||
padding: EdgeInsets.all(8),
|
||||
child: IconButton.filledTonal(
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'backup_and_recovery.dart';
|
||||
import 'developer.dart';
|
||||
import 'theme.dart';
|
||||
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(
|
||||
title: appLocalizations.other,
|
||||
items: [
|
||||
_DisclaimerItem(),
|
||||
if (enableDeveloperMode) _DeveloperItem(),
|
||||
_InfoItem(),
|
||||
],
|
||||
);
|
||||
@@ -82,7 +84,11 @@ class _ToolboxFragmentState extends ConsumerState<ToolsFragment> {
|
||||
|
||||
@override
|
||||
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 = [
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
@@ -99,7 +105,7 @@ class _ToolboxFragmentState extends ConsumerState<ToolsFragment> {
|
||||
},
|
||||
),
|
||||
..._getSettingList(),
|
||||
..._getOtherList(),
|
||||
..._getOtherList(vm2.b),
|
||||
];
|
||||
return ListView.builder(
|
||||
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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"The current application is already the latest version",
|
||||
),
|
||||
"checking": MessageLookupByLibrary.simpleMessage("Checking..."),
|
||||
"clearData": MessageLookupByLibrary.simpleMessage("Clear Data"),
|
||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("Export clipboard"),
|
||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("Clipboard import"),
|
||||
"colorExists": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -177,6 +178,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"core": MessageLookupByLibrary.simpleMessage("Core"),
|
||||
"coreInfo": MessageLookupByLibrary.simpleMessage("Core info"),
|
||||
"country": MessageLookupByLibrary.simpleMessage("Country"),
|
||||
"crashTest": MessageLookupByLibrary.simpleMessage("Crash test"),
|
||||
"create": MessageLookupByLibrary.simpleMessage("Create"),
|
||||
"cut": MessageLookupByLibrary.simpleMessage("Cut"),
|
||||
"dark": MessageLookupByLibrary.simpleMessage("Dark"),
|
||||
@@ -208,6 +210,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"detectionTip": MessageLookupByLibrary.simpleMessage(
|
||||
"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"),
|
||||
"disclaimer": MessageLookupByLibrary.simpleMessage("Disclaimer"),
|
||||
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -362,6 +368,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"loose": MessageLookupByLibrary.simpleMessage("Loose"),
|
||||
"memoryInfo": MessageLookupByLibrary.simpleMessage("Memory info"),
|
||||
"messageTest": MessageLookupByLibrary.simpleMessage("Message test"),
|
||||
"messageTestTip": MessageLookupByLibrary.simpleMessage(
|
||||
"This is a message.",
|
||||
),
|
||||
"min": MessageLookupByLibrary.simpleMessage("Min"),
|
||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("Minimize on exit"),
|
||||
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -627,6 +637,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Enabling it will allow TCP concurrency",
|
||||
),
|
||||
"testUrl": MessageLookupByLibrary.simpleMessage("Test url"),
|
||||
"textScale": MessageLookupByLibrary.simpleMessage("Text Scaling"),
|
||||
"theme": MessageLookupByLibrary.simpleMessage("Theme"),
|
||||
"themeColor": MessageLookupByLibrary.simpleMessage("Theme color"),
|
||||
"themeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
|
||||
@@ -104,6 +104,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"checkUpdate": MessageLookupByLibrary.simpleMessage("更新を確認"),
|
||||
"checkUpdateError": MessageLookupByLibrary.simpleMessage("アプリは最新版です"),
|
||||
"checking": MessageLookupByLibrary.simpleMessage("確認中..."),
|
||||
"clearData": MessageLookupByLibrary.simpleMessage("データを消去"),
|
||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("クリップボードにエクスポート"),
|
||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("クリップボードからインポート"),
|
||||
"colorExists": MessageLookupByLibrary.simpleMessage("この色は既に存在します"),
|
||||
@@ -127,6 +128,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"core": MessageLookupByLibrary.simpleMessage("コア"),
|
||||
"coreInfo": MessageLookupByLibrary.simpleMessage("コア情報"),
|
||||
"country": MessageLookupByLibrary.simpleMessage("国"),
|
||||
"crashTest": MessageLookupByLibrary.simpleMessage("クラッシュテスト"),
|
||||
"create": MessageLookupByLibrary.simpleMessage("作成"),
|
||||
"cut": MessageLookupByLibrary.simpleMessage("切り取り"),
|
||||
"dark": MessageLookupByLibrary.simpleMessage("ダーク"),
|
||||
@@ -150,6 +152,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"ClashMetaベースのマルチプラットフォームプロキシクライアント。シンプルで使いやすく、オープンソースで広告なし。",
|
||||
),
|
||||
"detectionTip": MessageLookupByLibrary.simpleMessage("サードパーティAPIに依存(参考値)"),
|
||||
"developerMode": MessageLookupByLibrary.simpleMessage("デベロッパーモード"),
|
||||
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage(
|
||||
"デベロッパーモードが有効になりました。",
|
||||
),
|
||||
"direct": MessageLookupByLibrary.simpleMessage("ダイレクト"),
|
||||
"disclaimer": MessageLookupByLibrary.simpleMessage("免責事項"),
|
||||
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -258,6 +264,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"loopbackDesc": MessageLookupByLibrary.simpleMessage("UWPループバック解除用"),
|
||||
"loose": MessageLookupByLibrary.simpleMessage("疎"),
|
||||
"memoryInfo": MessageLookupByLibrary.simpleMessage("メモリ情報"),
|
||||
"messageTest": MessageLookupByLibrary.simpleMessage("メッセージテスト"),
|
||||
"messageTestTip": MessageLookupByLibrary.simpleMessage("これはメッセージです。"),
|
||||
"min": MessageLookupByLibrary.simpleMessage("最小化"),
|
||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("終了時に最小化"),
|
||||
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -463,6 +471,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP並列処理"),
|
||||
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("TCP並列処理を許可"),
|
||||
"testUrl": MessageLookupByLibrary.simpleMessage("URLテスト"),
|
||||
"textScale": MessageLookupByLibrary.simpleMessage("テキストスケーリング"),
|
||||
"theme": MessageLookupByLibrary.simpleMessage("テーマ"),
|
||||
"themeColor": MessageLookupByLibrary.simpleMessage("テーマカラー"),
|
||||
"themeDesc": MessageLookupByLibrary.simpleMessage("ダークモードの設定、色の調整"),
|
||||
|
||||
@@ -148,6 +148,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Текущее приложение уже является последней версией",
|
||||
),
|
||||
"checking": MessageLookupByLibrary.simpleMessage("Проверка..."),
|
||||
"clearData": MessageLookupByLibrary.simpleMessage("Очистить данные"),
|
||||
"clipboardExport": MessageLookupByLibrary.simpleMessage(
|
||||
"Экспорт в буфер обмена",
|
||||
),
|
||||
@@ -183,6 +184,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"core": MessageLookupByLibrary.simpleMessage("Ядро"),
|
||||
"coreInfo": MessageLookupByLibrary.simpleMessage("Информация о ядре"),
|
||||
"country": MessageLookupByLibrary.simpleMessage("Страна"),
|
||||
"crashTest": MessageLookupByLibrary.simpleMessage("Тест на сбои"),
|
||||
"create": MessageLookupByLibrary.simpleMessage("Создать"),
|
||||
"cut": MessageLookupByLibrary.simpleMessage("Вырезать"),
|
||||
"dark": MessageLookupByLibrary.simpleMessage("Темный"),
|
||||
@@ -216,6 +218,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"detectionTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Опирается на сторонний API, только для справки",
|
||||
),
|
||||
"developerMode": MessageLookupByLibrary.simpleMessage("Режим разработчика"),
|
||||
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage(
|
||||
"Режим разработчика активирован.",
|
||||
),
|
||||
"direct": MessageLookupByLibrary.simpleMessage("Прямой"),
|
||||
"disclaimer": MessageLookupByLibrary.simpleMessage(
|
||||
"Отказ от ответственности",
|
||||
@@ -386,6 +392,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"loose": MessageLookupByLibrary.simpleMessage("Свободный"),
|
||||
"memoryInfo": MessageLookupByLibrary.simpleMessage("Информация о памяти"),
|
||||
"messageTest": MessageLookupByLibrary.simpleMessage(
|
||||
"Тестирование сообщения",
|
||||
),
|
||||
"messageTestTip": MessageLookupByLibrary.simpleMessage("Это сообщение."),
|
||||
"min": MessageLookupByLibrary.simpleMessage("Мин"),
|
||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage(
|
||||
"Свернуть при выходе",
|
||||
@@ -665,6 +675,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Включение позволит использовать параллелизм TCP",
|
||||
),
|
||||
"testUrl": MessageLookupByLibrary.simpleMessage("Тест URL"),
|
||||
"textScale": MessageLookupByLibrary.simpleMessage("Масштабирование текста"),
|
||||
"theme": MessageLookupByLibrary.simpleMessage("Тема"),
|
||||
"themeColor": MessageLookupByLibrary.simpleMessage("Цвет темы"),
|
||||
"themeDesc": MessageLookupByLibrary.simpleMessage(
|
||||
|
||||
@@ -94,6 +94,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"),
|
||||
"checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"),
|
||||
"checking": MessageLookupByLibrary.simpleMessage("检测中..."),
|
||||
"clearData": MessageLookupByLibrary.simpleMessage("清除数据"),
|
||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"),
|
||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"),
|
||||
"colorExists": MessageLookupByLibrary.simpleMessage("该颜色已存在"),
|
||||
@@ -117,6 +118,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
||||
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
|
||||
"country": MessageLookupByLibrary.simpleMessage("区域"),
|
||||
"crashTest": MessageLookupByLibrary.simpleMessage("崩溃测试"),
|
||||
"create": MessageLookupByLibrary.simpleMessage("创建"),
|
||||
"cut": MessageLookupByLibrary.simpleMessage("剪切"),
|
||||
"dark": MessageLookupByLibrary.simpleMessage("深色"),
|
||||
@@ -136,6 +138,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"基于ClashMeta的多平台代理客户端,简单易用,开源无广告。",
|
||||
),
|
||||
"detectionTip": MessageLookupByLibrary.simpleMessage("依赖第三方api,仅供参考"),
|
||||
"developerMode": MessageLookupByLibrary.simpleMessage("开发者模式"),
|
||||
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage("开发者模式已启用。"),
|
||||
"direct": MessageLookupByLibrary.simpleMessage("直连"),
|
||||
"disclaimer": MessageLookupByLibrary.simpleMessage("免责声明"),
|
||||
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -232,6 +236,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"loopbackDesc": MessageLookupByLibrary.simpleMessage("用于UWP回环解锁"),
|
||||
"loose": MessageLookupByLibrary.simpleMessage("宽松"),
|
||||
"memoryInfo": MessageLookupByLibrary.simpleMessage("内存信息"),
|
||||
"messageTest": MessageLookupByLibrary.simpleMessage("消息测试"),
|
||||
"messageTestTip": MessageLookupByLibrary.simpleMessage("这是一条消息。"),
|
||||
"min": MessageLookupByLibrary.simpleMessage("最小"),
|
||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
|
||||
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
|
||||
@@ -407,6 +413,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"),
|
||||
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许TCP并发"),
|
||||
"testUrl": MessageLookupByLibrary.simpleMessage("测速链接"),
|
||||
"textScale": MessageLookupByLibrary.simpleMessage("文本缩放"),
|
||||
"theme": MessageLookupByLibrary.simpleMessage("主题"),
|
||||
"themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"),
|
||||
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),
|
||||
|
||||
@@ -3004,6 +3004,61 @@ class AppLocalizations {
|
||||
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> {
|
||||
|
||||
@@ -73,7 +73,7 @@ class _ClashContainerState extends ConsumerState<ClashManager>
|
||||
void onLog(Log log) {
|
||||
ref.watch(logsProvider.notifier).addLog(log);
|
||||
if (log.logLevel == LogLevel.error) {
|
||||
globalState.showNotifier(log.payload ?? '');
|
||||
globalState.showNotifier(log.payload);
|
||||
}
|
||||
super.onLog(log);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class MessageManager extends StatefulWidget {
|
||||
class MessageManagerState extends State<MessageManager> {
|
||||
final _messagesNotifier = ValueNotifier<List<CommonMessage>>([]);
|
||||
final List<CommonMessage> _bufferMessages = [];
|
||||
Completer<bool>? _messageIngCompleter;
|
||||
bool _pushing = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -40,26 +40,27 @@ class MessageManagerState extends State<MessageManager> {
|
||||
text: text,
|
||||
);
|
||||
_bufferMessages.add(commonMessage);
|
||||
_showMessage();
|
||||
await _showMessage();
|
||||
}
|
||||
|
||||
_showMessage() async {
|
||||
if (_messageIngCompleter?.isCompleted == false) {
|
||||
if (_pushing == true) {
|
||||
return;
|
||||
}
|
||||
_pushing = true;
|
||||
while (_bufferMessages.isNotEmpty) {
|
||||
final commonMessage = _bufferMessages.removeAt(0);
|
||||
_messagesNotifier.value = List.from(_messagesNotifier.value)
|
||||
..add(
|
||||
commonMessage,
|
||||
);
|
||||
|
||||
_messageIngCompleter = Completer();
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
Future.delayed(commonMessage.duration, () {
|
||||
_handleRemove(commonMessage);
|
||||
});
|
||||
_messageIngCompleter?.complete(true);
|
||||
if (_bufferMessages.isEmpty) {
|
||||
_pushing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,41 +78,41 @@ class MessageManagerState extends State<MessageManager> {
|
||||
valueListenable: _messagesNotifier,
|
||||
builder: (_, messages, __) {
|
||||
return FadeThroughBox(
|
||||
margin: EdgeInsets.only(
|
||||
top: kToolbarHeight + 8,
|
||||
left: 12,
|
||||
right: 12,
|
||||
),
|
||||
alignment: Alignment.topRight,
|
||||
child: messages.isEmpty
|
||||
? SizedBox()
|
||||
: LayoutBuilder(
|
||||
key: Key(messages.last.id),
|
||||
builder: (_, constraints) {
|
||||
return Card(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
key: Key(messages.last.id),
|
||||
builder: (_, constraints) {
|
||||
return Card(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import 'dart:math';
|
||||
import 'package:fl_clash/common/constant.dart';
|
||||
import 'package:fl_clash/common/measure.dart';
|
||||
import 'package:fl_clash/common/theme.dart';
|
||||
import 'package:fl_clash/providers/config.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class ThemeManager extends StatelessWidget {
|
||||
class ThemeManager extends ConsumerWidget {
|
||||
final Widget child;
|
||||
|
||||
const ThemeManager({
|
||||
@@ -13,14 +16,30 @@ class ThemeManager extends StatelessWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
globalState.measure = Measure.of(context);
|
||||
globalState.theme = CommonTheme.of(context);
|
||||
Widget build(BuildContext context, ref) {
|
||||
final textScale = ref.read(
|
||||
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(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaler: TextScaler.linear(
|
||||
textScaleFactor,
|
||||
),
|
||||
padding: padding.copyWith(
|
||||
top: padding.top > height * 0.3 ? 0.0 : padding.top,
|
||||
),
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (_, container) {
|
||||
|
||||
@@ -16,7 +16,6 @@ class AppState with _$AppState {
|
||||
@Default(false) bool isInit,
|
||||
@Default(PageLabel.dashboard) PageLabel pageLabel,
|
||||
@Default([]) List<Package> packages,
|
||||
@Default(ColorSchemes()) ColorSchemes colorSchemes,
|
||||
@Default(0) int sortNum,
|
||||
required Size viewSize,
|
||||
@Default({}) DelayMap delayMap,
|
||||
|
||||
@@ -84,33 +84,33 @@ extension ConnectionExt on Connection {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class Log {
|
||||
@JsonKey(name: "LogLevel")
|
||||
LogLevel logLevel;
|
||||
@JsonKey(name: "Payload")
|
||||
String? payload;
|
||||
DateTime _dateTime;
|
||||
String _logDateTime(_) {
|
||||
return DateTime.now().toString();
|
||||
}
|
||||
|
||||
Log({
|
||||
required this.logLevel,
|
||||
this.payload,
|
||||
}) : _dateTime = DateTime.now();
|
||||
// String _logId(_) {
|
||||
// return utils.id;
|
||||
// }
|
||||
|
||||
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) {
|
||||
return _$LogFromJson(json);
|
||||
factory Log.app(
|
||||
String payload,
|
||||
) {
|
||||
return Log(
|
||||
payload: payload,
|
||||
dateTime: _logDateTime(null),
|
||||
// id: _logId(null),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$LogToJson(this);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Log{logLevel: $logLevel, payload: $payload, dateTime: $dateTime}';
|
||||
}
|
||||
factory Log.fromJson(Map<String, Object?> json) => _$LogFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -127,11 +127,10 @@ extension LogsStateExt on LogsState {
|
||||
final lowQuery = query.toLowerCase();
|
||||
return logs.where(
|
||||
(log) {
|
||||
final payload = log.payload?.toLowerCase();
|
||||
final payload = log.payload.toLowerCase();
|
||||
final logLevelName = log.logLevel.name;
|
||||
return {logLevelName}.containsAll(keywords) &&
|
||||
((payload?.contains(lowQuery) ?? false) ||
|
||||
logLevelName.contains(lowQuery));
|
||||
((payload.contains(lowQuery)) || logLevelName.contains(lowQuery));
|
||||
},
|
||||
).toList();
|
||||
}
|
||||
|
||||
@@ -37,7 +37,9 @@ const defaultNetworkProps = NetworkProps();
|
||||
const defaultProxiesStyle = ProxiesStyle();
|
||||
const defaultWindowProps = WindowProps();
|
||||
const defaultAccessControl = AccessControl();
|
||||
const defaultThemeProps = ThemeProps();
|
||||
final defaultThemeProps = ThemeProps(
|
||||
primaryColor: defaultPrimaryColor,
|
||||
);
|
||||
|
||||
const List<DashboardWidget> defaultDashboardWidgets = [
|
||||
DashboardWidget.networkSpeed,
|
||||
@@ -82,6 +84,7 @@ class AppSettingProps with _$AppSettingProps {
|
||||
@Default(false) bool disclaimerAccepted,
|
||||
@Default(true) bool minimizeOnExit,
|
||||
@Default(false) bool hidden,
|
||||
@Default(false) bool developerMode,
|
||||
}) = _AppSettingProps;
|
||||
|
||||
factory AppSettingProps.fromJson(Map<String, Object?> json) =>
|
||||
@@ -170,18 +173,41 @@ class ProxiesStyle with _$ProxiesStyle {
|
||||
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
|
||||
class ThemeProps with _$ThemeProps {
|
||||
const factory ThemeProps({
|
||||
@Default(defaultPrimaryColor) int? primaryColor,
|
||||
int? primaryColor,
|
||||
@Default(defaultPrimaryColors) List<int> primaryColors,
|
||||
@Default(ThemeMode.dark) ThemeMode themeMode,
|
||||
@Default(DynamicSchemeVariant.tonalSpot) DynamicSchemeVariant schemeVariant,
|
||||
@Default(false) bool pureBlack,
|
||||
@Default(TextScale()) TextScale textScale,
|
||||
}) = _ThemeProps;
|
||||
|
||||
factory ThemeProps.fromJson(Map<String, Object?>? json) =>
|
||||
json == null ? defaultThemeProps : _$ThemePropsFromJson(json);
|
||||
factory ThemeProps.fromJson(Map<String, Object?> json) =>
|
||||
_$ThemePropsFromJson(json);
|
||||
|
||||
factory ThemeProps.safeFromJson(Map<String, Object?>? json) {
|
||||
if (json == null) {
|
||||
return defaultThemeProps;
|
||||
}
|
||||
try {
|
||||
return ThemeProps.fromJson(json);
|
||||
} catch (_) {
|
||||
return defaultThemeProps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -197,7 +223,7 @@ class Config with _$Config {
|
||||
DAV? dav,
|
||||
@Default(defaultNetworkProps) NetworkProps networkProps,
|
||||
@Default(defaultVpnProps) VpnProps vpnProps,
|
||||
@Default(defaultThemeProps) ThemeProps themeProps,
|
||||
@JsonKey(fromJson: ThemeProps.safeFromJson) required ThemeProps themeProps,
|
||||
@Default(defaultProxiesStyle) ProxiesStyle proxiesStyle,
|
||||
@Default(defaultWindowProps) WindowProps windowProps,
|
||||
@Default(defaultClashConfig) ClashConfig patchClashConfig,
|
||||
|
||||
@@ -19,7 +19,6 @@ mixin _$AppState {
|
||||
bool get isInit => throw _privateConstructorUsedError;
|
||||
PageLabel get pageLabel => throw _privateConstructorUsedError;
|
||||
List<Package> get packages => throw _privateConstructorUsedError;
|
||||
ColorSchemes get colorSchemes => throw _privateConstructorUsedError;
|
||||
int get sortNum => throw _privateConstructorUsedError;
|
||||
Size get viewSize => throw _privateConstructorUsedError;
|
||||
Map<String, Map<String, int?>> get delayMap =>
|
||||
@@ -53,7 +52,6 @@ abstract class $AppStateCopyWith<$Res> {
|
||||
{bool isInit,
|
||||
PageLabel pageLabel,
|
||||
List<Package> packages,
|
||||
ColorSchemes colorSchemes,
|
||||
int sortNum,
|
||||
Size viewSize,
|
||||
Map<String, Map<String, int?>> delayMap,
|
||||
@@ -69,8 +67,6 @@ abstract class $AppStateCopyWith<$Res> {
|
||||
FixedList<Traffic> traffics,
|
||||
Traffic totalTraffic,
|
||||
bool needApply});
|
||||
|
||||
$ColorSchemesCopyWith<$Res> get colorSchemes;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -91,7 +87,6 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
|
||||
Object? isInit = null,
|
||||
Object? pageLabel = null,
|
||||
Object? packages = null,
|
||||
Object? colorSchemes = null,
|
||||
Object? sortNum = null,
|
||||
Object? viewSize = null,
|
||||
Object? delayMap = null,
|
||||
@@ -121,10 +116,6 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
|
||||
? _value.packages
|
||||
: packages // ignore: cast_nullable_to_non_nullable
|
||||
as List<Package>,
|
||||
colorSchemes: null == colorSchemes
|
||||
? _value.colorSchemes
|
||||
: colorSchemes // ignore: cast_nullable_to_non_nullable
|
||||
as ColorSchemes,
|
||||
sortNum: null == sortNum
|
||||
? _value.sortNum
|
||||
: sortNum // ignore: cast_nullable_to_non_nullable
|
||||
@@ -187,16 +178,6 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState>
|
||||
as bool,
|
||||
) 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
|
||||
@@ -211,7 +192,6 @@ abstract class _$$AppStateImplCopyWith<$Res>
|
||||
{bool isInit,
|
||||
PageLabel pageLabel,
|
||||
List<Package> packages,
|
||||
ColorSchemes colorSchemes,
|
||||
int sortNum,
|
||||
Size viewSize,
|
||||
Map<String, Map<String, int?>> delayMap,
|
||||
@@ -227,9 +207,6 @@ abstract class _$$AppStateImplCopyWith<$Res>
|
||||
FixedList<Traffic> traffics,
|
||||
Traffic totalTraffic,
|
||||
bool needApply});
|
||||
|
||||
@override
|
||||
$ColorSchemesCopyWith<$Res> get colorSchemes;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -248,7 +225,6 @@ class __$$AppStateImplCopyWithImpl<$Res>
|
||||
Object? isInit = null,
|
||||
Object? pageLabel = null,
|
||||
Object? packages = null,
|
||||
Object? colorSchemes = null,
|
||||
Object? sortNum = null,
|
||||
Object? viewSize = null,
|
||||
Object? delayMap = null,
|
||||
@@ -278,10 +254,6 @@ class __$$AppStateImplCopyWithImpl<$Res>
|
||||
? _value._packages
|
||||
: packages // ignore: cast_nullable_to_non_nullable
|
||||
as List<Package>,
|
||||
colorSchemes: null == colorSchemes
|
||||
? _value.colorSchemes
|
||||
: colorSchemes // ignore: cast_nullable_to_non_nullable
|
||||
as ColorSchemes,
|
||||
sortNum: null == sortNum
|
||||
? _value.sortNum
|
||||
: sortNum // ignore: cast_nullable_to_non_nullable
|
||||
@@ -353,7 +325,6 @@ class _$AppStateImpl implements _AppState {
|
||||
{this.isInit = false,
|
||||
this.pageLabel = PageLabel.dashboard,
|
||||
final List<Package> packages = const [],
|
||||
this.colorSchemes = const ColorSchemes(),
|
||||
this.sortNum = 0,
|
||||
required this.viewSize,
|
||||
final Map<String, Map<String, int?>> delayMap = const {},
|
||||
@@ -389,9 +360,6 @@ class _$AppStateImpl implements _AppState {
|
||||
return EqualUnmodifiableListView(_packages);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final ColorSchemes colorSchemes;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int sortNum;
|
||||
@@ -449,7 +417,7 @@ class _$AppStateImpl implements _AppState {
|
||||
|
||||
@override
|
||||
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
|
||||
@@ -461,8 +429,6 @@ class _$AppStateImpl implements _AppState {
|
||||
(identical(other.pageLabel, pageLabel) ||
|
||||
other.pageLabel == pageLabel) &&
|
||||
const DeepCollectionEquality().equals(other._packages, _packages) &&
|
||||
(identical(other.colorSchemes, colorSchemes) ||
|
||||
other.colorSchemes == colorSchemes) &&
|
||||
(identical(other.sortNum, sortNum) || other.sortNum == sortNum) &&
|
||||
(identical(other.viewSize, viewSize) ||
|
||||
other.viewSize == viewSize) &&
|
||||
@@ -489,28 +455,26 @@ class _$AppStateImpl implements _AppState {
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hashAll([
|
||||
runtimeType,
|
||||
isInit,
|
||||
pageLabel,
|
||||
const DeepCollectionEquality().hash(_packages),
|
||||
colorSchemes,
|
||||
sortNum,
|
||||
viewSize,
|
||||
const DeepCollectionEquality().hash(_delayMap),
|
||||
const DeepCollectionEquality().hash(_groups),
|
||||
checkIpNum,
|
||||
brightness,
|
||||
runTime,
|
||||
const DeepCollectionEquality().hash(_providers),
|
||||
localIp,
|
||||
requests,
|
||||
version,
|
||||
logs,
|
||||
traffics,
|
||||
totalTraffic,
|
||||
needApply
|
||||
]);
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
isInit,
|
||||
pageLabel,
|
||||
const DeepCollectionEquality().hash(_packages),
|
||||
sortNum,
|
||||
viewSize,
|
||||
const DeepCollectionEquality().hash(_delayMap),
|
||||
const DeepCollectionEquality().hash(_groups),
|
||||
checkIpNum,
|
||||
brightness,
|
||||
runTime,
|
||||
const DeepCollectionEquality().hash(_providers),
|
||||
localIp,
|
||||
requests,
|
||||
version,
|
||||
logs,
|
||||
traffics,
|
||||
totalTraffic,
|
||||
needApply);
|
||||
|
||||
/// Create a copy of AppState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -526,7 +490,6 @@ abstract class _AppState implements AppState {
|
||||
{final bool isInit,
|
||||
final PageLabel pageLabel,
|
||||
final List<Package> packages,
|
||||
final ColorSchemes colorSchemes,
|
||||
final int sortNum,
|
||||
required final Size viewSize,
|
||||
final Map<String, Map<String, int?>> delayMap,
|
||||
@@ -550,8 +513,6 @@ abstract class _AppState implements AppState {
|
||||
@override
|
||||
List<Package> get packages;
|
||||
@override
|
||||
ColorSchemes get colorSchemes;
|
||||
@override
|
||||
int get sortNum;
|
||||
@override
|
||||
Size get viewSize;
|
||||
|
||||
@@ -342,6 +342,7 @@ const _$LogLevelEnumMap = {
|
||||
LogLevel.warning: 'warning',
|
||||
LogLevel.error: 'error',
|
||||
LogLevel.silent: 'silent',
|
||||
LogLevel.app: 'app',
|
||||
};
|
||||
|
||||
const _$FindProcessModeEnumMap = {
|
||||
|
||||
@@ -1092,6 +1092,203 @@ abstract class _Connection implements Connection {
|
||||
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
|
||||
mixin _$LogsState {
|
||||
List<Log> get logs => throw _privateConstructorUsedError;
|
||||
|
||||
@@ -6,24 +6,6 @@ part of '../common.dart';
|
||||
// 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(
|
||||
packageName: json['packageName'] as String,
|
||||
@@ -87,6 +69,28 @@ Map<String, dynamic> _$$ConnectionImplToJson(_$ConnectionImpl instance) =>
|
||||
'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(
|
||||
uri: json['uri'] as String,
|
||||
user: json['user'] as String,
|
||||
|
||||
@@ -37,6 +37,7 @@ mixin _$AppSettingProps {
|
||||
bool get disclaimerAccepted => throw _privateConstructorUsedError;
|
||||
bool get minimizeOnExit => throw _privateConstructorUsedError;
|
||||
bool get hidden => throw _privateConstructorUsedError;
|
||||
bool get developerMode => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this AppSettingProps to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -70,7 +71,8 @@ abstract class $AppSettingPropsCopyWith<$Res> {
|
||||
bool showLabel,
|
||||
bool disclaimerAccepted,
|
||||
bool minimizeOnExit,
|
||||
bool hidden});
|
||||
bool hidden,
|
||||
bool developerMode});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -103,6 +105,7 @@ class _$AppSettingPropsCopyWithImpl<$Res, $Val extends AppSettingProps>
|
||||
Object? disclaimerAccepted = null,
|
||||
Object? minimizeOnExit = null,
|
||||
Object? hidden = null,
|
||||
Object? developerMode = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
locale: freezed == locale
|
||||
@@ -165,6 +168,10 @@ class _$AppSettingPropsCopyWithImpl<$Res, $Val extends AppSettingProps>
|
||||
? _value.hidden
|
||||
: hidden // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
developerMode: null == developerMode
|
||||
? _value.developerMode
|
||||
: developerMode // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -193,7 +200,8 @@ abstract class _$$AppSettingPropsImplCopyWith<$Res>
|
||||
bool showLabel,
|
||||
bool disclaimerAccepted,
|
||||
bool minimizeOnExit,
|
||||
bool hidden});
|
||||
bool hidden,
|
||||
bool developerMode});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -224,6 +232,7 @@ class __$$AppSettingPropsImplCopyWithImpl<$Res>
|
||||
Object? disclaimerAccepted = null,
|
||||
Object? minimizeOnExit = null,
|
||||
Object? hidden = null,
|
||||
Object? developerMode = null,
|
||||
}) {
|
||||
return _then(_$AppSettingPropsImpl(
|
||||
locale: freezed == locale
|
||||
@@ -286,6 +295,10 @@ class __$$AppSettingPropsImplCopyWithImpl<$Res>
|
||||
? _value.hidden
|
||||
: hidden // ignore: cast_nullable_to_non_nullable
|
||||
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.disclaimerAccepted = false,
|
||||
this.minimizeOnExit = true,
|
||||
this.hidden = false})
|
||||
this.hidden = false,
|
||||
this.developerMode = false})
|
||||
: _dashboardWidgets = dashboardWidgets;
|
||||
|
||||
factory _$AppSettingPropsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -366,10 +380,13 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool hidden;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool developerMode;
|
||||
|
||||
@override
|
||||
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
|
||||
@@ -402,7 +419,9 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
|
||||
other.disclaimerAccepted == disclaimerAccepted) &&
|
||||
(identical(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)
|
||||
@@ -423,7 +442,8 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
|
||||
showLabel,
|
||||
disclaimerAccepted,
|
||||
minimizeOnExit,
|
||||
hidden);
|
||||
hidden,
|
||||
developerMode);
|
||||
|
||||
/// Create a copy of AppSettingProps
|
||||
/// 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 disclaimerAccepted,
|
||||
final bool minimizeOnExit,
|
||||
final bool hidden}) = _$AppSettingPropsImpl;
|
||||
final bool hidden,
|
||||
final bool developerMode}) = _$AppSettingPropsImpl;
|
||||
|
||||
factory _AppSettingProps.fromJson(Map<String, dynamic> json) =
|
||||
_$AppSettingPropsImpl.fromJson;
|
||||
@@ -495,6 +516,8 @@ abstract class _AppSettingProps implements AppSettingProps {
|
||||
bool get minimizeOnExit;
|
||||
@override
|
||||
bool get hidden;
|
||||
@override
|
||||
bool get developerMode;
|
||||
|
||||
/// Create a copy of AppSettingProps
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -1717,6 +1740,170 @@ abstract class _ProxiesStyle implements ProxiesStyle {
|
||||
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) {
|
||||
return _ThemeProps.fromJson(json);
|
||||
}
|
||||
@@ -1728,6 +1915,7 @@ mixin _$ThemeProps {
|
||||
ThemeMode get themeMode => throw _privateConstructorUsedError;
|
||||
DynamicSchemeVariant get schemeVariant => throw _privateConstructorUsedError;
|
||||
bool get pureBlack => throw _privateConstructorUsedError;
|
||||
TextScale get textScale => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ThemeProps to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -1750,7 +1938,10 @@ abstract class $ThemePropsCopyWith<$Res> {
|
||||
List<int> primaryColors,
|
||||
ThemeMode themeMode,
|
||||
DynamicSchemeVariant schemeVariant,
|
||||
bool pureBlack});
|
||||
bool pureBlack,
|
||||
TextScale textScale});
|
||||
|
||||
$TextScaleCopyWith<$Res> get textScale;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -1773,6 +1964,7 @@ class _$ThemePropsCopyWithImpl<$Res, $Val extends ThemeProps>
|
||||
Object? themeMode = null,
|
||||
Object? schemeVariant = null,
|
||||
Object? pureBlack = null,
|
||||
Object? textScale = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
primaryColor: freezed == primaryColor
|
||||
@@ -1795,8 +1987,22 @@ class _$ThemePropsCopyWithImpl<$Res, $Val extends ThemeProps>
|
||||
? _value.pureBlack
|
||||
: pureBlack // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
textScale: null == textScale
|
||||
? _value.textScale
|
||||
: textScale // ignore: cast_nullable_to_non_nullable
|
||||
as TextScale,
|
||||
) 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
|
||||
@@ -1812,7 +2018,11 @@ abstract class _$$ThemePropsImplCopyWith<$Res>
|
||||
List<int> primaryColors,
|
||||
ThemeMode themeMode,
|
||||
DynamicSchemeVariant schemeVariant,
|
||||
bool pureBlack});
|
||||
bool pureBlack,
|
||||
TextScale textScale});
|
||||
|
||||
@override
|
||||
$TextScaleCopyWith<$Res> get textScale;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -1833,6 +2043,7 @@ class __$$ThemePropsImplCopyWithImpl<$Res>
|
||||
Object? themeMode = null,
|
||||
Object? schemeVariant = null,
|
||||
Object? pureBlack = null,
|
||||
Object? textScale = null,
|
||||
}) {
|
||||
return _then(_$ThemePropsImpl(
|
||||
primaryColor: freezed == primaryColor
|
||||
@@ -1855,6 +2066,10 @@ class __$$ThemePropsImplCopyWithImpl<$Res>
|
||||
? _value.pureBlack
|
||||
: pureBlack // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
textScale: null == textScale
|
||||
? _value.textScale
|
||||
: textScale // ignore: cast_nullable_to_non_nullable
|
||||
as TextScale,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1863,18 +2078,18 @@ class __$$ThemePropsImplCopyWithImpl<$Res>
|
||||
@JsonSerializable()
|
||||
class _$ThemePropsImpl implements _ThemeProps {
|
||||
const _$ThemePropsImpl(
|
||||
{this.primaryColor = defaultPrimaryColor,
|
||||
{this.primaryColor,
|
||||
final List<int> primaryColors = defaultPrimaryColors,
|
||||
this.themeMode = ThemeMode.dark,
|
||||
this.schemeVariant = DynamicSchemeVariant.tonalSpot,
|
||||
this.pureBlack = false})
|
||||
this.pureBlack = false,
|
||||
this.textScale = const TextScale()})
|
||||
: _primaryColors = primaryColors;
|
||||
|
||||
factory _$ThemePropsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ThemePropsImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final int? primaryColor;
|
||||
final List<int> _primaryColors;
|
||||
@override
|
||||
@@ -1894,10 +2109,13 @@ class _$ThemePropsImpl implements _ThemeProps {
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool pureBlack;
|
||||
@override
|
||||
@JsonKey()
|
||||
final TextScale textScale;
|
||||
|
||||
@override
|
||||
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
|
||||
@@ -1914,7 +2132,9 @@ class _$ThemePropsImpl implements _ThemeProps {
|
||||
(identical(other.schemeVariant, schemeVariant) ||
|
||||
other.schemeVariant == schemeVariant) &&
|
||||
(identical(other.pureBlack, pureBlack) ||
|
||||
other.pureBlack == pureBlack));
|
||||
other.pureBlack == pureBlack) &&
|
||||
(identical(other.textScale, textScale) ||
|
||||
other.textScale == textScale));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -1925,7 +2145,8 @@ class _$ThemePropsImpl implements _ThemeProps {
|
||||
const DeepCollectionEquality().hash(_primaryColors),
|
||||
themeMode,
|
||||
schemeVariant,
|
||||
pureBlack);
|
||||
pureBlack,
|
||||
textScale);
|
||||
|
||||
/// Create a copy of ThemeProps
|
||||
/// 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 ThemeMode themeMode,
|
||||
final DynamicSchemeVariant schemeVariant,
|
||||
final bool pureBlack}) = _$ThemePropsImpl;
|
||||
final bool pureBlack,
|
||||
final TextScale textScale}) = _$ThemePropsImpl;
|
||||
|
||||
factory _ThemeProps.fromJson(Map<String, dynamic> json) =
|
||||
_$ThemePropsImpl.fromJson;
|
||||
@@ -1964,6 +2186,8 @@ abstract class _ThemeProps implements ThemeProps {
|
||||
DynamicSchemeVariant get schemeVariant;
|
||||
@override
|
||||
bool get pureBlack;
|
||||
@override
|
||||
TextScale get textScale;
|
||||
|
||||
/// Create a copy of ThemeProps
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -1988,6 +2212,7 @@ mixin _$Config {
|
||||
DAV? get dav => throw _privateConstructorUsedError;
|
||||
NetworkProps get networkProps => throw _privateConstructorUsedError;
|
||||
VpnProps get vpnProps => throw _privateConstructorUsedError;
|
||||
@JsonKey(fromJson: ThemeProps.safeFromJson)
|
||||
ThemeProps get themeProps => throw _privateConstructorUsedError;
|
||||
ProxiesStyle get proxiesStyle => throw _privateConstructorUsedError;
|
||||
WindowProps get windowProps => throw _privateConstructorUsedError;
|
||||
@@ -2017,7 +2242,7 @@ abstract class $ConfigCopyWith<$Res> {
|
||||
DAV? dav,
|
||||
NetworkProps networkProps,
|
||||
VpnProps vpnProps,
|
||||
ThemeProps themeProps,
|
||||
@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps,
|
||||
ProxiesStyle proxiesStyle,
|
||||
WindowProps windowProps,
|
||||
ClashConfig patchClashConfig});
|
||||
@@ -2214,7 +2439,7 @@ abstract class _$$ConfigImplCopyWith<$Res> implements $ConfigCopyWith<$Res> {
|
||||
DAV? dav,
|
||||
NetworkProps networkProps,
|
||||
VpnProps vpnProps,
|
||||
ThemeProps themeProps,
|
||||
@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps,
|
||||
ProxiesStyle proxiesStyle,
|
||||
WindowProps windowProps,
|
||||
ClashConfig patchClashConfig});
|
||||
@@ -2329,7 +2554,7 @@ class _$ConfigImpl implements _Config {
|
||||
this.dav,
|
||||
this.networkProps = defaultNetworkProps,
|
||||
this.vpnProps = defaultVpnProps,
|
||||
this.themeProps = defaultThemeProps,
|
||||
@JsonKey(fromJson: ThemeProps.safeFromJson) required this.themeProps,
|
||||
this.proxiesStyle = defaultProxiesStyle,
|
||||
this.windowProps = defaultWindowProps,
|
||||
this.patchClashConfig = defaultClashConfig})
|
||||
@@ -2374,7 +2599,7 @@ class _$ConfigImpl implements _Config {
|
||||
@JsonKey()
|
||||
final VpnProps vpnProps;
|
||||
@override
|
||||
@JsonKey()
|
||||
@JsonKey(fromJson: ThemeProps.safeFromJson)
|
||||
final ThemeProps themeProps;
|
||||
@override
|
||||
@JsonKey()
|
||||
@@ -2464,7 +2689,8 @@ abstract class _Config implements Config {
|
||||
final DAV? dav,
|
||||
final NetworkProps networkProps,
|
||||
final VpnProps vpnProps,
|
||||
final ThemeProps themeProps,
|
||||
@JsonKey(fromJson: ThemeProps.safeFromJson)
|
||||
required final ThemeProps themeProps,
|
||||
final ProxiesStyle proxiesStyle,
|
||||
final WindowProps windowProps,
|
||||
final ClashConfig patchClashConfig}) = _$ConfigImpl;
|
||||
@@ -2489,6 +2715,7 @@ abstract class _Config implements Config {
|
||||
@override
|
||||
VpnProps get vpnProps;
|
||||
@override
|
||||
@JsonKey(fromJson: ThemeProps.safeFromJson)
|
||||
ThemeProps get themeProps;
|
||||
@override
|
||||
ProxiesStyle get proxiesStyle;
|
||||
|
||||
@@ -26,6 +26,7 @@ _$AppSettingPropsImpl _$$AppSettingPropsImplFromJson(
|
||||
disclaimerAccepted: json['disclaimerAccepted'] as bool? ?? false,
|
||||
minimizeOnExit: json['minimizeOnExit'] as bool? ?? true,
|
||||
hidden: json['hidden'] as bool? ?? false,
|
||||
developerMode: json['developerMode'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$AppSettingPropsImplToJson(
|
||||
@@ -48,10 +49,12 @@ Map<String, dynamic> _$$AppSettingPropsImplToJson(
|
||||
'disclaimerAccepted': instance.disclaimerAccepted,
|
||||
'minimizeOnExit': instance.minimizeOnExit,
|
||||
'hidden': instance.hidden,
|
||||
'developerMode': instance.developerMode,
|
||||
};
|
||||
|
||||
const _$DashboardWidgetEnumMap = {
|
||||
DashboardWidget.networkSpeed: 'networkSpeed',
|
||||
DashboardWidget.outboundModeV2: 'outboundModeV2',
|
||||
DashboardWidget.outboundMode: 'outboundMode',
|
||||
DashboardWidget.trafficUsage: 'trafficUsage',
|
||||
DashboardWidget.networkDetection: 'networkDetection',
|
||||
@@ -219,10 +222,21 @@ const _$ProxyCardTypeEnumMap = {
|
||||
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(
|
||||
primaryColor:
|
||||
(json['primaryColor'] as num?)?.toInt() ?? defaultPrimaryColor,
|
||||
primaryColor: (json['primaryColor'] as num?)?.toInt(),
|
||||
primaryColors: (json['primaryColors'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList() ??
|
||||
@@ -233,6 +247,9 @@ _$ThemePropsImpl _$$ThemePropsImplFromJson(Map<String, dynamic> json) =>
|
||||
_$DynamicSchemeVariantEnumMap, json['schemeVariant']) ??
|
||||
DynamicSchemeVariant.tonalSpot,
|
||||
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) =>
|
||||
@@ -242,6 +259,7 @@ Map<String, dynamic> _$$ThemePropsImplToJson(_$ThemePropsImpl instance) =>
|
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'schemeVariant': _$DynamicSchemeVariantEnumMap[instance.schemeVariant]!,
|
||||
'pureBlack': instance.pureBlack,
|
||||
'textScale': instance.textScale,
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
@@ -287,9 +305,8 @@ _$ConfigImpl _$$ConfigImplFromJson(Map<String, dynamic> json) => _$ConfigImpl(
|
||||
vpnProps: json['vpnProps'] == null
|
||||
? defaultVpnProps
|
||||
: VpnProps.fromJson(json['vpnProps'] as Map<String, dynamic>?),
|
||||
themeProps: json['themeProps'] == null
|
||||
? defaultThemeProps
|
||||
: ThemeProps.fromJson(json['themeProps'] as Map<String, dynamic>?),
|
||||
themeProps:
|
||||
ThemeProps.safeFromJson(json['themeProps'] as Map<String, Object?>?),
|
||||
proxiesStyle: json['proxiesStyle'] == null
|
||||
? defaultProxiesStyle
|
||||
: ProxiesStyle.fromJson(
|
||||
|
||||
@@ -345,6 +345,7 @@ const _$ActionMethodEnumMap = {
|
||||
ActionMethod.getCountryCode: 'getCountryCode',
|
||||
ActionMethod.getMemory: 'getMemory',
|
||||
ActionMethod.getProfile: 'getProfile',
|
||||
ActionMethod.crash: 'crash',
|
||||
ActionMethod.setFdMap: 'setFdMap',
|
||||
ActionMethod.setProcessMap: 'setProcessMap',
|
||||
ActionMethod.setState: 'setState',
|
||||
|
||||
@@ -129,6 +129,16 @@ class _HomePageViewState extends ConsumerState<_HomePageView> {
|
||||
controller: _pageController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
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) {
|
||||
final navigationItem = navigationItems[index];
|
||||
return KeepScope(
|
||||
|
||||
@@ -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
|
||||
class SortNum extends _$SortNum with AutoDisposeNotifierMixin {
|
||||
@override
|
||||
|
||||
@@ -247,21 +247,6 @@ final currentPageLabelProvider =
|
||||
);
|
||||
|
||||
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';
|
||||
|
||||
/// See also [SortNum].
|
||||
|
||||
@@ -1765,7 +1765,7 @@ class _GetProfileOverrideDataProviderElement
|
||||
String get profileId => (origin as GetProfileOverrideDataProvider).profileId;
|
||||
}
|
||||
|
||||
String _$genColorSchemeHash() => r'a27ccae9b5c11d47cd46804f42f8e9dc7946a6c2';
|
||||
String _$genColorSchemeHash() => r'b18f15c938a8132ee4ed02cdfc02f3b9f01724e2';
|
||||
|
||||
/// See also [genColorScheme].
|
||||
@ProviderFor(genColorScheme)
|
||||
@@ -1780,12 +1780,12 @@ class GenColorSchemeFamily extends Family<ColorScheme> {
|
||||
GenColorSchemeProvider call(
|
||||
Brightness brightness, {
|
||||
Color? color,
|
||||
bool isOverride = false,
|
||||
bool ignoreConfig = false,
|
||||
}) {
|
||||
return GenColorSchemeProvider(
|
||||
brightness,
|
||||
color: color,
|
||||
isOverride: isOverride,
|
||||
ignoreConfig: ignoreConfig,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1796,7 +1796,7 @@ class GenColorSchemeFamily extends Family<ColorScheme> {
|
||||
return call(
|
||||
provider.brightness,
|
||||
color: provider.color,
|
||||
isOverride: provider.isOverride,
|
||||
ignoreConfig: provider.ignoreConfig,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1821,13 +1821,13 @@ class GenColorSchemeProvider extends AutoDisposeProvider<ColorScheme> {
|
||||
GenColorSchemeProvider(
|
||||
Brightness brightness, {
|
||||
Color? color,
|
||||
bool isOverride = false,
|
||||
bool ignoreConfig = false,
|
||||
}) : this._internal(
|
||||
(ref) => genColorScheme(
|
||||
ref as GenColorSchemeRef,
|
||||
brightness,
|
||||
color: color,
|
||||
isOverride: isOverride,
|
||||
ignoreConfig: ignoreConfig,
|
||||
),
|
||||
from: genColorSchemeProvider,
|
||||
name: r'genColorSchemeProvider',
|
||||
@@ -1840,7 +1840,7 @@ class GenColorSchemeProvider extends AutoDisposeProvider<ColorScheme> {
|
||||
GenColorSchemeFamily._allTransitiveDependencies,
|
||||
brightness: brightness,
|
||||
color: color,
|
||||
isOverride: isOverride,
|
||||
ignoreConfig: ignoreConfig,
|
||||
);
|
||||
|
||||
GenColorSchemeProvider._internal(
|
||||
@@ -1852,12 +1852,12 @@ class GenColorSchemeProvider extends AutoDisposeProvider<ColorScheme> {
|
||||
required super.from,
|
||||
required this.brightness,
|
||||
required this.color,
|
||||
required this.isOverride,
|
||||
required this.ignoreConfig,
|
||||
}) : super.internal();
|
||||
|
||||
final Brightness brightness;
|
||||
final Color? color;
|
||||
final bool isOverride;
|
||||
final bool ignoreConfig;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
@@ -1874,7 +1874,7 @@ class GenColorSchemeProvider extends AutoDisposeProvider<ColorScheme> {
|
||||
debugGetCreateSourceHash: null,
|
||||
brightness: brightness,
|
||||
color: color,
|
||||
isOverride: isOverride,
|
||||
ignoreConfig: ignoreConfig,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1889,7 +1889,7 @@ class GenColorSchemeProvider extends AutoDisposeProvider<ColorScheme> {
|
||||
return other is GenColorSchemeProvider &&
|
||||
other.brightness == brightness &&
|
||||
other.color == color &&
|
||||
other.isOverride == isOverride;
|
||||
other.ignoreConfig == ignoreConfig;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1897,7 +1897,7 @@ class GenColorSchemeProvider extends AutoDisposeProvider<ColorScheme> {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, brightness.hashCode);
|
||||
hash = _SystemHash.combine(hash, color.hashCode);
|
||||
hash = _SystemHash.combine(hash, isOverride.hashCode);
|
||||
hash = _SystemHash.combine(hash, ignoreConfig.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
@@ -1912,8 +1912,8 @@ mixin GenColorSchemeRef on AutoDisposeProviderRef<ColorScheme> {
|
||||
/// The parameter `color` of this provider.
|
||||
Color? get color;
|
||||
|
||||
/// The parameter `isOverride` of this provider.
|
||||
bool get isOverride;
|
||||
/// The parameter `ignoreConfig` of this provider.
|
||||
bool get ignoreConfig;
|
||||
}
|
||||
|
||||
class _GenColorSchemeProviderElement
|
||||
@@ -1925,7 +1925,7 @@ class _GenColorSchemeProviderElement
|
||||
@override
|
||||
Color? get color => (origin as GenColorSchemeProvider).color;
|
||||
@override
|
||||
bool get isOverride => (origin as GenColorSchemeProvider).isOverride;
|
||||
bool get ignoreConfig => (origin as GenColorSchemeProvider).ignoreConfig;
|
||||
}
|
||||
|
||||
String _$profileOverrideStateHash() =>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -511,7 +513,7 @@ ColorScheme genColorScheme(
|
||||
Ref ref,
|
||||
Brightness brightness, {
|
||||
Color? color,
|
||||
bool isOverride = false,
|
||||
bool ignoreConfig = false,
|
||||
}) {
|
||||
final vm2 = ref.watch(
|
||||
themeSettingProvider.select(
|
||||
@@ -521,11 +523,17 @@ ColorScheme genColorScheme(
|
||||
),
|
||||
),
|
||||
);
|
||||
if (color == null && (isOverride == true || vm2.a == null)) {
|
||||
final colorSchemes = ref.watch(appSchemesProvider);
|
||||
return colorSchemes.getColorSchemeForBrightness(
|
||||
brightness,
|
||||
vm2.b,
|
||||
if (color == null && (ignoreConfig == true || vm2.a == null)) {
|
||||
// if (globalState.corePalette != null) {
|
||||
// return globalState.corePalette!.toColorScheme(brightness: brightness);
|
||||
// }
|
||||
return ColorScheme.fromSeed(
|
||||
seedColor: globalState.corePalette
|
||||
?.toColorScheme(brightness: brightness)
|
||||
.primary ??
|
||||
globalState.accentColor,
|
||||
brightness: brightness,
|
||||
dynamicSchemeVariant: vm2.b,
|
||||
);
|
||||
}
|
||||
return ColorScheme.fromSeed(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/theme.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/scaffold.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:url_launcher/url_launcher.dart';
|
||||
|
||||
@@ -21,6 +23,7 @@ typedef UpdateTasks = List<FutureOr Function()>;
|
||||
class GlobalState {
|
||||
static GlobalState? _instance;
|
||||
Map<Key, double> cacheScrollPosition = {};
|
||||
Map<Key, FixedMap<String, double>> cacheHeightMap = {};
|
||||
bool isService = false;
|
||||
Timer? timer;
|
||||
Timer? groupsUpdateTimer;
|
||||
@@ -31,6 +34,8 @@ class GlobalState {
|
||||
Function? updateCurrentDelayDebounce;
|
||||
late Measure measure;
|
||||
late CommonTheme theme;
|
||||
late Color accentColor;
|
||||
CorePalette? corePalette;
|
||||
DateTime? startTime;
|
||||
UpdateTasks tasks = [];
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
@@ -55,9 +60,18 @@ class GlobalState {
|
||||
traffics: FixedList(30),
|
||||
totalTraffic: Traffic(),
|
||||
);
|
||||
await _initDynamicColor();
|
||||
await init();
|
||||
}
|
||||
|
||||
_initDynamicColor() async {
|
||||
try {
|
||||
corePalette = await DynamicColorPlugin.getCorePalette();
|
||||
accentColor = await DynamicColorPlugin.getAccentColor() ??
|
||||
Color(defaultPrimaryColor);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
init() async {
|
||||
packageInfo = await PackageInfo.fromPlatform();
|
||||
config = await preferences.getConfig() ??
|
||||
|
||||
@@ -103,7 +103,7 @@ class PrimaryColorBox extends ConsumerWidget {
|
||||
genColorSchemeProvider(
|
||||
themeData.brightness,
|
||||
color: primaryColor,
|
||||
isOverride: true,
|
||||
ignoreConfig: true,
|
||||
),
|
||||
);
|
||||
return Theme(
|
||||
|
||||
73
lib/widgets/container.dart
Normal file
73
lib/widgets/container.dart
Normal 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'));
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ class DonutChartPainter extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
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 gapAngle = 2 * asin(strokeWidth * 1 / (2 * radius)) * 1.2;
|
||||
|
||||
@@ -36,11 +36,13 @@ class FadeBox extends StatelessWidget {
|
||||
class FadeThroughBox extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Alignment? alignment;
|
||||
final EdgeInsets? margin;
|
||||
|
||||
const FadeThroughBox({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.alignment,
|
||||
this.margin
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -52,6 +54,7 @@ class FadeThroughBox extends StatelessWidget {
|
||||
secondaryAnimation,
|
||||
) {
|
||||
return Container(
|
||||
margin: margin,
|
||||
alignment: alignment ?? Alignment.centerLeft,
|
||||
child: FadeThroughTransition(
|
||||
animation: animation,
|
||||
|
||||
@@ -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 {
|
||||
final List<T> options;
|
||||
final String title;
|
||||
@@ -138,6 +154,21 @@ class ListItem<T> extends StatelessWidget {
|
||||
this.tileTitleAlignment = ListTileTitleAlignment.center,
|
||||
}) : 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({
|
||||
super.key,
|
||||
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) {
|
||||
final optionsDelegate = delegate as OptionsDelegate<T>;
|
||||
return _buildListTile(
|
||||
@@ -353,14 +412,11 @@ class ListItem<T> extends StatelessWidget {
|
||||
radioDelegate.onChanged!(radioDelegate.value);
|
||||
}
|
||||
},
|
||||
leading: SizedBox(
|
||||
width: 32,
|
||||
height: 32,
|
||||
child: Radio<T>(
|
||||
value: radioDelegate.value,
|
||||
groupValue: radioDelegate.groupValue,
|
||||
onChanged: radioDelegate.onChanged,
|
||||
),
|
||||
leading: Radio<T>(
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
value: radioDelegate.value,
|
||||
groupValue: radioDelegate.groupValue,
|
||||
onChanged: radioDelegate.onChanged,
|
||||
),
|
||||
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({
|
||||
required Info info,
|
||||
required Iterable<Widget> items,
|
||||
@@ -497,4 +579,4 @@ Widget generateListView(List<Widget> items) {
|
||||
bottom: 16,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
33
lib/widgets/notification.dart
Normal file
33
lib/widgets/notification.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -125,25 +125,25 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
||||
}
|
||||
}
|
||||
|
||||
ThemeData _appBarTheme(BuildContext context) {
|
||||
Widget _buildSearchingAppBarTheme(Widget child) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final ColorScheme colorScheme = theme.colorScheme;
|
||||
return theme.copyWith(
|
||||
appBarTheme: AppBarTheme(
|
||||
systemOverlayStyle: colorScheme.brightness == Brightness.dark
|
||||
? SystemUiOverlayStyle.light
|
||||
: SystemUiOverlayStyle.dark,
|
||||
backgroundColor: colorScheme.brightness == Brightness.dark
|
||||
? Colors.grey[900]
|
||||
: Colors.white,
|
||||
iconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
|
||||
titleTextStyle: theme.textTheme.titleLarge,
|
||||
toolbarTextStyle: theme.textTheme.bodyMedium,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
hintStyle: theme.inputDecorationTheme.hintStyle,
|
||||
border: InputBorder.none,
|
||||
return Theme(
|
||||
data: theme.copyWith(
|
||||
appBarTheme: theme.appBarTheme.copyWith(
|
||||
backgroundColor: colorScheme.brightness == Brightness.dark
|
||||
? Colors.grey[900]
|
||||
: Colors.white,
|
||||
iconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
|
||||
titleTextStyle: theme.textTheme.titleLarge,
|
||||
toolbarTextStyle: theme.textTheme.bodyMedium,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
hintStyle: theme.inputDecorationTheme.hintStyle,
|
||||
border: InputBorder.none,
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -318,72 +318,66 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
||||
child: appBar,
|
||||
);
|
||||
}
|
||||
return _isSearch
|
||||
? Theme(
|
||||
data: _appBarTheme(context),
|
||||
child: CommonPopScope(
|
||||
onPop: () {
|
||||
if (_isSearch) {
|
||||
_handleExitSearching();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: appBar,
|
||||
),
|
||||
)
|
||||
: appBar;
|
||||
return _isSearch ? _buildSearchingAppBarTheme(appBar) : appBar;
|
||||
}
|
||||
|
||||
PreferredSizeWidget _buildAppBar() {
|
||||
return PreferredSize(
|
||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
ValueListenableBuilder<AppBarState>(
|
||||
valueListenable: _appBarState,
|
||||
builder: (_, state, __) {
|
||||
return _buildAppBarWrap(
|
||||
AppBar(
|
||||
centerTitle: widget.centerTitle ?? false,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness:
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
systemNavigationBarIconBrightness:
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
systemNavigationBarColor: widget.bottomNavigationBar != null
|
||||
? context.colorScheme.surfaceContainer
|
||||
: context.colorScheme.surface,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
),
|
||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||
leading: _buildLeading(),
|
||||
title: _buildTitle(state.searchState),
|
||||
actions: _buildActions(
|
||||
state.searchState != null,
|
||||
state.actions.isNotEmpty
|
||||
? state.actions
|
||||
: widget.actions ?? [],
|
||||
),
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
appBarTheme: AppBarTheme(
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness:
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
systemNavigationBarIconBrightness:
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
systemNavigationBarColor: widget.bottomNavigationBar != null
|
||||
? context.colorScheme.surfaceContainer
|
||||
: context.colorScheme.surface,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: widget.appBar ??
|
||||
Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
ValueListenableBuilder<AppBarState>(
|
||||
valueListenable: _appBarState,
|
||||
builder: (_, state, __) {
|
||||
return _buildAppBarWrap(
|
||||
AppBar(
|
||||
centerTitle: widget.centerTitle ?? false,
|
||||
automaticallyImplyLeading:
|
||||
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, __) {
|
||||
return value == true
|
||||
? const LinearProgressIndicator()
|
||||
: Container();
|
||||
},
|
||||
),
|
||||
],
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _loading,
|
||||
builder: (_, value, __) {
|
||||
return value == true
|
||||
? const LinearProgressIndicator()
|
||||
: Container();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -391,56 +385,62 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(widget.appBar != null || widget.title != null);
|
||||
final body = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _keywordsNotifier,
|
||||
builder: (_, keywords, __) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_onKeywordsUpdate != null) {
|
||||
_onKeywordsUpdate!(keywords);
|
||||
final body = SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _keywordsNotifier,
|
||||
builder: (_, keywords, __) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_onKeywordsUpdate != null) {
|
||||
_onKeywordsUpdate!(keywords);
|
||||
}
|
||||
});
|
||||
if (keywords.isEmpty) {
|
||||
return SizedBox();
|
||||
}
|
||||
});
|
||||
if (keywords.isEmpty) {
|
||||
return SizedBox();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
child: Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
children: [
|
||||
for (final keyword in keywords)
|
||||
CommonChip(
|
||||
label: keyword,
|
||||
type: ChipType.delete,
|
||||
onPressed: () {
|
||||
_deleteKeyword(keyword);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: widget.body,
|
||||
),
|
||||
],
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
child: Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
children: [
|
||||
for (final keyword in keywords)
|
||||
CommonChip(
|
||||
label: keyword,
|
||||
type: ChipType.delete,
|
||||
onPressed: () {
|
||||
_deleteKeyword(keyword);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: widget.body,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
final scaffold = Scaffold(
|
||||
appBar: widget.appBar ?? _buildAppBar(),
|
||||
appBar: _buildAppBar(),
|
||||
body: body,
|
||||
backgroundColor: widget.backgroundColor,
|
||||
floatingActionButton: ValueListenableBuilder<Widget?>(
|
||||
valueListenable: _floatingActionButton,
|
||||
builder: (_, value, __) {
|
||||
return FadeScaleBox(
|
||||
child: value ?? SizedBox(),
|
||||
return IntrinsicWidth(
|
||||
child: IntrinsicHeight(
|
||||
child: FadeScaleBox(
|
||||
child: value ?? SizedBox(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -369,6 +369,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
|
||||
}
|
||||
|
||||
_handleDelete(int index) async {
|
||||
await _transformCompleter?.future;
|
||||
_preTransformState();
|
||||
final indexWhere = _tempIndexList.indexWhere((i) => i == index);
|
||||
_tempIndexList.removeAt(indexWhere);
|
||||
@@ -484,9 +485,24 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
|
||||
Widget _draggableWrap({
|
||||
required Widget childWhenDragging,
|
||||
required Widget feedback,
|
||||
required Widget target,
|
||||
required Widget item,
|
||||
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(
|
||||
valueListenable: _dragIndexNotifier,
|
||||
builder: (_, dragIndex, child) {
|
||||
@@ -539,7 +555,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
|
||||
valueListenable: isEditNotifier,
|
||||
builder: (_, isEdit, child) {
|
||||
if (!isEdit) {
|
||||
return target;
|
||||
return item;
|
||||
}
|
||||
return child!;
|
||||
},
|
||||
@@ -558,12 +574,10 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
|
||||
_itemContexts[index] = context;
|
||||
final childWhenDragging = ActivateBox(
|
||||
child: Opacity(
|
||||
opacity: 0.3,
|
||||
opacity: 0.6,
|
||||
child: _sizeBoxWrap(
|
||||
CommonCard(
|
||||
child: Container(
|
||||
color: context.colorScheme.secondaryContainer,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
index,
|
||||
),
|
||||
@@ -580,25 +594,11 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
|
||||
index,
|
||||
),
|
||||
);
|
||||
final target = DragTarget<int>(
|
||||
builder: (_, __, ___) {
|
||||
return child;
|
||||
},
|
||||
onWillAcceptWithDetails: (_) {
|
||||
debouncer.call(
|
||||
DebounceTag.handleWill,
|
||||
_handleWill,
|
||||
args: [index],
|
||||
);
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
return _wrapTransform(
|
||||
_draggableWrap(
|
||||
childWhenDragging: childWhenDragging,
|
||||
feedback: feedback,
|
||||
target: target,
|
||||
item: child,
|
||||
index: index,
|
||||
),
|
||||
index,
|
||||
@@ -666,8 +666,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
|
||||
crossAxisSpacing: widget.crossAxisSpacing,
|
||||
mainAxisSpacing: widget.mainAxisSpacing,
|
||||
children: [
|
||||
for (int i = 0; i < children.length; i++)
|
||||
_builderItem(i),
|
||||
for (int i = 0; i < children.length; i++) _builderItem(i),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
1100
lib/widgets/tab.dart
Normal file
1100
lib/widgets/tab.dart
Normal file
File diff suppressed because it is too large
Load Diff
@@ -84,6 +84,7 @@ class EmojiText extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RichText(
|
||||
textScaler: MediaQuery.of(context).textScaler,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow ?? TextOverflow.clip,
|
||||
text: TextSpan(
|
||||
|
||||
@@ -30,3 +30,6 @@ export 'scroll.dart';
|
||||
export 'dialog.dart';
|
||||
export 'effect.dart';
|
||||
export 'palette.dart';
|
||||
export 'tab.dart';
|
||||
export 'container.dart';
|
||||
export 'notification.dart';
|
||||
|
||||
@@ -843,7 +843,7 @@ packages:
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: fl_clash
|
||||
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||
publish_to: 'none'
|
||||
version: 0.8.82+202504182
|
||||
version: 0.8.83+202504231
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
|
||||
@@ -53,6 +53,7 @@ dependencies:
|
||||
flutter_riverpod: ^2.6.1
|
||||
riverpod_annotation: ^2.6.1
|
||||
riverpod: ^2.6.1
|
||||
material_color_utilities: ^0.11.1
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
@@ -92,5 +93,5 @@ ffigen:
|
||||
flutter_intl:
|
||||
enabled: true
|
||||
class_name: AppLocalizations
|
||||
arb_dir: lib/l10n/arb
|
||||
arb_dir: arb
|
||||
output_dir: lib/l10n
|
||||
@@ -4,6 +4,7 @@ import requests
|
||||
|
||||
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||
TAG = os.getenv("TAG")
|
||||
RUN_ID = os.getenv("RUN_ID")
|
||||
|
||||
IS_STABLE = "-" not in TAG
|
||||
|
||||
@@ -45,7 +46,8 @@ if TAG:
|
||||
|
||||
if IS_STABLE:
|
||||
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):
|
||||
text += "\n"
|
||||
|
||||
83
windows/packaging/exe/inno_setup.iss
Normal file
83
windows/packaging/exe/inno_setup.iss
Normal 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
|
||||
@@ -1,3 +1,4 @@
|
||||
script_template: inno_setup.iss
|
||||
app_id: 728B3532-C74B-4870-9068-BE70FE12A3E6
|
||||
app_name: FlClash
|
||||
publisher: chen08209
|
||||
@@ -9,4 +10,5 @@ setup_icon_file: ..\windows\runner\resources\app_icon.ico
|
||||
locales:
|
||||
- lang: zh
|
||||
file: ..\windows\packaging\exe\ChineseSimplified.isl
|
||||
- lang: en
|
||||
- lang: en
|
||||
privileges_required: admin
|
||||
Reference in New Issue
Block a user