Compare commits
6 Commits
v0.8.83-pr
...
v0.8.85-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96a184a374 | ||
|
|
76c9f08d4a | ||
|
|
f83a8e0cce | ||
|
|
f5544f1af7 | ||
|
|
eeb543780a | ||
|
|
676f2d058a |
11
.github/workflows/build.yaml
vendored
11
.github/workflows/build.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
os: windows-latest
|
os: windows-latest
|
||||||
arch: amd64
|
arch: amd64
|
||||||
- platform: linux
|
- platform: linux
|
||||||
os: ubuntu-latest
|
os: ubuntu-22.04
|
||||||
arch: amd64
|
arch: amd64
|
||||||
- platform: macos
|
- platform: macos
|
||||||
os: macos-13
|
os: macos-13
|
||||||
@@ -201,6 +201,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||||
TAG: ${{ github.ref_name }}
|
TAG: ${{ github.ref_name }}
|
||||||
|
RUN_ID: ${{ github.run_id }}
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install requests
|
pip install requests
|
||||||
@@ -211,6 +212,14 @@ jobs:
|
|||||||
version=$(echo "${{ github.ref_name }}" | sed 's/^v//')
|
version=$(echo "${{ github.ref_name }}" | sed 's/^v//')
|
||||||
sed "s|VERSION|$version|g" ./.github/release_template.md >> release.md
|
sed "s|VERSION|$version|g" ./.github/release_template.md >> release.md
|
||||||
|
|
||||||
|
- name: Generate sha256
|
||||||
|
if: env.IS_STABLE == 'true'
|
||||||
|
run: |
|
||||||
|
cd ./dist
|
||||||
|
for file in $(find . -type f -not -name "*.sha256"); do
|
||||||
|
sha256sum "$file" > "${file}.sha256"
|
||||||
|
done
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
if: ${{ env.IS_STABLE == 'true' }}
|
if: ${{ env.IS_STABLE == 'true' }}
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
|
|||||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -1,3 +1,43 @@
|
|||||||
|
## v0.8.84
|
||||||
|
|
||||||
|
- Fix windows service verify issues
|
||||||
|
|
||||||
|
- Update changelog
|
||||||
|
|
||||||
|
## v0.8.83
|
||||||
|
|
||||||
|
- Add windows server mode start process verify
|
||||||
|
|
||||||
|
- Add linux deb dependencies
|
||||||
|
|
||||||
|
- Add backup recovery strategy select
|
||||||
|
|
||||||
|
- Support custom text scaling
|
||||||
|
|
||||||
|
- Optimize the display of different text scale
|
||||||
|
|
||||||
|
- Optimize windows setup experience
|
||||||
|
|
||||||
|
- Optimize startTun performance
|
||||||
|
|
||||||
|
- Optimize android tv experience
|
||||||
|
|
||||||
|
- Optimize default option
|
||||||
|
|
||||||
|
- Optimize computed text size
|
||||||
|
|
||||||
|
- Optimize hyperOS freeform window
|
||||||
|
|
||||||
|
- Add developer mode
|
||||||
|
|
||||||
|
- Update core
|
||||||
|
|
||||||
|
- Optimize more details
|
||||||
|
|
||||||
|
- Add issues template
|
||||||
|
|
||||||
|
- Update changelog
|
||||||
|
|
||||||
## v0.8.82
|
## v0.8.82
|
||||||
|
|
||||||
- Optimize android vpn performance
|
- Optimize android vpn performance
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ on Mobile:
|
|||||||
⚠️ Make sure to install the following dependencies before using them
|
⚠️ Make sure to install the following dependencies before using them
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install appindicator3-0.1 libappindicator3-dev
|
sudo apt-get install libayatana-appindicator3-dev
|
||||||
sudo apt-get install keybinder-3.0
|
sudo apt-get install libkeybinder-3.0-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Android
|
### Android
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ on Mobile:
|
|||||||
⚠️ 使用前请确保安装以下依赖
|
⚠️ 使用前请确保安装以下依赖
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install appindicator3-0.1 libappindicator3-dev
|
sudo apt-get install libayatana-appindicator3-dev
|
||||||
sudo apt-get install keybinder-3.0
|
sudo apt-get install libkeybinder-3.0-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Android
|
### Android
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ android {
|
|||||||
applicationIdSuffix '.debug'
|
applicationIdSuffix '.debug'
|
||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
debuggable false
|
||||||
|
|
||||||
if (isRelease) {
|
if (isRelease) {
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import com.follow.clash.plugins.VpnPlugin
|
|||||||
import io.flutter.FlutterInjector
|
import io.flutter.FlutterInjector
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.embedding.engine.dart.DartExecutor
|
import io.flutter.embedding.engine.dart.DartExecutor
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import kotlin.concurrent.withLock
|
import kotlin.concurrent.withLock
|
||||||
|
|
||||||
@@ -20,6 +24,10 @@ enum class RunState {
|
|||||||
object GlobalState {
|
object GlobalState {
|
||||||
val runLock = ReentrantLock()
|
val runLock = ReentrantLock()
|
||||||
|
|
||||||
|
const val NOTIFICATION_CHANNEL = "FlClash"
|
||||||
|
|
||||||
|
const val NOTIFICATION_ID = 1
|
||||||
|
|
||||||
val runState: MutableLiveData<RunState> = MutableLiveData<RunState>(RunState.STOP)
|
val runState: MutableLiveData<RunState> = MutableLiveData<RunState>(RunState.STOP)
|
||||||
var flutterEngine: FlutterEngine? = null
|
var flutterEngine: FlutterEngine? = null
|
||||||
private var serviceEngine: FlutterEngine? = null
|
private var serviceEngine: FlutterEngine? = null
|
||||||
@@ -29,6 +37,15 @@ object GlobalState {
|
|||||||
return currentEngine?.plugins?.get(AppPlugin::class.java) as AppPlugin?
|
return currentEngine?.plugins?.get(AppPlugin::class.java) as AppPlugin?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun syncStatus() {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
val status = getCurrentVPNPlugin()?.getStatus() ?: false
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
runState.value = if (status) RunState.START else RunState.STOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getText(text: String): String {
|
suspend fun getText(text: String): String {
|
||||||
return getCurrentAppPlugin()?.getText(text) ?: ""
|
return getCurrentAppPlugin()?.getText(text) ?: ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.follow.clash
|
package com.follow.clash
|
||||||
|
|
||||||
import com.follow.clash.core.Core
|
|
||||||
import com.follow.clash.plugins.AppPlugin
|
import com.follow.clash.plugins.AppPlugin
|
||||||
import com.follow.clash.plugins.ServicePlugin
|
import com.follow.clash.plugins.ServicePlugin
|
||||||
import com.follow.clash.plugins.TilePlugin
|
import com.follow.clash.plugins.TilePlugin
|
||||||
@@ -18,6 +17,7 @@ class MainActivity : FlutterActivity() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
GlobalState.flutterEngine = null
|
GlobalState.flutterEngine = null
|
||||||
|
GlobalState.runState.value = RunState.STOP
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,6 @@ import java.util.concurrent.locks.ReentrantLock
|
|||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
|
||||||
suspend fun Drawable.getBase64(): String {
|
suspend fun Drawable.getBase64(): String {
|
||||||
val drawable = this
|
val drawable = this
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.follow.clash.models
|
|||||||
data class Package(
|
data class Package(
|
||||||
val packageName: String,
|
val packageName: String,
|
||||||
val label: String,
|
val label: String,
|
||||||
val isSystem: Boolean,
|
val system: Boolean,
|
||||||
|
val internet: Boolean,
|
||||||
val lastUpdateTime: Long,
|
val lastUpdateTime: Long,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -293,19 +293,17 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware
|
|||||||
if (packages.isNotEmpty()) return packages
|
if (packages.isNotEmpty()) return packages
|
||||||
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA or PackageManager.GET_PERMISSIONS)
|
packageManager?.getInstalledPackages(PackageManager.GET_META_DATA or PackageManager.GET_PERMISSIONS)
|
||||||
?.filter {
|
?.filter {
|
||||||
it.packageName != FlClashApplication.getAppContext().packageName && (
|
it.packageName != FlClashApplication.getAppContext().packageName || it.packageName == "android"
|
||||||
it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|
|
||||||
|| it.packageName == "android"
|
|
||||||
)
|
|
||||||
|
|
||||||
}?.map {
|
}?.map {
|
||||||
Package(
|
Package(
|
||||||
packageName = it.packageName,
|
packageName = it.packageName,
|
||||||
label = it.applicationInfo?.loadLabel(packageManager).toString(),
|
label = it.applicationInfo?.loadLabel(packageManager).toString(),
|
||||||
isSystem = (it.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM)) == 1,
|
system = (it.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM)) == 1,
|
||||||
lastUpdateTime = it.lastUpdateTime
|
lastUpdateTime = it.lastUpdateTime,
|
||||||
)
|
internet = it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
|
||||||
}?.let { packages.addAll(it) }
|
)
|
||||||
|
}?.let { packages.addAll(it) }
|
||||||
return packages
|
return packages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ data object ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun handleDestroy() {
|
private fun handleDestroy() {
|
||||||
GlobalState.destroyServiceEngine()
|
GlobalState.destroyServiceEngine()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,8 +168,10 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
|||||||
try {
|
try {
|
||||||
if (GlobalState.runState.value != RunState.START) return
|
if (GlobalState.runState.value != RunState.START) return
|
||||||
val data = flutterMethodChannel.awaitResult<String>("getStartForegroundParams")
|
val data = flutterMethodChannel.awaitResult<String>("getStartForegroundParams")
|
||||||
val startForegroundParams = Gson().fromJson(
|
val startForegroundParams = if (data != null) Gson().fromJson(
|
||||||
data, StartForegroundParams::class.java
|
data, StartForegroundParams::class.java
|
||||||
|
) else StartForegroundParams(
|
||||||
|
title = "", content = ""
|
||||||
)
|
)
|
||||||
if (lastStartForegroundParams != startForegroundParams) {
|
if (lastStartForegroundParams != startForegroundParams) {
|
||||||
lastStartForegroundParams = startForegroundParams
|
lastStartForegroundParams = startForegroundParams
|
||||||
@@ -198,6 +200,13 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
|||||||
timerJob = null
|
timerJob = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun getStatus(): Boolean? {
|
||||||
|
return withContext(Dispatchers.Default) {
|
||||||
|
flutterMethodChannel.awaitResult<Boolean>("status", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleStartService() {
|
private fun handleStartService() {
|
||||||
if (flClashService == null) {
|
if (flClashService == null) {
|
||||||
bindService()
|
bindService()
|
||||||
@@ -246,9 +255,9 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
|||||||
GlobalState.runLock.withLock {
|
GlobalState.runLock.withLock {
|
||||||
if (GlobalState.runState.value == RunState.STOP) return
|
if (GlobalState.runState.value == RunState.STOP) return
|
||||||
GlobalState.runState.value = RunState.STOP
|
GlobalState.runState.value = RunState.STOP
|
||||||
|
flClashService?.stop()
|
||||||
stopForegroundJob()
|
stopForegroundJob()
|
||||||
Core.stopTun()
|
Core.stopTun()
|
||||||
flClashService?.stop()
|
|
||||||
GlobalState.handleTryDestroy()
|
GlobalState.handleTryDestroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,26 @@
|
|||||||
package com.follow.clash.services
|
package com.follow.clash.services
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import com.follow.clash.GlobalState
|
||||||
|
import com.follow.clash.MainActivity
|
||||||
|
import com.follow.clash.R
|
||||||
|
import com.follow.clash.extensions.getActionPendingIntent
|
||||||
import com.follow.clash.models.VpnOptions
|
import com.follow.clash.models.VpnOptions
|
||||||
|
import io.flutter.Log
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
|
||||||
interface BaseServiceInterface {
|
interface BaseServiceInterface {
|
||||||
|
|
||||||
@@ -9,4 +29,70 @@ interface BaseServiceInterface {
|
|||||||
fun stop()
|
fun stop()
|
||||||
|
|
||||||
suspend fun startForeground(title: String, content: String)
|
suspend fun startForeground(title: String, content: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Service.createFlClashNotificationBuilder(): Deferred<NotificationCompat.Builder> =
|
||||||
|
CoroutineScope(Dispatchers.Main).async {
|
||||||
|
val stopText = GlobalState.getText("stop")
|
||||||
|
val intent = Intent(this@createFlClashNotificationBuilder, MainActivity::class.java)
|
||||||
|
|
||||||
|
val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
this@createFlClashNotificationBuilder,
|
||||||
|
0,
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
this@createFlClashNotificationBuilder, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
with(
|
||||||
|
NotificationCompat.Builder(
|
||||||
|
this@createFlClashNotificationBuilder, GlobalState.NOTIFICATION_CHANNEL
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
setSmallIcon(R.drawable.ic_stat_name)
|
||||||
|
setContentTitle("FlClash")
|
||||||
|
setContentIntent(pendingIntent)
|
||||||
|
setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
|
||||||
|
}
|
||||||
|
setOngoing(true)
|
||||||
|
addAction(
|
||||||
|
0, stopText, getActionPendingIntent("STOP")
|
||||||
|
)
|
||||||
|
setShowWhen(false)
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ForegroundServiceType")
|
||||||
|
fun Service.startForeground(notification: Notification) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val manager = getSystemService(NotificationManager::class.java)
|
||||||
|
var channel = manager?.getNotificationChannel(GlobalState.NOTIFICATION_CHANNEL)
|
||||||
|
if (channel == null) {
|
||||||
|
Log.d("[FlClash]","createNotificationChannel===>")
|
||||||
|
channel = NotificationChannel(
|
||||||
|
GlobalState.NOTIFICATION_CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW
|
||||||
|
)
|
||||||
|
manager?.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
package com.follow.clash.services
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
|
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.follow.clash.GlobalState
|
import com.follow.clash.GlobalState
|
||||||
import com.follow.clash.MainActivity
|
|
||||||
import com.follow.clash.extensions.getActionPendingIntent
|
|
||||||
import com.follow.clash.models.VpnOptions
|
import com.follow.clash.models.VpnOptions
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
|
|
||||||
|
|
||||||
class FlClashService : Service(), BaseServiceInterface {
|
class FlClashService : Service(), BaseServiceInterface {
|
||||||
|
|
||||||
|
override fun start(options: VpnOptions) = 0
|
||||||
|
|
||||||
|
override fun stop() {
|
||||||
|
stopSelf()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cachedBuilder: NotificationCompat.Builder? = null
|
||||||
|
|
||||||
|
private suspend fun notificationBuilder(): NotificationCompat.Builder {
|
||||||
|
if (cachedBuilder == null) {
|
||||||
|
cachedBuilder = createFlClashNotificationBuilder().await()
|
||||||
|
}
|
||||||
|
return cachedBuilder!!
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ForegroundServiceType")
|
||||||
|
override suspend fun startForeground(title: String, content: String) {
|
||||||
|
startForeground(
|
||||||
|
notificationBuilder()
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(content).build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTrimMemory(level: Int) {
|
||||||
|
super.onTrimMemory(level)
|
||||||
|
GlobalState.getCurrentVPNPlugin()?.requestGc()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private val binder = LocalBinder()
|
private val binder = LocalBinder()
|
||||||
|
|
||||||
inner class LocalBinder : Binder() {
|
inner class LocalBinder : Binder() {
|
||||||
@@ -38,93 +60,8 @@ class FlClashService : Service(), BaseServiceInterface {
|
|||||||
return super.onUnbind(intent)
|
return super.onUnbind(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val CHANNEL = "FlClash"
|
override fun onDestroy() {
|
||||||
|
stop()
|
||||||
private val notificationId: Int = 1
|
super.onDestroy()
|
||||||
|
|
||||||
private val notificationBuilderDeferred: Deferred<NotificationCompat.Builder> by lazy {
|
|
||||||
CoroutineScope(Dispatchers.Main).async {
|
|
||||||
val stopText = GlobalState.getText("stop")
|
|
||||||
|
|
||||||
val intent = Intent(
|
|
||||||
this@FlClashService, MainActivity::class.java
|
|
||||||
)
|
|
||||||
|
|
||||||
val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
|
|
||||||
PendingIntent.getActivity(
|
|
||||||
this@FlClashService,
|
|
||||||
0,
|
|
||||||
intent,
|
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
PendingIntent.getActivity(
|
|
||||||
this@FlClashService,
|
|
||||||
0,
|
|
||||||
intent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
with(NotificationCompat.Builder(this@FlClashService, CHANNEL)) {
|
|
||||||
setSmallIcon(com.follow.clash.R.drawable.ic_stat_name)
|
|
||||||
setContentTitle("FlClash")
|
|
||||||
setContentIntent(pendingIntent)
|
|
||||||
setCategory(NotificationCompat.CATEGORY_SERVICE)
|
|
||||||
priority = NotificationCompat.PRIORITY_MIN
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
|
|
||||||
}
|
|
||||||
addAction(
|
|
||||||
0,
|
|
||||||
stopText, // 使用 suspend 函数获取的文本
|
|
||||||
getActionPendingIntent("STOP")
|
|
||||||
)
|
|
||||||
setOngoing(true)
|
|
||||||
setShowWhen(false)
|
|
||||||
setOnlyAlertOnce(true)
|
|
||||||
setAutoCancel(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getNotificationBuilder(): NotificationCompat.Builder {
|
|
||||||
return notificationBuilderDeferred.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun start(options: VpnOptions) = 0
|
|
||||||
|
|
||||||
override fun stop() {
|
|
||||||
stopSelf()
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("ForegroundServiceType")
|
|
||||||
override suspend fun startForeground(title: String, content: String) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
val manager = getSystemService(NotificationManager::class.java)
|
|
||||||
var channel = manager?.getNotificationChannel(CHANNEL)
|
|
||||||
if (channel == null) {
|
|
||||||
channel =
|
|
||||||
NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
|
|
||||||
manager?.createNotificationChannel(channel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val notification =
|
|
||||||
getNotificationBuilder()
|
|
||||||
.setContentTitle(title)
|
|
||||||
.setContentText(content).build()
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
||||||
try {
|
|
||||||
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
|
||||||
} catch (_: Exception) {
|
|
||||||
startForeground(notificationId, notification)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
startForeground(notificationId, notification)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,6 +33,7 @@ class FlClashTileService : TileService() {
|
|||||||
|
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
|
GlobalState.syncStatus()
|
||||||
GlobalState.runState.value?.let { updateTile(it) }
|
GlobalState.runState.value?.let { updateTile(it) }
|
||||||
GlobalState.runState.observeForever(observer)
|
GlobalState.runState.observeForever(observer)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
package com.follow.clash.services
|
package com.follow.clash.services
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Notification.FOREGROUND_SERVICE_IMMEDIATE
|
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
|
||||||
import android.net.ProxyInfo
|
import android.net.ProxyInfo
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
@@ -17,18 +12,13 @@ import android.os.RemoteException
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.follow.clash.GlobalState
|
import com.follow.clash.GlobalState
|
||||||
import com.follow.clash.MainActivity
|
|
||||||
import com.follow.clash.R
|
|
||||||
import com.follow.clash.extensions.getActionPendingIntent
|
|
||||||
import com.follow.clash.extensions.getIpv4RouteAddress
|
import com.follow.clash.extensions.getIpv4RouteAddress
|
||||||
import com.follow.clash.extensions.getIpv6RouteAddress
|
import com.follow.clash.extensions.getIpv6RouteAddress
|
||||||
import com.follow.clash.extensions.toCIDR
|
import com.follow.clash.extensions.toCIDR
|
||||||
import com.follow.clash.models.AccessControlMode
|
import com.follow.clash.models.AccessControlMode
|
||||||
import com.follow.clash.models.VpnOptions
|
import com.follow.clash.models.VpnOptions
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
@@ -43,6 +33,10 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
|
|||||||
if (options.ipv4Address.isNotEmpty()) {
|
if (options.ipv4Address.isNotEmpty()) {
|
||||||
val cidr = options.ipv4Address.toCIDR()
|
val cidr = options.ipv4Address.toCIDR()
|
||||||
addAddress(cidr.address, cidr.prefixLength)
|
addAddress(cidr.address, cidr.prefixLength)
|
||||||
|
Log.d(
|
||||||
|
"addAddress",
|
||||||
|
"address: ${cidr.address} prefixLength:${cidr.prefixLength}"
|
||||||
|
)
|
||||||
val routeAddress = options.getIpv4RouteAddress()
|
val routeAddress = options.getIpv4RouteAddress()
|
||||||
if (routeAddress.isNotEmpty()) {
|
if (routeAddress.isNotEmpty()) {
|
||||||
try {
|
try {
|
||||||
@@ -59,26 +53,39 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
|
|||||||
} else {
|
} else {
|
||||||
addRoute("0.0.0.0", 0)
|
addRoute("0.0.0.0", 0)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
addRoute("0.0.0.0", 0)
|
||||||
}
|
}
|
||||||
if (options.ipv6Address.isNotEmpty()) {
|
try {
|
||||||
val cidr = options.ipv6Address.toCIDR()
|
if (options.ipv6Address.isNotEmpty()) {
|
||||||
addAddress(cidr.address, cidr.prefixLength)
|
val cidr = options.ipv6Address.toCIDR()
|
||||||
val routeAddress = options.getIpv6RouteAddress()
|
Log.d(
|
||||||
if (routeAddress.isNotEmpty()) {
|
"addAddress6",
|
||||||
try {
|
"address: ${cidr.address} prefixLength:${cidr.prefixLength}"
|
||||||
routeAddress.forEach { i ->
|
)
|
||||||
Log.d(
|
addAddress(cidr.address, cidr.prefixLength)
|
||||||
"addRoute6",
|
val routeAddress = options.getIpv6RouteAddress()
|
||||||
"address: ${i.address} prefixLength:${i.prefixLength}"
|
if (routeAddress.isNotEmpty()) {
|
||||||
)
|
try {
|
||||||
addRoute(i.address, i.prefixLength)
|
routeAddress.forEach { i ->
|
||||||
|
Log.d(
|
||||||
|
"addRoute6",
|
||||||
|
"address: ${i.address} prefixLength:${i.prefixLength}"
|
||||||
|
)
|
||||||
|
addRoute(i.address, i.prefixLength)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
addRoute("::", 0)
|
||||||
}
|
}
|
||||||
} catch (_: Exception) {
|
} else {
|
||||||
addRoute("::", 0)
|
addRoute("::", 0)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
addRoute("::", 0)
|
|
||||||
}
|
}
|
||||||
|
}catch (_:Exception){
|
||||||
|
Log.d(
|
||||||
|
"addAddress6",
|
||||||
|
"IPv6 is not supported."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
addDnsServer(options.dnsServerAddress)
|
addDnsServer(options.dnsServerAddress)
|
||||||
setMtu(9000)
|
setMtu(9000)
|
||||||
@@ -128,82 +135,22 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val CHANNEL = "FlClash"
|
private var cachedBuilder: NotificationCompat.Builder? = null
|
||||||
|
|
||||||
private val notificationId: Int = 1
|
private suspend fun notificationBuilder(): NotificationCompat.Builder {
|
||||||
|
if (cachedBuilder == null) {
|
||||||
private val notificationBuilderDeferred: Deferred<NotificationCompat.Builder> by lazy {
|
cachedBuilder = createFlClashNotificationBuilder().await()
|
||||||
CoroutineScope(Dispatchers.Main).async {
|
|
||||||
val stopText = GlobalState.getText("stop")
|
|
||||||
val intent = Intent(this@FlClashVpnService, MainActivity::class.java)
|
|
||||||
|
|
||||||
val pendingIntent = if (Build.VERSION.SDK_INT >= 31) {
|
|
||||||
PendingIntent.getActivity(
|
|
||||||
this@FlClashVpnService,
|
|
||||||
0,
|
|
||||||
intent,
|
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
PendingIntent.getActivity(
|
|
||||||
this@FlClashVpnService,
|
|
||||||
0,
|
|
||||||
intent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
with(NotificationCompat.Builder(this@FlClashVpnService, CHANNEL)) {
|
|
||||||
setSmallIcon(R.drawable.ic_stat_name)
|
|
||||||
setContentTitle("FlClash")
|
|
||||||
setContentIntent(pendingIntent)
|
|
||||||
setCategory(NotificationCompat.CATEGORY_SERVICE)
|
|
||||||
priority = NotificationCompat.PRIORITY_MIN
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
|
|
||||||
}
|
|
||||||
setOngoing(true)
|
|
||||||
addAction(
|
|
||||||
0,
|
|
||||||
stopText,
|
|
||||||
getActionPendingIntent("STOP")
|
|
||||||
)
|
|
||||||
setShowWhen(false)
|
|
||||||
setOnlyAlertOnce(true)
|
|
||||||
setAutoCancel(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
return cachedBuilder!!
|
||||||
|
|
||||||
private suspend fun getNotificationBuilder(): NotificationCompat.Builder {
|
|
||||||
return notificationBuilderDeferred.await()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ForegroundServiceType")
|
@SuppressLint("ForegroundServiceType")
|
||||||
override suspend fun startForeground(title: String, content: String) {
|
override suspend fun startForeground(title: String, content: String) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
startForeground(
|
||||||
val manager = getSystemService(NotificationManager::class.java)
|
notificationBuilder()
|
||||||
var channel = manager?.getNotificationChannel(CHANNEL)
|
|
||||||
if (channel == null) {
|
|
||||||
channel =
|
|
||||||
NotificationChannel(CHANNEL, "FlClash", NotificationManager.IMPORTANCE_LOW)
|
|
||||||
manager?.createNotificationChannel(channel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val notification =
|
|
||||||
getNotificationBuilder()
|
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentText(content)
|
.setContentText(content).build()
|
||||||
.build()
|
)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
||||||
try {
|
|
||||||
startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
|
||||||
} catch (_: Exception) {
|
|
||||||
startForeground(notificationId, notification)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
startForeground(notificationId, notification)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTrimMemory(level: Int) {
|
override fun onTrimMemory(level: Int) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import com.android.build.gradle.tasks.MergeSourceSetFolders
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.argumentsWithVarargAsSingleArray
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
@@ -12,11 +12,11 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
|
isJniDebuggable = false
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
@@ -37,13 +37,17 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
|
||||||
}
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
implementation("androidx.annotation:annotation-jvm:1.9.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
val copyNativeLibs by tasks.register<Copy>("copyNativeLibs") {
|
val copyNativeLibs by tasks.register<Copy>("copyNativeLibs") {
|
||||||
@@ -58,8 +62,4 @@ afterEvaluate {
|
|||||||
tasks.named("preBuild") {
|
tasks.named("preBuild") {
|
||||||
dependsOn(copyNativeLibs)
|
dependsOn(copyNativeLibs)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation("androidx.core:core-ktx:1.16.0")
|
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,8 @@ project("core")
|
|||||||
|
|
||||||
message("CMAKE_SOURCE_DIR ${CMAKE_SOURCE_DIR}")
|
message("CMAKE_SOURCE_DIR ${CMAKE_SOURCE_DIR}")
|
||||||
|
|
||||||
|
message("CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE}")
|
||||||
|
|
||||||
if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||||
add_compile_options(-O3)
|
add_compile_options(-O3)
|
||||||
|
|
||||||
@@ -21,6 +23,8 @@ if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
|||||||
-Wl,--strip-all
|
-Wl,--strip-all
|
||||||
-Wl,--exclude-libs=ALL
|
-Wl,--exclude-libs=ALL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_compile_options(-fvisibility=hidden -fvisibility-inlines-hidden)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
set(LIB_CLASH_PATH "${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libclash.so")
|
set(LIB_CLASH_PATH "${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libclash.so")
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
#include <jni.h>
|
|
||||||
|
|
||||||
#ifdef LIBCLASH
|
#ifdef LIBCLASH
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <string>
|
|
||||||
#include "jni_helper.h"
|
#include "jni_helper.h"
|
||||||
#include "libclash.h"
|
#include "libclash.h"
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject thiz, jint fd, jobject cb) {
|
Java_com_follow_clash_core_Core_startTun(JNIEnv *env, jobject, const jint fd, jobject cb) {
|
||||||
auto interface = new_global(cb);
|
const auto interface = new_global(cb);
|
||||||
startTUN(fd, interface);
|
startTUN(fd, interface);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_follow_clash_core_Core_stopTun(JNIEnv *env, jobject thiz) {
|
Java_com_follow_clash_core_Core_stopTun(JNIEnv *) {
|
||||||
stopTun();
|
stopTun();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,50 +23,50 @@ static jmethodID m_tun_interface_resolve_process;
|
|||||||
|
|
||||||
static void release_jni_object_impl(void *obj) {
|
static void release_jni_object_impl(void *obj) {
|
||||||
ATTACH_JNI();
|
ATTACH_JNI();
|
||||||
del_global((jobject) obj);
|
del_global(static_cast<jobject>(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void call_tun_interface_protect_impl(void *tun_interface, int fd) {
|
static void call_tun_interface_protect_impl(void *tun_interface, const int fd) {
|
||||||
ATTACH_JNI();
|
ATTACH_JNI();
|
||||||
env->CallVoidMethod((jobject) tun_interface,
|
env->CallVoidMethod(static_cast<jobject>(tun_interface),
|
||||||
(jmethodID) m_tun_interface_protect,
|
m_tun_interface_protect,
|
||||||
(jint) fd);
|
fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char*
|
static const char *
|
||||||
call_tun_interface_resolve_process_impl(void *tun_interface, int protocol,
|
call_tun_interface_resolve_process_impl(void *tun_interface, int protocol,
|
||||||
const char *source,
|
const char *source,
|
||||||
const char *target,
|
const char *target,
|
||||||
int uid) {
|
const int uid) {
|
||||||
ATTACH_JNI();
|
ATTACH_JNI();
|
||||||
jstring packageName = (jstring)env->CallObjectMethod((jobject) tun_interface,
|
const auto packageName = reinterpret_cast<jstring>(env->CallObjectMethod(static_cast<jobject>(tun_interface),
|
||||||
(jmethodID) m_tun_interface_resolve_process,
|
m_tun_interface_resolve_process,
|
||||||
(jint) protocol,
|
protocol,
|
||||||
(jstring) new_string(source),
|
new_string(source),
|
||||||
(jstring) new_string(target),
|
new_string(target),
|
||||||
(jint) uid);
|
uid));
|
||||||
return get_string(packageName);
|
return get_string(packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
JNI_OnLoad(JavaVM *vm, void *reserved) {
|
JNI_OnLoad(JavaVM *vm, void *) {
|
||||||
JNIEnv *env = nullptr;
|
JNIEnv *env = nullptr;
|
||||||
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
|
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||||
return JNI_ERR;
|
return JNI_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize_jni(vm, env);
|
initialize_jni(vm, env);
|
||||||
|
|
||||||
jclass c_tun_interface = find_class("com/follow/clash/core/TunInterface");
|
const auto c_tun_interface = find_class("com/follow/clash/core/TunInterface");
|
||||||
|
|
||||||
m_tun_interface_protect = find_method(c_tun_interface, "protect", "(I)V");
|
m_tun_interface_protect = find_method(c_tun_interface, "protect", "(I)V");
|
||||||
m_tun_interface_resolve_process = find_method(c_tun_interface, "resolverProcess",
|
m_tun_interface_resolve_process = find_method(c_tun_interface, "resolverProcess",
|
||||||
"(ILjava/lang/String;Ljava/lang/String;I)Ljava/lang/String;");
|
"(ILjava/lang/String;Ljava/lang/String;I)Ljava/lang/String;");
|
||||||
|
|
||||||
registerCallbacks(&call_tun_interface_protect_impl,
|
registerCallbacks(&call_tun_interface_protect_impl,
|
||||||
&call_tun_interface_resolve_process_impl,
|
&call_tun_interface_resolve_process_impl,
|
||||||
&release_jni_object_impl);
|
&release_jni_object_impl);
|
||||||
return JNI_VERSION_1_6;
|
return JNI_VERSION_1_6;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "jni_helper.h"
|
#include "jni_helper.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ static jmethodID m_get_bytes;
|
|||||||
void initialize_jni(JavaVM *vm, JNIEnv *env) {
|
void initialize_jni(JavaVM *vm, JNIEnv *env) {
|
||||||
global_vm = vm;
|
global_vm = vm;
|
||||||
|
|
||||||
c_string = (jclass) new_global(find_class("java/lang/String"));
|
c_string = reinterpret_cast<jclass>(new_global(find_class("java/lang/String")));
|
||||||
m_new_string = find_method(c_string, "<init>", "([B)V");
|
m_new_string = find_method(c_string, "<init>", "([B)V");
|
||||||
m_get_bytes = find_method(c_string, "getBytes", "()[B");
|
m_get_bytes = find_method(c_string, "getBytes", "()[B");
|
||||||
}
|
}
|
||||||
@@ -22,23 +23,23 @@ JavaVM *global_java_vm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *jni_get_string(JNIEnv *env, jstring str) {
|
char *jni_get_string(JNIEnv *env, jstring str) {
|
||||||
auto array = (jbyteArray) env->CallObjectMethod(str, m_get_bytes);
|
const auto array = reinterpret_cast<jbyteArray>(env->CallObjectMethod(str, m_get_bytes));
|
||||||
int length = env->GetArrayLength(array);
|
const int length = env->GetArrayLength(array);
|
||||||
char *content = (char *) malloc(length + 1);
|
const auto content = static_cast<char *>(malloc(length + 1));
|
||||||
env->GetByteArrayRegion(array, 0, length, (jbyte *) content);
|
env->GetByteArrayRegion(array, 0, length, reinterpret_cast<jbyte *>(content));
|
||||||
content[length] = 0;
|
content[length] = 0;
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
jstring jni_new_string(JNIEnv *env, const char *str) {
|
jstring jni_new_string(JNIEnv *env, const char *str) {
|
||||||
auto length = (int) strlen(str);
|
const auto length = static_cast<int>(strlen(str));
|
||||||
jbyteArray array = env->NewByteArray(length);
|
const auto array = env->NewByteArray(length);
|
||||||
env->SetByteArrayRegion(array, 0, length, (const jbyte *) str);
|
env->SetByteArrayRegion(array, 0, length, reinterpret_cast<const jbyte *>(str));
|
||||||
return (jstring) env->NewObject(c_string, m_new_string, array);
|
return reinterpret_cast<jstring>(env->NewObject(c_string, m_new_string, array));
|
||||||
}
|
}
|
||||||
|
|
||||||
int jni_catch_exception(JNIEnv *env) {
|
int jni_catch_exception(JNIEnv *env) {
|
||||||
int result = env->ExceptionCheck();
|
const int result = env->ExceptionCheck();
|
||||||
if (result) {
|
if (result) {
|
||||||
env->ExceptionDescribe();
|
env->ExceptionDescribe();
|
||||||
env->ExceptionClear();
|
env->ExceptionClear();
|
||||||
@@ -46,9 +47,9 @@ int jni_catch_exception(JNIEnv *env) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void jni_attach_thread(struct scoped_jni *jni) {
|
void jni_attach_thread(scoped_jni *jni) {
|
||||||
JavaVM *vm = global_java_vm();
|
JavaVM *vm = global_java_vm();
|
||||||
if (vm->GetEnv((void **) &jni->env, JNI_VERSION_1_6) == JNI_OK) {
|
if (vm->GetEnv(reinterpret_cast<void **>(&jni->env), JNI_VERSION_1_6) == JNI_OK) {
|
||||||
jni->require_release = 0;
|
jni->require_release = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -58,9 +59,9 @@ void jni_attach_thread(struct scoped_jni *jni) {
|
|||||||
jni->require_release = 1;
|
jni->require_release = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void jni_detach_thread(struct scoped_jni *jni) {
|
void jni_detach_thread(const scoped_jni *env) {
|
||||||
JavaVM *vm = global_java_vm();
|
JavaVM *vm = global_java_vm();
|
||||||
if (jni->require_release) {
|
if (env->require_release) {
|
||||||
vm->DetachCurrentThread();
|
vm->DetachCurrentThread();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
struct scoped_jni {
|
struct scoped_jni {
|
||||||
JNIEnv *env;
|
JNIEnv *env;
|
||||||
@@ -18,14 +15,14 @@ extern char *jni_get_string(JNIEnv *env, jstring str);
|
|||||||
|
|
||||||
extern int jni_catch_exception(JNIEnv *env);
|
extern int jni_catch_exception(JNIEnv *env);
|
||||||
|
|
||||||
extern void jni_attach_thread(struct scoped_jni *jni);
|
extern void jni_attach_thread(scoped_jni *jni);
|
||||||
|
|
||||||
extern void jni_detach_thread(struct scoped_jni *env);
|
extern void jni_detach_thread(const scoped_jni *env);
|
||||||
|
|
||||||
extern void release_string(char **str);
|
extern void release_string(char **str);
|
||||||
|
|
||||||
#define ATTACH_JNI() __attribute__((unused, cleanup(jni_detach_thread))) \
|
#define ATTACH_JNI() __attribute__((unused, cleanup(jni_detach_thread))) \
|
||||||
struct scoped_jni _jni; \
|
scoped_jni _jni{}; \
|
||||||
jni_attach_thread(&_jni); \
|
jni_attach_thread(&_jni); \
|
||||||
JNIEnv *env = _jni.env
|
JNIEnv *env = _jni.env
|
||||||
|
|
||||||
@@ -36,4 +33,4 @@ extern void release_string(char **str);
|
|||||||
#define new_global(obj) env->NewGlobalRef(obj)
|
#define new_global(obj) env->NewGlobalRef(obj)
|
||||||
#define del_global(obj) env->DeleteGlobalRef(obj)
|
#define del_global(obj) env->DeleteGlobalRef(obj)
|
||||||
#define get_string(jstr) jni_get_string(env, jstr)
|
#define get_string(jstr) jni_get_string(env, jstr)
|
||||||
#define new_string(cstr) jni_new_string(env, cstr)
|
#define new_string(cstr) jni_new_string(env, cstr)
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx4G
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
kotlin_version=1.9.22
|
kotlin_version=1.9.22
|
||||||
agp_version=8.9.1
|
agp_version=8.9.2
|
||||||
|
|||||||
@@ -385,5 +385,20 @@
|
|||||||
"expressiveScheme": "Expressive",
|
"expressiveScheme": "Expressive",
|
||||||
"contentScheme": "Content",
|
"contentScheme": "Content",
|
||||||
"rainbowScheme": "Rainbow",
|
"rainbowScheme": "Rainbow",
|
||||||
"fruitSaladScheme": "FruitSalad"
|
"fruitSaladScheme": "FruitSalad",
|
||||||
|
"developerMode": "Developer mode",
|
||||||
|
"developerModeEnableTip": "Developer mode is enabled.",
|
||||||
|
"messageTest": "Message test",
|
||||||
|
"messageTestTip": "This is a message.",
|
||||||
|
"crashTest": "Crash test",
|
||||||
|
"clearData": "Clear Data",
|
||||||
|
"textScale": "Text Scaling",
|
||||||
|
"internet": "Internet",
|
||||||
|
"systemApp": "System APP",
|
||||||
|
"noNetworkApp": "No network APP",
|
||||||
|
"contactMe": "Contact me",
|
||||||
|
"recoveryStrategy": "Recovery strategy",
|
||||||
|
"recoveryStrategy_override": "Override",
|
||||||
|
"recoveryStrategy_compatible": "Compatible",
|
||||||
|
"logsTest": "Logs test"
|
||||||
}
|
}
|
||||||
@@ -385,5 +385,21 @@
|
|||||||
"expressiveScheme": "エクスプレッシブ",
|
"expressiveScheme": "エクスプレッシブ",
|
||||||
"contentScheme": "コンテンツテーマ",
|
"contentScheme": "コンテンツテーマ",
|
||||||
"rainbowScheme": "レインボー",
|
"rainbowScheme": "レインボー",
|
||||||
"fruitSaladScheme": "フルーツサラダ"
|
"fruitSaladScheme": "フルーツサラダ",
|
||||||
|
"developerMode": "デベロッパーモード",
|
||||||
|
"developerModeEnableTip": "デベロッパーモードが有効になりました。",
|
||||||
|
"messageTest": "メッセージテスト",
|
||||||
|
"messageTestTip": "これはメッセージです。",
|
||||||
|
"crashTest": "クラッシュテスト",
|
||||||
|
"clearData": "データを消去",
|
||||||
|
"zoom": "ズーム",
|
||||||
|
"textScale": "テキストスケーリング",
|
||||||
|
"internet": "インターネット",
|
||||||
|
"systemApp": "システムアプリ",
|
||||||
|
"noNetworkApp": "ネットワークなしアプリ",
|
||||||
|
"contactMe": "連絡する",
|
||||||
|
"recoveryStrategy": "リカバリー戦略",
|
||||||
|
"recoveryStrategy_override": "オーバーライド",
|
||||||
|
"recoveryStrategy_compatible": "互換性",
|
||||||
|
"logsTest": "ログテスト"
|
||||||
}
|
}
|
||||||
@@ -385,5 +385,21 @@
|
|||||||
"expressiveScheme": "Экспрессивные",
|
"expressiveScheme": "Экспрессивные",
|
||||||
"contentScheme": "Контентная тема",
|
"contentScheme": "Контентная тема",
|
||||||
"rainbowScheme": "Радужные",
|
"rainbowScheme": "Радужные",
|
||||||
"fruitSaladScheme": "Фруктовый микс"
|
"fruitSaladScheme": "Фруктовый микс",
|
||||||
|
"developerMode": "Режим разработчика",
|
||||||
|
"developerModeEnableTip": "Режим разработчика активирован.",
|
||||||
|
"messageTest": "Тестирование сообщения",
|
||||||
|
"messageTestTip": "Это сообщение.",
|
||||||
|
"crashTest": "Тест на сбои",
|
||||||
|
"clearData": "Очистить данные",
|
||||||
|
"zoom": "Масштаб",
|
||||||
|
"textScale": "Масштабирование текста",
|
||||||
|
"internet": "Интернет",
|
||||||
|
"systemApp": "Системное приложение",
|
||||||
|
"noNetworkApp": "Приложение без сети",
|
||||||
|
"contactMe": "Свяжитесь со мной",
|
||||||
|
"recoveryStrategy": "Стратегия восстановления",
|
||||||
|
"recoveryStrategy_override": "Переопределение",
|
||||||
|
"recoveryStrategy_compatible": "Совместимый",
|
||||||
|
"logsTest": "Тест журналов"
|
||||||
}
|
}
|
||||||
@@ -385,5 +385,21 @@
|
|||||||
"expressiveScheme": "表现力",
|
"expressiveScheme": "表现力",
|
||||||
"contentScheme": "内容主题",
|
"contentScheme": "内容主题",
|
||||||
"rainbowScheme": "彩虹",
|
"rainbowScheme": "彩虹",
|
||||||
"fruitSaladScheme": "果缤纷"
|
"fruitSaladScheme": "果缤纷",
|
||||||
|
"developerMode": "开发者模式",
|
||||||
|
"developerModeEnableTip": "开发者模式已启用。",
|
||||||
|
"messageTest": "消息测试",
|
||||||
|
"messageTestTip": "这是一条消息。",
|
||||||
|
"crashTest": "崩溃测试",
|
||||||
|
"clearData": "清除数据",
|
||||||
|
"zoom": "缩放",
|
||||||
|
"textScale": "文本缩放",
|
||||||
|
"internet": "互联网",
|
||||||
|
"systemApp": "系统应用",
|
||||||
|
"noNetworkApp": "无网络应用",
|
||||||
|
"contactMe": "联系我",
|
||||||
|
"recoveryStrategy": "恢复策略",
|
||||||
|
"recoveryStrategy_override": "覆盖",
|
||||||
|
"recoveryStrategy_compatible": "兼容",
|
||||||
|
"logsTest": "日志测试"
|
||||||
}
|
}
|
||||||
Submodule core/Clash.Meta updated: f19dad529f...a77851dd9a
@@ -87,6 +87,9 @@ func handleAction(action *Action, result func(data interface{})) {
|
|||||||
case closeConnectionsMethod:
|
case closeConnectionsMethod:
|
||||||
result(handleCloseConnections())
|
result(handleCloseConnections())
|
||||||
return
|
return
|
||||||
|
case resetConnectionsMethod:
|
||||||
|
result(handleResetConnections())
|
||||||
|
return
|
||||||
case closeConnectionMethod:
|
case closeConnectionMethod:
|
||||||
id := action.Data.(string)
|
id := action.Data.(string)
|
||||||
result(handleCloseConnection(id))
|
result(handleCloseConnection(id))
|
||||||
@@ -166,6 +169,9 @@ func handleAction(action *Action, result func(data interface{})) {
|
|||||||
data := action.Data.(string)
|
data := action.Data.(string)
|
||||||
handleSetState(data)
|
handleSetState(data)
|
||||||
result(true)
|
result(true)
|
||||||
|
case crashMethod:
|
||||||
|
result(true)
|
||||||
|
handleCrash()
|
||||||
default:
|
default:
|
||||||
handle := nextHandle(action, result)
|
handle := nextHandle(action, result)
|
||||||
if handle {
|
if handle {
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func protect(callback unsafe.Pointer, fd int) {
|
func Protect(callback unsafe.Pointer, fd int) {
|
||||||
if globalCallbacks.protectFunc != nil {
|
if globalCallbacks.protectFunc != nil {
|
||||||
C.protect(globalCallbacks.protectFunc, callback, C.int(fd))
|
C.protect(globalCallbacks.protectFunc, callback, C.int(fd))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveProcess(callback unsafe.Pointer, protocol int, source, target string, uid int) string {
|
func ResolveProcess(callback unsafe.Pointer, protocol int, source, target string, uid int) string {
|
||||||
if globalCallbacks.resolveProcessFunc == nil {
|
if globalCallbacks.resolveProcessFunc == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ func getRawConfigWithId(id string) *config.RawConfig {
|
|||||||
path := getProfilePath(id)
|
path := getProfilePath(id)
|
||||||
bytes, err := readFile(path)
|
bytes, err := readFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("profile is not exist")
|
|
||||||
return config.DefaultRawConfig()
|
return config.DefaultRawConfig()
|
||||||
}
|
}
|
||||||
prof, err := config.UnmarshalRawConfig(bytes)
|
prof, err := config.UnmarshalRawConfig(bytes)
|
||||||
@@ -238,6 +237,7 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
|
|||||||
targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
|
targetConfig.Tun.DNSHijack = patchConfig.Tun.DNSHijack
|
||||||
targetConfig.Tun.Stack = patchConfig.Tun.Stack
|
targetConfig.Tun.Stack = patchConfig.Tun.Stack
|
||||||
targetConfig.Tun.RouteAddress = patchConfig.Tun.RouteAddress
|
targetConfig.Tun.RouteAddress = patchConfig.Tun.RouteAddress
|
||||||
|
targetConfig.Tun.AutoRoute = patchConfig.Tun.AutoRoute
|
||||||
targetConfig.GeodataLoader = patchConfig.GeodataLoader
|
targetConfig.GeodataLoader = patchConfig.GeodataLoader
|
||||||
targetConfig.Profile.StoreSelected = false
|
targetConfig.Profile.StoreSelected = false
|
||||||
targetConfig.GeoXUrl = patchConfig.GeoXUrl
|
targetConfig.GeoXUrl = patchConfig.GeoXUrl
|
||||||
@@ -362,10 +362,11 @@ func applyConfig(rawConfig *config.RawConfig) error {
|
|||||||
}
|
}
|
||||||
if configParams.IsPatch {
|
if configParams.IsPatch {
|
||||||
patchConfig()
|
patchConfig()
|
||||||
|
updateListeners(false)
|
||||||
} else {
|
} else {
|
||||||
hub.ApplyConfig(currentConfig)
|
hub.ApplyConfig(currentConfig)
|
||||||
patchSelectGroup()
|
patchSelectGroup()
|
||||||
|
updateListeners(true)
|
||||||
}
|
}
|
||||||
updateListeners(false)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ const (
|
|||||||
asyncTestDelayMethod Method = "asyncTestDelay"
|
asyncTestDelayMethod Method = "asyncTestDelay"
|
||||||
getConnectionsMethod Method = "getConnections"
|
getConnectionsMethod Method = "getConnections"
|
||||||
closeConnectionsMethod Method = "closeConnections"
|
closeConnectionsMethod Method = "closeConnections"
|
||||||
|
resetConnectionsMethod Method = "resetConnectionsMethod"
|
||||||
closeConnectionMethod Method = "closeConnection"
|
closeConnectionMethod Method = "closeConnection"
|
||||||
getExternalProvidersMethod Method = "getExternalProviders"
|
getExternalProvidersMethod Method = "getExternalProviders"
|
||||||
getExternalProviderMethod Method = "getExternalProvider"
|
getExternalProviderMethod Method = "getExternalProvider"
|
||||||
@@ -82,6 +83,7 @@ const (
|
|||||||
getRunTimeMethod Method = "getRunTime"
|
getRunTimeMethod Method = "getRunTime"
|
||||||
getCurrentProfileNameMethod Method = "getCurrentProfileName"
|
getCurrentProfileNameMethod Method = "getCurrentProfileName"
|
||||||
getProfileMethod Method = "getProfile"
|
getProfileMethod Method = "getProfile"
|
||||||
|
crashMethod Method = "crash"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Method string
|
type Method string
|
||||||
|
|||||||
48
core/go.mod
48
core/go.mod
@@ -6,12 +6,13 @@ replace github.com/metacubex/mihomo => ./Clash.Meta
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/metacubex/mihomo v0.0.0-00010101000000-000000000000
|
github.com/metacubex/mihomo v0.0.0-00010101000000-000000000000
|
||||||
github.com/samber/lo v1.49.1
|
github.com/samber/lo v1.50.0
|
||||||
|
golang.org/x/sync v0.11.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/3andne/restls-client-go v0.1.6 // indirect
|
github.com/3andne/restls-client-go v0.1.6 // indirect
|
||||||
github.com/RyuaNerin/go-krypto v1.2.4 // indirect
|
github.com/RyuaNerin/go-krypto v1.3.0 // indirect
|
||||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
|
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||||
@@ -20,13 +21,13 @@ require (
|
|||||||
github.com/cloudflare/circl v1.3.7 // indirect
|
github.com/cloudflare/circl v1.3.7 // indirect
|
||||||
github.com/coreos/go-iptables v0.8.0 // indirect
|
github.com/coreos/go-iptables v0.8.0 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
github.com/ebitengine/purego v0.8.2 // indirect
|
github.com/ebitengine/purego v0.8.3-0.20250507171810-1638563e3615 // indirect
|
||||||
github.com/enfein/mieru/v3 v3.13.0 // indirect
|
github.com/enfein/mieru/v3 v3.13.0 // indirect
|
||||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
|
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
|
||||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
|
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||||
github.com/go-chi/chi/v5 v5.2.1 // indirect
|
github.com/go-chi/chi/v5 v5.2.1 // indirect
|
||||||
github.com/go-chi/render v1.0.3 // indirect
|
github.com/go-chi/render v1.0.3 // indirect
|
||||||
@@ -35,7 +36,7 @@ require (
|
|||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/gobwas/ws v1.4.0 // indirect
|
github.com/gobwas/ws v1.4.0 // indirect
|
||||||
github.com/gofrs/uuid/v5 v5.3.1 // indirect
|
github.com/gofrs/uuid/v5 v5.3.2 // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||||
@@ -52,20 +53,25 @@ require (
|
|||||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect
|
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect
|
||||||
github.com/metacubex/bart v0.19.0 // indirect
|
github.com/metacubex/bart v0.19.0 // indirect
|
||||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 // indirect
|
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 // indirect
|
||||||
github.com/metacubex/chacha v0.1.1 // indirect
|
github.com/metacubex/chacha v0.1.2 // indirect
|
||||||
|
github.com/metacubex/fswatch v0.1.1 // indirect
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
|
||||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect
|
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect
|
||||||
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 // indirect
|
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
|
||||||
|
github.com/metacubex/quic-go v0.51.1-0.20250511032541-4e34341cf18b // indirect
|
||||||
github.com/metacubex/randv2 v0.2.0 // indirect
|
github.com/metacubex/randv2 v0.2.0 // indirect
|
||||||
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 // indirect
|
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 // indirect
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 // indirect
|
github.com/metacubex/sing-mux v0.3.2 // indirect
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.8 // indirect
|
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2 // indirect
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 // indirect
|
github.com/metacubex/sing-shadowsocks v0.2.9 // indirect
|
||||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 // indirect
|
github.com/metacubex/sing-shadowsocks2 v0.2.3 // indirect
|
||||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 // indirect
|
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 // indirect
|
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 // indirect
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect
|
github.com/metacubex/sing-vmess v0.2.1 // indirect
|
||||||
github.com/metacubex/utls v1.6.8-alpha.4 // indirect
|
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
|
||||||
|
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee // indirect
|
||||||
|
github.com/metacubex/tfo-go v0.0.0-20250503140532-decbcfccbfdf // indirect
|
||||||
|
github.com/metacubex/utls v1.7.0-alpha.3 // indirect
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
|
||||||
github.com/miekg/dns v1.1.63 // indirect
|
github.com/miekg/dns v1.1.63 // indirect
|
||||||
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
|
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
|
||||||
@@ -77,15 +83,8 @@ require (
|
|||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
|
||||||
github.com/sagernet/cors v1.2.1 // indirect
|
github.com/sagernet/cors v1.2.1 // indirect
|
||||||
github.com/sagernet/fswatch v0.1.1 // indirect
|
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||||
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/shirou/gopsutil/v4 v4.25.1 // indirect
|
||||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
||||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
|
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
|
||||||
@@ -107,7 +106,6 @@ require (
|
|||||||
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
|
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
|
||||||
golang.org/x/mod v0.20.0 // indirect
|
golang.org/x/mod v0.20.0 // indirect
|
||||||
golang.org/x/net v0.35.0 // indirect
|
golang.org/x/net v0.35.0 // indirect
|
||||||
golang.org/x/sync v0.11.0 // indirect
|
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.22.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
golang.org/x/time v0.7.0 // indirect
|
golang.org/x/time v0.7.0 // indirect
|
||||||
|
|||||||
105
core/go.sum
105
core/go.sum
@@ -1,8 +1,8 @@
|
|||||||
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
|
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
|
||||||
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
|
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
|
||||||
github.com/RyuaNerin/elliptic2 v1.0.0/go.mod h1:wWB8fWrJI/6EPJkyV/r1Rj0hxUgrusmqSj8JN6yNf/A=
|
github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg=
|
||||||
github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4b4Go=
|
github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM=
|
||||||
github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8=
|
github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss=
|
||||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
|
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
|
||||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
|
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
@@ -26,12 +26,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
github.com/ebitengine/purego v0.8.3-0.20250507171810-1638563e3615 h1:W7mpP4uiOAbBOdDnRXT9EUdauFv7bz+ERT5rPIord00=
|
||||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.8.3-0.20250507171810-1638563e3615/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98=
|
github.com/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98=
|
||||||
github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
|
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
|
||||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
|
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
|
||||||
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
|
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
|
||||||
@@ -39,8 +39,8 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBE
|
|||||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
|
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
|
||||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
|
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
|
||||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
|
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||||
@@ -59,8 +59,8 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
|||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||||
github.com/gofrs/uuid/v5 v5.3.1 h1:aPx49MwJbekCzOyhZDjJVb0hx3A0KLjlbLx6p2gY0p0=
|
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
|
||||||
github.com/gofrs/uuid/v5 v5.3.1/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
@@ -101,34 +101,45 @@ github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY
|
|||||||
github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
||||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
||||||
github.com/metacubex/chacha v0.1.1 h1:OHIv11Nd9CISAIzegpjfupIoZp9DYm6uQw41RxvmU/c=
|
github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc=
|
||||||
github.com/metacubex/chacha v0.1.1/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||||
|
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
|
||||||
|
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
|
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
|
||||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
||||||
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 h1:B+AP/Pj2/jBDS/kCYjz/x+0BCOKfd2VODYevyeIt+Ds=
|
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
|
||||||
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996/go.mod h1:ExVjGyEwTUjCFqx+5uxgV7MOoA3fZI+th4D40H35xmY=
|
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
|
||||||
|
github.com/metacubex/quic-go v0.51.1-0.20250511032541-4e34341cf18b h1:8oDU32eJ+RRhl1FodGgPfxQxtoBAiD9D40XG2XtU/SE=
|
||||||
|
github.com/metacubex/quic-go v0.51.1-0.20250511032541-4e34341cf18b/go.mod h1:9R1NOzCgTcWsdWvOMlmtMuF0uKzuOpsfvEf7U3I8zM0=
|
||||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||||
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 h1:aHsYiTvubfgMa3JMTDY//hDXVvFWrHg6ARckR52ttZs=
|
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||||
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629/go.mod h1:TTeIOZLdGmzc07Oedn++vWUUfkZoXLF4sEMxWuhBFr8=
|
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 h1:m4nSxvw46JEgxMzzmnXams+ebwabcry4Ydep/zNiesQ=
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 h1:UkPoRAnoBQMn7IK5qpoIV3OejU15q+rqel3NrbSCFKA=
|
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
|
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
|
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
|
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2 h1:wfmYgtECbEYo1slMtyo+2kMqscYYDSjU/TVgS3018F4=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
|
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2/go.mod h1:P1kd57U6XXmXv9PbwWdznUGT0k9bKgFJXF0fEORbIlk=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00=
|
||||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 h1:B211C+i/I8CWf4I/BaAV0mmkEHrDBJ0XR9EWxjPbFEg=
|
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA=
|
||||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
|
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0=
|
||||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
|
github.com/metacubex/sing-shadowsocks2 v0.2.3/go.mod h1:/WNy/Q8ahLCoPRriWuFZFD0Jy+JNp1MEQl28Zw6SaF8=
|
||||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
|
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
|
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
|
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 h1:TAwL91XPa6x1QK55CRm+VTzPvLPUfEr/uFDnOZArqEU=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
github.com/metacubex/sing-vmess v0.2.1 h1:I6gM3VUjtvJ15D805EUbNH+SRBuqzJeFnuIbKYUsWZ0=
|
||||||
github.com/metacubex/utls v1.6.8-alpha.4 h1:5EvsCHxDNneaOtAyc8CztoNSpmonLvkvuGs01lIeeEI=
|
github.com/metacubex/sing-vmess v0.2.1/go.mod h1:DsODWItJtOMZNna8Qhheg8r3tUivrcO3vWgaTYKnfTo=
|
||||||
github.com/metacubex/utls v1.6.8-alpha.4/go.mod h1:MEZ5WO/VLKYs/s/dOzEK/mlXOQxc04ESeLzRgjmLYtk=
|
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||||
|
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
|
||||||
|
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
|
||||||
|
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
|
||||||
|
github.com/metacubex/tfo-go v0.0.0-20250503140532-decbcfccbfdf h1:LwID1wz4tzypidd412dd4dC1H0m1TgRCQ/XvRvMJDFM=
|
||||||
|
github.com/metacubex/tfo-go v0.0.0-20250503140532-decbcfccbfdf/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||||
|
github.com/metacubex/utls v1.7.0-alpha.3 h1:cp1cEMUnoifiWrGHRzo+nCwPRveN9yPD8QaRFmfcYxA=
|
||||||
|
github.com/metacubex/utls v1.7.0-alpha.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
||||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||||
@@ -159,27 +170,12 @@ github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++
|
|||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
|
||||||
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
||||||
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
||||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
|
||||||
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
|
||||||
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
|
||||||
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=
|
|
||||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
|
||||||
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
|
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
|
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
|
||||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
||||||
@@ -191,9 +187,16 @@ github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e
|
|||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
@@ -253,7 +256,7 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
|
|||||||
17
core/hub.go
17
core/hub.go
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/metacubex/mihomo/common/observable"
|
"github.com/metacubex/mihomo/common/observable"
|
||||||
"github.com/metacubex/mihomo/common/utils"
|
"github.com/metacubex/mihomo/common/utils"
|
||||||
"github.com/metacubex/mihomo/component/mmdb"
|
"github.com/metacubex/mihomo/component/mmdb"
|
||||||
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
"github.com/metacubex/mihomo/component/updater"
|
"github.com/metacubex/mihomo/component/updater"
|
||||||
"github.com/metacubex/mihomo/config"
|
"github.com/metacubex/mihomo/config"
|
||||||
"github.com/metacubex/mihomo/constant"
|
"github.com/metacubex/mihomo/constant"
|
||||||
@@ -53,6 +54,7 @@ func handleStartListener() bool {
|
|||||||
defer runLock.Unlock()
|
defer runLock.Unlock()
|
||||||
isRunning = true
|
isRunning = true
|
||||||
updateListeners(true)
|
updateListeners(true)
|
||||||
|
resolver.ResetConnection()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,6 +268,11 @@ func handleGetConnections() string {
|
|||||||
func handleCloseConnections() bool {
|
func handleCloseConnections() bool {
|
||||||
runLock.Lock()
|
runLock.Lock()
|
||||||
defer runLock.Unlock()
|
defer runLock.Unlock()
|
||||||
|
closeConnections()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeConnections() {
|
||||||
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
||||||
err := c.Close()
|
err := c.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -273,6 +280,12 @@ func handleCloseConnections() bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleResetConnections() bool {
|
||||||
|
runLock.Lock()
|
||||||
|
defer runLock.Unlock()
|
||||||
|
resolver.ResetConnection()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,6 +455,10 @@ func handleSetState(params string) {
|
|||||||
_ = json.Unmarshal([]byte(params), state.CurrentState)
|
_ = json.Unmarshal([]byte(params), state.CurrentState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleCrash() {
|
||||||
|
panic("handle invoke crash")
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
adapter.UrlTestHook = func(url string, name string, delay uint16) {
|
adapter.UrlTestHook = func(url string, name string, delay uint16) {
|
||||||
delayData := &Delay{
|
delayData := &Delay{
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (t *TunHandler) handleProtect(fd int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
protect(t.callback, fd)
|
Protect(t.callback, fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TunHandler) handleResolveProcess(source, target net.Addr) string {
|
func (t *TunHandler) handleResolveProcess(source, target net.Addr) string {
|
||||||
@@ -79,7 +79,7 @@ func (t *TunHandler) handleResolveProcess(source, target net.Addr) string {
|
|||||||
if version < 29 {
|
if version < 29 {
|
||||||
uid = platform.QuerySocketUidFromProcFs(source, target)
|
uid = platform.QuerySocketUidFromProcFs(source, target)
|
||||||
}
|
}
|
||||||
return resolveProcess(t.callback, protocol, source.String(), target.String(), uid)
|
return ResolveProcess(t.callback, protocol, source.String(), target.String(), uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -98,13 +98,13 @@ func handleStopTun() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleStartTun(fd int, callback unsafe.Pointer) bool {
|
func handleStartTun(fd int, callback unsafe.Pointer) {
|
||||||
handleStopTun()
|
handleStopTun()
|
||||||
|
tunLock.Lock()
|
||||||
|
defer tunLock.Unlock()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
runTime = &now
|
runTime = &now
|
||||||
if fd != 0 {
|
if fd != 0 {
|
||||||
tunLock.Lock()
|
|
||||||
defer tunLock.Unlock()
|
|
||||||
tunHandler = &TunHandler{
|
tunHandler = &TunHandler{
|
||||||
callback: callback,
|
callback: callback,
|
||||||
limit: semaphore.NewWeighted(4),
|
limit: semaphore.NewWeighted(4),
|
||||||
@@ -113,13 +113,11 @@ func handleStartTun(fd int, callback unsafe.Pointer) bool {
|
|||||||
tunListener, _ := t.Start(fd, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack)
|
tunListener, _ := t.Start(fd, currentConfig.General.Tun.Device, currentConfig.General.Tun.Stack)
|
||||||
if tunListener != nil {
|
if tunListener != nil {
|
||||||
log.Infoln("TUN address: %v", tunListener.Address())
|
log.Infoln("TUN address: %v", tunListener.Address())
|
||||||
|
tunHandler.listener = tunListener
|
||||||
} else {
|
} else {
|
||||||
removeTunHook()
|
removeTunHook()
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
tunHandler.listener = tunListener
|
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetRunTime() string {
|
func handleGetRunTime() string {
|
||||||
@@ -228,7 +226,10 @@ func quickStart(initParamsChar *C.char, paramsChar *C.char, stateParamsChar *C.c
|
|||||||
|
|
||||||
//export startTUN
|
//export startTUN
|
||||||
func startTUN(fd C.int, callback unsafe.Pointer) bool {
|
func startTUN(fd C.int, callback unsafe.Pointer) bool {
|
||||||
return handleStartTun(int(fd), callback)
|
go func() {
|
||||||
|
handleStartTun(int(fd), callback)
|
||||||
|
}()
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
//export getRunTime
|
//export getRunTime
|
||||||
@@ -238,7 +239,9 @@ func getRunTime() *C.char {
|
|||||||
|
|
||||||
//export stopTun
|
//export stopTun
|
||||||
func stopTun() {
|
func stopTun() {
|
||||||
handleStopTun()
|
go func() {
|
||||||
|
handleStopTun()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
//export getCurrentProfileName
|
//export getCurrentProfileName
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ type AccessControl struct {
|
|||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
AcceptList []string `json:"acceptList"`
|
AcceptList []string `json:"acceptList"`
|
||||||
RejectList []string `json:"rejectList"`
|
RejectList []string `json:"rejectList"`
|
||||||
IsFilterSystemApp bool `json:"isFilterSystemApp"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AndroidVpnRawOptions struct {
|
type AndroidVpnRawOptions struct {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
|
||||||
import 'package:fl_clash/clash/clash.dart';
|
import 'package:fl_clash/clash/clash.dart';
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/l10n/l10n.dart';
|
import 'package:fl_clash/l10n/l10n.dart';
|
||||||
@@ -14,7 +13,6 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
import 'models/models.dart';
|
|
||||||
import 'pages/pages.dart';
|
import 'pages/pages.dart';
|
||||||
|
|
||||||
class Application extends ConsumerStatefulWidget {
|
class Application extends ConsumerStatefulWidget {
|
||||||
@@ -27,7 +25,6 @@ class Application extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ApplicationState extends ConsumerState<Application> {
|
class ApplicationState extends ConsumerState<Application> {
|
||||||
late ColorSchemes systemColorSchemes;
|
|
||||||
Timer? _autoUpdateGroupTaskTimer;
|
Timer? _autoUpdateGroupTaskTimer;
|
||||||
Timer? _autoUpdateProfilesTaskTimer;
|
Timer? _autoUpdateProfilesTaskTimer;
|
||||||
|
|
||||||
@@ -103,7 +100,8 @@ class ApplicationState extends ConsumerState<Application> {
|
|||||||
return AppStateManager(
|
return AppStateManager(
|
||||||
child: ClashManager(
|
child: ClashManager(
|
||||||
child: ConnectivityManager(
|
child: ConnectivityManager(
|
||||||
onConnectivityChanged: () {
|
onConnectivityChanged: () async {
|
||||||
|
await clashCore.resetConnections();
|
||||||
globalState.appController.updateLocalIp();
|
globalState.appController.updateLocalIp();
|
||||||
globalState.appController.addCheckIpNumDebounce();
|
globalState.appController.addCheckIpNumDebounce();
|
||||||
},
|
},
|
||||||
@@ -132,19 +130,6 @@ class ApplicationState extends ConsumerState<Application> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateSystemColorSchemes(
|
|
||||||
ColorScheme? lightDynamic,
|
|
||||||
ColorScheme? darkDynamic,
|
|
||||||
) {
|
|
||||||
systemColorSchemes = ColorSchemes(
|
|
||||||
lightColorScheme: lightDynamic,
|
|
||||||
darkColorScheme: darkDynamic,
|
|
||||||
);
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
globalState.appController.updateSystemColorSchemes(systemColorSchemes);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(context) {
|
Widget build(context) {
|
||||||
return _buildPlatformState(
|
return _buildPlatformState(
|
||||||
@@ -154,49 +139,44 @@ class ApplicationState extends ConsumerState<Application> {
|
|||||||
final locale =
|
final locale =
|
||||||
ref.watch(appSettingProvider.select((state) => state.locale));
|
ref.watch(appSettingProvider.select((state) => state.locale));
|
||||||
final themeProps = ref.watch(themeSettingProvider);
|
final themeProps = ref.watch(themeSettingProvider);
|
||||||
return DynamicColorBuilder(
|
return MaterialApp(
|
||||||
builder: (lightDynamic, darkDynamic) {
|
debugShowCheckedModeBanner: false,
|
||||||
_updateSystemColorSchemes(lightDynamic, darkDynamic);
|
navigatorKey: globalState.navigatorKey,
|
||||||
return MaterialApp(
|
localizationsDelegates: const [
|
||||||
debugShowCheckedModeBanner: false,
|
AppLocalizations.delegate,
|
||||||
navigatorKey: globalState.navigatorKey,
|
GlobalMaterialLocalizations.delegate,
|
||||||
localizationsDelegates: const [
|
GlobalCupertinoLocalizations.delegate,
|
||||||
AppLocalizations.delegate,
|
GlobalWidgetsLocalizations.delegate
|
||||||
GlobalMaterialLocalizations.delegate,
|
],
|
||||||
GlobalCupertinoLocalizations.delegate,
|
builder: (_, child) {
|
||||||
GlobalWidgetsLocalizations.delegate
|
return AppEnvManager(
|
||||||
],
|
child: _buildPlatformApp(
|
||||||
builder: (_, child) {
|
_buildApp(child!),
|
||||||
return AppEnvManager(
|
|
||||||
child: _buildPlatformApp(
|
|
||||||
_buildApp(child!),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
scrollBehavior: BaseScrollBehavior(),
|
|
||||||
title: appName,
|
|
||||||
locale: utils.getLocaleForString(locale),
|
|
||||||
supportedLocales: AppLocalizations.delegate.supportedLocales,
|
|
||||||
themeMode: themeProps.themeMode,
|
|
||||||
theme: ThemeData(
|
|
||||||
useMaterial3: true,
|
|
||||||
pageTransitionsTheme: _pageTransitionsTheme,
|
|
||||||
colorScheme: _getAppColorScheme(
|
|
||||||
brightness: Brightness.light,
|
|
||||||
primaryColor: themeProps.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
darkTheme: ThemeData(
|
|
||||||
useMaterial3: true,
|
|
||||||
pageTransitionsTheme: _pageTransitionsTheme,
|
|
||||||
colorScheme: _getAppColorScheme(
|
|
||||||
brightness: Brightness.dark,
|
|
||||||
primaryColor: themeProps.primaryColor,
|
|
||||||
).toPureBlack(themeProps.pureBlack),
|
|
||||||
),
|
|
||||||
home: child,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
scrollBehavior: BaseScrollBehavior(),
|
||||||
|
title: appName,
|
||||||
|
locale: utils.getLocaleForString(locale),
|
||||||
|
supportedLocales: AppLocalizations.delegate.supportedLocales,
|
||||||
|
themeMode: themeProps.themeMode,
|
||||||
|
theme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
pageTransitionsTheme: _pageTransitionsTheme,
|
||||||
|
colorScheme: _getAppColorScheme(
|
||||||
|
brightness: Brightness.light,
|
||||||
|
primaryColor: themeProps.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
darkTheme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
pageTransitionsTheme: _pageTransitionsTheme,
|
||||||
|
colorScheme: _getAppColorScheme(
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
primaryColor: themeProps.primaryColor,
|
||||||
|
).toPureBlack(themeProps.pureBlack),
|
||||||
|
),
|
||||||
|
home: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: const HomePage(),
|
child: const HomePage(),
|
||||||
|
|||||||
@@ -143,6 +143,10 @@ class ClashCore {
|
|||||||
clashInterface.closeConnections();
|
clashInterface.closeConnections();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetConnections() {
|
||||||
|
clashInterface.resetConnections();
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<ExternalProvider>> getExternalProviders() async {
|
Future<List<ExternalProvider>> getExternalProviders() async {
|
||||||
final externalProvidersRawString =
|
final externalProvidersRawString =
|
||||||
await clashInterface.getExternalProviders();
|
await clashInterface.getExternalProviders();
|
||||||
|
|||||||
@@ -58,12 +58,16 @@ mixin ClashInterface {
|
|||||||
|
|
||||||
stopLog();
|
stopLog();
|
||||||
|
|
||||||
|
Future<bool> crash();
|
||||||
|
|
||||||
FutureOr<String> getConnections();
|
FutureOr<String> getConnections();
|
||||||
|
|
||||||
FutureOr<bool> closeConnection(String id);
|
FutureOr<bool> closeConnection(String id);
|
||||||
|
|
||||||
FutureOr<bool> closeConnections();
|
FutureOr<bool> closeConnections();
|
||||||
|
|
||||||
|
FutureOr<bool> resetConnections();
|
||||||
|
|
||||||
FutureOr<String> getProfile(String id);
|
FutureOr<String> getProfile(String id);
|
||||||
|
|
||||||
Future<bool> setState(CoreState state);
|
Future<bool> setState(CoreState state);
|
||||||
@@ -104,6 +108,7 @@ abstract class ClashHandlerInterface with ClashInterface {
|
|||||||
case ActionMethod.closeConnection:
|
case ActionMethod.closeConnection:
|
||||||
case ActionMethod.stopListener:
|
case ActionMethod.stopListener:
|
||||||
case ActionMethod.setState:
|
case ActionMethod.setState:
|
||||||
|
case ActionMethod.crash:
|
||||||
completer?.complete(result.data as bool);
|
completer?.complete(result.data as bool);
|
||||||
return;
|
return;
|
||||||
case ActionMethod.changeProxy:
|
case ActionMethod.changeProxy:
|
||||||
@@ -242,6 +247,13 @@ abstract class ClashHandlerInterface with ClashInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> crash() {
|
||||||
|
return invoke<bool>(
|
||||||
|
method: ActionMethod.crash,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> getProxies() {
|
Future<String> getProxies() {
|
||||||
return invoke<String>(
|
return invoke<String>(
|
||||||
@@ -318,6 +330,13 @@ abstract class ClashHandlerInterface with ClashInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> resetConnections() {
|
||||||
|
return invoke<bool>(
|
||||||
|
method: ActionMethod.resetConnections,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> closeConnection(String id) {
|
Future<bool> closeConnection(String id) {
|
||||||
return invoke<bool>(
|
return invoke<bool>(
|
||||||
|
|||||||
@@ -267,6 +267,15 @@ class ClashLibHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DateTime? getRunTime() {
|
||||||
|
final runTimeRaw = clashFFI.getRunTime();
|
||||||
|
final runTimeString = runTimeRaw.cast<Utf8>().toDartString();
|
||||||
|
if (runTimeString.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> quickStart(
|
Future<String> quickStart(
|
||||||
InitParams initParams,
|
InitParams initParams,
|
||||||
UpdateConfigParams updateConfigParams,
|
UpdateConfigParams updateConfigParams,
|
||||||
@@ -297,15 +306,10 @@ class ClashLibHandler {
|
|||||||
malloc.free(stateParamsChar);
|
malloc.free(stateParamsChar);
|
||||||
return completer.future;
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime? getRunTime() {
|
|
||||||
final runTimeRaw = clashFFI.getRunTime();
|
|
||||||
final runTimeString = runTimeRaw.cast<Utf8>().toDartString();
|
|
||||||
clashFFI.freeCString(runTimeRaw);
|
|
||||||
if (runTimeString.isEmpty) return null;
|
|
||||||
return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ClashLib? get clashLib =>
|
ClashLib? get clashLib =>
|
||||||
Platform.isAndroid && !globalState.isService ? ClashLib() : null;
|
Platform.isAndroid && !globalState.isService ? ClashLib() : null;
|
||||||
|
|
||||||
|
ClashLibHandler? get clashLibHandler =>
|
||||||
|
Platform.isAndroid ? ClashLibHandler() : null;
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ class ClashService extends ClashHandlerInterface {
|
|||||||
}
|
}
|
||||||
}, (error, stack) {
|
}, (error, stack) {
|
||||||
commonPrint.log(error.toString());
|
commonPrint.log(error.toString());
|
||||||
if(error is SocketException){
|
if (error is SocketException) {
|
||||||
globalState.showNotifier(error.toString());
|
globalState.showNotifier(error.toString());
|
||||||
globalState.appController.restartCore();
|
// globalState.appController.restartCore();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -92,12 +92,11 @@ class ClashService extends ClashHandlerInterface {
|
|||||||
final arg = Platform.isWindows
|
final arg = Platform.isWindows
|
||||||
? "${serverSocket.port}"
|
? "${serverSocket.port}"
|
||||||
: serverSocket.address.address;
|
: serverSocket.address.address;
|
||||||
bool isSuccess = false;
|
|
||||||
if (Platform.isWindows && await system.checkIsAdmin()) {
|
if (Platform.isWindows && await system.checkIsAdmin()) {
|
||||||
isSuccess = await request.startCoreByHelper(arg);
|
final isSuccess = await request.startCoreByHelper(arg);
|
||||||
}
|
if (isSuccess) {
|
||||||
if (isSuccess) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
process = await Process.start(
|
process = await Process.start(
|
||||||
appPath.corePath,
|
appPath.corePath,
|
||||||
|
|||||||
@@ -4,36 +4,37 @@ export 'color.dart';
|
|||||||
export 'constant.dart';
|
export 'constant.dart';
|
||||||
export 'context.dart';
|
export 'context.dart';
|
||||||
export 'datetime.dart';
|
export 'datetime.dart';
|
||||||
|
export 'fixed.dart';
|
||||||
export 'function.dart';
|
export 'function.dart';
|
||||||
export 'future.dart';
|
export 'future.dart';
|
||||||
export 'http.dart';
|
export 'http.dart';
|
||||||
export 'icons.dart';
|
export 'icons.dart';
|
||||||
export 'iterable.dart';
|
export 'iterable.dart';
|
||||||
|
export 'javascript.dart';
|
||||||
export 'keyboard.dart';
|
export 'keyboard.dart';
|
||||||
export 'launch.dart';
|
export 'launch.dart';
|
||||||
export 'link.dart';
|
export 'link.dart';
|
||||||
export 'list.dart';
|
|
||||||
export 'lock.dart';
|
export 'lock.dart';
|
||||||
export 'measure.dart';
|
export 'measure.dart';
|
||||||
|
export 'mixin.dart';
|
||||||
export 'navigation.dart';
|
export 'navigation.dart';
|
||||||
export 'navigator.dart';
|
export 'navigator.dart';
|
||||||
export 'network.dart';
|
export 'network.dart';
|
||||||
export 'num.dart';
|
export 'num.dart';
|
||||||
export 'utils.dart';
|
|
||||||
export 'package.dart';
|
export 'package.dart';
|
||||||
export 'path.dart';
|
export 'path.dart';
|
||||||
export 'picker.dart';
|
export 'picker.dart';
|
||||||
export 'preferences.dart';
|
export 'preferences.dart';
|
||||||
|
export 'print.dart';
|
||||||
export 'protocol.dart';
|
export 'protocol.dart';
|
||||||
export 'proxy.dart';
|
export 'proxy.dart';
|
||||||
|
export 'render.dart';
|
||||||
export 'request.dart';
|
export 'request.dart';
|
||||||
export 'scroll.dart';
|
export 'scroll.dart';
|
||||||
export 'string.dart';
|
export 'string.dart';
|
||||||
export 'system.dart';
|
export 'system.dart';
|
||||||
export 'text.dart';
|
export 'text.dart';
|
||||||
export 'tray.dart';
|
export 'tray.dart';
|
||||||
|
export 'utils.dart';
|
||||||
export 'window.dart';
|
export 'window.dart';
|
||||||
export 'windows.dart';
|
export 'windows.dart';
|
||||||
export 'render.dart';
|
|
||||||
export 'mixin.dart';
|
|
||||||
export 'print.dart';
|
|
||||||
@@ -16,16 +16,15 @@ const browserUa =
|
|||||||
const packageName = "com.follow.clash";
|
const packageName = "com.follow.clash";
|
||||||
final unixSocketPath = "/tmp/FlClashSocket_${Random().nextInt(10000)}.sock";
|
final unixSocketPath = "/tmp/FlClashSocket_${Random().nextInt(10000)}.sock";
|
||||||
const helperPort = 47890;
|
const helperPort = 47890;
|
||||||
const helperTag = "2024125";
|
const maxTextScale = 1.4;
|
||||||
const baseInfoEdgeInsets = EdgeInsets.symmetric(
|
const minTextScale = 0.8;
|
||||||
vertical: 16,
|
final baseInfoEdgeInsets = EdgeInsets.symmetric(
|
||||||
horizontal: 16,
|
vertical: 16.ap,
|
||||||
|
horizontal: 16.ap,
|
||||||
);
|
);
|
||||||
|
|
||||||
double textScaleFactor = min(
|
final defaultTextScaleFactor =
|
||||||
WidgetsBinding.instance.platformDispatcher.textScaleFactor,
|
WidgetsBinding.instance.platformDispatcher.textScaleFactor;
|
||||||
1.2,
|
|
||||||
);
|
|
||||||
const httpTimeoutDuration = Duration(milliseconds: 5000);
|
const httpTimeoutDuration = Duration(milliseconds: 5000);
|
||||||
const moreDuration = Duration(milliseconds: 100);
|
const moreDuration = Duration(milliseconds: 100);
|
||||||
const animateDuration = Duration(milliseconds: 100);
|
const animateDuration = Duration(milliseconds: 100);
|
||||||
@@ -44,7 +43,6 @@ const profilesDirectoryName = "profiles";
|
|||||||
const localhost = "127.0.0.1";
|
const localhost = "127.0.0.1";
|
||||||
const clashConfigKey = "clash_config";
|
const clashConfigKey = "clash_config";
|
||||||
const configKey = "config";
|
const configKey = "config";
|
||||||
const listItemPadding = EdgeInsets.symmetric(horizontal: 16);
|
|
||||||
const double dialogCommonWidth = 300;
|
const double dialogCommonWidth = 300;
|
||||||
const repository = "chen08209/FlClash";
|
const repository = "chen08209/FlClash";
|
||||||
const defaultExternalController = "127.0.0.1:9090";
|
const defaultExternalController = "127.0.0.1:9090";
|
||||||
@@ -60,6 +58,7 @@ final commonFilter = ImageFilter.blur(
|
|||||||
const navigationItemListEquality = ListEquality<NavigationItem>();
|
const navigationItemListEquality = ListEquality<NavigationItem>();
|
||||||
const connectionListEquality = ListEquality<Connection>();
|
const connectionListEquality = ListEquality<Connection>();
|
||||||
const stringListEquality = ListEquality<String>();
|
const stringListEquality = ListEquality<String>();
|
||||||
|
const intListEquality = ListEquality<int>();
|
||||||
const logListEquality = ListEquality<Log>();
|
const logListEquality = ListEquality<Log>();
|
||||||
const groupListEquality = ListEquality<Group>();
|
const groupListEquality = ListEquality<Group>();
|
||||||
const externalProviderListEquality = ListEquality<ExternalProvider>();
|
const externalProviderListEquality = ListEquality<ExternalProvider>();
|
||||||
@@ -78,22 +77,28 @@ const viewModeColumnsMap = {
|
|||||||
ViewMode.desktop: [4, 3],
|
ViewMode.desktop: [4, 3],
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultPrimaryColor = 0xFF795548;
|
// const proxiesStoreKey = PageStorageKey<String>('proxies');
|
||||||
|
// const toolsStoreKey = PageStorageKey<String>('tools');
|
||||||
|
// const profilesStoreKey = PageStorageKey<String>('profiles');
|
||||||
|
|
||||||
|
const defaultPrimaryColor = 0XFFD8C0C3;
|
||||||
|
|
||||||
double getWidgetHeight(num lines) {
|
double getWidgetHeight(num lines) {
|
||||||
return max(lines * 84 * textScaleFactor + (lines - 1) * 16, 0);
|
return max(lines * 84 + (lines - 1) * 16, 0).ap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxLength = 150;
|
||||||
|
|
||||||
final mainIsolate = "FlClashMainIsolate";
|
final mainIsolate = "FlClashMainIsolate";
|
||||||
|
|
||||||
final serviceIsolate = "FlClashServiceIsolate";
|
final serviceIsolate = "FlClashServiceIsolate";
|
||||||
|
|
||||||
const defaultPrimaryColors = [
|
const defaultPrimaryColors = [
|
||||||
defaultPrimaryColor,
|
0xFF795548,
|
||||||
0xFF03A9F4,
|
0xFF03A9F4,
|
||||||
0xFFFFFF00,
|
0xFFFFFF00,
|
||||||
0XFFBBC9CC,
|
0XFFBBC9CC,
|
||||||
0XFFABD397,
|
0XFFABD397,
|
||||||
0XFFD8C0C3,
|
defaultPrimaryColor,
|
||||||
0XFF665390,
|
0XFF665390,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:fl_clash/manager/manager.dart';
|
import 'package:fl_clash/manager/message_manager.dart';
|
||||||
import 'package:fl_clash/widgets/scaffold.dart';
|
import 'package:fl_clash/widgets/scaffold.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@@ -11,6 +11,36 @@ extension BuildContextExtension on BuildContext {
|
|||||||
return findAncestorStateOfType<MessageManagerState>()?.message(text);
|
return findAncestorStateOfType<MessageManagerState>()?.message(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showSnackBar(
|
||||||
|
String message, {
|
||||||
|
SnackBarAction? action,
|
||||||
|
}) {
|
||||||
|
final width = viewWidth;
|
||||||
|
EdgeInsets margin;
|
||||||
|
if (width < 600) {
|
||||||
|
margin = const EdgeInsets.only(
|
||||||
|
bottom: 16,
|
||||||
|
right: 16,
|
||||||
|
left: 16,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
margin = EdgeInsets.only(
|
||||||
|
bottom: 16,
|
||||||
|
left: 16,
|
||||||
|
right: width - 316,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ScaffoldMessenger.of(this).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
action: action,
|
||||||
|
content: Text(message),
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
duration: const Duration(milliseconds: 1500),
|
||||||
|
margin: margin,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Size get appSize {
|
Size get appSize {
|
||||||
return MediaQuery.of(this).size;
|
return MediaQuery.of(this).size;
|
||||||
}
|
}
|
||||||
@@ -27,10 +57,10 @@ extension BuildContextExtension on BuildContext {
|
|||||||
T? state;
|
T? state;
|
||||||
|
|
||||||
visitor(Element element) {
|
visitor(Element element) {
|
||||||
if(!element.mounted){
|
if (!element.mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(element is StatefulElement){
|
if (element is StatefulElement) {
|
||||||
if (element.state is T) {
|
if (element.state is T) {
|
||||||
state = element.state as T;
|
state = element.state as T;
|
||||||
}
|
}
|
||||||
|
|||||||
79
lib/common/fixed.dart
Normal file
79
lib/common/fixed.dart
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import 'iterable.dart';
|
||||||
|
|
||||||
|
class FixedList<T> {
|
||||||
|
final int maxLength;
|
||||||
|
final List<T> _list;
|
||||||
|
|
||||||
|
FixedList(this.maxLength, {List<T>? list})
|
||||||
|
: _list = (list ?? [])..truncate(maxLength);
|
||||||
|
|
||||||
|
add(T item) {
|
||||||
|
_list.add(item);
|
||||||
|
_list.truncate(maxLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
_list.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<T> get list => List.unmodifiable(_list);
|
||||||
|
|
||||||
|
int get length => _list.length;
|
||||||
|
|
||||||
|
T operator [](int index) => _list[index];
|
||||||
|
|
||||||
|
FixedList<T> copyWith() {
|
||||||
|
return FixedList(
|
||||||
|
maxLength,
|
||||||
|
list: _list,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FixedMap<K, V> {
|
||||||
|
int maxLength;
|
||||||
|
late Map<K, V> _map;
|
||||||
|
|
||||||
|
FixedMap(this.maxLength, {Map<K, V>? map}) {
|
||||||
|
_map = map ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCacheValue(K key, V Function() callback) {
|
||||||
|
final realValue = _map.updateCacheValue(
|
||||||
|
key,
|
||||||
|
callback,
|
||||||
|
);
|
||||||
|
_adjustMap();
|
||||||
|
return realValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
_map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMaxLength(int size) {
|
||||||
|
maxLength = size;
|
||||||
|
_adjustMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMap(Map<K, V> map) {
|
||||||
|
_map = map;
|
||||||
|
_adjustMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
_adjustMap() {
|
||||||
|
if (_map.length > maxLength) {
|
||||||
|
_map = Map.fromEntries(
|
||||||
|
map.entries.toList()..truncate(maxLength),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
V? get(K key) => _map[key];
|
||||||
|
|
||||||
|
bool containsKey(K key) => _map.containsKey(key);
|
||||||
|
|
||||||
|
int get length => _map.length;
|
||||||
|
|
||||||
|
Map<K, V> get map => Map.unmodifiable(_map);
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
|
|
||||||
class Debouncer {
|
class Debouncer {
|
||||||
final Map<dynamic, Timer?> _operations = {};
|
final Map<FunctionTag, Timer?> _operations = {};
|
||||||
|
|
||||||
call(
|
call(
|
||||||
dynamic tag,
|
FunctionTag tag,
|
||||||
Function func, {
|
Function func, {
|
||||||
List<dynamic>? args,
|
List<dynamic>? args,
|
||||||
Duration duration = const Duration(milliseconds: 600),
|
Duration duration = const Duration(milliseconds: 600),
|
||||||
@@ -33,10 +35,10 @@ class Debouncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Throttler {
|
class Throttler {
|
||||||
final Map<dynamic, Timer?> _operations = {};
|
final Map<FunctionTag, Timer?> _operations = {};
|
||||||
|
|
||||||
call(
|
call(
|
||||||
dynamic tag,
|
FunctionTag tag,
|
||||||
Function func, {
|
Function func, {
|
||||||
List<dynamic>? args,
|
List<dynamic>? args,
|
||||||
Duration duration = const Duration(milliseconds: 600),
|
Duration duration = const Duration(milliseconds: 600),
|
||||||
|
|||||||
@@ -38,6 +38,43 @@ extension IterableExt<T> on Iterable<T> {
|
|||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Iterable<T> takeLast({int count = 50}) {
|
||||||
|
if (count <= 0) return Iterable.empty();
|
||||||
|
return count >= length ? this : toList().skip(length - count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ListExt<T> on List<T> {
|
||||||
|
void truncate(int maxLength) {
|
||||||
|
assert(maxLength > 0);
|
||||||
|
if (length > maxLength) {
|
||||||
|
removeRange(0, length - maxLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<T> intersection(List<T> list) {
|
||||||
|
return where((item) => list.contains(item)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<T>> batch(int maxConcurrent) {
|
||||||
|
final batches = (length / maxConcurrent).ceil();
|
||||||
|
final List<List<T>> res = [];
|
||||||
|
for (int i = 0; i < batches; i++) {
|
||||||
|
if (i != batches - 1) {
|
||||||
|
res.add(sublist(i * maxConcurrent, maxConcurrent * (i + 1)));
|
||||||
|
} else {
|
||||||
|
res.add(sublist(i * maxConcurrent, length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<T> safeSublist(int start) {
|
||||||
|
if (start <= 0) return this;
|
||||||
|
if (start > length) return [];
|
||||||
|
return sublist(start);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DoubleListExt on List<double> {
|
extension DoubleListExt on List<double> {
|
||||||
@@ -67,9 +104,9 @@ extension DoubleListExt on List<double> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension MapExt<K, V> on Map<K, V> {
|
extension MapExt<K, V> on Map<K, V> {
|
||||||
getCacheValue(K key, V defaultValue) {
|
updateCacheValue(K key, V Function() callback) {
|
||||||
if (this[key] == null) {
|
if (this[key] == null) {
|
||||||
this[key] = defaultValue;
|
this[key] = callback();
|
||||||
}
|
}
|
||||||
return this[key];
|
return this[key];
|
||||||
}
|
}
|
||||||
|
|||||||
22
lib/common/javascript.dart
Normal file
22
lib/common/javascript.dart
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import 'package:flutter_js/flutter_js.dart';
|
||||||
|
|
||||||
|
class Javascript {
|
||||||
|
static Javascript? _instance;
|
||||||
|
late JavascriptRuntime runtime;
|
||||||
|
|
||||||
|
Javascript._internal() {
|
||||||
|
runtime = getJavascriptRuntime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> evaluate(String js) async {
|
||||||
|
final evaluate = runtime.evaluate(js);
|
||||||
|
return evaluate.stringResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Javascript() {
|
||||||
|
_instance ??= Javascript._internal();
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final js = Javascript();
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import 'dart:collection';
|
|
||||||
|
|
||||||
class FixedList<T> {
|
|
||||||
final int maxLength;
|
|
||||||
final List<T> _list;
|
|
||||||
|
|
||||||
FixedList(this.maxLength, {List<T>? list}) : _list = list ?? [];
|
|
||||||
|
|
||||||
add(T item) {
|
|
||||||
if (_list.length == maxLength) {
|
|
||||||
_list.removeAt(0);
|
|
||||||
}
|
|
||||||
_list.add(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
_list.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<T> get list => List.unmodifiable(_list);
|
|
||||||
|
|
||||||
int get length => _list.length;
|
|
||||||
|
|
||||||
T operator [](int index) => _list[index];
|
|
||||||
|
|
||||||
FixedList<T> copyWith() {
|
|
||||||
return FixedList(
|
|
||||||
maxLength,
|
|
||||||
list: _list,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FixedMap<K, V> {
|
|
||||||
int maxSize;
|
|
||||||
final Map<K, V> _map = {};
|
|
||||||
final Queue<K> _queue = Queue<K>();
|
|
||||||
|
|
||||||
FixedMap(this.maxSize);
|
|
||||||
|
|
||||||
put(K key, V value) {
|
|
||||||
if (_map.length == maxSize) {
|
|
||||||
final oldestKey = _queue.removeFirst();
|
|
||||||
_map.remove(oldestKey);
|
|
||||||
}
|
|
||||||
_map[key] = value;
|
|
||||||
_queue.add(key);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
_map.clear();
|
|
||||||
_queue.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMaxSize(int size){
|
|
||||||
maxSize = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
V? get(K key) => _map[key];
|
|
||||||
|
|
||||||
|
|
||||||
bool containsKey(K key) => _map.containsKey(key);
|
|
||||||
|
|
||||||
int get length => _map.length;
|
|
||||||
|
|
||||||
Map<K, V> get map => Map.unmodifiable(_map);
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ListExtension<T> on List<T> {
|
|
||||||
List<T> intersection(List<T> list) {
|
|
||||||
return where((item) => list.contains(item)).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<List<T>> batch(int maxConcurrent) {
|
|
||||||
final batches = (length / maxConcurrent).ceil();
|
|
||||||
final List<List<T>> res = [];
|
|
||||||
for (int i = 0; i < batches; i++) {
|
|
||||||
if (i != batches - 1) {
|
|
||||||
res.add(sublist(i * maxConcurrent, maxConcurrent * (i + 1)));
|
|
||||||
} else {
|
|
||||||
res.add(sublist(i * maxConcurrent, length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<T> safeSublist(int start) {
|
|
||||||
if (start <= 0) return this;
|
|
||||||
if (start > length) return [];
|
|
||||||
return sublist(start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,11 +3,13 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class Measure {
|
class Measure {
|
||||||
final TextScaler _textScale;
|
final TextScaler _textScaler;
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
|
final Map<String, dynamic> _measureMap;
|
||||||
|
|
||||||
Measure.of(this.context)
|
Measure.of(this.context, double textScaleFactor)
|
||||||
: _textScale = TextScaler.linear(
|
: _measureMap = {},
|
||||||
|
_textScaler = TextScaler.linear(
|
||||||
textScaleFactor,
|
textScaleFactor,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -21,7 +23,7 @@ class Measure {
|
|||||||
style: text.style,
|
style: text.style,
|
||||||
),
|
),
|
||||||
maxLines: text.maxLines,
|
maxLines: text.maxLines,
|
||||||
textScaler: _textScale,
|
textScaler: _textScaler,
|
||||||
textDirection: text.textDirection ?? TextDirection.ltr,
|
textDirection: text.textDirection ?? TextDirection.ltr,
|
||||||
)..layout(
|
)..layout(
|
||||||
maxWidth: maxWidth,
|
maxWidth: maxWidth,
|
||||||
@@ -29,81 +31,87 @@ class Measure {
|
|||||||
return textPainter.size;
|
return textPainter.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
double? _bodyMediumHeight;
|
|
||||||
Size? _bodyLargeSize;
|
|
||||||
double? _bodySmallHeight;
|
|
||||||
double? _labelSmallHeight;
|
|
||||||
double? _labelMediumHeight;
|
|
||||||
double? _titleLargeHeight;
|
|
||||||
double? _titleMediumHeight;
|
|
||||||
|
|
||||||
double get bodyMediumHeight {
|
double get bodyMediumHeight {
|
||||||
_bodyMediumHeight ??= computeTextSize(
|
return _measureMap.updateCacheValue(
|
||||||
Text(
|
"bodyMediumHeight",
|
||||||
"X",
|
() => computeTextSize(
|
||||||
style: context.textTheme.bodyMedium,
|
Text(
|
||||||
),
|
"X",
|
||||||
).height;
|
style: context.textTheme.bodyMedium,
|
||||||
return _bodyMediumHeight!;
|
),
|
||||||
|
).height,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Size get bodyLargeSize {
|
double get bodyLargeHeight {
|
||||||
_bodyLargeSize ??= computeTextSize(
|
return _measureMap.updateCacheValue(
|
||||||
Text(
|
"bodyLargeHeight",
|
||||||
"X",
|
() => computeTextSize(
|
||||||
style: context.textTheme.bodyLarge,
|
Text(
|
||||||
),
|
"X",
|
||||||
|
style: context.textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
).height,
|
||||||
);
|
);
|
||||||
return _bodyLargeSize!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double get bodySmallHeight {
|
double get bodySmallHeight {
|
||||||
_bodySmallHeight ??= computeTextSize(
|
return _measureMap.updateCacheValue(
|
||||||
Text(
|
"bodySmallHeight",
|
||||||
"X",
|
() => computeTextSize(
|
||||||
style: context.textTheme.bodySmall,
|
Text(
|
||||||
),
|
"X",
|
||||||
).height;
|
style: context.textTheme.bodySmall,
|
||||||
return _bodySmallHeight!;
|
),
|
||||||
|
).height,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
double get labelSmallHeight {
|
double get labelSmallHeight {
|
||||||
_labelSmallHeight ??= computeTextSize(
|
return _measureMap.updateCacheValue(
|
||||||
Text(
|
"labelSmallHeight",
|
||||||
"X",
|
() => computeTextSize(
|
||||||
style: context.textTheme.labelSmall,
|
Text(
|
||||||
),
|
"X",
|
||||||
).height;
|
style: context.textTheme.labelSmall,
|
||||||
return _labelSmallHeight!;
|
),
|
||||||
|
).height,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
double get labelMediumHeight {
|
double get labelMediumHeight {
|
||||||
_labelMediumHeight ??= computeTextSize(
|
return _measureMap.updateCacheValue(
|
||||||
Text(
|
"labelMediumHeight",
|
||||||
"X",
|
() => computeTextSize(
|
||||||
style: context.textTheme.labelMedium,
|
Text(
|
||||||
),
|
"X",
|
||||||
).height;
|
style: context.textTheme.labelMedium,
|
||||||
return _labelMediumHeight!;
|
),
|
||||||
|
).height,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
double get titleLargeHeight {
|
double get titleLargeHeight {
|
||||||
_titleLargeHeight ??= computeTextSize(
|
return _measureMap.updateCacheValue(
|
||||||
Text(
|
"titleLargeHeight",
|
||||||
"X",
|
() => computeTextSize(
|
||||||
style: context.textTheme.titleLarge,
|
Text(
|
||||||
),
|
"X",
|
||||||
).height;
|
style: context.textTheme.titleLarge,
|
||||||
return _titleLargeHeight!;
|
),
|
||||||
|
).height,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
double get titleMediumHeight {
|
double get titleMediumHeight {
|
||||||
_titleMediumHeight ??= computeTextSize(
|
return _measureMap.updateCacheValue(
|
||||||
Text(
|
"titleMediumHeight",
|
||||||
"X",
|
() => computeTextSize(
|
||||||
style: context.textTheme.titleMedium,
|
Text(
|
||||||
),
|
"X",
|
||||||
).height;
|
style: context.textTheme.titleMedium,
|
||||||
return _titleMediumHeight!;
|
),
|
||||||
|
).height,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ class Navigation {
|
|||||||
}) {
|
}) {
|
||||||
return [
|
return [
|
||||||
const NavigationItem(
|
const NavigationItem(
|
||||||
|
keep: false,
|
||||||
icon: Icon(Icons.space_dashboard),
|
icon: Icon(Icons.space_dashboard),
|
||||||
label: PageLabel.dashboard,
|
label: PageLabel.dashboard,
|
||||||
keep: false,
|
|
||||||
fragment: DashboardFragment(
|
fragment: DashboardFragment(
|
||||||
key: GlobalObjectKey(PageLabel.dashboard),
|
key: GlobalObjectKey(PageLabel.dashboard),
|
||||||
),
|
),
|
||||||
@@ -66,7 +66,6 @@ class Navigation {
|
|||||||
icon: Icon(Icons.storage),
|
icon: Icon(Icons.storage),
|
||||||
label: PageLabel.resources,
|
label: PageLabel.resources,
|
||||||
description: "resourcesDesc",
|
description: "resourcesDesc",
|
||||||
keep: false,
|
|
||||||
fragment: Resources(
|
fragment: Resources(
|
||||||
key: GlobalObjectKey(
|
key: GlobalObjectKey(
|
||||||
PageLabel.resources,
|
PageLabel.resources,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@@ -12,6 +13,10 @@ extension NumExt on num {
|
|||||||
}
|
}
|
||||||
return formatted;
|
return formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double get ap {
|
||||||
|
return this * (1 + (globalState.theme.textScaleFactor - 1) * 0.5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DoubleExt on double {
|
extension DoubleExt on double {
|
||||||
|
|||||||
@@ -73,14 +73,12 @@ class AppPath {
|
|||||||
return join(directory.path, profilesDirectoryName);
|
return join(directory.path, profilesDirectoryName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getProfilePath(String? id) async {
|
Future<String> getProfilePath(String id) async {
|
||||||
if (id == null) return null;
|
|
||||||
final directory = await profilesPath;
|
final directory = await profilesPath;
|
||||||
return join(directory, "$id.yaml");
|
return join(directory, "$id.yaml");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getProvidersPath(String? id) async {
|
Future<String> getProvidersPath(String id) async {
|
||||||
if (id == null) return null;
|
|
||||||
final directory = await profilesPath;
|
final directory = await profilesPath;
|
||||||
return join(directory, "providers", id);
|
return join(directory, "providers", id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:fl_clash/enum/enum.dart';
|
|
||||||
import 'package:fl_clash/models/models.dart';
|
import 'package:fl_clash/models/models.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
@@ -16,14 +15,11 @@ class CommonPrint {
|
|||||||
log(String? text) {
|
log(String? text) {
|
||||||
final payload = "[FlClash] $text";
|
final payload = "[FlClash] $text";
|
||||||
debugPrint(payload);
|
debugPrint(payload);
|
||||||
if (globalState.isService) {
|
if (!globalState.isInit) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
globalState.appController.addLog(
|
globalState.appController.addLog(
|
||||||
Log(
|
Log.app(payload),
|
||||||
logLevel: LogLevel.info,
|
|
||||||
payload: payload,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ class Render {
|
|||||||
|
|
||||||
pause() {
|
pause() {
|
||||||
throttler.call(
|
throttler.call(
|
||||||
DebounceTag.renderPause,
|
FunctionTag.renderPause,
|
||||||
_pause,
|
_pause,
|
||||||
duration: Duration(seconds: 5),
|
duration: Duration(seconds: 5),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
resume() {
|
resume() {
|
||||||
throttler.cancel(DebounceTag.renderPause);
|
throttler.cancel(FunctionTag.renderPause);
|
||||||
_resume();
|
_resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class Request {
|
|||||||
if (response.statusCode != HttpStatus.ok) {
|
if (response.statusCode != HttpStatus.ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (response.data as String) == helperTag;
|
return (response.data as String) == globalState.coreSHA256;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ extension StringExtension on String {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bool containsToLower(String target) {
|
||||||
|
// return toLowerCase().contains(target);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StringExtensionSafe on String? {
|
extension StringExtensionSafe on String? {
|
||||||
|
|||||||
@@ -55,18 +55,24 @@ class System {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<AuthorizeCode> authorizeCore() async {
|
Future<AuthorizeCode> authorizeCore() async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
return AuthorizeCode.none;
|
||||||
|
}
|
||||||
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
|
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
|
||||||
final isAdmin = await checkIsAdmin();
|
final isAdmin = await checkIsAdmin();
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
return AuthorizeCode.none;
|
return AuthorizeCode.none;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
final result = await windows?.registerService();
|
final result = await windows?.registerService();
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
return AuthorizeCode.success;
|
return AuthorizeCode.success;
|
||||||
}
|
}
|
||||||
return AuthorizeCode.error;
|
return AuthorizeCode.error;
|
||||||
} else if (Platform.isMacOS) {
|
}
|
||||||
|
|
||||||
|
if (Platform.isMacOS) {
|
||||||
final shell = 'chown root:admin $corePath; chmod +sx $corePath';
|
final shell = 'chown root:admin $corePath; chmod +sx $corePath';
|
||||||
final arguments = [
|
final arguments = [
|
||||||
"-e",
|
"-e",
|
||||||
|
|||||||
@@ -4,36 +4,43 @@ import 'package:flutter/material.dart';
|
|||||||
class CommonTheme {
|
class CommonTheme {
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
final Map<String, Color> _colorMap;
|
final Map<String, Color> _colorMap;
|
||||||
|
final double textScaleFactor;
|
||||||
|
|
||||||
CommonTheme.of(this.context) : _colorMap = {};
|
CommonTheme.of(
|
||||||
|
this.context,
|
||||||
|
this.textScaleFactor,
|
||||||
|
) : _colorMap = {};
|
||||||
|
|
||||||
Color get darkenSecondaryContainer {
|
Color get darkenSecondaryContainer {
|
||||||
return _colorMap.getCacheValue(
|
return _colorMap.updateCacheValue(
|
||||||
"darkenSecondaryContainer",
|
"darkenSecondaryContainer",
|
||||||
context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.1),
|
() => context.colorScheme.secondaryContainer
|
||||||
|
.blendDarken(context, factor: 0.1),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color get darkenSecondaryContainerLighter {
|
Color get darkenSecondaryContainerLighter {
|
||||||
return _colorMap.getCacheValue(
|
return _colorMap.updateCacheValue(
|
||||||
"darkenSecondaryContainerLighter",
|
"darkenSecondaryContainerLighter",
|
||||||
context.colorScheme.secondaryContainer
|
() => context.colorScheme.secondaryContainer
|
||||||
.blendDarken(context, factor: 0.1)
|
.blendDarken(context, factor: 0.1)
|
||||||
.opacity60,
|
.opacity60,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color get darken2SecondaryContainer {
|
Color get darken2SecondaryContainer {
|
||||||
return _colorMap.getCacheValue(
|
return _colorMap.updateCacheValue(
|
||||||
"darken2SecondaryContainer",
|
"darken2SecondaryContainer",
|
||||||
context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.2),
|
() => context.colorScheme.secondaryContainer
|
||||||
|
.blendDarken(context, factor: 0.2),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color get darken3PrimaryContainer {
|
Color get darken3PrimaryContainer {
|
||||||
return _colorMap.getCacheValue(
|
return _colorMap.updateCacheValue(
|
||||||
"darken3PrimaryContainer",
|
"darken3PrimaryContainer",
|
||||||
context.colorScheme.primaryContainer.blendDarken(context, factor: 0.3),
|
() => context.colorScheme.primaryContainer
|
||||||
|
.blendDarken(context, factor: 0.3),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class Tray {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
menuItems.add(MenuItem.separator());
|
menuItems.add(MenuItem.separator());
|
||||||
if (!Platform.isWindows) {
|
if (Platform.isMacOS) {
|
||||||
for (final group in trayState.groups) {
|
for (final group in trayState.groups) {
|
||||||
List<MenuItem> subMenuItems = [];
|
List<MenuItem> subMenuItems = [];
|
||||||
for (final proxy in group.all) {
|
for (final proxy in group.all) {
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import 'dart:ui';
|
|||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:lpinyin/lpinyin.dart';
|
import 'package:lpinyin/lpinyin.dart';
|
||||||
|
import 'package:yaml/yaml.dart';
|
||||||
|
|
||||||
class Utils {
|
class Utils {
|
||||||
Color? getDelayColor(int? delay) {
|
Color? getDelayColor(int? delay) {
|
||||||
@@ -230,7 +232,7 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int getProfilesColumns(double viewWidth) {
|
int getProfilesColumns(double viewWidth) {
|
||||||
return max((viewWidth / 350).floor(), 1);
|
return max((viewWidth / 320).floor(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
final _indexPrimary = [
|
final _indexPrimary = [
|
||||||
@@ -323,6 +325,40 @@ class Utils {
|
|||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SingleActivator controlSingleActivator(LogicalKeyboardKey trigger) {
|
||||||
|
final control = Platform.isMacOS ? false : true;
|
||||||
|
return SingleActivator(
|
||||||
|
trigger,
|
||||||
|
control: control,
|
||||||
|
meta: !control,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic convertLoadYaml(dynamic node) {
|
||||||
|
if (node is YamlMap) {
|
||||||
|
final map = <String, dynamic>{};
|
||||||
|
node.nodes.forEach((key, value) {
|
||||||
|
String stringKey;
|
||||||
|
if (key is YamlScalar) {
|
||||||
|
stringKey = key.value.toString();
|
||||||
|
} else {
|
||||||
|
stringKey = key.toString();
|
||||||
|
}
|
||||||
|
map[stringKey] = convertLoadYaml(value.value);
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
} else if (node is YamlList) {
|
||||||
|
final list = <dynamic>[];
|
||||||
|
for (final item in node.nodes) {
|
||||||
|
list.add(convertLoadYaml(item.value));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
} else if (node is YamlScalar) {
|
||||||
|
return node.value;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final utils = Utils();
|
final utils = Utils();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:archive/archive.dart';
|
|||||||
import 'package:fl_clash/clash/clash.dart';
|
import 'package:fl_clash/clash/clash.dart';
|
||||||
import 'package:fl_clash/common/archive.dart';
|
import 'package:fl_clash/common/archive.dart';
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
|
import 'package:fl_clash/plugins/app.dart';
|
||||||
import 'package:fl_clash/providers/providers.dart';
|
import 'package:fl_clash/providers/providers.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:fl_clash/widgets/dialog.dart';
|
import 'package:fl_clash/widgets/dialog.dart';
|
||||||
@@ -30,18 +31,18 @@ class AppController {
|
|||||||
AppController(this.context, WidgetRef ref) : _ref = ref;
|
AppController(this.context, WidgetRef ref) : _ref = ref;
|
||||||
|
|
||||||
updateClashConfigDebounce() {
|
updateClashConfigDebounce() {
|
||||||
debouncer.call(DebounceTag.updateClashConfig, () async {
|
debouncer.call(FunctionTag.updateClashConfig, () async {
|
||||||
final isPatch = globalState.appState.needApply ? false : true;
|
final isPatch = globalState.appState.needApply ? false : true;
|
||||||
await updateClashConfig(isPatch);
|
await updateClashConfig(isPatch);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGroupsDebounce() {
|
updateGroupsDebounce() {
|
||||||
debouncer.call(DebounceTag.updateGroups, updateGroups);
|
debouncer.call(FunctionTag.updateGroups, updateGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
addCheckIpNumDebounce() {
|
addCheckIpNumDebounce() {
|
||||||
debouncer.call(DebounceTag.addCheckIpNum, () {
|
debouncer.call(FunctionTag.addCheckIpNum, () {
|
||||||
_ref.read(checkIpNumProvider.notifier).add();
|
_ref.read(checkIpNumProvider.notifier).add();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -49,17 +50,17 @@ class AppController {
|
|||||||
applyProfileDebounce({
|
applyProfileDebounce({
|
||||||
bool silence = false,
|
bool silence = false,
|
||||||
}) {
|
}) {
|
||||||
debouncer.call(DebounceTag.applyProfile, (silence) {
|
debouncer.call(FunctionTag.applyProfile, (silence) {
|
||||||
applyProfile(silence: silence);
|
applyProfile(silence: silence);
|
||||||
}, args: [silence]);
|
}, args: [silence]);
|
||||||
}
|
}
|
||||||
|
|
||||||
savePreferencesDebounce() {
|
savePreferencesDebounce() {
|
||||||
debouncer.call(DebounceTag.savePreferences, savePreferences);
|
debouncer.call(FunctionTag.savePreferences, savePreferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
changeProxyDebounce(String groupName, String proxyName) {
|
changeProxyDebounce(String groupName, String proxyName) {
|
||||||
debouncer.call(DebounceTag.changeProxy,
|
debouncer.call(FunctionTag.changeProxy,
|
||||||
(String groupName, String proxyName) async {
|
(String groupName, String proxyName) async {
|
||||||
await changeProxy(
|
await changeProxy(
|
||||||
groupName: groupName,
|
groupName: groupName,
|
||||||
@@ -260,9 +261,7 @@ class AppController {
|
|||||||
final patchConfig = _ref.read(patchClashConfigProvider);
|
final patchConfig = _ref.read(patchClashConfigProvider);
|
||||||
final appSetting = _ref.read(appSettingProvider);
|
final appSetting = _ref.read(appSettingProvider);
|
||||||
bool enableTun = patchConfig.tun.enable;
|
bool enableTun = patchConfig.tun.enable;
|
||||||
if (enableTun != lastTunEnable &&
|
if (enableTun != lastTunEnable && lastTunEnable == false) {
|
||||||
lastTunEnable == false &&
|
|
||||||
!Platform.isAndroid) {
|
|
||||||
final code = await system.authorizeCore();
|
final code = await system.authorizeCore();
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case AuthorizeCode.none:
|
case AuthorizeCode.none:
|
||||||
@@ -314,6 +313,10 @@ class AppController {
|
|||||||
handleChangeProfile() {
|
handleChangeProfile() {
|
||||||
_ref.read(delayDataSourceProvider.notifier).value = {};
|
_ref.read(delayDataSourceProvider.notifier).value = {};
|
||||||
applyProfile();
|
applyProfile();
|
||||||
|
_ref.read(logsProvider.notifier).value = FixedList(500);
|
||||||
|
_ref.read(requestsProvider.notifier).value = FixedList(500);
|
||||||
|
globalState.cacheHeightMap = {};
|
||||||
|
globalState.cacheScrollPosition = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBrightness(Brightness brightness) {
|
updateBrightness(Brightness brightness) {
|
||||||
@@ -334,23 +337,22 @@ class AppController {
|
|||||||
try {
|
try {
|
||||||
await updateProfile(profile);
|
await updateProfile(profile);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_ref.read(logsProvider.notifier).addLog(
|
commonPrint.log(e.toString());
|
||||||
Log(
|
|
||||||
logLevel: LogLevel.info,
|
|
||||||
payload: e.toString(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateGroups() async {
|
Future<void> updateGroups() async {
|
||||||
_ref.read(groupsProvider.notifier).value = await retry(
|
try {
|
||||||
task: () async {
|
_ref.read(groupsProvider.notifier).value = await retry(
|
||||||
return await clashCore.getProxiesGroups();
|
task: () async {
|
||||||
},
|
return await clashCore.getProxiesGroups();
|
||||||
retryIf: (res) => res.isEmpty,
|
},
|
||||||
);
|
retryIf: (res) => res.isEmpty,
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
_ref.read(groupsProvider.notifier).value = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProfiles() async {
|
updateProfiles() async {
|
||||||
@@ -362,10 +364,6 @@ class AppController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSystemColorSchemes(ColorSchemes colorSchemes) {
|
|
||||||
_ref.read(appSchemesProvider.notifier).value = colorSchemes;
|
|
||||||
}
|
|
||||||
|
|
||||||
savePreferences() async {
|
savePreferences() async {
|
||||||
commonPrint.log("save preferences");
|
commonPrint.log("save preferences");
|
||||||
await preferences.saveConfig(globalState.config);
|
await preferences.saveConfig(globalState.config);
|
||||||
@@ -382,12 +380,15 @@ class AppController {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (_ref.read(appSettingProvider).closeConnections) {
|
if (_ref.read(appSettingProvider).closeConnections) {
|
||||||
clashCore.closeConnections();
|
clashCore.resetConnections();
|
||||||
}
|
}
|
||||||
addCheckIpNumDebounce();
|
addCheckIpNumDebounce();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBackOrExit() async {
|
handleBackOrExit() async {
|
||||||
|
if (_ref.read(backBlockProvider)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (_ref.read(appSettingProvider).minimizeOnExit) {
|
if (_ref.read(appSettingProvider).minimizeOnExit) {
|
||||||
if (system.isDesktop) {
|
if (system.isDesktop) {
|
||||||
await savePreferencesDebounce();
|
await savePreferencesDebounce();
|
||||||
@@ -398,18 +399,34 @@ class AppController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backBlock() {
|
||||||
|
_ref.read(backBlockProvider.notifier).value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unBackBlock() {
|
||||||
|
_ref.read(backBlockProvider.notifier).value = false;
|
||||||
|
}
|
||||||
|
|
||||||
handleExit() async {
|
handleExit() async {
|
||||||
try {
|
try {
|
||||||
await updateStatus(false);
|
await updateStatus(false);
|
||||||
|
await proxy?.stopProxy();
|
||||||
await clashCore.shutdown();
|
await clashCore.shutdown();
|
||||||
await clashService?.destroy();
|
await clashService?.destroy();
|
||||||
await proxy?.stopProxy();
|
|
||||||
await savePreferences();
|
await savePreferences();
|
||||||
} finally {
|
} finally {
|
||||||
system.exit();
|
system.exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future handleClear() async {
|
||||||
|
await preferences.clearPreferences();
|
||||||
|
commonPrint.log("clear preferences");
|
||||||
|
globalState.config = Config(
|
||||||
|
themeProps: defaultThemeProps,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
autoCheckUpdate() async {
|
autoCheckUpdate() async {
|
||||||
if (!_ref.read(appSettingProvider).autoCheckUpdate) return;
|
if (!_ref.read(appSettingProvider).autoCheckUpdate) return;
|
||||||
final res = await request.checkForUpdate();
|
final res = await request.checkForUpdate();
|
||||||
@@ -484,17 +501,18 @@ class AppController {
|
|||||||
Future<void> _initCore() async {
|
Future<void> _initCore() async {
|
||||||
final isInit = await clashCore.isInit;
|
final isInit = await clashCore.isInit;
|
||||||
if (!isInit) {
|
if (!isInit) {
|
||||||
|
await clashCore.init();
|
||||||
await clashCore.setState(
|
await clashCore.setState(
|
||||||
globalState.getCoreState(),
|
globalState.getCoreState(),
|
||||||
);
|
);
|
||||||
await clashCore.init();
|
|
||||||
}
|
}
|
||||||
await applyProfile();
|
await applyProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
init() async {
|
init() async {
|
||||||
await _handlePreference();
|
FlutterError.onError = (details) {
|
||||||
await _handlerDisclaimer();
|
commonPrint.log(details.stack.toString());
|
||||||
|
};
|
||||||
await _initCore();
|
await _initCore();
|
||||||
await _initStatus();
|
await _initStatus();
|
||||||
updateTray(true);
|
updateTray(true);
|
||||||
@@ -508,6 +526,8 @@ class AppController {
|
|||||||
} else {
|
} else {
|
||||||
window?.hide();
|
window?.hide();
|
||||||
}
|
}
|
||||||
|
await _handlePreference();
|
||||||
|
await _handlerDisclaimer();
|
||||||
_ref.read(initProvider.notifier).value = true;
|
_ref.read(initProvider.notifier).value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,10 +705,16 @@ class AppController {
|
|||||||
return List.of(proxies)
|
return List.of(proxies)
|
||||||
..sort(
|
..sort(
|
||||||
(a, b) {
|
(a, b) {
|
||||||
final aDelay =
|
final aDelay = _ref.read(getDelayProvider(
|
||||||
_ref.read(getDelayProvider(proxyName: a.name, testUrl: testUrl));
|
proxyName: a.name,
|
||||||
final bDelay =
|
testUrl: testUrl,
|
||||||
_ref.read(getDelayProvider(proxyName: b.name, testUrl: testUrl));
|
));
|
||||||
|
final bDelay = _ref.read(
|
||||||
|
getDelayProvider(
|
||||||
|
proxyName: b.name,
|
||||||
|
testUrl: testUrl,
|
||||||
|
),
|
||||||
|
);
|
||||||
if (aDelay == null && bDelay == null) {
|
if (aDelay == null && bDelay == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -718,19 +744,15 @@ class AppController {
|
|||||||
final profilePath = await appPath.getProfilePath(profileId);
|
final profilePath = await appPath.getProfilePath(profileId);
|
||||||
final providersPath = await appPath.getProvidersPath(profileId);
|
final providersPath = await appPath.getProvidersPath(profileId);
|
||||||
return await Isolate.run(() async {
|
return await Isolate.run(() async {
|
||||||
if (profilePath != null) {
|
final profileFile = File(profilePath);
|
||||||
final profileFile = File(profilePath);
|
final isExists = await profileFile.exists();
|
||||||
final isExists = await profileFile.exists();
|
if (isExists) {
|
||||||
if (isExists) {
|
profileFile.delete(recursive: true);
|
||||||
profileFile.delete(recursive: true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (providersPath != null) {
|
final providersFileDir = File(providersPath);
|
||||||
final providersFileDir = File(providersPath);
|
final providersFileIsExists = await providersFileDir.exists();
|
||||||
final isExists = await providersFileDir.exists();
|
if (providersFileIsExists) {
|
||||||
if (isExists) {
|
providersFileDir.delete(recursive: true);
|
||||||
providersFileDir.delete(recursive: true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -749,6 +771,17 @@ class AppController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Package>> getPackages() async {
|
||||||
|
if (_ref.read(isMobileViewProvider)) {
|
||||||
|
await Future.delayed(commonDuration);
|
||||||
|
}
|
||||||
|
if (_ref.read(packagesProvider).isEmpty) {
|
||||||
|
_ref.read(packagesProvider.notifier).value =
|
||||||
|
await app?.getPackages() ?? [];
|
||||||
|
}
|
||||||
|
return _ref.read(packagesProvider);
|
||||||
|
}
|
||||||
|
|
||||||
updateStart() {
|
updateStart() {
|
||||||
updateStatus(!_ref.read(runTimeProvider.notifier).isStart);
|
updateStatus(!_ref.read(runTimeProvider.notifier).isStart);
|
||||||
}
|
}
|
||||||
@@ -937,30 +970,39 @@ class AppController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_recovery(Config config, RecoveryOption recoveryOption) {
|
_recovery(Config config, RecoveryOption recoveryOption) {
|
||||||
|
final recoveryStrategy = _ref.read(appSettingProvider.select(
|
||||||
|
(state) => state.recoveryStrategy,
|
||||||
|
));
|
||||||
final profiles = config.profiles;
|
final profiles = config.profiles;
|
||||||
for (final profile in profiles) {
|
if (recoveryStrategy == RecoveryStrategy.override) {
|
||||||
_ref.read(profilesProvider.notifier).setProfile(profile);
|
_ref.read(profilesProvider.notifier).value = profiles;
|
||||||
|
} else {
|
||||||
|
for (final profile in profiles) {
|
||||||
|
_ref.read(profilesProvider.notifier).setProfile(
|
||||||
|
profile,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final onlyProfiles = recoveryOption == RecoveryOption.onlyProfiles;
|
final onlyProfiles = recoveryOption == RecoveryOption.onlyProfiles;
|
||||||
if (onlyProfiles) {
|
if (!onlyProfiles) {
|
||||||
final currentProfile = _ref.read(currentProfileProvider);
|
_ref.read(patchClashConfigProvider.notifier).value =
|
||||||
if (currentProfile != null) {
|
config.patchClashConfig;
|
||||||
_ref.read(currentProfileIdProvider.notifier).value = profiles.first.id;
|
_ref.read(appSettingProvider.notifier).value = config.appSetting;
|
||||||
}
|
_ref.read(currentProfileIdProvider.notifier).value =
|
||||||
return;
|
config.currentProfileId;
|
||||||
|
_ref.read(appDAVSettingProvider.notifier).value = config.dav;
|
||||||
|
_ref.read(themeSettingProvider.notifier).value = config.themeProps;
|
||||||
|
_ref.read(windowSettingProvider.notifier).value = config.windowProps;
|
||||||
|
_ref.read(vpnSettingProvider.notifier).value = config.vpnProps;
|
||||||
|
_ref.read(proxiesStyleSettingProvider.notifier).value =
|
||||||
|
config.proxiesStyle;
|
||||||
|
_ref.read(overrideDnsProvider.notifier).value = config.overrideDns;
|
||||||
|
_ref.read(networkSettingProvider.notifier).value = config.networkProps;
|
||||||
|
_ref.read(hotKeyActionsProvider.notifier).value = config.hotKeyActions;
|
||||||
|
}
|
||||||
|
final currentProfile = _ref.read(currentProfileProvider);
|
||||||
|
if (currentProfile == null) {
|
||||||
|
_ref.read(currentProfileIdProvider.notifier).value = profiles.first.id;
|
||||||
}
|
}
|
||||||
_ref.read(patchClashConfigProvider.notifier).value =
|
|
||||||
config.patchClashConfig;
|
|
||||||
_ref.read(appSettingProvider.notifier).value = config.appSetting;
|
|
||||||
_ref.read(currentProfileIdProvider.notifier).value =
|
|
||||||
config.currentProfileId;
|
|
||||||
_ref.read(appDAVSettingProvider.notifier).value = config.dav;
|
|
||||||
_ref.read(themeSettingProvider.notifier).value = config.themeProps;
|
|
||||||
_ref.read(windowSettingProvider.notifier).value = config.windowProps;
|
|
||||||
_ref.read(vpnSettingProvider.notifier).value = config.vpnProps;
|
|
||||||
_ref.read(proxiesStyleSettingProvider.notifier).value = config.proxiesStyle;
|
|
||||||
_ref.read(overrideDnsProvider.notifier).value = config.overrideDns;
|
|
||||||
_ref.read(networkSettingProvider.notifier).value = config.networkProps;
|
|
||||||
_ref.read(hotKeyActionsProvider.notifier).value = config.hotKeyActions;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,14 @@ enum Mode { rule, global, direct }
|
|||||||
|
|
||||||
enum ViewMode { mobile, laptop, desktop }
|
enum ViewMode { mobile, laptop, desktop }
|
||||||
|
|
||||||
enum LogLevel { debug, info, warning, error, silent }
|
enum LogLevel {
|
||||||
|
debug,
|
||||||
|
info,
|
||||||
|
warning,
|
||||||
|
error,
|
||||||
|
silent,
|
||||||
|
app,
|
||||||
|
}
|
||||||
|
|
||||||
enum TransportProtocol { udp, tcp }
|
enum TransportProtocol { udp, tcp }
|
||||||
|
|
||||||
@@ -249,6 +256,7 @@ enum ActionMethod {
|
|||||||
asyncTestDelay,
|
asyncTestDelay,
|
||||||
getConnections,
|
getConnections,
|
||||||
closeConnections,
|
closeConnections,
|
||||||
|
resetConnections,
|
||||||
closeConnection,
|
closeConnection,
|
||||||
getExternalProviders,
|
getExternalProviders,
|
||||||
getExternalProvider,
|
getExternalProvider,
|
||||||
@@ -262,6 +270,7 @@ enum ActionMethod {
|
|||||||
getCountryCode,
|
getCountryCode,
|
||||||
getMemory,
|
getMemory,
|
||||||
getProfile,
|
getProfile,
|
||||||
|
crash,
|
||||||
|
|
||||||
///Android,
|
///Android,
|
||||||
setFdMap,
|
setFdMap,
|
||||||
@@ -283,8 +292,9 @@ enum WindowsHelperServiceStatus {
|
|||||||
running,
|
running,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DebounceTag {
|
enum FunctionTag {
|
||||||
updateClashConfig,
|
updateClashConfig,
|
||||||
|
updateStatus,
|
||||||
updateGroups,
|
updateGroups,
|
||||||
addCheckIpNum,
|
addCheckIpNum,
|
||||||
applyProfile,
|
applyProfile,
|
||||||
@@ -299,6 +309,8 @@ enum DebounceTag {
|
|||||||
updatePageIndex,
|
updatePageIndex,
|
||||||
pageChange,
|
pageChange,
|
||||||
proxiesTabChange,
|
proxiesTabChange,
|
||||||
|
logs,
|
||||||
|
requests,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DashboardWidget {
|
enum DashboardWidget {
|
||||||
@@ -308,6 +320,12 @@ enum DashboardWidget {
|
|||||||
child: NetworkSpeed(),
|
child: NetworkSpeed(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
outboundModeV2(
|
||||||
|
GridItem(
|
||||||
|
crossAxisCellCount: 8,
|
||||||
|
child: OutboundModeV2(),
|
||||||
|
),
|
||||||
|
),
|
||||||
outboundMode(
|
outboundMode(
|
||||||
GridItem(
|
GridItem(
|
||||||
crossAxisCellCount: 4,
|
crossAxisCellCount: 4,
|
||||||
@@ -333,6 +351,15 @@ enum DashboardWidget {
|
|||||||
),
|
),
|
||||||
platforms: desktopPlatforms,
|
platforms: desktopPlatforms,
|
||||||
),
|
),
|
||||||
|
vpnButton(
|
||||||
|
GridItem(
|
||||||
|
crossAxisCellCount: 4,
|
||||||
|
child: VpnButton(),
|
||||||
|
),
|
||||||
|
platforms: [
|
||||||
|
SupportPlatform.Android,
|
||||||
|
],
|
||||||
|
),
|
||||||
systemProxyButton(
|
systemProxyButton(
|
||||||
GridItem(
|
GridItem(
|
||||||
crossAxisCellCount: 4,
|
crossAxisCellCount: 4,
|
||||||
@@ -447,3 +474,14 @@ enum RuleTarget {
|
|||||||
DIRECT,
|
DIRECT,
|
||||||
REJECT,
|
REJECT,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RecoveryStrategy {
|
||||||
|
compatible,
|
||||||
|
override,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CacheTag {
|
||||||
|
logs,
|
||||||
|
rules,
|
||||||
|
requests,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
|
import 'package:fl_clash/providers/config.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:fl_clash/widgets/list.dart';
|
import 'package:fl_clash/widgets/list.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class Contributor {
|
class Contributor {
|
||||||
@@ -43,6 +47,15 @@ class AboutFragment extends StatelessWidget {
|
|||||||
_checkUpdate(context);
|
_checkUpdate(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListItem(
|
||||||
|
title: Text(appLocalizations.contactMe),
|
||||||
|
onTap: () {
|
||||||
|
globalState.showMessage(
|
||||||
|
title: appLocalizations.contactMe,
|
||||||
|
message: TextSpan(text: "chen08209@gmail.com"),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
ListItem(
|
ListItem(
|
||||||
title: const Text("Telegram"),
|
title: const Text("Telegram"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -116,33 +129,43 @@ class AboutFragment extends StatelessWidget {
|
|||||||
title: Column(
|
title: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Wrap(
|
Consumer(builder: (_, ref, ___) {
|
||||||
spacing: 16,
|
return _DeveloperModeDetector(
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
child: Wrap(
|
||||||
children: [
|
spacing: 16,
|
||||||
Padding(
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/images/icon.png',
|
|
||||||
width: 64,
|
|
||||||
height: 64,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Padding(
|
||||||
appName,
|
padding: const EdgeInsets.all(12),
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
child: Image.asset(
|
||||||
|
'assets/images/icon.png',
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Column(
|
||||||
globalState.packageInfo.version,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: Theme.of(context).textTheme.labelLarge,
|
children: [
|
||||||
|
Text(
|
||||||
|
appName,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
globalState.packageInfo.version,
|
||||||
|
style: Theme.of(context).textTheme.labelLarge,
|
||||||
|
)
|
||||||
|
],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
],
|
onEnterDeveloperMode: () {
|
||||||
),
|
ref.read(appSettingProvider.notifier).updateState(
|
||||||
|
(state) => state.copyWith(developerMode: true),
|
||||||
|
);
|
||||||
|
context.showNotifier(appLocalizations.developerModeEnableTip);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 24,
|
height: 24,
|
||||||
),
|
),
|
||||||
@@ -179,10 +202,7 @@ class Avatar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InkWell(
|
return GestureDetector(
|
||||||
splashColor: Colors.transparent,
|
|
||||||
highlightColor: Colors.transparent,
|
|
||||||
hoverColor: Colors.transparent,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
@@ -209,3 +229,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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
@@ -22,21 +23,14 @@ class _AccessFragmentState extends ConsumerState<AccessFragment> {
|
|||||||
List<String> acceptList = [];
|
List<String> acceptList = [];
|
||||||
List<String> rejectList = [];
|
List<String> rejectList = [];
|
||||||
late ScrollController _controller;
|
late ScrollController _controller;
|
||||||
|
final _completer = Completer();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_updateInitList();
|
_updateInitList();
|
||||||
_controller = ScrollController();
|
_controller = ScrollController();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
_completer.complete(globalState.appController.getPackages());
|
||||||
final appState = globalState.appState;
|
|
||||||
if (appState.packages.isEmpty) {
|
|
||||||
Future.delayed(const Duration(milliseconds: 300), () async {
|
|
||||||
ref.read(packagesProvider.notifier).value =
|
|
||||||
await app?.getPackages() ?? [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -113,15 +107,11 @@ class _AccessFragmentState extends ConsumerState<AccessFragment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_intelligentSelected() async {
|
_intelligentSelected() async {
|
||||||
final appState = globalState.appState;
|
final packageNames = ref.read(
|
||||||
final config = globalState.config;
|
packageListSelectorStateProvider.select(
|
||||||
final accessControl = config.vpnProps.accessControl;
|
(state) => state.list.map((item) => item.packageName),
|
||||||
final packageNames = appState.packages
|
),
|
||||||
.where(
|
);
|
||||||
(item) =>
|
|
||||||
accessControl.isFilterSystemApp ? item.isSystem == false : true,
|
|
||||||
)
|
|
||||||
.map((item) => item.packageName);
|
|
||||||
final commonScaffoldState = context.commonScaffoldState;
|
final commonScaffoldState = context.commonScaffoldState;
|
||||||
if (commonScaffoldState?.mounted != true) return;
|
if (commonScaffoldState?.mounted != true) return;
|
||||||
final selectedPackageNames =
|
final selectedPackageNames =
|
||||||
@@ -194,7 +184,7 @@ class _AccessFragmentState extends ConsumerState<AccessFragment> {
|
|||||||
final state = ref.watch(packageListSelectorStateProvider);
|
final state = ref.watch(packageListSelectorStateProvider);
|
||||||
final accessControl = state.accessControl;
|
final accessControl = state.accessControl;
|
||||||
final accessControlMode = accessControl.mode;
|
final accessControlMode = accessControl.mode;
|
||||||
final packages = state.getList(
|
final packages = state.getSortList(
|
||||||
accessControlMode == AccessControlMode.acceptSelected
|
accessControlMode == AccessControlMode.acceptSelected
|
||||||
? acceptList
|
? acceptList
|
||||||
: rejectList,
|
: rejectList,
|
||||||
@@ -323,31 +313,42 @@ class _AccessFragmentState extends ConsumerState<AccessFragment> {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: packages.isEmpty
|
child: FutureBuilder(
|
||||||
? const Center(
|
future: _completer.future,
|
||||||
child: CircularProgressIndicator(),
|
builder: (_, snapshot) {
|
||||||
)
|
if (snapshot.connectionState != ConnectionState.done) {
|
||||||
: CommonScrollBar(
|
return Center(
|
||||||
controller: _controller,
|
child: CircularProgressIndicator(),
|
||||||
child: ListView.builder(
|
);
|
||||||
controller: _controller,
|
}
|
||||||
itemCount: packages.length,
|
return packages.isEmpty
|
||||||
itemExtent: 72,
|
? NullStatus(
|
||||||
itemBuilder: (_, index) {
|
label: appLocalizations.noData,
|
||||||
final package = packages[index];
|
)
|
||||||
return PackageListItem(
|
: CommonScrollBar(
|
||||||
key: Key(package.packageName),
|
controller: _controller,
|
||||||
package: package,
|
child: ListView.builder(
|
||||||
value: valueList.contains(package.packageName),
|
controller: _controller,
|
||||||
isActive: accessControl.enable,
|
itemCount: packages.length,
|
||||||
onChanged: (value) {
|
itemExtent: 72,
|
||||||
_handleSelected(valueList, package, value);
|
itemBuilder: (_, index) {
|
||||||
},
|
final package = packages[index];
|
||||||
|
return PackageListItem(
|
||||||
|
key: Key(package.packageName),
|
||||||
|
package: package,
|
||||||
|
value: valueList
|
||||||
|
.contains(package.packageName),
|
||||||
|
isActive: accessControl.enable,
|
||||||
|
onChanged: (value) {
|
||||||
|
_handleSelected(
|
||||||
|
valueList, package, value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
}),
|
||||||
),
|
)
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -482,14 +483,20 @@ class AccessControlSearchDelegate extends SearchDelegate {
|
|||||||
final lowQuery = query.toLowerCase();
|
final lowQuery = query.toLowerCase();
|
||||||
return Consumer(
|
return Consumer(
|
||||||
builder: (context, ref, __) {
|
builder: (context, ref, __) {
|
||||||
final state = ref.watch(packageListSelectorStateProvider);
|
final vm3 = ref.watch(
|
||||||
final accessControl = state.accessControl;
|
packageListSelectorStateProvider.select(
|
||||||
final accessControlMode = accessControl.mode;
|
(state) => VM3(
|
||||||
final packages = state.getList(
|
a: state.getSortList(
|
||||||
accessControlMode == AccessControlMode.acceptSelected
|
state.accessControl.mode == AccessControlMode.acceptSelected
|
||||||
? acceptList
|
? acceptList
|
||||||
: rejectList,
|
: rejectList,
|
||||||
|
),
|
||||||
|
b: state.accessControl.enable,
|
||||||
|
c: state.accessControl.currentList,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
final packages = vm3.a;
|
||||||
final queryPackages = packages
|
final queryPackages = packages
|
||||||
.where(
|
.where(
|
||||||
(package) =>
|
(package) =>
|
||||||
@@ -497,8 +504,8 @@ class AccessControlSearchDelegate extends SearchDelegate {
|
|||||||
package.packageName.contains(lowQuery),
|
package.packageName.contains(lowQuery),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
final isAccessControl = state.accessControl.enable;
|
final isAccessControl = vm3.b;
|
||||||
final currentList = accessControl.currentList;
|
final currentList = vm3.c;
|
||||||
final packageNameList = packages.map((e) => e.packageName).toList();
|
final packageNameList = packages.map((e) => e.packageName).toList();
|
||||||
final valueList = currentList.intersection(packageNameList);
|
final valueList = currentList.intersection(packageNameList);
|
||||||
return DisabledMask(
|
return DisabledMask(
|
||||||
@@ -579,13 +586,6 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getTextWithIsFilterSystemApp(bool isFilterSystemApp) {
|
|
||||||
return switch (isFilterSystemApp) {
|
|
||||||
true => appLocalizations.onlyOtherApps,
|
|
||||||
false => appLocalizations.allApps,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _buildModeSetting() {
|
List<Widget> _buildModeSetting() {
|
||||||
return generateSection(
|
return generateSection(
|
||||||
title: appLocalizations.mode,
|
title: appLocalizations.mode,
|
||||||
@@ -673,25 +673,39 @@ class _AccessControlPanelState extends ConsumerState<AccessControlPanel> {
|
|||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Consumer(
|
child: Consumer(
|
||||||
builder: (_, ref, __) {
|
builder: (_, ref, __) {
|
||||||
final isFilterSystemApp = ref.watch(
|
final vm2 = ref.watch(
|
||||||
vpnSettingProvider
|
vpnSettingProvider.select(
|
||||||
.select((state) => state.accessControl.isFilterSystemApp),
|
(state) => VM2(
|
||||||
|
a: state.accessControl.isFilterSystemApp,
|
||||||
|
b: state.accessControl.isFilterNonInternetApp,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return Wrap(
|
return Wrap(
|
||||||
spacing: 16,
|
spacing: 16,
|
||||||
children: [
|
children: [
|
||||||
for (final item in [false, true])
|
SettingTextCard(
|
||||||
SettingTextCard(
|
appLocalizations.systemApp,
|
||||||
_getTextWithIsFilterSystemApp(item),
|
isSelected: vm2.a == false,
|
||||||
isSelected: isFilterSystemApp == item,
|
onPressed: () {
|
||||||
onPressed: () {
|
ref.read(vpnSettingProvider.notifier).updateState(
|
||||||
ref.read(vpnSettingProvider.notifier).updateState(
|
(state) => state.copyWith.accessControl(
|
||||||
(state) => state.copyWith.accessControl(
|
isFilterSystemApp: !vm2.a,
|
||||||
isFilterSystemApp: item,
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
)
|
SettingTextCard(
|
||||||
|
appLocalizations.noNetworkApp,
|
||||||
|
isSelected: vm2.b == false,
|
||||||
|
onPressed: () {
|
||||||
|
ref.read(vpnSettingProvider.notifier).updateState(
|
||||||
|
(state) => state.copyWith.accessControl(
|
||||||
|
isFilterNonInternetApp: !vm2.b,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import 'package:fl_clash/providers/config.dart';
|
|||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:fl_clash/widgets/dialog.dart';
|
import 'package:fl_clash/widgets/dialog.dart';
|
||||||
import 'package:fl_clash/widgets/fade_box.dart';
|
import 'package:fl_clash/widgets/fade_box.dart';
|
||||||
|
import 'package:fl_clash/widgets/input.dart';
|
||||||
import 'package:fl_clash/widgets/list.dart';
|
import 'package:fl_clash/widgets/list.dart';
|
||||||
import 'package:fl_clash/widgets/text.dart';
|
import 'package:fl_clash/widgets/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class BackupAndRecovery extends ConsumerWidget {
|
class BackupAndRecovery extends ConsumerWidget {
|
||||||
const BackupAndRecovery({super.key});
|
const BackupAndRecovery({super.key});
|
||||||
@@ -134,6 +136,30 @@ class BackupAndRecovery extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleUpdateRecoveryStrategy(WidgetRef ref) async {
|
||||||
|
final recoveryStrategy = ref.read(appSettingProvider.select(
|
||||||
|
(state) => state.recoveryStrategy,
|
||||||
|
));
|
||||||
|
final res = await globalState.showCommonDialog(
|
||||||
|
child: OptionsDialog<RecoveryStrategy>(
|
||||||
|
title: appLocalizations.recoveryStrategy,
|
||||||
|
options: RecoveryStrategy.values,
|
||||||
|
textBuilder: (mode) => Intl.message(
|
||||||
|
"recoveryStrategy_${mode.name}",
|
||||||
|
),
|
||||||
|
value: recoveryStrategy,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (res == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ref.read(appSettingProvider.notifier).updateState(
|
||||||
|
(state) => state.copyWith(
|
||||||
|
recoveryStrategy: res,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final dav = ref.watch(appDAVSettingProvider);
|
final dav = ref.watch(appDAVSettingProvider);
|
||||||
@@ -176,25 +202,25 @@ class BackupAndRecovery extends ConsumerWidget {
|
|||||||
builder: (_, snapshot) {
|
builder: (_, snapshot) {
|
||||||
return Center(
|
return Center(
|
||||||
child: FadeThroughBox(
|
child: FadeThroughBox(
|
||||||
child: snapshot.connectionState ==
|
child:
|
||||||
ConnectionState.waiting
|
snapshot.connectionState != ConnectionState.done
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
width: 12,
|
width: 12,
|
||||||
height: 12,
|
height: 12,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Container(
|
: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: snapshot.data == true
|
color: snapshot.data == true
|
||||||
? Colors.green
|
? Colors.green
|
||||||
: Colors.red,
|
: Colors.red,
|
||||||
),
|
),
|
||||||
width: 12,
|
width: 12,
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -256,6 +282,26 @@ class BackupAndRecovery extends ConsumerWidget {
|
|||||||
title: Text(appLocalizations.recovery),
|
title: Text(appLocalizations.recovery),
|
||||||
subtitle: Text(appLocalizations.localRecoveryDesc),
|
subtitle: Text(appLocalizations.localRecoveryDesc),
|
||||||
),
|
),
|
||||||
|
ListHeader(title: appLocalizations.options),
|
||||||
|
Consumer(builder: (_, ref, __) {
|
||||||
|
final recoveryStrategy = ref.watch(appSettingProvider.select(
|
||||||
|
(state) => state.recoveryStrategy,
|
||||||
|
));
|
||||||
|
return ListItem(
|
||||||
|
onTap: () {
|
||||||
|
_handleUpdateRecoveryStrategy(ref);
|
||||||
|
},
|
||||||
|
title: Text(appLocalizations.recoveryStrategy),
|
||||||
|
trailing: FilledButton(
|
||||||
|
onPressed: () {
|
||||||
|
_handleUpdateRecoveryStrategy(ref);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
Intl.message("recoveryStrategy_${recoveryStrategy.name}"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,8 +301,11 @@ class RouteAddressItem extends ConsumerWidget {
|
|||||||
title: appLocalizations.routeAddress,
|
title: appLocalizations.routeAddress,
|
||||||
widget: Consumer(
|
widget: Consumer(
|
||||||
builder: (_, ref, __) {
|
builder: (_, ref, __) {
|
||||||
final routeAddress = ref.watch(patchClashConfigProvider
|
final routeAddress = ref.watch(
|
||||||
.select((state) => state.tun.routeAddress));
|
patchClashConfigProvider.select(
|
||||||
|
(state) => state.tun.routeAddress,
|
||||||
|
),
|
||||||
|
);
|
||||||
return ListInputPage(
|
return ListInputPage(
|
||||||
title: appLocalizations.routeAddress,
|
title: appLocalizations.routeAddress,
|
||||||
items: routeAddress,
|
items: routeAddress,
|
||||||
@@ -371,7 +374,9 @@ class NetworkListView extends ConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ref.read(vpnSettingProvider.notifier).updateState(
|
ref.read(vpnSettingProvider.notifier).updateState(
|
||||||
(state) => defaultVpnProps,
|
(state) => defaultVpnProps.copyWith(
|
||||||
|
accessControl: state.accessControl,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
ref.read(patchClashConfigProvider.notifier).updateState(
|
ref.read(patchClashConfigProvider.notifier).updateState(
|
||||||
(state) => state.copyWith(
|
(state) => state.copyWith(
|
||||||
|
|||||||
@@ -78,113 +78,72 @@ class ConnectionItem extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
return CommonPopupBox(
|
return CommonPopupBox(
|
||||||
targetBuilder: (open) {
|
targetBuilder: (open) {
|
||||||
return ListItem(
|
openPopup(Offset offset) {
|
||||||
padding: const EdgeInsets.symmetric(
|
open(
|
||||||
horizontal: 16,
|
offset: offset.translate(
|
||||||
vertical: 4,
|
0,
|
||||||
),
|
0,
|
||||||
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
|
||||||
leading: value
|
|
||||||
? GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
if (onClickKeyword == null) return;
|
|
||||||
final process = connection.metadata.process;
|
|
||||||
if (process.isEmpty) return;
|
|
||||||
onClickKeyword!(process);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.only(top: 4),
|
|
||||||
width: 48,
|
|
||||||
height: 48,
|
|
||||||
child: FutureBuilder<ImageProvider?>(
|
|
||||||
future: _getPackageIcon(connection),
|
|
||||||
builder: (_, snapshot) {
|
|
||||||
if (!snapshot.hasData && snapshot.data == null) {
|
|
||||||
return Container();
|
|
||||||
} else {
|
|
||||||
return Image(
|
|
||||||
image: snapshot.data!,
|
|
||||||
gaplessPlayback: true,
|
|
||||||
width: 48,
|
|
||||||
height: 48,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
: null,
|
}
|
||||||
title: title,
|
|
||||||
subtitle: subTitle,
|
return InkWell(
|
||||||
trailing: trailing,
|
child: GestureDetector(
|
||||||
|
onLongPressStart: (details) {
|
||||||
|
if (!system.isDesktop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openPopup(details.localPosition);
|
||||||
|
},
|
||||||
|
onSecondaryTapDown: (details) {
|
||||||
|
if (!system.isDesktop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openPopup(details.localPosition);
|
||||||
|
},
|
||||||
|
child: ListItem(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
||||||
|
leading: value
|
||||||
|
? GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
if (onClickKeyword == null) return;
|
||||||
|
final process = connection.metadata.process;
|
||||||
|
if (process.isEmpty) return;
|
||||||
|
onClickKeyword!(process);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(top: 4),
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
child: FutureBuilder<ImageProvider?>(
|
||||||
|
future: _getPackageIcon(connection),
|
||||||
|
builder: (_, snapshot) {
|
||||||
|
if (!snapshot.hasData && snapshot.data == null) {
|
||||||
|
return Container();
|
||||||
|
} else {
|
||||||
|
return Image(
|
||||||
|
image: snapshot.data!,
|
||||||
|
gaplessPlayback: true,
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
title: title,
|
||||||
|
subtitle: subTitle,
|
||||||
|
trailing: trailing,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {},
|
||||||
);
|
);
|
||||||
// return InkWell(
|
|
||||||
// child: GestureDetector(
|
|
||||||
// onLongPressStart: (details) {
|
|
||||||
// if (!system.isDesktop) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// open(
|
|
||||||
// offset: details.localPosition.translate(
|
|
||||||
// 0,
|
|
||||||
// -12,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// onSecondaryTapDown: (details) {
|
|
||||||
// if (!system.isDesktop) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// open(
|
|
||||||
// offset: details.localPosition.translate(
|
|
||||||
// 0,
|
|
||||||
// -12,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// child: ListItem(
|
|
||||||
// padding: const EdgeInsets.symmetric(
|
|
||||||
// horizontal: 16,
|
|
||||||
// vertical: 4,
|
|
||||||
// ),
|
|
||||||
// tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
|
||||||
// leading: value
|
|
||||||
// ? GestureDetector(
|
|
||||||
// onTap: () {
|
|
||||||
// if (onClickKeyword == null) return;
|
|
||||||
// final process = connection.metadata.process;
|
|
||||||
// if (process.isEmpty) return;
|
|
||||||
// onClickKeyword!(process);
|
|
||||||
// },
|
|
||||||
// child: Container(
|
|
||||||
// margin: const EdgeInsets.only(top: 4),
|
|
||||||
// width: 48,
|
|
||||||
// height: 48,
|
|
||||||
// child: FutureBuilder<ImageProvider?>(
|
|
||||||
// future: _getPackageIcon(connection),
|
|
||||||
// builder: (_, snapshot) {
|
|
||||||
// if (!snapshot.hasData && snapshot.data == null) {
|
|
||||||
// return Container();
|
|
||||||
// } else {
|
|
||||||
// return Image(
|
|
||||||
// image: snapshot.data!,
|
|
||||||
// gaplessPlayback: true,
|
|
||||||
// width: 48,
|
|
||||||
// height: 48,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// : null,
|
|
||||||
// title: title,
|
|
||||||
// subtitle: subTitle,
|
|
||||||
// trailing: trailing,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// onTap: () {},
|
|
||||||
// );
|
|
||||||
},
|
},
|
||||||
popup: CommonPopupMenu(
|
popup: CommonPopupMenu(
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
|
|||||||
@@ -20,12 +20,13 @@ class RequestsFragment extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||||
with PageMixin {
|
with PageMixin {
|
||||||
final GlobalKey<CacheItemExtentListViewState> _key = GlobalKey();
|
final _requestsStateNotifier = ValueNotifier<ConnectionsState>(
|
||||||
final _requestsStateNotifier =
|
const ConnectionsState(loading: true),
|
||||||
ValueNotifier<ConnectionsState>(const ConnectionsState());
|
);
|
||||||
List<Connection> _requests = [];
|
List<Connection> _requests = [];
|
||||||
final _cacheKey = ValueKey("requests_list");
|
final _tag = CacheTag.requests;
|
||||||
late ScrollController _scrollController;
|
late ScrollController _scrollController;
|
||||||
|
bool _isLoad = false;
|
||||||
|
|
||||||
double _currentMaxWidth = 0;
|
double _currentMaxWidth = 0;
|
||||||
|
|
||||||
@@ -45,12 +46,13 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final preOffset = globalState.cacheScrollPosition[_cacheKey] ?? -1;
|
final preOffset = globalState.cacheScrollPosition[_tag] ?? -1;
|
||||||
_scrollController = ScrollController(
|
_scrollController = ScrollController(
|
||||||
initialScrollOffset: preOffset > 0 ? preOffset : double.maxFinite,
|
initialScrollOffset: preOffset > 0 ? preOffset : double.maxFinite,
|
||||||
);
|
);
|
||||||
|
_requests = globalState.appState.requests.list;
|
||||||
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
|
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
|
||||||
connections: globalState.appState.requests.list,
|
connections: _requests,
|
||||||
);
|
);
|
||||||
ref.listenManual(
|
ref.listenManual(
|
||||||
isCurrentPageProvider(
|
isCurrentPageProvider(
|
||||||
@@ -73,7 +75,6 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
|||||||
updateRequestsThrottler();
|
updateRequestsThrottler();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fireImmediately: true,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,14 +99,7 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
|||||||
final lines = (chainSize.height / baseHeight).round();
|
final lines = (chainSize.height / baseHeight).round();
|
||||||
final computerHeight =
|
final computerHeight =
|
||||||
size.height + chainSize.height + 24 + 24 * (lines - 1);
|
size.height + chainSize.height + 24 + 24 * (lines - 1);
|
||||||
return computerHeight;
|
return computerHeight + 8 + 32 + globalState.measure.bodyMediumHeight;
|
||||||
}
|
|
||||||
|
|
||||||
_handleTryClearCache(double maxWidth) {
|
|
||||||
if (_currentMaxWidth != maxWidth) {
|
|
||||||
_currentMaxWidth = maxWidth;
|
|
||||||
_key.currentState?.clearCache();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -117,7 +111,7 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateRequestsThrottler() {
|
updateRequestsThrottler() {
|
||||||
throttler.call("request", () {
|
throttler.call(FunctionTag.requests, () {
|
||||||
final isEquality = connectionListEquality.equals(
|
final isEquality = connectionListEquality.equals(
|
||||||
_requests,
|
_requests,
|
||||||
_requestsStateNotifier.value.connections,
|
_requestsStateNotifier.value.connections,
|
||||||
@@ -126,13 +120,51 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
|
if(mounted){
|
||||||
connections: _requests,
|
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
|
||||||
);
|
connections: _requests,
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, duration: commonDuration);
|
}, duration: commonDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_preLoad() {
|
||||||
|
if (_isLoad == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isLoad = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final isMobileView = ref.read(isMobileViewProvider);
|
||||||
|
if (isMobileView) {
|
||||||
|
await Future.delayed(Duration(milliseconds: 300));
|
||||||
|
}
|
||||||
|
final parts = _requests.batch(10);
|
||||||
|
globalState.cacheHeightMap[_tag] ??= FixedMap(
|
||||||
|
_requests.length,
|
||||||
|
);
|
||||||
|
for (int i = 0; i < parts.length; i++) {
|
||||||
|
final part = parts[i];
|
||||||
|
await Future(
|
||||||
|
() {
|
||||||
|
for (final request in part) {
|
||||||
|
globalState.cacheHeightMap[_tag]?.updateCacheValue(
|
||||||
|
request.id,
|
||||||
|
() => _calcCacheHeight(request),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
|
||||||
|
loading: false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
@@ -146,75 +178,86 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
|||||||
Platform.isAndroid,
|
Platform.isAndroid,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_handleTryClearCache(constraints.maxWidth - 40 - (value ? 60 : 0));
|
_currentMaxWidth = constraints.maxWidth - 40 - (value ? 60 : 0);
|
||||||
return child!;
|
return child!;
|
||||||
},
|
},
|
||||||
child: ValueListenableBuilder<ConnectionsState>(
|
child: TextScaleNotification(
|
||||||
valueListenable: _requestsStateNotifier,
|
child: ValueListenableBuilder<ConnectionsState>(
|
||||||
builder: (_, state, __) {
|
valueListenable: _requestsStateNotifier,
|
||||||
final connections = state.list;
|
builder: (_, state, __) {
|
||||||
if (connections.isEmpty) {
|
_preLoad();
|
||||||
return NullStatus(
|
final connections = state.list;
|
||||||
label: appLocalizations.nullRequestsDesc,
|
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();
|
||||||
|
final content = connections.isEmpty
|
||||||
|
? NullStatus(
|
||||||
|
label: appLocalizations.nullRequestsDesc,
|
||||||
|
)
|
||||||
|
: Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: ScrollToEndBox(
|
||||||
|
controller: _scrollController,
|
||||||
|
tag: _tag,
|
||||||
|
dataSource: connections,
|
||||||
|
child: CommonScrollBar(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: CacheItemExtentListView(
|
||||||
|
tag: _tag,
|
||||||
|
reverse: true,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: NextClampingScrollPhysics(),
|
||||||
|
controller: _scrollController,
|
||||||
|
itemExtentBuilder: (index) {
|
||||||
|
if (index.isOdd) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return _calcCacheHeight(
|
||||||
|
connections[index ~/ 2]);
|
||||||
|
},
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
return items[index];
|
||||||
|
},
|
||||||
|
itemCount: items.length,
|
||||||
|
keyBuilder: (int index) {
|
||||||
|
if (index.isOdd) {
|
||||||
|
return "divider";
|
||||||
|
}
|
||||||
|
return connections[index ~/ 2].id;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return FadeBox(
|
||||||
|
child: state.loading
|
||||||
|
? Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
)
|
||||||
|
: content,
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
final items = connections
|
),
|
||||||
.map<Widget>(
|
onNotification: (_) {
|
||||||
(connection) => ConnectionItem(
|
globalState.cacheHeightMap[_tag]?.clear();
|
||||||
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(
|
|
||||||
controller: _scrollController,
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:fl_clash/providers/providers.dart';
|
|||||||
import 'package:fl_clash/widgets/widgets.dart';
|
import 'package:fl_clash/widgets/widgets.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'widgets/start_button.dart';
|
import 'widgets/start_button.dart';
|
||||||
|
|
||||||
class DashboardFragment extends ConsumerStatefulWidget {
|
class DashboardFragment extends ConsumerStatefulWidget {
|
||||||
@@ -66,7 +67,16 @@ class _DashboardFragmentState extends ConsumerState<DashboardFragment>
|
|||||||
valueListenable: key.currentState!.isEditNotifier,
|
valueListenable: key.currentState!.isEditNotifier,
|
||||||
builder: (_, isEdit, ___) {
|
builder: (_, isEdit, ___) {
|
||||||
return isEdit
|
return isEdit
|
||||||
? Icon(Icons.save)
|
? SystemBackBlock(
|
||||||
|
child: CommonPopScope(
|
||||||
|
child: Icon(Icons.save),
|
||||||
|
onPop: () {
|
||||||
|
key.currentState!.isEditNotifier.value =
|
||||||
|
!key.currentState!.isEditNotifier.value;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
: Icon(
|
: Icon(
|
||||||
Icons.edit,
|
Icons.edit,
|
||||||
);
|
);
|
||||||
@@ -93,7 +103,7 @@ class _DashboardFragmentState extends ConsumerState<DashboardFragment>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final dashboardState = ref.watch(dashboardStateProvider);
|
final dashboardState = ref.watch(dashboardStateProvider);
|
||||||
final columns = max(4 * ((dashboardState.viewWidth / 350).ceil()), 8);
|
final columns = max(4 * ((dashboardState.viewWidth / 320).ceil()), 8);
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@@ -103,8 +113,8 @@ class _DashboardFragmentState extends ConsumerState<DashboardFragment>
|
|||||||
child: SuperGrid(
|
child: SuperGrid(
|
||||||
key: key,
|
key: key,
|
||||||
crossAxisCount: columns,
|
crossAxisCount: columns,
|
||||||
crossAxisSpacing: 16,
|
crossAxisSpacing: 16.ap,
|
||||||
mainAxisSpacing: 16,
|
mainAxisSpacing: 16.ap,
|
||||||
children: [
|
children: [
|
||||||
...dashboardState.dashboardWidgets
|
...dashboardState.dashboardWidgets
|
||||||
.where(
|
.where(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
|||||||
import 'package:fl_clash/clash/clash.dart';
|
import 'package:fl_clash/clash/clash.dart';
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/models/common.dart';
|
import 'package:fl_clash/models/common.dart';
|
||||||
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:fl_clash/widgets/widgets.dart';
|
import 'package:fl_clash/widgets/widgets.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@@ -57,37 +58,43 @@ class _MemoryInfoState extends State<MemoryInfo> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
clashCore.requestGc();
|
clashCore.requestGc();
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Container(
|
||||||
children: [
|
padding: baseInfoEdgeInsets.copyWith(
|
||||||
ValueListenableBuilder(
|
top: 0,
|
||||||
valueListenable: _memoryInfoStateNotifier,
|
),
|
||||||
builder: (_, trafficValue, __) {
|
child: Column(
|
||||||
return Padding(
|
mainAxisSize: MainAxisSize.max,
|
||||||
padding: baseInfoEdgeInsets.copyWith(
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
bottom: 0,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
top: 12,
|
children: [
|
||||||
),
|
SizedBox(
|
||||||
child: Row(
|
height: globalState.measure.bodyMediumHeight + 2,
|
||||||
children: [
|
child: ValueListenableBuilder(
|
||||||
Text(
|
valueListenable: _memoryInfoStateNotifier,
|
||||||
trafficValue.showValue,
|
builder: (_, trafficValue, __) {
|
||||||
style:
|
return Row(
|
||||||
context.textTheme.bodyMedium?.toLight.adjustSize(1),
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
SizedBox(
|
Text(
|
||||||
width: 8,
|
trafficValue.showValue,
|
||||||
),
|
style: context.textTheme.bodyMedium?.toLight
|
||||||
Text(
|
.adjustSize(1),
|
||||||
trafficValue.showUnit,
|
),
|
||||||
style:
|
SizedBox(
|
||||||
context.textTheme.bodyMedium?.toLight.adjustSize(1),
|
width: 8,
|
||||||
)
|
),
|
||||||
],
|
Text(
|
||||||
),
|
trafficValue.showUnit,
|
||||||
);
|
style: context.textTheme.bodyMedium?.toLight
|
||||||
},
|
.adjustSize(1),
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,23 +1,11 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:fl_clash/models/models.dart';
|
import 'package:fl_clash/models/models.dart';
|
||||||
import 'package:fl_clash/providers/app.dart';
|
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:fl_clash/widgets/widgets.dart';
|
import 'package:fl_clash/widgets/widgets.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
final _networkDetectionState = ValueNotifier<NetworkDetectionState>(
|
|
||||||
const NetworkDetectionState(
|
|
||||||
isTesting: false,
|
|
||||||
isLoading: true,
|
|
||||||
ipInfo: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
class NetworkDetection extends ConsumerStatefulWidget {
|
class NetworkDetection extends ConsumerStatefulWidget {
|
||||||
const NetworkDetection({super.key});
|
const NetworkDetection({super.key});
|
||||||
|
|
||||||
@@ -26,101 +14,6 @@ class NetworkDetection extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
|
class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
|
||||||
bool? _preIsStart;
|
|
||||||
Timer? _setTimeoutTimer;
|
|
||||||
CancelToken? cancelToken;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
ref.listenManual(checkIpNumProvider, (prev, next) {
|
|
||||||
if (prev != next) {
|
|
||||||
_startCheck();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!_networkDetectionState.value.isTesting &&
|
|
||||||
_networkDetectionState.value.isLoading) {
|
|
||||||
_startCheck();
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
_startCheck() async {
|
|
||||||
if (cancelToken != null) {
|
|
||||||
cancelToken!.cancel();
|
|
||||||
cancelToken = null;
|
|
||||||
}
|
|
||||||
debouncer.call(
|
|
||||||
DebounceTag.checkIp,
|
|
||||||
_checkIp,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_checkIp() async {
|
|
||||||
final appState = globalState.appState;
|
|
||||||
final isInit = appState.isInit;
|
|
||||||
if (!isInit) return;
|
|
||||||
final isStart = appState.runTime != null;
|
|
||||||
if (_preIsStart == false &&
|
|
||||||
_preIsStart == isStart &&
|
|
||||||
_networkDetectionState.value.ipInfo != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_clearSetTimeoutTimer();
|
|
||||||
_networkDetectionState.value = _networkDetectionState.value.copyWith(
|
|
||||||
isLoading: true,
|
|
||||||
ipInfo: null,
|
|
||||||
);
|
|
||||||
_preIsStart = isStart;
|
|
||||||
if (cancelToken != null) {
|
|
||||||
cancelToken!.cancel();
|
|
||||||
cancelToken = null;
|
|
||||||
}
|
|
||||||
cancelToken = CancelToken();
|
|
||||||
try {
|
|
||||||
_networkDetectionState.value = _networkDetectionState.value.copyWith(
|
|
||||||
isTesting: true,
|
|
||||||
);
|
|
||||||
final ipInfo = await request.checkIp(cancelToken: cancelToken);
|
|
||||||
_networkDetectionState.value = _networkDetectionState.value.copyWith(
|
|
||||||
isTesting: false,
|
|
||||||
);
|
|
||||||
if (ipInfo != null) {
|
|
||||||
_networkDetectionState.value = _networkDetectionState.value.copyWith(
|
|
||||||
isLoading: false,
|
|
||||||
ipInfo: ipInfo,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_clearSetTimeoutTimer();
|
|
||||||
_setTimeoutTimer = Timer(const Duration(milliseconds: 300), () {
|
|
||||||
_networkDetectionState.value = _networkDetectionState.value.copyWith(
|
|
||||||
isLoading: false,
|
|
||||||
ipInfo: null,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e.toString() == "cancelled") {
|
|
||||||
_networkDetectionState.value = _networkDetectionState.value.copyWith(
|
|
||||||
isLoading: true,
|
|
||||||
ipInfo: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_clearSetTimeoutTimer();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_clearSetTimeoutTimer() {
|
|
||||||
if (_setTimeoutTimer != null) {
|
|
||||||
_setTimeoutTimer?.cancel();
|
|
||||||
_setTimeoutTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_countryCodeToEmoji(String countryCode) {
|
_countryCodeToEmoji(String countryCode) {
|
||||||
final String code = countryCode.toUpperCase();
|
final String code = countryCode.toUpperCase();
|
||||||
if (code.length != 2) {
|
if (code.length != 2) {
|
||||||
@@ -136,7 +29,7 @@ class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
|
|||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: getWidgetHeight(1),
|
height: getWidgetHeight(1),
|
||||||
child: ValueListenableBuilder<NetworkDetectionState>(
|
child: ValueListenableBuilder<NetworkDetectionState>(
|
||||||
valueListenable: _networkDetectionState,
|
valueListenable: detectionState.state,
|
||||||
builder: (_, state, __) {
|
builder: (_, state, __) {
|
||||||
final ipInfo = state.ipInfo;
|
final ipInfo = state.ipInfo;
|
||||||
final isLoading = state.isLoading;
|
final isLoading = state.isLoading;
|
||||||
@@ -206,7 +99,7 @@ class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
size: 16,
|
size: 16.ap,
|
||||||
Icons.info_outline,
|
Icons.info_outline,
|
||||||
color: context.colorScheme.onSurfaceVariant,
|
color: context.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -17,58 +17,146 @@ class OutboundMode extends StatelessWidget {
|
|||||||
height: height,
|
height: height,
|
||||||
child: Consumer(
|
child: Consumer(
|
||||||
builder: (_, ref, __) {
|
builder: (_, ref, __) {
|
||||||
final mode =
|
final mode = ref.watch(
|
||||||
ref.watch(patchClashConfigProvider.select((state) => state.mode));
|
patchClashConfigProvider.select(
|
||||||
return CommonCard(
|
(state) => state.mode,
|
||||||
onPressed: () {},
|
|
||||||
info: Info(
|
|
||||||
label: appLocalizations.outboundMode,
|
|
||||||
iconData: Icons.call_split_sharp,
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 12,
|
|
||||||
bottom: 16,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
for (final item in Mode.values)
|
|
||||||
Flexible(
|
|
||||||
child: ListItem.radio(
|
|
||||||
dense: true,
|
|
||||||
horizontalTitleGap: 4,
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
left: 12,
|
|
||||||
right: 16,
|
|
||||||
),
|
|
||||||
delegate: RadioDelegate(
|
|
||||||
value: item,
|
|
||||||
groupValue: mode,
|
|
||||||
onChanged: (value) async {
|
|
||||||
if (value == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
globalState.appController.changeMode(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
Intl.message(item.name),
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyMedium
|
|
||||||
?.toSoftBold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
return Theme(
|
||||||
|
data: Theme.of(context).copyWith(
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
hoverColor: Colors.transparent),
|
||||||
|
child: CommonCard(
|
||||||
|
onPressed: () {},
|
||||||
|
info: Info(
|
||||||
|
label: appLocalizations.outboundMode,
|
||||||
|
iconData: Icons.call_split_sharp,
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 12,
|
||||||
|
bottom: 16,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
for (final item in Mode.values)
|
||||||
|
Flexible(
|
||||||
|
fit: FlexFit.tight,
|
||||||
|
child: ListItem.radio(
|
||||||
|
dense: true,
|
||||||
|
horizontalTitleGap: 4,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 12.ap,
|
||||||
|
right: 16.ap,
|
||||||
|
),
|
||||||
|
delegate: RadioDelegate(
|
||||||
|
value: item,
|
||||||
|
groupValue: mode,
|
||||||
|
onChanged: (value) async {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
globalState.appController.changeMode(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
Intl.message(item.name),
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.toSoftBold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class OutboundModeV2 extends StatelessWidget {
|
||||||
|
const OutboundModeV2({super.key});
|
||||||
|
|
||||||
|
Color _getTextColor(BuildContext context, Mode mode) {
|
||||||
|
return switch (mode) {
|
||||||
|
Mode.rule => context.colorScheme.onSecondaryContainer,
|
||||||
|
Mode.global => context.colorScheme.onPrimaryContainer,
|
||||||
|
Mode.direct => context.colorScheme.onTertiaryContainer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final height = getWidgetHeight(0.72);
|
||||||
|
return SizedBox(
|
||||||
|
height: height,
|
||||||
|
child: CommonCard(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: Consumer(
|
||||||
|
builder: (_, ref, __) {
|
||||||
|
final mode = ref.watch(
|
||||||
|
patchClashConfigProvider.select(
|
||||||
|
(state) => state.mode,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final thumbColor = switch (mode) {
|
||||||
|
Mode.rule => context.colorScheme.secondaryContainer,
|
||||||
|
Mode.global => globalState.theme.darken3PrimaryContainer,
|
||||||
|
Mode.direct => context.colorScheme.tertiaryContainer,
|
||||||
|
};
|
||||||
|
return Container(
|
||||||
|
constraints: BoxConstraints.expand(),
|
||||||
|
child: CommonTabBar<Mode>(
|
||||||
|
children: Map.fromEntries(
|
||||||
|
Mode.values.map(
|
||||||
|
(item) => MapEntry(
|
||||||
|
item,
|
||||||
|
Container(
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(),
|
||||||
|
height: height - 16,
|
||||||
|
child: Text(
|
||||||
|
Intl.message(item.name),
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleSmall
|
||||||
|
?.adjustSize(1)
|
||||||
|
.copyWith(
|
||||||
|
color: item == mode
|
||||||
|
? _getTextColor(
|
||||||
|
context,
|
||||||
|
item,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
groupValue: mode,
|
||||||
|
onValueChanged: (value) {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
globalState.appController.changeMode(value);
|
||||||
|
},
|
||||||
|
thumbColor: thumbColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -165,3 +165,87 @@ class SystemProxyButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class VpnButton extends StatelessWidget {
|
||||||
|
const VpnButton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: getWidgetHeight(1),
|
||||||
|
child: CommonCard(
|
||||||
|
onPressed: () {
|
||||||
|
showSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (_, type) {
|
||||||
|
return AdaptiveSheetScaffold(
|
||||||
|
type: type,
|
||||||
|
body: generateListView(
|
||||||
|
generateSection(
|
||||||
|
items: [
|
||||||
|
const VPNItem(),
|
||||||
|
const VpnSystemProxyItem(),
|
||||||
|
const TunStackItem(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: "VPN",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
info: Info(
|
||||||
|
label: "VPN",
|
||||||
|
iconData: Icons.stacked_line_chart,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
padding: baseInfoEdgeInsets.copyWith(
|
||||||
|
top: 4,
|
||||||
|
bottom: 8,
|
||||||
|
right: 8,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
flex: 1,
|
||||||
|
child: TooltipText(
|
||||||
|
text: Text(
|
||||||
|
appLocalizations.options,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleSmall
|
||||||
|
?.adjustSize(-2)
|
||||||
|
.toLight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Consumer(
|
||||||
|
builder: (_, ref, __) {
|
||||||
|
final enable = ref.watch(
|
||||||
|
vpnSettingProvider.select(
|
||||||
|
(state) => state.enable,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return Switch(
|
||||||
|
value: enable,
|
||||||
|
onChanged: (value) {
|
||||||
|
ref.read(vpnSettingProvider.notifier).updateState(
|
||||||
|
(state) => state.copyWith(
|
||||||
|
enable: value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/models/models.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:fl_clash/providers/providers.dart';
|
import 'package:fl_clash/providers/providers.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
class StartButton extends StatefulWidget {
|
class StartButton extends ConsumerStatefulWidget {
|
||||||
const StartButton({super.key});
|
const StartButton({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StartButton> createState() => _StartButtonState();
|
ConsumerState<StartButton> createState() => _StartButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _StartButtonState extends State<StartButton>
|
class _StartButtonState extends ConsumerState<StartButton>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late AnimationController _controller;
|
late AnimationController _controller;
|
||||||
|
late Animation<double> _animation;
|
||||||
bool isStart = false;
|
bool isStart = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -26,6 +27,20 @@ class _StartButtonState extends State<StartButton>
|
|||||||
value: isStart ? 1 : 0,
|
value: isStart ? 1 : 0,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
);
|
);
|
||||||
|
_animation = CurvedAnimation(
|
||||||
|
parent: _controller,
|
||||||
|
curve: Curves.easeOutBack,
|
||||||
|
);
|
||||||
|
ref.listenManual(
|
||||||
|
runTimeProvider.select((state) => state != null),
|
||||||
|
(prev, next) {
|
||||||
|
if (next != isStart) {
|
||||||
|
isStart = next;
|
||||||
|
updateController();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fireImmediately: true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -35,102 +50,98 @@ class _StartButtonState extends State<StartButton>
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSwitchStart() {
|
handleSwitchStart() {
|
||||||
if (isStart == globalState.appState.isStart) {
|
isStart = !isStart;
|
||||||
isStart = !isStart;
|
updateController();
|
||||||
updateController();
|
debouncer.call(
|
||||||
globalState.appController.updateStatus(isStart);
|
FunctionTag.updateStatus,
|
||||||
}
|
() {
|
||||||
|
globalState.appController.updateStatus(isStart);
|
||||||
|
},
|
||||||
|
duration: commonDuration,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateController() {
|
updateController() {
|
||||||
if (isStart) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_controller.forward();
|
if (isStart) {
|
||||||
} else {
|
_controller.forward();
|
||||||
_controller.reverse();
|
} else {
|
||||||
}
|
_controller.reverse();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer(
|
final state = ref.watch(startButtonSelectorStateProvider);
|
||||||
builder: (_, ref, child) {
|
if (!state.isInit || !state.hasProfile) {
|
||||||
final state = ref.watch(startButtonSelectorStateProvider);
|
return Container();
|
||||||
if (!state.isInit || !state.hasProfile) {
|
}
|
||||||
return Container();
|
return Theme(
|
||||||
}
|
data: Theme.of(context).copyWith(
|
||||||
ref.listenManual(
|
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||||
runTimeProvider.select((state) => state != null),
|
sizeConstraints: BoxConstraints(
|
||||||
(prev, next) {
|
minWidth: 56,
|
||||||
if (next != isStart) {
|
maxWidth: 200,
|
||||||
isStart = next;
|
),
|
||||||
updateController();
|
),
|
||||||
}
|
),
|
||||||
},
|
child: AnimatedBuilder(
|
||||||
fireImmediately: true,
|
animation: _controller.view,
|
||||||
);
|
builder: (_, child) {
|
||||||
final textWidth = globalState.measure
|
final textWidth = globalState.measure
|
||||||
.computeTextSize(
|
.computeTextSize(
|
||||||
Text(
|
Text(
|
||||||
utils.getTimeDifference(
|
utils.getTimeDifference(
|
||||||
DateTime.now(),
|
DateTime.now(),
|
||||||
|
),
|
||||||
|
style: context.textTheme.titleMedium?.toSoftBold,
|
||||||
),
|
),
|
||||||
style: context.textTheme.titleMedium?.toSoftBold,
|
)
|
||||||
|
.width +
|
||||||
|
16;
|
||||||
|
return FloatingActionButton(
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.padded,
|
||||||
|
heroTag: null,
|
||||||
|
onPressed: () {
|
||||||
|
handleSwitchStart();
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 56,
|
||||||
|
width: 56,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: AnimatedIcon(
|
||||||
|
icon: AnimatedIcons.play_pause,
|
||||||
|
progress: _animation,
|
||||||
),
|
),
|
||||||
)
|
|
||||||
.width +
|
|
||||||
16;
|
|
||||||
return AnimatedBuilder(
|
|
||||||
animation: _controller.view,
|
|
||||||
builder: (_, child) {
|
|
||||||
return SizedBox(
|
|
||||||
width: 56 + textWidth * _controller.value,
|
|
||||||
height: 56,
|
|
||||||
child: FloatingActionButton(
|
|
||||||
heroTag: null,
|
|
||||||
onPressed: () {
|
|
||||||
handleSwitchStart();
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 56,
|
|
||||||
height: 56,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: AnimatedIcon(
|
|
||||||
icon: AnimatedIcons.play_pause,
|
|
||||||
progress: _controller,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ClipRect(
|
|
||||||
child: OverflowBox(
|
|
||||||
maxWidth: textWidth,
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: child!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
SizedBox(
|
||||||
);
|
width: textWidth * _animation.value,
|
||||||
},
|
child: child!,
|
||||||
child: child,
|
)
|
||||||
);
|
],
|
||||||
},
|
|
||||||
child: Consumer(
|
|
||||||
builder: (_, ref, __) {
|
|
||||||
final runTime = ref.watch(runTimeProvider);
|
|
||||||
final text = utils.getTimeText(runTime);
|
|
||||||
return Text(
|
|
||||||
text,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.toSoftBold.copyWith(
|
|
||||||
color: context.colorScheme.onPrimaryContainer
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
child: Consumer(
|
||||||
|
builder: (_, ref, __) {
|
||||||
|
final runTime = ref.watch(runTimeProvider);
|
||||||
|
final text = utils.getTimeText(runTime);
|
||||||
|
return Text(
|
||||||
|
text,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.visible,
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.titleMedium?.toSoftBold.copyWith(
|
||||||
|
color: context.colorScheme.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
118
lib/fragments/developer.dart
Normal file
118
lib/fragments/developer.dart
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
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/models/common.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';
|
||||||
|
|
||||||
|
import '../providers/app.dart';
|
||||||
|
|
||||||
|
class DeveloperView extends ConsumerWidget {
|
||||||
|
const DeveloperView({super.key});
|
||||||
|
|
||||||
|
Widget _getDeveloperList(BuildContext context, WidgetRef ref) {
|
||||||
|
return generateSectionV2(
|
||||||
|
title: appLocalizations.options,
|
||||||
|
items: [
|
||||||
|
ListItem(
|
||||||
|
title: Text(appLocalizations.messageTest),
|
||||||
|
onTap: () {
|
||||||
|
context.showNotifier(
|
||||||
|
appLocalizations.messageTestTip,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListItem(
|
||||||
|
title: Text(appLocalizations.logsTest),
|
||||||
|
onTap: () {
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
ref.read(requestsProvider.notifier).addRequest(Connection(
|
||||||
|
id: utils.id,
|
||||||
|
start: DateTime.now(),
|
||||||
|
metadata: Metadata(
|
||||||
|
uid: i * i,
|
||||||
|
network: utils.generateRandomString(
|
||||||
|
maxLength: 1000,
|
||||||
|
minLength: 20,
|
||||||
|
),
|
||||||
|
sourceIP: '',
|
||||||
|
sourcePort: '',
|
||||||
|
destinationIP: '',
|
||||||
|
destinationPort: '',
|
||||||
|
host: '',
|
||||||
|
process: '',
|
||||||
|
remoteDestination: "",
|
||||||
|
),
|
||||||
|
chains: ["chains"],
|
||||||
|
));
|
||||||
|
globalState.appController.addLog(
|
||||||
|
Log.app(
|
||||||
|
utils.generateRandomString(
|
||||||
|
maxLength: 200,
|
||||||
|
minLength: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListItem(
|
||||||
|
title: Text(appLocalizations.crashTest),
|
||||||
|
onTap: () {
|
||||||
|
clashCore.clashInterface.crash();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListItem(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
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, ref)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,3 +11,4 @@ export 'backup_and_recovery.dart';
|
|||||||
export 'resources.dart';
|
export 'resources.dart';
|
||||||
export 'connection/requests.dart';
|
export 'connection/requests.dart';
|
||||||
export 'connection/connections.dart';
|
export 'connection/connections.dart';
|
||||||
|
export 'developer.dart';
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class _HotKeyRecorderState extends State<HotKeyRecorder> {
|
|||||||
modifiers: modifiers,
|
modifiers: modifiers,
|
||||||
key: key.usbHidUsage,
|
key: key.usbHidUsage,
|
||||||
);
|
);
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -157,59 +157,65 @@ class _HotKeyRecorderState extends State<HotKeyRecorder> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CommonDialog(
|
return Focus(
|
||||||
title: IntlExt.actionMessage(widget.hotKeyAction.action.name),
|
onKeyEvent: (_, __) {
|
||||||
actions: [
|
return KeyEventResult.handled;
|
||||||
TextButton(
|
},
|
||||||
onPressed: () {
|
autofocus: true,
|
||||||
_handleRemove();
|
child: CommonDialog(
|
||||||
},
|
title: IntlExt.actionMessage(widget.hotKeyAction.action.name),
|
||||||
child: Text(appLocalizations.remove),
|
actions: [
|
||||||
),
|
TextButton(
|
||||||
const SizedBox(
|
onPressed: () {
|
||||||
width: 8,
|
_handleRemove();
|
||||||
),
|
},
|
||||||
TextButton(
|
child: Text(appLocalizations.remove),
|
||||||
onPressed: () {
|
|
||||||
_handleConfirm();
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
appLocalizations.confirm,
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(
|
||||||
],
|
width: 8,
|
||||||
child: ValueListenableBuilder(
|
),
|
||||||
valueListenable: hotKeyActionNotifier,
|
TextButton(
|
||||||
builder: (_, hotKeyAction, ___) {
|
onPressed: () {
|
||||||
final key = hotKeyAction.key;
|
_handleConfirm();
|
||||||
final modifiers = hotKeyAction.modifiers;
|
},
|
||||||
return SizedBox(
|
child: Text(
|
||||||
width: dialogCommonWidth,
|
appLocalizations.confirm,
|
||||||
child: key != null
|
),
|
||||||
? Wrap(
|
),
|
||||||
spacing: 8,
|
],
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
child: ValueListenableBuilder(
|
||||||
children: [
|
valueListenable: hotKeyActionNotifier,
|
||||||
for (final modifier in modifiers)
|
builder: (_, hotKeyAction, ___) {
|
||||||
|
final key = hotKeyAction.key;
|
||||||
|
final modifiers = hotKeyAction.modifiers;
|
||||||
|
return SizedBox(
|
||||||
|
width: dialogCommonWidth,
|
||||||
|
child: key != null
|
||||||
|
? Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
|
children: [
|
||||||
|
for (final modifier in modifiers)
|
||||||
|
KeyboardKeyBox(
|
||||||
|
keyboardKey: modifier.physicalKeys.first,
|
||||||
|
),
|
||||||
|
if (modifiers.isNotEmpty)
|
||||||
|
Text(
|
||||||
|
"+",
|
||||||
|
style: context.textTheme.titleMedium,
|
||||||
|
),
|
||||||
KeyboardKeyBox(
|
KeyboardKeyBox(
|
||||||
keyboardKey: modifier.physicalKeys.first,
|
keyboardKey: PhysicalKeyboardKey(key),
|
||||||
),
|
),
|
||||||
if (modifiers.isNotEmpty)
|
],
|
||||||
Text(
|
)
|
||||||
"+",
|
: Text(
|
||||||
style: context.textTheme.titleMedium,
|
appLocalizations.pressKeyboard,
|
||||||
),
|
style: context.textTheme.titleMedium,
|
||||||
KeyboardKeyBox(
|
),
|
||||||
keyboardKey: PhysicalKeyboardKey(key),
|
);
|
||||||
),
|
},
|
||||||
],
|
),
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
appLocalizations.pressKeyboard,
|
|
||||||
style: context.textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:fl_clash/providers/providers.dart';
|
import 'package:fl_clash/providers/providers.dart';
|
||||||
@@ -16,36 +18,27 @@ class LogsFragment extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
||||||
final _logsStateNotifier = ValueNotifier<LogsState>(LogsState());
|
final _logsStateNotifier = ValueNotifier<LogsState>(
|
||||||
final _cacheKey = ValueKey("logs_list");
|
LogsState(loading: true),
|
||||||
|
);
|
||||||
late ScrollController _scrollController;
|
late ScrollController _scrollController;
|
||||||
|
|
||||||
double _currentMaxWidth = 0;
|
double _currentMaxWidth = 0;
|
||||||
final GlobalKey<CacheItemExtentListViewState> _key = GlobalKey();
|
final _tag = CacheTag.rules;
|
||||||
|
bool _isLoad = false;
|
||||||
|
|
||||||
List<Log> _logs = [];
|
List<Log> _logs = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final preOffset = globalState.cacheScrollPosition[_cacheKey] ?? -1;
|
final position = globalState.cacheScrollPosition[_tag] ?? -1;
|
||||||
_scrollController = ScrollController(
|
_scrollController = ScrollController(
|
||||||
initialScrollOffset: preOffset > 0 ? preOffset : double.maxFinite,
|
initialScrollOffset: position > 0 ? position : double.maxFinite,
|
||||||
);
|
);
|
||||||
|
_logs = globalState.appState.logs.list;
|
||||||
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
|
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
|
||||||
logs: globalState.appState.logs.list,
|
logs: _logs,
|
||||||
);
|
|
||||||
ref.listenManual(
|
|
||||||
logsProvider.select((state) => state.list),
|
|
||||||
(prev, next) {
|
|
||||||
if (prev != next) {
|
|
||||||
final isEquality = logListEquality.equals(prev, next);
|
|
||||||
if (!isEquality) {
|
|
||||||
_logs = next;
|
|
||||||
updateLogsThrottler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fireImmediately: true,
|
|
||||||
);
|
);
|
||||||
ref.listenManual(
|
ref.listenManual(
|
||||||
isCurrentPageProvider(
|
isCurrentPageProvider(
|
||||||
@@ -60,6 +53,18 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
|||||||
},
|
},
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
);
|
);
|
||||||
|
ref.listenManual(
|
||||||
|
logsProvider.select((state) => state.list),
|
||||||
|
(prev, next) {
|
||||||
|
if (prev != next) {
|
||||||
|
final isEquality = logListEquality.equals(prev, next);
|
||||||
|
if (!isEquality) {
|
||||||
|
_logs = next;
|
||||||
|
updateLogsThrottler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -94,13 +99,6 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleTryClearCache(double maxWidth) {
|
|
||||||
if (_currentMaxWidth != maxWidth) {
|
|
||||||
_currentMaxWidth = maxWidth;
|
|
||||||
_key.currentState?.clearCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleExport() async {
|
_handleExport() async {
|
||||||
final commonScaffoldState = context.commonScaffoldState;
|
final commonScaffoldState = context.commonScaffoldState;
|
||||||
final res = await commonScaffoldState?.loadingRun<bool>(
|
final res = await commonScaffoldState?.loadingRun<bool>(
|
||||||
@@ -123,17 +121,17 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
|||||||
final height = globalState.measure
|
final height = globalState.measure
|
||||||
.computeTextSize(
|
.computeTextSize(
|
||||||
Text(
|
Text(
|
||||||
log.payload ?? "",
|
log.payload,
|
||||||
style: globalState.appController.context.textTheme.bodyLarge,
|
style: context.textTheme.bodyLarge,
|
||||||
),
|
),
|
||||||
maxWidth: _currentMaxWidth,
|
maxWidth: _currentMaxWidth,
|
||||||
)
|
)
|
||||||
.height;
|
.height;
|
||||||
return height + bodySmallHeight + 8 + bodyMediumHeight + 40;
|
return height + bodySmallHeight + 8 + bodyMediumHeight + 40 + 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLogsThrottler() {
|
updateLogsThrottler() {
|
||||||
throttler.call("logs", () {
|
throttler.call(FunctionTag.logs, () {
|
||||||
final isEquality = logListEquality.equals(
|
final isEquality = logListEquality.equals(
|
||||||
_logs,
|
_logs,
|
||||||
_logsStateNotifier.value.logs,
|
_logsStateNotifier.value.logs,
|
||||||
@@ -142,82 +140,123 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
|
if (mounted) {
|
||||||
logs: _logs,
|
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
|
||||||
);
|
logs: _logs,
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, duration: commonDuration);
|
}, duration: commonDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_preLoad() {
|
||||||
|
if (_isLoad == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isLoad = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final isMobileView = ref.read(isMobileViewProvider);
|
||||||
|
if (isMobileView) {
|
||||||
|
await Future.delayed(Duration(milliseconds: 300));
|
||||||
|
}
|
||||||
|
final parts = _logs.batch(10);
|
||||||
|
globalState.cacheHeightMap[_tag] ??= FixedMap(
|
||||||
|
_logs.length,
|
||||||
|
);
|
||||||
|
for (int i = 0; i < parts.length; i++) {
|
||||||
|
final part = parts[i];
|
||||||
|
await Future(
|
||||||
|
() {
|
||||||
|
for (final log in part) {
|
||||||
|
globalState.cacheHeightMap[_tag]?.updateCacheValue(
|
||||||
|
log.payload,
|
||||||
|
() => _getItemHeight(log),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
|
||||||
|
loading: false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (_, constraints) {
|
builder: (_, constraints) {
|
||||||
_handleTryClearCache(constraints.maxWidth - 40);
|
_currentMaxWidth = constraints.maxWidth - 40;
|
||||||
return Align(
|
return ValueListenableBuilder<LogsState>(
|
||||||
alignment: Alignment.topCenter,
|
valueListenable: _logsStateNotifier,
|
||||||
child: ValueListenableBuilder<LogsState>(
|
builder: (_, state, __) {
|
||||||
valueListenable: _logsStateNotifier,
|
_preLoad();
|
||||||
builder: (_, state, __) {
|
final logs = state.list;
|
||||||
final logs = state.list;
|
final items = logs
|
||||||
if (logs.isEmpty) {
|
.map<Widget>(
|
||||||
return NullStatus(
|
(log) => LogItem(
|
||||||
label: appLocalizations.nullLogsDesc,
|
key: Key(log.dateTime),
|
||||||
);
|
log: log,
|
||||||
}
|
onClick: (value) {
|
||||||
final items = logs
|
context.commonScaffoldState?.addKeyword(value);
|
||||||
.map<Widget>(
|
|
||||||
(log) => LogItem(
|
|
||||||
key: Key(log.dateTime.toString()),
|
|
||||||
log: log,
|
|
||||||
onClick: (value) {
|
|
||||||
context.commonScaffoldState?.addKeyword(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.separated(
|
|
||||||
const Divider(
|
|
||||||
height: 0,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
return ScrollToEndBox<Log>(
|
|
||||||
controller: _scrollController,
|
|
||||||
cacheKey: _cacheKey,
|
|
||||||
dataSource: logs,
|
|
||||||
child: CommonScrollBar(
|
|
||||||
controller: _scrollController,
|
|
||||||
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 ?? "";
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
);
|
.separated(
|
||||||
},
|
const Divider(
|
||||||
),
|
height: 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
final content = logs.isEmpty
|
||||||
|
? NullStatus(
|
||||||
|
label: appLocalizations.nullLogsDesc,
|
||||||
|
)
|
||||||
|
: Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: CommonScrollBar(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: ScrollToEndBox(
|
||||||
|
controller: _scrollController,
|
||||||
|
tag: _tag,
|
||||||
|
dataSource: logs,
|
||||||
|
child: CacheItemExtentListView(
|
||||||
|
tag: _tag,
|
||||||
|
reverse: true,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: NextClampingScrollPhysics(),
|
||||||
|
controller: _scrollController,
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
return items[index];
|
||||||
|
},
|
||||||
|
itemExtentBuilder: (index) {
|
||||||
|
if (index.isOdd) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return _getItemHeight(logs[index ~/ 2]);
|
||||||
|
},
|
||||||
|
itemCount: items.length,
|
||||||
|
keyBuilder: (int index) {
|
||||||
|
if (index.isOdd) {
|
||||||
|
return "divider";
|
||||||
|
}
|
||||||
|
return logs[index ~/ 2].payload;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return FadeBox(
|
||||||
|
child: state.loading
|
||||||
|
? Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
)
|
||||||
|
: content,
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -242,14 +281,14 @@ class LogItem extends StatelessWidget {
|
|||||||
vertical: 4,
|
vertical: 4,
|
||||||
),
|
),
|
||||||
title: SelectableText(
|
title: SelectableText(
|
||||||
log.payload ?? '',
|
log.payload,
|
||||||
style: context.textTheme.bodyLarge,
|
style: context.textTheme.bodyLarge,
|
||||||
),
|
),
|
||||||
subtitle: Column(
|
subtitle: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SelectableText(
|
SelectableText(
|
||||||
"${log.dateTime}",
|
log.dateTime,
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: context.colorScheme.primary,
|
color: context.colorScheme.primary,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -104,8 +104,13 @@ class _URLFormDialogState extends State<URLFormDialog> {
|
|||||||
runSpacing: 16,
|
runSpacing: 16,
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
maxLines: 5,
|
keyboardType: TextInputType.url,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
|
maxLines: 5,
|
||||||
|
onSubmitted: (_) {
|
||||||
|
_handleAddProfileFormURL();
|
||||||
|
},
|
||||||
|
onEditingComplete: _handleAddProfileFormURL,
|
||||||
controller: urlController,
|
controller: urlController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ class _EditProfileState extends State<EditProfile> {
|
|||||||
text: widget.profile.autoUpdateDuration.inMinutes.toString(),
|
text: widget.profile.autoUpdateDuration.inMinutes.toString(),
|
||||||
);
|
);
|
||||||
appPath.getProfilePath(widget.profile.id).then((path) async {
|
appPath.getProfilePath(widget.profile.id).then((path) async {
|
||||||
if (path == null) return;
|
|
||||||
fileInfoNotifier.value = await _getFileInfo(path);
|
fileInfoNotifier.value = await _getFileInfo(path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -143,9 +142,10 @@ class _EditProfileState extends State<EditProfile> {
|
|||||||
_editProfileFile() async {
|
_editProfileFile() async {
|
||||||
if (rawText == null) {
|
if (rawText == null) {
|
||||||
final profilePath = await appPath.getProfilePath(widget.profile.id);
|
final profilePath = await appPath.getProfilePath(widget.profile.id);
|
||||||
if (profilePath == null) return;
|
|
||||||
final file = File(profilePath);
|
final file = File(profilePath);
|
||||||
rawText = await file.readAsString();
|
if (await file.exists()) {
|
||||||
|
rawText = await file.readAsString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final title = widget.profile.label ?? widget.profile.id;
|
final title = widget.profile.label ?? widget.profile.id;
|
||||||
@@ -214,6 +214,7 @@ class _EditProfileState extends State<EditProfile> {
|
|||||||
final items = [
|
final items = [
|
||||||
ListItem(
|
ListItem(
|
||||||
title: TextFormField(
|
title: TextFormField(
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
controller: labelController,
|
controller: labelController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
@@ -230,6 +231,8 @@ class _EditProfileState extends State<EditProfile> {
|
|||||||
if (widget.profile.type == ProfileType.url) ...[
|
if (widget.profile.type == ProfileType.url) ...[
|
||||||
ListItem(
|
ListItem(
|
||||||
title: TextFormField(
|
title: TextFormField(
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
controller: urlController,
|
controller: urlController,
|
||||||
maxLines: 5,
|
maxLines: 5,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
@@ -258,6 +261,7 @@ class _EditProfileState extends State<EditProfile> {
|
|||||||
if (autoUpdate)
|
if (autoUpdate)
|
||||||
ListItem(
|
ListItem(
|
||||||
title: TextFormField(
|
title: TextFormField(
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
controller: autoUpdateDurationController,
|
controller: autoUpdateDurationController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ class OverrideProfile extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _OverrideProfileState extends State<OverrideProfile> {
|
class _OverrideProfileState extends State<OverrideProfile> {
|
||||||
final GlobalKey<CacheItemExtentListViewState> _ruleListKey = GlobalKey();
|
|
||||||
final _controller = ScrollController();
|
final _controller = ScrollController();
|
||||||
double _currentMaxWidth = 0;
|
double _currentMaxWidth = 0;
|
||||||
|
|
||||||
@@ -86,13 +85,6 @@ class _OverrideProfileState extends State<OverrideProfile> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleTryClearCache(double maxWidth) {
|
|
||||||
if (_currentMaxWidth != maxWidth) {
|
|
||||||
_currentMaxWidth = maxWidth;
|
|
||||||
_ruleListKey.currentState?.clearCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_buildContent() {
|
_buildContent() {
|
||||||
return Consumer(
|
return Consumer(
|
||||||
builder: (_, ref, child) {
|
builder: (_, ref, child) {
|
||||||
@@ -116,7 +108,7 @@ class _OverrideProfileState extends State<OverrideProfile> {
|
|||||||
},
|
},
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (_, constraints) {
|
builder: (_, constraints) {
|
||||||
_handleTryClearCache(constraints.maxWidth - 104);
|
_currentMaxWidth = constraints.maxWidth - 104;
|
||||||
return CommonAutoHiddenScrollBar(
|
return CommonAutoHiddenScrollBar(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
@@ -148,7 +140,6 @@ class _OverrideProfileState extends State<OverrideProfile> {
|
|||||||
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 0),
|
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 0),
|
||||||
sliver: RuleContent(
|
sliver: RuleContent(
|
||||||
maxWidth: _currentMaxWidth,
|
maxWidth: _currentMaxWidth,
|
||||||
ruleListKey: _ruleListKey,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
@@ -228,7 +219,7 @@ class _OverrideProfileState extends State<OverrideProfile> {
|
|||||||
message: TextSpan(
|
message: TextSpan(
|
||||||
text: appLocalizations.saveTip,
|
text: appLocalizations.saveTip,
|
||||||
),
|
),
|
||||||
confirmText: appLocalizations.tip,
|
confirmText: appLocalizations.save,
|
||||||
);
|
);
|
||||||
if (res != true) {
|
if (res != true) {
|
||||||
return;
|
return;
|
||||||
@@ -314,8 +305,6 @@ class OverrideSwitch extends ConsumerWidget {
|
|||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
left: 16,
|
left: 16,
|
||||||
right: 16,
|
right: 16,
|
||||||
top: 4,
|
|
||||||
bottom: 4,
|
|
||||||
),
|
),
|
||||||
title: Text(appLocalizations.enableOverride),
|
title: Text(appLocalizations.enableOverride),
|
||||||
delegate: SwitchDelegate(
|
delegate: SwitchDelegate(
|
||||||
@@ -449,12 +438,10 @@ class RuleTitle extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class RuleContent extends ConsumerWidget {
|
class RuleContent extends ConsumerWidget {
|
||||||
final Key ruleListKey;
|
|
||||||
final double maxWidth;
|
final double maxWidth;
|
||||||
|
|
||||||
const RuleContent({
|
const RuleContent({
|
||||||
super.key,
|
super.key,
|
||||||
required this.ruleListKey,
|
|
||||||
required this.maxWidth,
|
required this.maxWidth,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -602,7 +589,7 @@ class RuleContent extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return CacheItemExtentSliverReorderableList(
|
return CacheItemExtentSliverReorderableList(
|
||||||
key: ruleListKey,
|
tag: CacheTag.rules,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final rule = rules[index];
|
final rule = rules[index];
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
@@ -873,6 +860,8 @@ class _AddRuleDialogState extends State<AddRuleDialog> {
|
|||||||
builder: (filed) {
|
builder: (filed) {
|
||||||
return DropdownMenu(
|
return DropdownMenu(
|
||||||
width: 200,
|
width: 200,
|
||||||
|
enableFilter: false,
|
||||||
|
enableSearch: false,
|
||||||
controller: _subRuleController,
|
controller: _subRuleController,
|
||||||
label: Text(appLocalizations.subRule),
|
label: Text(appLocalizations.subRule),
|
||||||
menuHeight: 250,
|
menuHeight: 250,
|
||||||
@@ -890,11 +879,11 @@ class _AddRuleDialogState extends State<AddRuleDialog> {
|
|||||||
builder: (filed) {
|
builder: (filed) {
|
||||||
return DropdownMenu(
|
return DropdownMenu(
|
||||||
controller: _ruleTargetController,
|
controller: _ruleTargetController,
|
||||||
initialSelection: filed.value,
|
|
||||||
label: Text(appLocalizations.ruleTarget),
|
label: Text(appLocalizations.ruleTarget),
|
||||||
width: 200,
|
width: 200,
|
||||||
menuHeight: 250,
|
menuHeight: 250,
|
||||||
enableFilter: true,
|
enableFilter: false,
|
||||||
|
enableSearch: false,
|
||||||
dropdownMenuEntries: _targetItems,
|
dropdownMenuEntries: _targetItems,
|
||||||
errorText: filed.errorText,
|
errorText: filed.errorText,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -349,12 +349,10 @@ class ProfileItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
PopupMenuItemData(
|
PopupMenuItemData(
|
||||||
icon: Icons.delete_outlined,
|
icon: Icons.delete_outlined,
|
||||||
iconSize: 20,
|
|
||||||
label: appLocalizations.delete,
|
label: appLocalizations.delete,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_handleDeleteProfile(context);
|
_handleDeleteProfile(context);
|
||||||
},
|
},
|
||||||
type: PopupMenuItemType.danger,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -370,7 +368,7 @@ class ProfileItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Container(
|
title: Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|||||||
@@ -133,7 +133,6 @@ class ProxyCard extends StatelessWidget {
|
|||||||
ref.watch(getSelectedProxyNameProvider(groupName));
|
ref.watch(getSelectedProxyNameProvider(groupName));
|
||||||
return CommonCard(
|
return CommonCard(
|
||||||
key: key,
|
key: key,
|
||||||
enterAnimated: true,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_changeProxy(ref);
|
_changeProxy(ref);
|
||||||
},
|
},
|
||||||
@@ -177,8 +176,8 @@ class ProxyCard extends StatelessWidget {
|
|||||||
proxy.type,
|
proxy.type,
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
color:
|
color: context
|
||||||
context.textTheme.bodySmall?.color?.opacity80,
|
.textTheme.bodySmall?.color?.opacity80,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:fl_clash/state.dart';
|
|||||||
|
|
||||||
double get listHeaderHeight {
|
double get listHeaderHeight {
|
||||||
final measure = globalState.measure;
|
final measure = globalState.measure;
|
||||||
return 24 + measure.titleMediumHeight + 4 + measure.bodyMediumHeight;
|
return 20 + measure.titleMediumHeight + 4 + measure.bodyMediumHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
double getItemHeight(ProxyCardType proxyCardType) {
|
double getItemHeight(ProxyCardType proxyCardType) {
|
||||||
|
|||||||
@@ -114,12 +114,16 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
|
|||||||
required int columns,
|
required int columns,
|
||||||
required Set<String> currentUnfoldSet,
|
required Set<String> currentUnfoldSet,
|
||||||
required ProxyCardType type,
|
required ProxyCardType type,
|
||||||
|
required String query,
|
||||||
}) {
|
}) {
|
||||||
final items = <Widget>[];
|
final items = <Widget>[];
|
||||||
final GroupNameProxiesMap groupNameProxiesMap = {};
|
final GroupNameProxiesMap groupNameProxiesMap = {};
|
||||||
for (final groupName in groupNames) {
|
for (final groupName in groupNames) {
|
||||||
final group =
|
final group = ref.read(
|
||||||
ref.read(groupsProvider.select((state) => state.getGroup(groupName)));
|
groupsProvider.select(
|
||||||
|
(state) => state.getGroup(groupName),
|
||||||
|
),
|
||||||
|
);
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -140,7 +144,9 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
|
|||||||
]);
|
]);
|
||||||
if (isExpand) {
|
if (isExpand) {
|
||||||
final sortedProxies = globalState.appController.getSortProxies(
|
final sortedProxies = globalState.appController.getSortProxies(
|
||||||
group.all,
|
group.all
|
||||||
|
.where((item) => item.name.toLowerCase().contains(query))
|
||||||
|
.toList(),
|
||||||
group.testUrl,
|
group.testUrl,
|
||||||
);
|
);
|
||||||
groupNameProxiesMap[groupName] = sortedProxies;
|
groupNameProxiesMap[groupName] = sortedProxies;
|
||||||
@@ -250,6 +256,7 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
|
|||||||
return Consumer(
|
return Consumer(
|
||||||
builder: (_, ref, __) {
|
builder: (_, ref, __) {
|
||||||
final state = ref.watch(proxiesListSelectorStateProvider);
|
final state = ref.watch(proxiesListSelectorStateProvider);
|
||||||
|
ref.watch(themeSettingProvider.select((state) => state.textScale));
|
||||||
if (state.groupNames.isEmpty) {
|
if (state.groupNames.isEmpty) {
|
||||||
return NullStatus(
|
return NullStatus(
|
||||||
label: appLocalizations.nullProxies,
|
label: appLocalizations.nullProxies,
|
||||||
@@ -261,6 +268,7 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
|
|||||||
currentUnfoldSet: state.currentUnfoldSet,
|
currentUnfoldSet: state.currentUnfoldSet,
|
||||||
columns: state.columns,
|
columns: state.columns,
|
||||||
type: state.proxyCardType,
|
type: state.proxyCardType,
|
||||||
|
query: state.query,
|
||||||
);
|
);
|
||||||
final itemsOffset = _getItemHeightList(items, state.proxyCardType);
|
final itemsOffset = _getItemHeightList(items, state.proxyCardType);
|
||||||
return CommonScrollBar(
|
return CommonScrollBar(
|
||||||
@@ -414,7 +422,10 @@ class _ListHeaderState extends State<ListHeader>
|
|||||||
return Consumer(
|
return Consumer(
|
||||||
builder: (_, ref, child) {
|
builder: (_, ref, child) {
|
||||||
final iconStyle = ref.watch(
|
final iconStyle = ref.watch(
|
||||||
proxiesStyleSettingProvider.select((state) => state.iconStyle));
|
proxiesStyleSettingProvider.select(
|
||||||
|
(state) => state.iconStyle,
|
||||||
|
),
|
||||||
|
);
|
||||||
final icon = ref.watch(proxiesStyleSettingProvider.select((state) {
|
final icon = ref.watch(proxiesStyleSettingProvider.select((state) {
|
||||||
final iconMapEntryList = state.iconMap.entries.toList();
|
final iconMapEntryList = state.iconMap.entries.toList();
|
||||||
final index = iconMapEntryList.indexWhere((item) {
|
final index = iconMapEntryList.indexWhere((item) {
|
||||||
@@ -430,30 +441,44 @@ class _ListHeaderState extends State<ListHeader>
|
|||||||
return this.icon;
|
return this.icon;
|
||||||
}));
|
}));
|
||||||
return switch (iconStyle) {
|
return switch (iconStyle) {
|
||||||
ProxiesIconStyle.standard => Container(
|
ProxiesIconStyle.standard => LayoutBuilder(
|
||||||
height: 48,
|
builder: (_, constraints) {
|
||||||
width: 48,
|
return Container(
|
||||||
margin: const EdgeInsets.only(
|
margin: const EdgeInsets.only(
|
||||||
right: 16,
|
right: 16,
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(8),
|
child: AspectRatio(
|
||||||
decoration: BoxDecoration(
|
aspectRatio: 1,
|
||||||
color: context.colorScheme.secondaryContainer,
|
child: Container(
|
||||||
borderRadius: BorderRadius.circular(12),
|
height: constraints.maxHeight,
|
||||||
),
|
width: constraints.maxWidth,
|
||||||
clipBehavior: Clip.antiAlias,
|
alignment: Alignment.center,
|
||||||
child: CommonTargetIcon(
|
padding: EdgeInsets.all(6.ap),
|
||||||
src: icon,
|
decoration: BoxDecoration(
|
||||||
size: 32,
|
color: context.colorScheme.secondaryContainer,
|
||||||
),
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: CommonTargetIcon(
|
||||||
|
src: icon,
|
||||||
|
size: constraints.maxHeight - 12.ap,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
ProxiesIconStyle.icon => Container(
|
ProxiesIconStyle.icon => Container(
|
||||||
margin: const EdgeInsets.only(
|
margin: const EdgeInsets.only(
|
||||||
right: 16,
|
right: 16,
|
||||||
),
|
),
|
||||||
child: CommonTargetIcon(
|
child: LayoutBuilder(
|
||||||
src: icon,
|
builder: (_, constraints) {
|
||||||
size: 42,
|
return CommonTargetIcon(
|
||||||
|
src: icon,
|
||||||
|
size: constraints.maxHeight - 8,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ProxiesIconStyle.none => Container(),
|
ProxiesIconStyle.none => Container(),
|
||||||
@@ -467,7 +492,7 @@ class _ListHeaderState extends State<ListHeader>
|
|||||||
return CommonCard(
|
return CommonCard(
|
||||||
enterAnimated: widget.enterAnimated,
|
enterAnimated: widget.enterAnimated,
|
||||||
key: widget.key,
|
key: widget.key,
|
||||||
radius: 14,
|
radius: 16.ap,
|
||||||
type: CommonCardType.filled,
|
type: CommonCardType.filled,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@@ -570,7 +595,10 @@ class _ListHeaderState extends State<ListHeader>
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 6,
|
width: 6,
|
||||||
),
|
),
|
||||||
],
|
] else
|
||||||
|
SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
AnimatedBuilder(
|
AnimatedBuilder(
|
||||||
animation: _animationController.view,
|
animation: _animationController.view,
|
||||||
builder: (_, __) {
|
builder: (_, __) {
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import 'package:fl_clash/common/common.dart';
|
|||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:fl_clash/fragments/proxies/list.dart';
|
import 'package:fl_clash/fragments/proxies/list.dart';
|
||||||
import 'package:fl_clash/fragments/proxies/providers.dart';
|
import 'package:fl_clash/fragments/proxies/providers.dart';
|
||||||
|
import 'package:fl_clash/models/common.dart';
|
||||||
import 'package:fl_clash/providers/providers.dart';
|
import 'package:fl_clash/providers/providers.dart';
|
||||||
import 'package:fl_clash/widgets/widgets.dart';
|
import 'package:fl_clash/widgets/widgets.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
import 'setting.dart';
|
import 'setting.dart';
|
||||||
import 'tab.dart';
|
import 'tab.dart';
|
||||||
@@ -25,70 +27,97 @@ class _ProxiesFragmentState extends ConsumerState<ProxiesFragment>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
get actions => [
|
get actions => [
|
||||||
if (_hasProviders)
|
CommonPopupBox(
|
||||||
IconButton(
|
targetBuilder: (open) {
|
||||||
onPressed: () {
|
return IconButton(
|
||||||
showExtend(
|
onPressed: () {
|
||||||
context,
|
open(
|
||||||
builder: (_, type) {
|
offset: Offset(0, 20),
|
||||||
return ProvidersView(
|
);
|
||||||
type: type,
|
},
|
||||||
);
|
icon: Icon(
|
||||||
},
|
Icons.more_vert,
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
icon: const Icon(
|
},
|
||||||
Icons.poll_outlined,
|
popup: CommonPopupMenu(
|
||||||
),
|
items: [
|
||||||
),
|
PopupMenuItemData(
|
||||||
_isTab
|
icon: Icons.tune,
|
||||||
? IconButton(
|
label: appLocalizations.settings,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_proxiesTabKey.currentState?.scrollToGroupSelected();
|
showSheet(
|
||||||
},
|
context: context,
|
||||||
icon: const Icon(
|
props: SheetProps(
|
||||||
Icons.adjust_outlined,
|
isScrollControlled: true,
|
||||||
),
|
),
|
||||||
)
|
|
||||||
: IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
showExtend(
|
|
||||||
context,
|
|
||||||
builder: (_, type) {
|
builder: (_, type) {
|
||||||
return AdaptiveSheetScaffold(
|
return AdaptiveSheetScaffold(
|
||||||
type: type,
|
type: type,
|
||||||
body: const _IconConfigView(),
|
body: const ProxiesSetting(),
|
||||||
title: appLocalizations.iconConfiguration,
|
title: appLocalizations.settings,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: const Icon(
|
),
|
||||||
Icons.style_outlined,
|
if (_hasProviders)
|
||||||
|
PopupMenuItemData(
|
||||||
|
icon: Icons.poll_outlined,
|
||||||
|
label: appLocalizations.providers,
|
||||||
|
onPressed: () {
|
||||||
|
showExtend(
|
||||||
|
context,
|
||||||
|
builder: (_, type) {
|
||||||
|
return ProvidersView(
|
||||||
|
type: type,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
_isTab
|
||||||
IconButton(
|
? PopupMenuItemData(
|
||||||
onPressed: () {
|
icon: Icons.adjust_outlined,
|
||||||
showSheet(
|
label: "聚焦",
|
||||||
context: context,
|
onPressed: () {
|
||||||
props: SheetProps(
|
_proxiesTabKey.currentState?.scrollToGroupSelected();
|
||||||
isScrollControlled: true,
|
},
|
||||||
),
|
)
|
||||||
builder: (_, type) {
|
: PopupMenuItemData(
|
||||||
return AdaptiveSheetScaffold(
|
icon: Icons.style_outlined,
|
||||||
type: type,
|
label: appLocalizations.iconConfiguration,
|
||||||
body: const ProxiesSetting(),
|
onPressed: () {
|
||||||
title: appLocalizations.proxiesSetting,
|
showExtend(
|
||||||
);
|
context,
|
||||||
},
|
builder: (_, type) {
|
||||||
);
|
return AdaptiveSheetScaffold(
|
||||||
},
|
type: type,
|
||||||
icon: const Icon(
|
body: const _IconConfigView(),
|
||||||
Icons.tune,
|
title: appLocalizations.iconConfiguration,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
get onSearch => (value) {
|
||||||
|
ref.read(proxiesQueryProvider.notifier).value = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
ref.read(proxiesQueryProvider.notifier).value = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get floatingActionButton => _isTab
|
get floatingActionButton => _isTab
|
||||||
? DelayTestButton(
|
? DelayTestButton(
|
||||||
@@ -103,6 +132,70 @@ class _ProxiesFragmentState extends ConsumerState<ProxiesFragment>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
[
|
||||||
|
if (_hasProviders)
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
showExtend(
|
||||||
|
context,
|
||||||
|
builder: (_, type) {
|
||||||
|
return ProvidersView(
|
||||||
|
type: type,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.poll_outlined,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_isTab
|
||||||
|
? IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
_proxiesTabKey.currentState?.scrollToGroupSelected();
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.adjust_outlined,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
showExtend(
|
||||||
|
context,
|
||||||
|
builder: (_, type) {
|
||||||
|
return AdaptiveSheetScaffold(
|
||||||
|
type: type,
|
||||||
|
body: const _IconConfigView(),
|
||||||
|
title: appLocalizations.iconConfiguration,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.style_outlined,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
showSheet(
|
||||||
|
context: context,
|
||||||
|
props: SheetProps(
|
||||||
|
isScrollControlled: true,
|
||||||
|
),
|
||||||
|
builder: (_, type) {
|
||||||
|
return AdaptiveSheetScaffold(
|
||||||
|
type: type,
|
||||||
|
body: const ProxiesSetting(),
|
||||||
|
title: appLocalizations.settings,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.tune,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
];
|
||||||
ref.listenManual(
|
ref.listenManual(
|
||||||
proxiesActionsStateProvider,
|
proxiesActionsStateProvider,
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
@@ -123,8 +216,11 @@ class _ProxiesFragmentState extends ConsumerState<ProxiesFragment>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final proxiesType =
|
final proxiesType = ref.watch(
|
||||||
ref.watch(proxiesStyleSettingProvider.select((state) => state.type));
|
proxiesStyleSettingProvider.select(
|
||||||
|
(state) => state.type,
|
||||||
|
),
|
||||||
|
);
|
||||||
return switch (proxiesType) {
|
return switch (proxiesType) {
|
||||||
ProxiesType.tab => ProxiesTabFragment(
|
ProxiesType.tab => ProxiesTabFragment(
|
||||||
key: _proxiesTabKey,
|
key: _proxiesTabKey,
|
||||||
@@ -139,8 +235,9 @@ class _IconConfigView extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final iconMap =
|
final iconMap = ref.watch(proxiesStyleSettingProvider.select(
|
||||||
ref.watch(proxiesStyleSettingProvider.select((state) => state.iconMap));
|
(state) => state.iconMap,
|
||||||
|
));
|
||||||
return MapInputPage(
|
return MapInputPage(
|
||||||
title: appLocalizations.iconConfiguration,
|
title: appLocalizations.iconConfiguration,
|
||||||
map: iconMap,
|
map: iconMap,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/models/common.dart';
|
|
||||||
import 'package:fl_clash/providers/providers.dart';
|
import 'package:fl_clash/providers/providers.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:fl_clash/widgets/widgets.dart';
|
import 'package:fl_clash/widgets/widgets.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../models/common.dart';
|
||||||
import 'card.dart';
|
import 'card.dart';
|
||||||
import 'common.dart';
|
import 'common.dart';
|
||||||
|
|
||||||
@@ -127,8 +127,6 @@ class ProxiesTabFragmentState extends ConsumerState<ProxiesTabFragment>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final currentGroup = currentGroups[groupIndex];
|
final currentGroup = currentGroups[groupIndex];
|
||||||
currentTabProxies = currentGroup.all;
|
|
||||||
currentTabTestUrl = currentGroup.testUrl;
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
globalState.appController.updateCurrentGroupName(
|
globalState.appController.updateCurrentGroupName(
|
||||||
currentGroup.name,
|
currentGroup.name,
|
||||||
@@ -164,7 +162,7 @@ class ProxiesTabFragmentState extends ConsumerState<ProxiesTabFragment>
|
|||||||
if (prev == next) {
|
if (prev == next) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (prev?.groupNames.length != next.groupNames.length) {
|
if (!stringListEquality.equals(prev?.groupNames, next.groupNames)) {
|
||||||
_destroyTabController();
|
_destroyTabController();
|
||||||
final index = next.groupNames.indexWhere(
|
final index = next.groupNames.indexWhere(
|
||||||
(item) => item == next.currentGroupName,
|
(item) => item == next.currentGroupName,
|
||||||
@@ -178,6 +176,7 @@ class ProxiesTabFragmentState extends ConsumerState<ProxiesTabFragment>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
ref.watch(themeSettingProvider.select((state) => state.textScale));
|
||||||
final state = ref.watch(groupNamesStateProvider);
|
final state = ref.watch(groupNamesStateProvider);
|
||||||
final groupNames = state.groupNames;
|
final groupNames = state.groupNames;
|
||||||
if (groupNames.isEmpty) {
|
if (groupNames.isEmpty) {
|
||||||
@@ -294,17 +293,12 @@ class ProxyGroupViewState extends ConsumerState<ProxyGroupView> {
|
|||||||
if (_controller.position.maxScrollExtent == 0) {
|
if (_controller.position.maxScrollExtent == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final sortedProxies = globalState.appController.getSortProxies(
|
|
||||||
currentTabProxies,
|
|
||||||
currentTabTestUrl,
|
|
||||||
);
|
|
||||||
_controller.animateTo(
|
_controller.animateTo(
|
||||||
min(
|
min(
|
||||||
16 +
|
16 +
|
||||||
getScrollToSelectedOffset(
|
getScrollToSelectedOffset(
|
||||||
groupName: groupName,
|
groupName: groupName,
|
||||||
proxies: sortedProxies,
|
proxies: currentTabProxies,
|
||||||
),
|
),
|
||||||
_controller.position.maxScrollExtent,
|
_controller.position.maxScrollExtent,
|
||||||
),
|
),
|
||||||
@@ -323,6 +317,8 @@ class ProxyGroupViewState extends ConsumerState<ProxyGroupView> {
|
|||||||
proxies,
|
proxies,
|
||||||
state.testUrl,
|
state.testUrl,
|
||||||
);
|
);
|
||||||
|
currentTabProxies = sortedProxies;
|
||||||
|
currentTabTestUrl = state.testUrl;
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
child: CommonAutoHiddenScrollBar(
|
child: CommonAutoHiddenScrollBar(
|
||||||
|
|||||||
@@ -147,22 +147,21 @@ class _GeoDataListItemState extends State<GeoDataListItem> {
|
|||||||
FutureBuilder<FileInfo>(
|
FutureBuilder<FileInfo>(
|
||||||
future: _getGeoFileLastModified(geoItem.fileName),
|
future: _getGeoFileLastModified(geoItem.fileName),
|
||||||
builder: (_, snapshot) {
|
builder: (_, snapshot) {
|
||||||
|
final height = globalState.measure.bodyMediumHeight;
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 24,
|
height: height,
|
||||||
child: FadeThroughBox(
|
child: snapshot.data == null
|
||||||
key: Key("fade_box_${geoItem.label}"),
|
? SizedBox(
|
||||||
child: snapshot.data == null
|
width: height,
|
||||||
? const SizedBox(
|
height: height,
|
||||||
width: 24,
|
child: CircularProgressIndicator(
|
||||||
height: 24,
|
strokeWidth: 2,
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
snapshot.data!.desc,
|
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
|
: Text(
|
||||||
|
snapshot.data!.desc,
|
||||||
|
style: context.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// ignore_for_file: deprecated_member_use
|
||||||
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
@@ -39,7 +41,20 @@ class ThemeFragment extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SingleChildScrollView(child: ThemeColorsBox());
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
_ThemeModeItem(),
|
||||||
|
_PrimaryColorItem(),
|
||||||
|
_PrueBlackItem(),
|
||||||
|
_TextScaleFactorItem(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 64,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,42 +72,14 @@ class ItemCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Wrap(
|
||||||
padding: const EdgeInsets.only(
|
runSpacing: 16,
|
||||||
top: 16,
|
|
||||||
),
|
|
||||||
child: Wrap(
|
|
||||||
runSpacing: 16,
|
|
||||||
children: [
|
|
||||||
InfoHeader(
|
|
||||||
info: info,
|
|
||||||
actions: actions,
|
|
||||||
),
|
|
||||||
child,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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: [
|
children: [
|
||||||
_ThemeModeItem(),
|
InfoHeader(
|
||||||
_PrimaryColorItem(),
|
info: info,
|
||||||
_PrueBlackItem(),
|
actions: actions,
|
||||||
const SizedBox(
|
|
||||||
height: 64,
|
|
||||||
),
|
),
|
||||||
|
child,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -296,137 +283,153 @@ class _PrimaryColorItemState extends ConsumerState<_PrimaryColorItem> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final vm3 = ref.watch(
|
final vm4 = ref.watch(
|
||||||
themeSettingProvider.select(
|
themeSettingProvider.select(
|
||||||
(state) => VM3(
|
(state) => VM4(
|
||||||
a: state.primaryColor,
|
a: state.primaryColor,
|
||||||
b: state.primaryColors,
|
b: state.primaryColors,
|
||||||
c: state.schemeVariant,
|
c: state.schemeVariant,
|
||||||
|
d: state.primaryColor == defaultPrimaryColor &&
|
||||||
|
intListEquality.equals(state.primaryColors, defaultPrimaryColors),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final primaryColor = vm3.a;
|
final primaryColor = vm4.a;
|
||||||
final primaryColors = [null, ...vm3.b];
|
final primaryColors = [null, ...vm4.b];
|
||||||
final schemeVariant = vm3.c;
|
final schemeVariant = vm4.c;
|
||||||
|
final isEquals = vm4.d;
|
||||||
|
|
||||||
return ItemCard(
|
return CommonPopScope(
|
||||||
info: Info(
|
onPop: () {
|
||||||
label: appLocalizations.themeColor,
|
if (_removablePrimaryColor != null) {
|
||||||
iconData: Icons.palette,
|
setState(() {
|
||||||
),
|
_removablePrimaryColor = null;
|
||||||
actions: genActions(
|
});
|
||||||
[
|
return false;
|
||||||
if (_removablePrimaryColor == null)
|
}
|
||||||
FilledButton(
|
return true;
|
||||||
style: ButtonStyle(
|
},
|
||||||
visualDensity: VisualDensity.compact,
|
child: ItemCard(
|
||||||
),
|
info: Info(
|
||||||
onPressed: _handleChangeSchemeVariant,
|
label: appLocalizations.themeColor,
|
||||||
child: Text(Intl.message("${schemeVariant.name}Scheme")),
|
iconData: Icons.palette,
|
||||||
),
|
|
||||||
_removablePrimaryColor != null
|
|
||||||
? FilledButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
visualDensity: VisualDensity.compact,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_removablePrimaryColor = null;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Text(appLocalizations.cancel),
|
|
||||||
)
|
|
||||||
: IconButton.filledTonal(
|
|
||||||
iconSize: 20,
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
visualDensity: VisualDensity.compact,
|
|
||||||
onPressed: _handleReset,
|
|
||||||
icon: Icon(Icons.replay),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
space: 8,
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
right: 16,
|
|
||||||
bottom: 16,
|
|
||||||
),
|
),
|
||||||
child: LayoutBuilder(
|
actions: genActions(
|
||||||
builder: (_, constraints) {
|
[
|
||||||
final columns = _calcColumns(constraints.maxWidth);
|
if (_removablePrimaryColor == null)
|
||||||
final itemWidth =
|
FilledButton(
|
||||||
(constraints.maxWidth - (columns - 1) * 16) / columns;
|
style: ButtonStyle(
|
||||||
return Wrap(
|
visualDensity: VisualDensity.compact,
|
||||||
spacing: 16,
|
),
|
||||||
runSpacing: 16,
|
onPressed: _handleChangeSchemeVariant,
|
||||||
children: [
|
child: Text(Intl.message("${schemeVariant.name}Scheme")),
|
||||||
for (final color in primaryColors)
|
),
|
||||||
Container(
|
if (_removablePrimaryColor != null)
|
||||||
clipBehavior: Clip.none,
|
FilledButton(
|
||||||
width: itemWidth,
|
style: ButtonStyle(
|
||||||
height: itemWidth,
|
visualDensity: VisualDensity.compact,
|
||||||
child: Stack(
|
),
|
||||||
alignment: Alignment.center,
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_removablePrimaryColor = null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text(appLocalizations.cancel),
|
||||||
|
),
|
||||||
|
if (_removablePrimaryColor == null && !isEquals)
|
||||||
|
IconButton.filledTonal(
|
||||||
|
iconSize: 20,
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
onPressed: _handleReset,
|
||||||
|
icon: Icon(Icons.replay),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
space: 8,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (_, constraints) {
|
||||||
|
final columns = _calcColumns(constraints.maxWidth);
|
||||||
|
final itemWidth =
|
||||||
|
(constraints.maxWidth - (columns - 1) * 16) / columns;
|
||||||
|
return Wrap(
|
||||||
|
spacing: 16,
|
||||||
|
runSpacing: 16,
|
||||||
|
children: [
|
||||||
|
for (final color in primaryColors)
|
||||||
|
Container(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
width: itemWidth,
|
||||||
EffectGestureDetector(
|
height: itemWidth,
|
||||||
child: ColorSchemeBox(
|
child: Stack(
|
||||||
isSelected: color == primaryColor,
|
alignment: Alignment.center,
|
||||||
primaryColor: color != null ? Color(color) : null,
|
clipBehavior: Clip.none,
|
||||||
onPressed: () {
|
children: [
|
||||||
ref
|
EffectGestureDetector(
|
||||||
.read(themeSettingProvider.notifier)
|
child: ColorSchemeBox(
|
||||||
.updateState(
|
isSelected: color == primaryColor,
|
||||||
(state) => state.copyWith(
|
primaryColor: color != null ? Color(color) : null,
|
||||||
primaryColor: color,
|
onPressed: () {
|
||||||
),
|
setState(() {
|
||||||
);
|
_removablePrimaryColor = null;
|
||||||
|
});
|
||||||
|
ref
|
||||||
|
.read(themeSettingProvider.notifier)
|
||||||
|
.updateState(
|
||||||
|
(state) => state.copyWith(
|
||||||
|
primaryColor: color,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onLongPress: () {
|
||||||
|
setState(() {
|
||||||
|
_removablePrimaryColor = color;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
onLongPress: () {
|
if (_removablePrimaryColor != null &&
|
||||||
setState(() {
|
_removablePrimaryColor == color)
|
||||||
_removablePrimaryColor = color;
|
Container(
|
||||||
});
|
color: Colors.white.opacity0,
|
||||||
},
|
padding: EdgeInsets.all(8),
|
||||||
),
|
child: IconButton.filledTonal(
|
||||||
if (_removablePrimaryColor != null &&
|
onPressed: _handleDel,
|
||||||
_removablePrimaryColor == color)
|
padding: EdgeInsets.all(12),
|
||||||
Container(
|
iconSize: 30,
|
||||||
color: Colors.white.opacity0,
|
icon: Icon(
|
||||||
padding: EdgeInsets.all(8),
|
color: context.colorScheme.primary,
|
||||||
child: IconButton.filledTonal(
|
Icons.delete,
|
||||||
onPressed: _handleDel,
|
),
|
||||||
padding: EdgeInsets.all(12),
|
|
||||||
iconSize: 30,
|
|
||||||
icon: Icon(
|
|
||||||
color: context.colorScheme.primary,
|
|
||||||
Icons.delete,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_removablePrimaryColor == null)
|
|
||||||
Container(
|
|
||||||
width: itemWidth,
|
|
||||||
height: itemWidth,
|
|
||||||
padding: EdgeInsets.all(
|
|
||||||
4,
|
|
||||||
),
|
|
||||||
child: IconButton.filledTonal(
|
|
||||||
onPressed: _handleAdd,
|
|
||||||
iconSize: 32,
|
|
||||||
icon: Icon(
|
|
||||||
color: context.colorScheme.primary,
|
|
||||||
Icons.add,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
if (_removablePrimaryColor == null)
|
||||||
],
|
Container(
|
||||||
);
|
width: itemWidth,
|
||||||
},
|
height: itemWidth,
|
||||||
|
padding: EdgeInsets.all(
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
child: IconButton.filledTonal(
|
||||||
|
onPressed: _handleAdd,
|
||||||
|
iconSize: 32,
|
||||||
|
icon: Icon(
|
||||||
|
color: context.colorScheme.primary,
|
||||||
|
Icons.add,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -438,33 +441,118 @@ class _PrueBlackItem extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final prueBlack =
|
final prueBlack = ref.watch(
|
||||||
ref.watch(themeSettingProvider.select((state) => state.pureBlack));
|
themeSettingProvider.select(
|
||||||
return Padding(
|
(state) => state.pureBlack,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
child: ListItem.switchItem(
|
|
||||||
leading: Icon(
|
|
||||||
Icons.contrast,
|
|
||||||
),
|
|
||||||
horizontalTitleGap: 12,
|
|
||||||
title: Text(
|
|
||||||
appLocalizations.pureBlackMode,
|
|
||||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
|
||||||
color: context.colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
delegate: SwitchDelegate(
|
|
||||||
value: prueBlack,
|
|
||||||
onChanged: (value) {
|
|
||||||
ref.read(themeSettingProvider.notifier).updateState(
|
|
||||||
(state) => state.copyWith(
|
|
||||||
pureBlack: value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
return ListItem.switchItem(
|
||||||
|
leading: Icon(
|
||||||
|
Icons.contrast,
|
||||||
|
),
|
||||||
|
horizontalTitleGap: 12,
|
||||||
|
title: Text(
|
||||||
|
appLocalizations.pureBlackMode,
|
||||||
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
|
color: context.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
delegate: SwitchDelegate(
|
||||||
|
value: prueBlack,
|
||||||
|
onChanged: (value) {
|
||||||
|
ref.read(themeSettingProvider.notifier).updateState(
|
||||||
|
(state) => state.copyWith(
|
||||||
|
pureBlack: value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: EdgeInsets.only(bottom: 8),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: DisabledMask(
|
||||||
|
status: !textScale.enable,
|
||||||
|
child: ActivateBox(
|
||||||
|
active: textScale.enable,
|
||||||
|
child: SliderTheme(
|
||||||
|
data: _SliderDefaultsM3(context),
|
||||||
|
child: Slider(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
min: minTextScale,
|
||||||
|
max: maxTextScale,
|
||||||
|
value: textScale.scale,
|
||||||
|
onChanged: (value) {
|
||||||
|
ref.read(themeSettingProvider.notifier).updateState(
|
||||||
|
(state) => state.copyWith.textScale(
|
||||||
|
scale: value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(right: 4),
|
||||||
|
child: Text(
|
||||||
|
process,
|
||||||
|
style: context.textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,3 +618,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:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'backup_and_recovery.dart';
|
import 'backup_and_recovery.dart';
|
||||||
|
import 'developer.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
import 'package:path/path.dart' show dirname, join;
|
import 'package:path/path.dart' show dirname, join;
|
||||||
|
|
||||||
@@ -54,11 +55,12 @@ class _ToolboxFragmentState extends ConsumerState<ToolsFragment> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _getOtherList() {
|
List<Widget> _getOtherList(bool enableDeveloperMode) {
|
||||||
return generateSection(
|
return generateSection(
|
||||||
title: appLocalizations.other,
|
title: appLocalizations.other,
|
||||||
items: [
|
items: [
|
||||||
_DisclaimerItem(),
|
_DisclaimerItem(),
|
||||||
|
if (enableDeveloperMode) _DeveloperItem(),
|
||||||
_InfoItem(),
|
_InfoItem(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -82,7 +84,11 @@ class _ToolboxFragmentState extends ConsumerState<ToolsFragment> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
ref.watch(appSettingProvider.select((state) => state.locale));
|
final vm2 = ref.watch(
|
||||||
|
appSettingProvider.select(
|
||||||
|
(state) => VM2(a: state.locale, b: state.developerMode),
|
||||||
|
),
|
||||||
|
);
|
||||||
final items = [
|
final items = [
|
||||||
Consumer(
|
Consumer(
|
||||||
builder: (_, ref, __) {
|
builder: (_, ref, __) {
|
||||||
@@ -99,7 +105,7 @@ class _ToolboxFragmentState extends ConsumerState<ToolsFragment> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
..._getSettingList(),
|
..._getSettingList(),
|
||||||
..._getOtherList(),
|
..._getOtherList(vm2.b),
|
||||||
];
|
];
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: items.length,
|
itemCount: items.length,
|
||||||
@@ -297,3 +303,19 @@ class _InfoItem extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _DeveloperItem extends StatelessWidget {
|
||||||
|
const _DeveloperItem();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListItem.open(
|
||||||
|
leading: const Icon(Icons.developer_board),
|
||||||
|
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",
|
"The current application is already the latest version",
|
||||||
),
|
),
|
||||||
"checking": MessageLookupByLibrary.simpleMessage("Checking..."),
|
"checking": MessageLookupByLibrary.simpleMessage("Checking..."),
|
||||||
|
"clearData": MessageLookupByLibrary.simpleMessage("Clear Data"),
|
||||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("Export clipboard"),
|
"clipboardExport": MessageLookupByLibrary.simpleMessage("Export clipboard"),
|
||||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("Clipboard import"),
|
"clipboardImport": MessageLookupByLibrary.simpleMessage("Clipboard import"),
|
||||||
"colorExists": MessageLookupByLibrary.simpleMessage(
|
"colorExists": MessageLookupByLibrary.simpleMessage(
|
||||||
@@ -163,6 +164,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"View current connections data",
|
"View current connections data",
|
||||||
),
|
),
|
||||||
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
|
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
|
||||||
|
"contactMe": MessageLookupByLibrary.simpleMessage("Contact me"),
|
||||||
"content": MessageLookupByLibrary.simpleMessage("Content"),
|
"content": MessageLookupByLibrary.simpleMessage("Content"),
|
||||||
"contentEmptyTip": MessageLookupByLibrary.simpleMessage(
|
"contentEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||||
"Content cannot be empty",
|
"Content cannot be empty",
|
||||||
@@ -177,6 +179,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"core": MessageLookupByLibrary.simpleMessage("Core"),
|
"core": MessageLookupByLibrary.simpleMessage("Core"),
|
||||||
"coreInfo": MessageLookupByLibrary.simpleMessage("Core info"),
|
"coreInfo": MessageLookupByLibrary.simpleMessage("Core info"),
|
||||||
"country": MessageLookupByLibrary.simpleMessage("Country"),
|
"country": MessageLookupByLibrary.simpleMessage("Country"),
|
||||||
|
"crashTest": MessageLookupByLibrary.simpleMessage("Crash test"),
|
||||||
"create": MessageLookupByLibrary.simpleMessage("Create"),
|
"create": MessageLookupByLibrary.simpleMessage("Create"),
|
||||||
"cut": MessageLookupByLibrary.simpleMessage("Cut"),
|
"cut": MessageLookupByLibrary.simpleMessage("Cut"),
|
||||||
"dark": MessageLookupByLibrary.simpleMessage("Dark"),
|
"dark": MessageLookupByLibrary.simpleMessage("Dark"),
|
||||||
@@ -208,6 +211,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"detectionTip": MessageLookupByLibrary.simpleMessage(
|
"detectionTip": MessageLookupByLibrary.simpleMessage(
|
||||||
"Relying on third-party api is for reference only",
|
"Relying on third-party api is for reference only",
|
||||||
),
|
),
|
||||||
|
"developerMode": MessageLookupByLibrary.simpleMessage("Developer mode"),
|
||||||
|
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Developer mode is enabled.",
|
||||||
|
),
|
||||||
"direct": MessageLookupByLibrary.simpleMessage("Direct"),
|
"direct": MessageLookupByLibrary.simpleMessage("Direct"),
|
||||||
"disclaimer": MessageLookupByLibrary.simpleMessage("Disclaimer"),
|
"disclaimer": MessageLookupByLibrary.simpleMessage("Disclaimer"),
|
||||||
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
@@ -320,6 +327,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"intelligentSelected": MessageLookupByLibrary.simpleMessage(
|
"intelligentSelected": MessageLookupByLibrary.simpleMessage(
|
||||||
"Intelligent selection",
|
"Intelligent selection",
|
||||||
),
|
),
|
||||||
|
"internet": MessageLookupByLibrary.simpleMessage("Internet"),
|
||||||
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
|
"intranetIP": MessageLookupByLibrary.simpleMessage("Intranet IP"),
|
||||||
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
|
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
|
||||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
|
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
|
||||||
@@ -356,12 +364,17 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
),
|
),
|
||||||
"logs": MessageLookupByLibrary.simpleMessage("Logs"),
|
"logs": MessageLookupByLibrary.simpleMessage("Logs"),
|
||||||
"logsDesc": MessageLookupByLibrary.simpleMessage("Log capture records"),
|
"logsDesc": MessageLookupByLibrary.simpleMessage("Log capture records"),
|
||||||
|
"logsTest": MessageLookupByLibrary.simpleMessage("Logs test"),
|
||||||
"loopback": MessageLookupByLibrary.simpleMessage("Loopback unlock tool"),
|
"loopback": MessageLookupByLibrary.simpleMessage("Loopback unlock tool"),
|
||||||
"loopbackDesc": MessageLookupByLibrary.simpleMessage(
|
"loopbackDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"Used for UWP loopback unlocking",
|
"Used for UWP loopback unlocking",
|
||||||
),
|
),
|
||||||
"loose": MessageLookupByLibrary.simpleMessage("Loose"),
|
"loose": MessageLookupByLibrary.simpleMessage("Loose"),
|
||||||
"memoryInfo": MessageLookupByLibrary.simpleMessage("Memory info"),
|
"memoryInfo": MessageLookupByLibrary.simpleMessage("Memory info"),
|
||||||
|
"messageTest": MessageLookupByLibrary.simpleMessage("Message test"),
|
||||||
|
"messageTestTip": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"This is a message.",
|
||||||
|
),
|
||||||
"min": MessageLookupByLibrary.simpleMessage("Min"),
|
"min": MessageLookupByLibrary.simpleMessage("Min"),
|
||||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("Minimize on exit"),
|
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("Minimize on exit"),
|
||||||
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
|
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
@@ -399,6 +412,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"noInfo": MessageLookupByLibrary.simpleMessage("No info"),
|
"noInfo": MessageLookupByLibrary.simpleMessage("No info"),
|
||||||
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("No more info"),
|
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("No more info"),
|
||||||
"noNetwork": MessageLookupByLibrary.simpleMessage("No network"),
|
"noNetwork": MessageLookupByLibrary.simpleMessage("No network"),
|
||||||
|
"noNetworkApp": MessageLookupByLibrary.simpleMessage("No network APP"),
|
||||||
"noProxy": MessageLookupByLibrary.simpleMessage("No proxy"),
|
"noProxy": MessageLookupByLibrary.simpleMessage("No proxy"),
|
||||||
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
|
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"Please create a profile or add a valid profile",
|
"Please create a profile or add a valid profile",
|
||||||
@@ -526,6 +540,15 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
|
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
|
||||||
"Only recovery profiles",
|
"Only recovery profiles",
|
||||||
),
|
),
|
||||||
|
"recoveryStrategy": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Recovery strategy",
|
||||||
|
),
|
||||||
|
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Compatible",
|
||||||
|
),
|
||||||
|
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Override",
|
||||||
|
),
|
||||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("Recovery success"),
|
"recoverySuccess": MessageLookupByLibrary.simpleMessage("Recovery success"),
|
||||||
"redo": MessageLookupByLibrary.simpleMessage("redo"),
|
"redo": MessageLookupByLibrary.simpleMessage("redo"),
|
||||||
"regExp": MessageLookupByLibrary.simpleMessage("RegExp"),
|
"regExp": MessageLookupByLibrary.simpleMessage("RegExp"),
|
||||||
@@ -612,6 +635,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"submit": MessageLookupByLibrary.simpleMessage("Submit"),
|
"submit": MessageLookupByLibrary.simpleMessage("Submit"),
|
||||||
"sync": MessageLookupByLibrary.simpleMessage("Sync"),
|
"sync": MessageLookupByLibrary.simpleMessage("Sync"),
|
||||||
"system": MessageLookupByLibrary.simpleMessage("System"),
|
"system": MessageLookupByLibrary.simpleMessage("System"),
|
||||||
|
"systemApp": MessageLookupByLibrary.simpleMessage("System APP"),
|
||||||
"systemFont": MessageLookupByLibrary.simpleMessage("System font"),
|
"systemFont": MessageLookupByLibrary.simpleMessage("System font"),
|
||||||
"systemProxy": MessageLookupByLibrary.simpleMessage("System proxy"),
|
"systemProxy": MessageLookupByLibrary.simpleMessage("System proxy"),
|
||||||
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(
|
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
@@ -627,6 +651,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"Enabling it will allow TCP concurrency",
|
"Enabling it will allow TCP concurrency",
|
||||||
),
|
),
|
||||||
"testUrl": MessageLookupByLibrary.simpleMessage("Test url"),
|
"testUrl": MessageLookupByLibrary.simpleMessage("Test url"),
|
||||||
|
"textScale": MessageLookupByLibrary.simpleMessage("Text Scaling"),
|
||||||
"theme": MessageLookupByLibrary.simpleMessage("Theme"),
|
"theme": MessageLookupByLibrary.simpleMessage("Theme"),
|
||||||
"themeColor": MessageLookupByLibrary.simpleMessage("Theme color"),
|
"themeColor": MessageLookupByLibrary.simpleMessage("Theme color"),
|
||||||
"themeDesc": MessageLookupByLibrary.simpleMessage(
|
"themeDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"checkUpdate": MessageLookupByLibrary.simpleMessage("更新を確認"),
|
"checkUpdate": MessageLookupByLibrary.simpleMessage("更新を確認"),
|
||||||
"checkUpdateError": MessageLookupByLibrary.simpleMessage("アプリは最新版です"),
|
"checkUpdateError": MessageLookupByLibrary.simpleMessage("アプリは最新版です"),
|
||||||
"checking": MessageLookupByLibrary.simpleMessage("確認中..."),
|
"checking": MessageLookupByLibrary.simpleMessage("確認中..."),
|
||||||
|
"clearData": MessageLookupByLibrary.simpleMessage("データを消去"),
|
||||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("クリップボードにエクスポート"),
|
"clipboardExport": MessageLookupByLibrary.simpleMessage("クリップボードにエクスポート"),
|
||||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("クリップボードからインポート"),
|
"clipboardImport": MessageLookupByLibrary.simpleMessage("クリップボードからインポート"),
|
||||||
"colorExists": MessageLookupByLibrary.simpleMessage("この色は既に存在します"),
|
"colorExists": MessageLookupByLibrary.simpleMessage("この色は既に存在します"),
|
||||||
@@ -117,6 +118,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"connections": MessageLookupByLibrary.simpleMessage("接続"),
|
"connections": MessageLookupByLibrary.simpleMessage("接続"),
|
||||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage("現在の接続データを表示"),
|
"connectionsDesc": MessageLookupByLibrary.simpleMessage("現在の接続データを表示"),
|
||||||
"connectivity": MessageLookupByLibrary.simpleMessage("接続性:"),
|
"connectivity": MessageLookupByLibrary.simpleMessage("接続性:"),
|
||||||
|
"contactMe": MessageLookupByLibrary.simpleMessage("連絡する"),
|
||||||
"content": MessageLookupByLibrary.simpleMessage("内容"),
|
"content": MessageLookupByLibrary.simpleMessage("内容"),
|
||||||
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容は必須です"),
|
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容は必須です"),
|
||||||
"contentScheme": MessageLookupByLibrary.simpleMessage("コンテンツテーマ"),
|
"contentScheme": MessageLookupByLibrary.simpleMessage("コンテンツテーマ"),
|
||||||
@@ -127,6 +129,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"core": MessageLookupByLibrary.simpleMessage("コア"),
|
"core": MessageLookupByLibrary.simpleMessage("コア"),
|
||||||
"coreInfo": MessageLookupByLibrary.simpleMessage("コア情報"),
|
"coreInfo": MessageLookupByLibrary.simpleMessage("コア情報"),
|
||||||
"country": MessageLookupByLibrary.simpleMessage("国"),
|
"country": MessageLookupByLibrary.simpleMessage("国"),
|
||||||
|
"crashTest": MessageLookupByLibrary.simpleMessage("クラッシュテスト"),
|
||||||
"create": MessageLookupByLibrary.simpleMessage("作成"),
|
"create": MessageLookupByLibrary.simpleMessage("作成"),
|
||||||
"cut": MessageLookupByLibrary.simpleMessage("切り取り"),
|
"cut": MessageLookupByLibrary.simpleMessage("切り取り"),
|
||||||
"dark": MessageLookupByLibrary.simpleMessage("ダーク"),
|
"dark": MessageLookupByLibrary.simpleMessage("ダーク"),
|
||||||
@@ -150,6 +153,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"ClashMetaベースのマルチプラットフォームプロキシクライアント。シンプルで使いやすく、オープンソースで広告なし。",
|
"ClashMetaベースのマルチプラットフォームプロキシクライアント。シンプルで使いやすく、オープンソースで広告なし。",
|
||||||
),
|
),
|
||||||
"detectionTip": MessageLookupByLibrary.simpleMessage("サードパーティAPIに依存(参考値)"),
|
"detectionTip": MessageLookupByLibrary.simpleMessage("サードパーティAPIに依存(参考値)"),
|
||||||
|
"developerMode": MessageLookupByLibrary.simpleMessage("デベロッパーモード"),
|
||||||
|
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"デベロッパーモードが有効になりました。",
|
||||||
|
),
|
||||||
"direct": MessageLookupByLibrary.simpleMessage("ダイレクト"),
|
"direct": MessageLookupByLibrary.simpleMessage("ダイレクト"),
|
||||||
"disclaimer": MessageLookupByLibrary.simpleMessage("免責事項"),
|
"disclaimer": MessageLookupByLibrary.simpleMessage("免責事項"),
|
||||||
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
@@ -230,6 +237,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"init": MessageLookupByLibrary.simpleMessage("初期化"),
|
"init": MessageLookupByLibrary.simpleMessage("初期化"),
|
||||||
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("正しいホットキーを入力"),
|
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("正しいホットキーを入力"),
|
||||||
"intelligentSelected": MessageLookupByLibrary.simpleMessage("インテリジェント選択"),
|
"intelligentSelected": MessageLookupByLibrary.simpleMessage("インテリジェント選択"),
|
||||||
|
"internet": MessageLookupByLibrary.simpleMessage("インターネット"),
|
||||||
"intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"),
|
"intranetIP": MessageLookupByLibrary.simpleMessage("イントラネットIP"),
|
||||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
|
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
|
||||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"),
|
"ipv6Desc": MessageLookupByLibrary.simpleMessage("有効化するとIPv6トラフィックを受信可能"),
|
||||||
@@ -254,10 +262,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"logcatDesc": MessageLookupByLibrary.simpleMessage("無効化するとログエントリを非表示"),
|
"logcatDesc": MessageLookupByLibrary.simpleMessage("無効化するとログエントリを非表示"),
|
||||||
"logs": MessageLookupByLibrary.simpleMessage("ログ"),
|
"logs": MessageLookupByLibrary.simpleMessage("ログ"),
|
||||||
"logsDesc": MessageLookupByLibrary.simpleMessage("ログキャプチャ記録"),
|
"logsDesc": MessageLookupByLibrary.simpleMessage("ログキャプチャ記録"),
|
||||||
|
"logsTest": MessageLookupByLibrary.simpleMessage("ログテスト"),
|
||||||
"loopback": MessageLookupByLibrary.simpleMessage("ループバック解除ツール"),
|
"loopback": MessageLookupByLibrary.simpleMessage("ループバック解除ツール"),
|
||||||
"loopbackDesc": MessageLookupByLibrary.simpleMessage("UWPループバック解除用"),
|
"loopbackDesc": MessageLookupByLibrary.simpleMessage("UWPループバック解除用"),
|
||||||
"loose": MessageLookupByLibrary.simpleMessage("疎"),
|
"loose": MessageLookupByLibrary.simpleMessage("疎"),
|
||||||
"memoryInfo": MessageLookupByLibrary.simpleMessage("メモリ情報"),
|
"memoryInfo": MessageLookupByLibrary.simpleMessage("メモリ情報"),
|
||||||
|
"messageTest": MessageLookupByLibrary.simpleMessage("メッセージテスト"),
|
||||||
|
"messageTestTip": MessageLookupByLibrary.simpleMessage("これはメッセージです。"),
|
||||||
"min": MessageLookupByLibrary.simpleMessage("最小化"),
|
"min": MessageLookupByLibrary.simpleMessage("最小化"),
|
||||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("終了時に最小化"),
|
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("終了時に最小化"),
|
||||||
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
|
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
@@ -287,6 +298,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"noInfo": MessageLookupByLibrary.simpleMessage("情報なし"),
|
"noInfo": MessageLookupByLibrary.simpleMessage("情報なし"),
|
||||||
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("追加情報なし"),
|
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("追加情報なし"),
|
||||||
"noNetwork": MessageLookupByLibrary.simpleMessage("ネットワークなし"),
|
"noNetwork": MessageLookupByLibrary.simpleMessage("ネットワークなし"),
|
||||||
|
"noNetworkApp": MessageLookupByLibrary.simpleMessage("ネットワークなしアプリ"),
|
||||||
"noProxy": MessageLookupByLibrary.simpleMessage("プロキシなし"),
|
"noProxy": MessageLookupByLibrary.simpleMessage("プロキシなし"),
|
||||||
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
|
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"プロファイルを作成するか、有効なプロファイルを追加してください",
|
"プロファイルを作成するか、有効なプロファイルを追加してください",
|
||||||
@@ -384,6 +396,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"recovery": MessageLookupByLibrary.simpleMessage("復元"),
|
"recovery": MessageLookupByLibrary.simpleMessage("復元"),
|
||||||
"recoveryAll": MessageLookupByLibrary.simpleMessage("全データ復元"),
|
"recoveryAll": MessageLookupByLibrary.simpleMessage("全データ復元"),
|
||||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("プロファイルのみ復元"),
|
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("プロファイルのみ復元"),
|
||||||
|
"recoveryStrategy": MessageLookupByLibrary.simpleMessage("リカバリー戦略"),
|
||||||
|
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("互換性"),
|
||||||
|
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"オーバーライド",
|
||||||
|
),
|
||||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("復元成功"),
|
"recoverySuccess": MessageLookupByLibrary.simpleMessage("復元成功"),
|
||||||
"redo": MessageLookupByLibrary.simpleMessage("やり直す"),
|
"redo": MessageLookupByLibrary.simpleMessage("やり直す"),
|
||||||
"regExp": MessageLookupByLibrary.simpleMessage("正規表現"),
|
"regExp": MessageLookupByLibrary.simpleMessage("正規表現"),
|
||||||
@@ -452,6 +469,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"submit": MessageLookupByLibrary.simpleMessage("送信"),
|
"submit": MessageLookupByLibrary.simpleMessage("送信"),
|
||||||
"sync": MessageLookupByLibrary.simpleMessage("同期"),
|
"sync": MessageLookupByLibrary.simpleMessage("同期"),
|
||||||
"system": MessageLookupByLibrary.simpleMessage("システム"),
|
"system": MessageLookupByLibrary.simpleMessage("システム"),
|
||||||
|
"systemApp": MessageLookupByLibrary.simpleMessage("システムアプリ"),
|
||||||
"systemFont": MessageLookupByLibrary.simpleMessage("システムフォント"),
|
"systemFont": MessageLookupByLibrary.simpleMessage("システムフォント"),
|
||||||
"systemProxy": MessageLookupByLibrary.simpleMessage("システムプロキシ"),
|
"systemProxy": MessageLookupByLibrary.simpleMessage("システムプロキシ"),
|
||||||
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(
|
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
@@ -463,6 +481,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP並列処理"),
|
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP並列処理"),
|
||||||
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("TCP並列処理を許可"),
|
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("TCP並列処理を許可"),
|
||||||
"testUrl": MessageLookupByLibrary.simpleMessage("URLテスト"),
|
"testUrl": MessageLookupByLibrary.simpleMessage("URLテスト"),
|
||||||
|
"textScale": MessageLookupByLibrary.simpleMessage("テキストスケーリング"),
|
||||||
"theme": MessageLookupByLibrary.simpleMessage("テーマ"),
|
"theme": MessageLookupByLibrary.simpleMessage("テーマ"),
|
||||||
"themeColor": MessageLookupByLibrary.simpleMessage("テーマカラー"),
|
"themeColor": MessageLookupByLibrary.simpleMessage("テーマカラー"),
|
||||||
"themeDesc": MessageLookupByLibrary.simpleMessage("ダークモードの設定、色の調整"),
|
"themeDesc": MessageLookupByLibrary.simpleMessage("ダークモードの設定、色の調整"),
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"Текущее приложение уже является последней версией",
|
"Текущее приложение уже является последней версией",
|
||||||
),
|
),
|
||||||
"checking": MessageLookupByLibrary.simpleMessage("Проверка..."),
|
"checking": MessageLookupByLibrary.simpleMessage("Проверка..."),
|
||||||
|
"clearData": MessageLookupByLibrary.simpleMessage("Очистить данные"),
|
||||||
"clipboardExport": MessageLookupByLibrary.simpleMessage(
|
"clipboardExport": MessageLookupByLibrary.simpleMessage(
|
||||||
"Экспорт в буфер обмена",
|
"Экспорт в буфер обмена",
|
||||||
),
|
),
|
||||||
@@ -169,6 +170,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"Просмотр текущих данных о соединениях",
|
"Просмотр текущих данных о соединениях",
|
||||||
),
|
),
|
||||||
"connectivity": MessageLookupByLibrary.simpleMessage("Связь:"),
|
"connectivity": MessageLookupByLibrary.simpleMessage("Связь:"),
|
||||||
|
"contactMe": MessageLookupByLibrary.simpleMessage("Свяжитесь со мной"),
|
||||||
"content": MessageLookupByLibrary.simpleMessage("Содержание"),
|
"content": MessageLookupByLibrary.simpleMessage("Содержание"),
|
||||||
"contentEmptyTip": MessageLookupByLibrary.simpleMessage(
|
"contentEmptyTip": MessageLookupByLibrary.simpleMessage(
|
||||||
"Содержание не может быть пустым",
|
"Содержание не может быть пустым",
|
||||||
@@ -183,6 +185,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"core": MessageLookupByLibrary.simpleMessage("Ядро"),
|
"core": MessageLookupByLibrary.simpleMessage("Ядро"),
|
||||||
"coreInfo": MessageLookupByLibrary.simpleMessage("Информация о ядре"),
|
"coreInfo": MessageLookupByLibrary.simpleMessage("Информация о ядре"),
|
||||||
"country": MessageLookupByLibrary.simpleMessage("Страна"),
|
"country": MessageLookupByLibrary.simpleMessage("Страна"),
|
||||||
|
"crashTest": MessageLookupByLibrary.simpleMessage("Тест на сбои"),
|
||||||
"create": MessageLookupByLibrary.simpleMessage("Создать"),
|
"create": MessageLookupByLibrary.simpleMessage("Создать"),
|
||||||
"cut": MessageLookupByLibrary.simpleMessage("Вырезать"),
|
"cut": MessageLookupByLibrary.simpleMessage("Вырезать"),
|
||||||
"dark": MessageLookupByLibrary.simpleMessage("Темный"),
|
"dark": MessageLookupByLibrary.simpleMessage("Темный"),
|
||||||
@@ -216,6 +219,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"detectionTip": MessageLookupByLibrary.simpleMessage(
|
"detectionTip": MessageLookupByLibrary.simpleMessage(
|
||||||
"Опирается на сторонний API, только для справки",
|
"Опирается на сторонний API, только для справки",
|
||||||
),
|
),
|
||||||
|
"developerMode": MessageLookupByLibrary.simpleMessage("Режим разработчика"),
|
||||||
|
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Режим разработчика активирован.",
|
||||||
|
),
|
||||||
"direct": MessageLookupByLibrary.simpleMessage("Прямой"),
|
"direct": MessageLookupByLibrary.simpleMessage("Прямой"),
|
||||||
"disclaimer": MessageLookupByLibrary.simpleMessage(
|
"disclaimer": MessageLookupByLibrary.simpleMessage(
|
||||||
"Отказ от ответственности",
|
"Отказ от ответственности",
|
||||||
@@ -342,6 +349,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"intelligentSelected": MessageLookupByLibrary.simpleMessage(
|
"intelligentSelected": MessageLookupByLibrary.simpleMessage(
|
||||||
"Интеллектуальный выбор",
|
"Интеллектуальный выбор",
|
||||||
),
|
),
|
||||||
|
"internet": MessageLookupByLibrary.simpleMessage("Интернет"),
|
||||||
"intranetIP": MessageLookupByLibrary.simpleMessage("Внутренний IP"),
|
"intranetIP": MessageLookupByLibrary.simpleMessage("Внутренний IP"),
|
||||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
|
"ipcidr": MessageLookupByLibrary.simpleMessage("IPCIDR"),
|
||||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
|
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
|
||||||
@@ -378,6 +386,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
),
|
),
|
||||||
"logs": MessageLookupByLibrary.simpleMessage("Логи"),
|
"logs": MessageLookupByLibrary.simpleMessage("Логи"),
|
||||||
"logsDesc": MessageLookupByLibrary.simpleMessage("Записи захвата логов"),
|
"logsDesc": MessageLookupByLibrary.simpleMessage("Записи захвата логов"),
|
||||||
|
"logsTest": MessageLookupByLibrary.simpleMessage("Тест журналов"),
|
||||||
"loopback": MessageLookupByLibrary.simpleMessage(
|
"loopback": MessageLookupByLibrary.simpleMessage(
|
||||||
"Инструмент разблокировки Loopback",
|
"Инструмент разблокировки Loopback",
|
||||||
),
|
),
|
||||||
@@ -386,6 +395,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
),
|
),
|
||||||
"loose": MessageLookupByLibrary.simpleMessage("Свободный"),
|
"loose": MessageLookupByLibrary.simpleMessage("Свободный"),
|
||||||
"memoryInfo": MessageLookupByLibrary.simpleMessage("Информация о памяти"),
|
"memoryInfo": MessageLookupByLibrary.simpleMessage("Информация о памяти"),
|
||||||
|
"messageTest": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Тестирование сообщения",
|
||||||
|
),
|
||||||
|
"messageTestTip": MessageLookupByLibrary.simpleMessage("Это сообщение."),
|
||||||
"min": MessageLookupByLibrary.simpleMessage("Мин"),
|
"min": MessageLookupByLibrary.simpleMessage("Мин"),
|
||||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage(
|
"minimizeOnExit": MessageLookupByLibrary.simpleMessage(
|
||||||
"Свернуть при выходе",
|
"Свернуть при выходе",
|
||||||
@@ -427,6 +440,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"Нет дополнительной информации",
|
"Нет дополнительной информации",
|
||||||
),
|
),
|
||||||
"noNetwork": MessageLookupByLibrary.simpleMessage("Нет сети"),
|
"noNetwork": MessageLookupByLibrary.simpleMessage("Нет сети"),
|
||||||
|
"noNetworkApp": MessageLookupByLibrary.simpleMessage("Приложение без сети"),
|
||||||
"noProxy": MessageLookupByLibrary.simpleMessage("Нет прокси"),
|
"noProxy": MessageLookupByLibrary.simpleMessage("Нет прокси"),
|
||||||
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
|
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"Пожалуйста, создайте профиль или добавьте действительный профиль",
|
"Пожалуйста, создайте профиль или добавьте действительный профиль",
|
||||||
@@ -560,6 +574,15 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
|
"recoveryProfiles": MessageLookupByLibrary.simpleMessage(
|
||||||
"Только восстановление профилей",
|
"Только восстановление профилей",
|
||||||
),
|
),
|
||||||
|
"recoveryStrategy": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Стратегия восстановления",
|
||||||
|
),
|
||||||
|
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Совместимый",
|
||||||
|
),
|
||||||
|
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage(
|
||||||
|
"Переопределение",
|
||||||
|
),
|
||||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage(
|
"recoverySuccess": MessageLookupByLibrary.simpleMessage(
|
||||||
"Восстановление успешно",
|
"Восстановление успешно",
|
||||||
),
|
),
|
||||||
@@ -650,6 +673,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"submit": MessageLookupByLibrary.simpleMessage("Отправить"),
|
"submit": MessageLookupByLibrary.simpleMessage("Отправить"),
|
||||||
"sync": MessageLookupByLibrary.simpleMessage("Синхронизация"),
|
"sync": MessageLookupByLibrary.simpleMessage("Синхронизация"),
|
||||||
"system": MessageLookupByLibrary.simpleMessage("Система"),
|
"system": MessageLookupByLibrary.simpleMessage("Система"),
|
||||||
|
"systemApp": MessageLookupByLibrary.simpleMessage("Системное приложение"),
|
||||||
"systemFont": MessageLookupByLibrary.simpleMessage("Системный шрифт"),
|
"systemFont": MessageLookupByLibrary.simpleMessage("Системный шрифт"),
|
||||||
"systemProxy": MessageLookupByLibrary.simpleMessage("Системный прокси"),
|
"systemProxy": MessageLookupByLibrary.simpleMessage("Системный прокси"),
|
||||||
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(
|
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
@@ -665,6 +689,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"Включение позволит использовать параллелизм TCP",
|
"Включение позволит использовать параллелизм TCP",
|
||||||
),
|
),
|
||||||
"testUrl": MessageLookupByLibrary.simpleMessage("Тест URL"),
|
"testUrl": MessageLookupByLibrary.simpleMessage("Тест URL"),
|
||||||
|
"textScale": MessageLookupByLibrary.simpleMessage("Масштабирование текста"),
|
||||||
"theme": MessageLookupByLibrary.simpleMessage("Тема"),
|
"theme": MessageLookupByLibrary.simpleMessage("Тема"),
|
||||||
"themeColor": MessageLookupByLibrary.simpleMessage("Цвет темы"),
|
"themeColor": MessageLookupByLibrary.simpleMessage("Цвет темы"),
|
||||||
"themeDesc": MessageLookupByLibrary.simpleMessage(
|
"themeDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"),
|
"checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"),
|
||||||
"checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"),
|
"checkUpdateError": MessageLookupByLibrary.simpleMessage("当前应用已经是最新版了"),
|
||||||
"checking": MessageLookupByLibrary.simpleMessage("检测中..."),
|
"checking": MessageLookupByLibrary.simpleMessage("检测中..."),
|
||||||
|
"clearData": MessageLookupByLibrary.simpleMessage("清除数据"),
|
||||||
"clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"),
|
"clipboardExport": MessageLookupByLibrary.simpleMessage("导出剪贴板"),
|
||||||
"clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"),
|
"clipboardImport": MessageLookupByLibrary.simpleMessage("剪贴板导入"),
|
||||||
"colorExists": MessageLookupByLibrary.simpleMessage("该颜色已存在"),
|
"colorExists": MessageLookupByLibrary.simpleMessage("该颜色已存在"),
|
||||||
@@ -107,6 +108,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"connections": MessageLookupByLibrary.simpleMessage("连接"),
|
"connections": MessageLookupByLibrary.simpleMessage("连接"),
|
||||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
|
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
|
||||||
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
||||||
|
"contactMe": MessageLookupByLibrary.simpleMessage("联系我"),
|
||||||
"content": MessageLookupByLibrary.simpleMessage("内容"),
|
"content": MessageLookupByLibrary.simpleMessage("内容"),
|
||||||
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容不能为空"),
|
"contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容不能为空"),
|
||||||
"contentScheme": MessageLookupByLibrary.simpleMessage("内容主题"),
|
"contentScheme": MessageLookupByLibrary.simpleMessage("内容主题"),
|
||||||
@@ -117,6 +119,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
||||||
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
|
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
|
||||||
"country": MessageLookupByLibrary.simpleMessage("区域"),
|
"country": MessageLookupByLibrary.simpleMessage("区域"),
|
||||||
|
"crashTest": MessageLookupByLibrary.simpleMessage("崩溃测试"),
|
||||||
"create": MessageLookupByLibrary.simpleMessage("创建"),
|
"create": MessageLookupByLibrary.simpleMessage("创建"),
|
||||||
"cut": MessageLookupByLibrary.simpleMessage("剪切"),
|
"cut": MessageLookupByLibrary.simpleMessage("剪切"),
|
||||||
"dark": MessageLookupByLibrary.simpleMessage("深色"),
|
"dark": MessageLookupByLibrary.simpleMessage("深色"),
|
||||||
@@ -136,6 +139,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"基于ClashMeta的多平台代理客户端,简单易用,开源无广告。",
|
"基于ClashMeta的多平台代理客户端,简单易用,开源无广告。",
|
||||||
),
|
),
|
||||||
"detectionTip": MessageLookupByLibrary.simpleMessage("依赖第三方api,仅供参考"),
|
"detectionTip": MessageLookupByLibrary.simpleMessage("依赖第三方api,仅供参考"),
|
||||||
|
"developerMode": MessageLookupByLibrary.simpleMessage("开发者模式"),
|
||||||
|
"developerModeEnableTip": MessageLookupByLibrary.simpleMessage("开发者模式已启用。"),
|
||||||
"direct": MessageLookupByLibrary.simpleMessage("直连"),
|
"direct": MessageLookupByLibrary.simpleMessage("直连"),
|
||||||
"disclaimer": MessageLookupByLibrary.simpleMessage("免责声明"),
|
"disclaimer": MessageLookupByLibrary.simpleMessage("免责声明"),
|
||||||
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
"disclaimerDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
@@ -206,6 +211,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"init": MessageLookupByLibrary.simpleMessage("初始化"),
|
"init": MessageLookupByLibrary.simpleMessage("初始化"),
|
||||||
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("请输入正确的快捷键"),
|
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("请输入正确的快捷键"),
|
||||||
"intelligentSelected": MessageLookupByLibrary.simpleMessage("智能选择"),
|
"intelligentSelected": MessageLookupByLibrary.simpleMessage("智能选择"),
|
||||||
|
"internet": MessageLookupByLibrary.simpleMessage("互联网"),
|
||||||
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
|
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
|
||||||
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
|
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
|
||||||
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
|
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
|
||||||
@@ -228,10 +234,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"),
|
"logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"),
|
||||||
"logs": MessageLookupByLibrary.simpleMessage("日志"),
|
"logs": MessageLookupByLibrary.simpleMessage("日志"),
|
||||||
"logsDesc": MessageLookupByLibrary.simpleMessage("日志捕获记录"),
|
"logsDesc": MessageLookupByLibrary.simpleMessage("日志捕获记录"),
|
||||||
|
"logsTest": MessageLookupByLibrary.simpleMessage("日志测试"),
|
||||||
"loopback": MessageLookupByLibrary.simpleMessage("回环解锁工具"),
|
"loopback": MessageLookupByLibrary.simpleMessage("回环解锁工具"),
|
||||||
"loopbackDesc": MessageLookupByLibrary.simpleMessage("用于UWP回环解锁"),
|
"loopbackDesc": MessageLookupByLibrary.simpleMessage("用于UWP回环解锁"),
|
||||||
"loose": MessageLookupByLibrary.simpleMessage("宽松"),
|
"loose": MessageLookupByLibrary.simpleMessage("宽松"),
|
||||||
"memoryInfo": MessageLookupByLibrary.simpleMessage("内存信息"),
|
"memoryInfo": MessageLookupByLibrary.simpleMessage("内存信息"),
|
||||||
|
"messageTest": MessageLookupByLibrary.simpleMessage("消息测试"),
|
||||||
|
"messageTestTip": MessageLookupByLibrary.simpleMessage("这是一条消息。"),
|
||||||
"min": MessageLookupByLibrary.simpleMessage("最小"),
|
"min": MessageLookupByLibrary.simpleMessage("最小"),
|
||||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
|
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
|
||||||
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
|
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
|
||||||
@@ -257,6 +266,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"noInfo": MessageLookupByLibrary.simpleMessage("暂无信息"),
|
"noInfo": MessageLookupByLibrary.simpleMessage("暂无信息"),
|
||||||
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("暂无更多信息"),
|
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("暂无更多信息"),
|
||||||
"noNetwork": MessageLookupByLibrary.simpleMessage("无网络"),
|
"noNetwork": MessageLookupByLibrary.simpleMessage("无网络"),
|
||||||
|
"noNetworkApp": MessageLookupByLibrary.simpleMessage("无网络应用"),
|
||||||
"noProxy": MessageLookupByLibrary.simpleMessage("暂无代理"),
|
"noProxy": MessageLookupByLibrary.simpleMessage("暂无代理"),
|
||||||
"noProxyDesc": MessageLookupByLibrary.simpleMessage("请创建配置文件或者添加有效配置文件"),
|
"noProxyDesc": MessageLookupByLibrary.simpleMessage("请创建配置文件或者添加有效配置文件"),
|
||||||
"noResolve": MessageLookupByLibrary.simpleMessage("不解析IP"),
|
"noResolve": MessageLookupByLibrary.simpleMessage("不解析IP"),
|
||||||
@@ -338,6 +348,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"recovery": MessageLookupByLibrary.simpleMessage("恢复"),
|
"recovery": MessageLookupByLibrary.simpleMessage("恢复"),
|
||||||
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
|
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
|
||||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
|
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
|
||||||
|
"recoveryStrategy": MessageLookupByLibrary.simpleMessage("恢复策略"),
|
||||||
|
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("兼容"),
|
||||||
|
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage("覆盖"),
|
||||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
|
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
|
||||||
"redo": MessageLookupByLibrary.simpleMessage("重做"),
|
"redo": MessageLookupByLibrary.simpleMessage("重做"),
|
||||||
"regExp": MessageLookupByLibrary.simpleMessage("正则"),
|
"regExp": MessageLookupByLibrary.simpleMessage("正则"),
|
||||||
@@ -398,6 +411,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"submit": MessageLookupByLibrary.simpleMessage("提交"),
|
"submit": MessageLookupByLibrary.simpleMessage("提交"),
|
||||||
"sync": MessageLookupByLibrary.simpleMessage("同步"),
|
"sync": MessageLookupByLibrary.simpleMessage("同步"),
|
||||||
"system": MessageLookupByLibrary.simpleMessage("系统"),
|
"system": MessageLookupByLibrary.simpleMessage("系统"),
|
||||||
|
"systemApp": MessageLookupByLibrary.simpleMessage("系统应用"),
|
||||||
"systemFont": MessageLookupByLibrary.simpleMessage("系统字体"),
|
"systemFont": MessageLookupByLibrary.simpleMessage("系统字体"),
|
||||||
"systemProxy": MessageLookupByLibrary.simpleMessage("系统代理"),
|
"systemProxy": MessageLookupByLibrary.simpleMessage("系统代理"),
|
||||||
"systemProxyDesc": MessageLookupByLibrary.simpleMessage("设置系统代理"),
|
"systemProxyDesc": MessageLookupByLibrary.simpleMessage("设置系统代理"),
|
||||||
@@ -407,6 +421,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"),
|
"tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"),
|
||||||
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许TCP并发"),
|
"tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许TCP并发"),
|
||||||
"testUrl": MessageLookupByLibrary.simpleMessage("测速链接"),
|
"testUrl": MessageLookupByLibrary.simpleMessage("测速链接"),
|
||||||
|
"textScale": MessageLookupByLibrary.simpleMessage("文本缩放"),
|
||||||
"theme": MessageLookupByLibrary.simpleMessage("主题"),
|
"theme": MessageLookupByLibrary.simpleMessage("主题"),
|
||||||
"themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"),
|
"themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"),
|
||||||
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),
|
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),
|
||||||
|
|||||||
@@ -3004,6 +3004,121 @@ class AppLocalizations {
|
|||||||
args: [],
|
args: [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Developer mode`
|
||||||
|
String get developerMode {
|
||||||
|
return Intl.message(
|
||||||
|
'Developer mode',
|
||||||
|
name: 'developerMode',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Developer mode is enabled.`
|
||||||
|
String get developerModeEnableTip {
|
||||||
|
return Intl.message(
|
||||||
|
'Developer mode is enabled.',
|
||||||
|
name: 'developerModeEnableTip',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Message test`
|
||||||
|
String get messageTest {
|
||||||
|
return Intl.message(
|
||||||
|
'Message test',
|
||||||
|
name: 'messageTest',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `This is a message.`
|
||||||
|
String get messageTestTip {
|
||||||
|
return Intl.message(
|
||||||
|
'This is a message.',
|
||||||
|
name: 'messageTestTip',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Crash test`
|
||||||
|
String get crashTest {
|
||||||
|
return Intl.message('Crash test', name: 'crashTest', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Clear Data`
|
||||||
|
String get clearData {
|
||||||
|
return Intl.message('Clear Data', name: 'clearData', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Text Scaling`
|
||||||
|
String get textScale {
|
||||||
|
return Intl.message('Text Scaling', name: 'textScale', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Internet`
|
||||||
|
String get internet {
|
||||||
|
return Intl.message('Internet', name: 'internet', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `System APP`
|
||||||
|
String get systemApp {
|
||||||
|
return Intl.message('System APP', name: 'systemApp', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `No network APP`
|
||||||
|
String get noNetworkApp {
|
||||||
|
return Intl.message(
|
||||||
|
'No network APP',
|
||||||
|
name: 'noNetworkApp',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Contact me`
|
||||||
|
String get contactMe {
|
||||||
|
return Intl.message('Contact me', name: 'contactMe', desc: '', args: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Recovery strategy`
|
||||||
|
String get recoveryStrategy {
|
||||||
|
return Intl.message(
|
||||||
|
'Recovery strategy',
|
||||||
|
name: 'recoveryStrategy',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Override`
|
||||||
|
String get recoveryStrategy_override {
|
||||||
|
return Intl.message(
|
||||||
|
'Override',
|
||||||
|
name: 'recoveryStrategy_override',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Compatible`
|
||||||
|
String get recoveryStrategy_compatible {
|
||||||
|
return Intl.message(
|
||||||
|
'Compatible',
|
||||||
|
name: 'recoveryStrategy_compatible',
|
||||||
|
desc: '',
|
||||||
|
args: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Logs test`
|
||||||
|
String get logsTest {
|
||||||
|
return Intl.message('Logs test', name: 'logsTest', desc: '', args: []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ Future<void> main() async {
|
|||||||
await globalState.initApp(version);
|
await globalState.initApp(version);
|
||||||
await android?.init();
|
await android?.init();
|
||||||
await window?.init(version);
|
await window?.init(version);
|
||||||
globalState.isPre = const String.fromEnvironment("APP_ENV") != 'stable';
|
|
||||||
HttpOverrides.global = FlClashHttpOverrides();
|
HttpOverrides.global = FlClashHttpOverrides();
|
||||||
runApp(ProviderScope(
|
runApp(ProviderScope(
|
||||||
child: const Application(),
|
child: const Application(),
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
|
import 'package:fl_clash/providers/providers.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
class AppStateManager extends StatefulWidget {
|
class AppStateManager extends ConsumerStatefulWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const AppStateManager({
|
const AppStateManager({
|
||||||
@@ -14,15 +16,31 @@ class AppStateManager extends StatefulWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AppStateManager> createState() => _AppStateManagerState();
|
ConsumerState<AppStateManager> createState() => _AppStateManagerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppStateManagerState extends State<AppStateManager>
|
class _AppStateManagerState extends ConsumerState<AppStateManager>
|
||||||
with WidgetsBindingObserver {
|
with WidgetsBindingObserver {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
ref.listenManual(layoutChangeProvider, (prev, next) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (prev != next) {
|
||||||
|
globalState.cacheHeightMap = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ref.listenManual(
|
||||||
|
checkIpProvider,
|
||||||
|
(prev, next) {
|
||||||
|
if (prev != next && next.b) {
|
||||||
|
detectionState.startCheck();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fireImmediately: true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user