Compare commits

..

5 Commits

Author SHA1 Message Date
chen08209
30dc3f4d13 Fix android system dns issues
Optimize dns default option

Fix some issues
2024-09-20 11:20:31 +08:00
chen08209
2c5f8525a7 Update readme 2024-09-18 00:41:46 +08:00
chen08209
5566f2b54f Update README.md 2 2024-09-18 00:22:31 +08:00
chen08209
511b7c521a Update README.md 2 2024-09-18 00:20:00 +08:00
chen08209
fb01d87371 Update README.md 2024-09-18 00:17:12 +08:00
158 changed files with 33133 additions and 42737 deletions

View File

@@ -1,57 +0,0 @@
<div align=center>
[![Release Downloads](https://img.shields.io/github/downloads/chen08209/FlClash/vVERSION/total?style=flat-square&logo=github)](https://img.shields.io/github/downloads/chen08209/FlClash/vVERSION/)
</div>
**Download based on your OS:**
<div align=left>
<table>
<thead align=left>
<tr>
<th>OS</th>
<th>Download</th>
</tr>
</thead>
<tbody align=left>
<tr>
<td>Android</td>
<td>
<a href="https://github.com/chen08209/FlClash/releases/download/vVERSION/FlClash-VERSION-android-arm64-v8a.apk"><img src="https://img.shields.io/badge/APK-ARMv8-168039.svg?logo=android"></a><br>
<a href="https://github.com/chen08209/FlClash/releases/download/vVERSION/FlClash-VERSION-android-armeabi-v7a.apk"><img src="https://img.shields.io/badge/APK-ARMv7-45bf55.svg?logo=android"></a><br>
<a href="https://github.com/chen08209/FlClash/releases/download/vVERSION/FlClash-VERSION-android-x86_64.apk"><img src="https://img.shields.io/badge/APK-x64-96ed89.svg?logo=android"></a>
</td>
</tr>
<tr>
<td>Windows</td>
<td>
<a href="https://github.com/chen08209/FlClash/releases/download/vVERSION/FlClash-VERSION-windows-amd64-setup.exe"><img src="https://img.shields.io/badge/Setup-x64-2d7d9a.svg?logo=windows"></a><br>
<a href="https://github.com/chen08209/FlClash/releases/download/vVERSION/FlClash-VERSION-windows-amd64.zip"><img src="https://img.shields.io/badge/Portable-x64-67b7d1.svg?logo=windows"></a>
</td>
</tr>
<tr>
<td>macOS (v10.15+)</td>
<td>
<a href="https://github.com/chen08209/FlClash/releases/download/vVERSION/FlClash-VERSION-macos-amd64.dmg"><img src="https://img.shields.io/badge/DMG-Universal-ea005e.svg?logo=apple"></a><br>
</td>
</tr>
<tr>
<td>Linux</td>
<td>
<a href="https://github.com/chen08209/FlClash/releases/download/vVERSION/FlClash-VERSION-linux-amd64.AppImage"><img src="https://img.shields.io/badge/AppImage-x64-f84e29.svg?logo=linux"> </a><br>
<a href="https://github.com/chen08209/FlClash/releases/download/vVERSION/FlClash-VERSION-linux-amd64.deb"><img src="https://img.shields.io/badge/DebPackage-x64-FF9966.svg?logo=debian"> </a><br>
<a href="https://github.com/chen08209/FlClash/releases/download/vVERSION/FlClash-VERSION-linux-amd64.deb"><img src="https://img.shields.io/badge/RpmPackage-x64-F1B42F.svg?logo=redhat"> </a>
</td>
</tr>
</tbody>
</table>
</div>
<div dir="ltr">
**List of all changes:** [ChangeLog](https://github.com/chen08209/FlClash/blob/main/CHANGELOG.md)
</div>

View File

@@ -35,6 +35,7 @@ jobs:
install: mingw-w64-x86_64-gcc
update: true
- name: Set Mingw64 Env
if: startsWith(matrix.platform,'windows')
run: |
@@ -86,7 +87,7 @@ jobs:
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.x'
flutter-version: 3.22.x
channel: 'stable'
cache: true
@@ -101,21 +102,15 @@ jobs:
with:
name: artifact-${{ matrix.platform }}${{ matrix.arch && format('-{0}', matrix.arch) }}
path: ./dist
retention-days: 1
overwrite: true
upload:
upload-release:
if: ${{ !contains(github.ref, '+') }}
permissions: write-all
needs: [ build ]
runs-on: ubuntu-latest
services:
telegram-bot-api:
image: aiogram/telegram-bot-api:latest
env:
TELEGRAM_API_ID: ${{ secrets.TELEGRAM_API_ID }}
TELEGRAM_API_HASH: ${{ secrets.TELEGRAM_API_HASH }}
ports:
- 8081:8081
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -129,63 +124,32 @@ jobs:
pattern: artifact-*
merge-multiple: true
- name: Generate release.md
- name: Pre Release
run: |
tags=($(git tag --merged $(git rev-parse HEAD) --sort=-creatordate))
preTag=$(curl --silent "https://api.github.com/repos/chen08209/FlClash/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")' || echo "")
currentTag=""
for ((i = 0; i <= ${#tags[@]}; i++)); do
if (( i < ${#tags[@]} )); then
tag=${tags[$i]}
else
tag=""
fi
if [ -n "$currentTag" ]; then
if [ "$(echo -e "$currentTag\n$preTag" | sort -V | head -n 1)" == "$currentTag" ]; then
break
fi
fi
if [ -n "$currentTag" ]; then
if [ -n "$tag" ]; then
git log --pretty=format:"%B" "$tag..$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> release.md
else
git log --pretty=format:"%B" "$currentTag" | awk 'NF {print "- " $0} !NF {print ""}' >> release.md
fi
echo "" >> release.md
fi
currentTag=$tag
done
- name: Push to telegram
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TAG: ${{ github.ref_name }}
run: |
python -m pip install --upgrade pip
pip install requests
python release.py
- name: Patch release.md
run: |
version=$(echo "${{ github.ref_name }}" | sed 's/^v//')
sed "s|VERSION|$version|g" ./.github/release_template.md >> release.md
pip install gitchangelog pystache mustache markdown
pre=$(curl --silent "https://api.github.com/repos/chen08209/FlClash/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")' || echo "")
if [ -z "pre" ]; then
echo "init" > release.md
else
current="${{ github.ref_name }}"
echo -e "\n\n<details markdown=1><summary>All changes from $current to the latest commit:</summary>\n\n" >> release.md
gitchangelog "${pre}.." >> release.md 2>&1 || echo "Error in gitchangelog"
echo -e "\n\n</details>" >> release.md
fi
- name: Release
if: ${{ !contains(github.ref, '+') }}
uses: softprops/action-gh-release@v2
with:
files: ./dist/*
body_path: './release.md'
- name: Create Fdroid Source Dir
if: ${{ !contains(github.ref, '+') }}
run: |
mkdir -p ./tmp
cp ./dist/*android-arm64-v8a* ./tmp/ || true
echo "Files copied successfully"
- name: Push to fdroid repo
if: ${{ !contains(github.ref, '+') }}
uses: cpina/github-action-push-to-another-repository@v1.7.2
env:
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
@@ -197,4 +161,4 @@ jobs:
user-email: 'github-actions[bot]@users.noreply.github.com'
target-branch: action-pr
commit-message: Update from ${{ github.ref_name }}
target-directory: /tmp/
target-directory: /tmp/

View File

@@ -1,45 +0,0 @@
name: change
on:
push:
branches:
- 'main'
jobs:
changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate
run: |
tags=$(git tag --sort=creatordate)
previous=""
if [ ! -f CHANGELOG.md ]; then
echo "" > CHANGELOG.md
else
previous=$(grep -oP '^## \K.*' CHANGELOG.md | tail -n 1)
fi
for tag in $tags; do
if [ -n "$previous" ]; then
echo "## $tag" >> CHANGELOG.md
git log --pretty=format:"* %s (%h)" "$previous..$tag" >> CHANGELOG.md
echo -e "\n" >> CHANGELOG.md
fi
previous=$tag
done
- name: Commit
run: |
if git diff --cached --quiet; then
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add CHANGELOG.md
git commit -m "Update Changelog"
git push
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,630 +0,0 @@
## v0.8.63
- Fix windows admin auto launch issues
- Add android vpn options
- Support proxies icon configuration
- Optimize android immersion display
- Fix some issues
## v0.8.62
- Optimize ip detection
- Support android vpn ipv6 inbound switch
- Support log export
- Optimize more details
- Fix android system dns issues
- Optimize dns default option
- Fix some issues
- Update readme
- Update README.md 2
- Update README.md 2
- Update README.md
## v0.8.60
- Fix build error2
- Fix build error
- Support desktop hotkey
- Support android ipv6 inbound
- Support android system dns
- fix some bugs
## v0.8.59
- Fix delete profile error
## v0.8.58
- Fix submit error 2
- Fix submit error
- Optimize DNS strategy
- Fix the problem that the tray is not displayed in some cases
- Optimize tray
- Update core
- Fix some error
## v0.8.57
- Fix tun update issues
- Add DNS override
- Fixed some bugs
- Optimize more detail
- Add Hosts override
## v0.8.56
- fix android tip error
- fix windows auto launch error
## v0.8.55
- Fix windows tray issues
- Optimize windows logic
- Optimize app logic
- Support windows administrator auto launch
- Support android close vpn
## v0.8.53
- Change flutter version
- Support profiles sort
- Support windows country flags display
- Optimize proxies page and profiles page columns
## v0.8.52
- Update flutter version
- Update version
- Update timeout time
- Update access control page
- Fix bug
## v0.8.51
- Optimize provider page
- Optimize delay test
- Support local backup and recovery
- Fix android tile service issues
## v0.8.49
- Fix linux core build error
- Add proxy-only traffic statistics
- Update core
- Optimize more details
- Merge pull request #140 from txyyh/main
- 添加自建 F-Droid 仓库相关 workflow
- Rename readme fingerprint
- Rename workflow deploy repo name
- Add download guide to README
- Add push release files to fdroid-repo
## v0.8.48
- Optimize proxies page
- Fix ua issues
- Optimize more details
## v0.8.47
- Fix windows build error
## v0.8.46
- Update app icon
- Fix desktop backup error
- Optimize request ua
- Change android icon
- Optimize dashboard
## v0.8.44
- Remove request validate certificate
- Sync core
## v0.8.43
- Fix windows error
## v0.8.42
- Fix setup.dart error
- Fix android system proxy not effective
- Add macos arm64
## v0.8.41
- Optimize proxies page
- Support mouse drag scroll
- Adjust desktop ui
- Revert "Fix android vpn issues"
- This reverts commit 891977408e6938e2acd74e9b9adb959c48c79988.
## v0.8.40
- Fix android vpn issues
- Fix android vpn issues
- Rollback partial modification
## v0.8.39
- Fix the problem that ui can't be synchronized when android vpn is occupied by an external
- Override default socksPort,port
## v0.8.38
- Fix fab issues
## v0.8.37
- Update version
- Fix the problem that vpn cannot be started in some cases
- Fix the problem that geodata url does not take effect
## v0.8.36
- Update ua
- Fix change outbound mode without check ip issues
- Separate android ui and vpn
- Fix url validate issues 2
- Add android hidden from the recent task
- Add geoip file
- Support modify geoData URL
## v0.8.35
- Fix url validate issues
- Fix check ip performance problem
- Optimize resources page
## v0.8.34
- Add ua selector
- Support modify test url
- Optimize android proxy
- Fix the error that async proxy provider could not selected the proxy
## v0.8.33
- Fix android proxy error
- Fix submit error
- Add windows tun
- Optimize android proxy
- Optimize change profile
- Update application ua
- Optimize delay test
## v0.8.32
- Fix android repeated request notification issues
## v0.8.31
- Fix memory overflow issues
## v0.8.30
- Optimize proxies expansion panel 2
- Fix android scan qrcode error
## v0.8.29
- Optimize proxies expansion panel
- Fix text error
## v0.8.28
- Optimize proxy
- Optimize delayed sorting performance
- Add expansion panel proxies page
- Support to adjust the proxy card size
- Support to adjust proxies columns number
- Fix autoRun show issues
- Fix Android 10 issues
- Optimize ip show
## v0.8.26
- Add intranet IP display
- Add connections page
- Add search in connections, requests
- Add keyword search in connections, requests, logs
- Add basic viewing editing capabilities
- Optimize update profile
## v0.8.25
- Update version
- Fix the problem of excessive memory usage in traffic usage.
- Add lightBlue theme color
- Fix start unable to update profile issues
- Fix flashback caused by process
## v0.8.23
- Add build version
- Optimize quick start
- Update system default option
## v0.8.22
- Update build.yml
- Fix android vpn close issues
- Add requests page
- Fix checkUpdate dark mode style error
- Fix quickStart error open app
- Add memory proxies tab index
- Support hidden group
- Optimize logs
- Fix externalController hot load error
## v0.8.21
- Add tcp concurrent switch
- Add system proxy switch
- Add geodata loader switch
- Add external controller switch
- Add auto gc on trim memory
- Fix android notification error
## v0.8.20
- Fix ipv6 error
- Fix android udp direct error
- Add ipv6 switch
- Add access all selected button
- Remove android low version splash
## v0.8.19
- Update version
- Add allowBypass
- Fix Android only pick .text file issues
## v0.8.18
- Fix search issues
## v0.8.17
- Fix LoadBalance, Relay load error
- Fix build.yml4
- Fix build.yml3
- Fix build.yml2
- Fix build.yml
- Add search function at access control
- Fix the issues with the profile add button to cover the edit button
- Adapt LoadBalance and Relay
- Add arm
- Fix android notification icon error
## v0.8.16
- Add one-click update all profiles
- Add expire show
## v0.8.15
- Temp remove tun mode
- Remove macos in workflow
- Change go version
## v0.8.14
- Update Version
- Fix tun unable to open
## v0.8.13
- Optimize delay test2
- Optimize delay test
- Add check ip
- add check ip request
## v0.8.12
- Fix the problem that the download of remote resources failed after GeodataMode was turned on, which caused the application to flash back.
- Fix edit profile error
- Fix quickStart change proxy error
- Fix core version
## v0.8.10
- Fix core version
## v0.8.9
- Update file_picker
- Add resources page
- Optimize more detail
- Add access selected sorted
- Fix notification duplicate creation issue
- Fix AccessControl click issue
## v0.8.7
- Fix Workflow
- Fix Linux unable to open
- Update README.md 3
- Create LICENSE
- Update README.md 2
- Update README.md
- Optimize workFlow
## v0.8.6
- optimize checkUpdate
## v0.8.5
- Fix submit error
## v0.8.4
- add WebDAV
- add Auto check updates
- Optimize more details
- optimize delayTest
## v0.8.2
- upgrade flutter version
## v0.8.1
- Update kernel
- Add import profile via QR code image
## v0.8.0
- Add compatibility mode and adapt clash scheme.
## v0.7.14
- update Version
- Reconstruction application proxy logic
## v0.7.13
- Fix Tab destroy error
## v0.7.12
- Optimize repeat healthcheck
## v0.7.11
- Optimize Direct mode ui
## v0.7.10
- Optimize Healthcheck
- Remove proxies position animation, improve performance
- Add Telegram Link
- Update healthcheck policy
- New Check URLTest
- Fix the problem of invalid auto-selection
## v0.7.8
- New Async UpdateConfig
- add changeProfileDebounce
- Update Workflow
- Fix ChangeProfile block
- Fix Release Message Error
## v0.7.7
- Update Selector 2
## v0.7.6
- Update Version
- Fix Proxies Select Error
## v0.7.5
- Fix the problem that the proxy group is empty in global mode.
- Fix the problem that the proxy group is empty in global mode.
## v0.7.4
- Add ProxyProvider2
## v0.7.3
- Add ProxyProvider
- Update Version
- Update ProxyGroup Sort
- Fix Android quickStart VpnService some problems
## v0.7.1
- Update version
- Set Android notification low importance
- Fix the issue that VpnService can't be closed correctly in special cases
- Fix the problem that TileService is not destroyed correctly in some cases
- Adjust tab animation defaults
- Add Telegram in README_zh_CN.md
- Add Telegram
## v0.7.0
- update mobile_scanner
- Initial commit

View File

@@ -34,22 +34,22 @@ def isRelease = defStoreFile.exists() && defStorePassword != null && defKeyAlias
android {
namespace "com.follow.clash"
compileSdkVersion 34
ndkVersion "27.1.12297006"
ndkVersion "25.1.8937393"
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '17'
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
signingConfigs {
if (isRelease) {
if (isRelease){
release {
storeFile defStoreFile
storePassword defStorePassword
@@ -74,9 +74,10 @@ android {
applicationIdSuffix '.debug'
}
release {
if (isRelease) {
minifyEnabled true
if(isRelease){
signingConfig signingConfigs.release
} else {
}else{
signingConfig signingConfigs.debug
}
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"

View File

@@ -10,22 +10,25 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
tools:ignore="SystemPermissionTypo" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
tools:ignore="SystemPermissionTypo" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application
android:name="${applicationName}"
android:enableOnBackInvokedCallback="true"
android:extractNativeLibs="true"
android:icon="@mipmap/ic_launcher"
android:hardwareAccelerated="true"
android:label="FlClash">
android:label="FlClash"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="tiramisu">
<activity
android:name="com.follow.clash.MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
@@ -61,16 +64,6 @@
<data android:host="install-config" />
</intent-filter>
</activity>
<!-- <meta-data-->
<!-- android:name="io.flutter.embedding.android.EnableImpeller"-->
<!-- android:value="true" />-->
<activity
android:name=".TempActivity"
android:exported="true"
android:theme="@style/TransparentTheme">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="com.follow.clash.action.START" />
@@ -81,6 +74,14 @@
</intent-filter>
</activity>
<!-- <meta-data-->
<!-- android:name="io.flutter.embedding.android.EnableImpeller"-->
<!-- android:value="true" />-->
<activity
android:name=".TempActivity"
android:theme="@style/TransparentTheme" />
<service
android:name=".services.FlClashTileService"
android:exported="true"
@@ -126,19 +127,12 @@
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="vpn" />
</service>
<service
android:name=".services.FlClashService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="service" />
</service>
android:foregroundServiceType="specialUse" />
<meta-data
android:name="flutterEmbedding"

View File

@@ -1,10 +1,10 @@
package com.follow.clash
import com.follow.clash.models.VpnOptions
import com.follow.clash.models.Props
import com.follow.clash.models.TunProps
interface BaseServiceInterface {
fun start(options: VpnOptions): Int
fun start(port: Int, props: Props?): TunProps?
fun stop()
fun startForeground(title: String, content: String)
}

View File

@@ -1,6 +1,8 @@
package com.follow.clash
import android.content.Intent
import android.os.Bundle
import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ServicePlugin
import com.follow.clash.plugins.VpnPlugin
@@ -9,6 +11,19 @@ import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
class MainActivity : FlutterActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
when (intent.action) {
"com.follow.clash.action.START" -> {
GlobalState.getCurrentTilePlugin()?.handleStart()
}
"com.follow.clash.action.STOP" -> {
GlobalState.getCurrentTilePlugin()?.handleStop()
}
}
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine.plugins.add(AppPlugin())

View File

@@ -6,15 +6,6 @@ import android.os.Bundle
class TempActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
when (intent.action) {
"com.follow.clash.action.START" -> {
GlobalState.getCurrentTilePlugin()?.handleStart()
}
"com.follow.clash.action.STOP" -> {
GlobalState.getCurrentTilePlugin()?.handleStop()
}
}
finishAndRemoveTask()
}
}

View File

@@ -8,7 +8,6 @@ import android.system.OsConstants.IPPROTO_TCP
import android.system.OsConstants.IPPROTO_UDP
import android.util.Base64
import androidx.core.graphics.drawable.toBitmap
import com.follow.clash.models.CIDR
import com.follow.clash.models.Metadata
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -34,25 +33,6 @@ fun Metadata.getProtocol(): Int? {
return null
}
fun String.toCIDR(): CIDR {
val parts = split("/")
if (parts.size != 2) {
throw IllegalArgumentException("Invalid CIDR format")
}
val ipAddress = parts[0]
val prefixLength = parts[1].toIntOrNull()
?: throw IllegalArgumentException("Invalid prefix length")
val address = InetAddress.getByName(ipAddress)
val maxPrefix = if (address.address.size == 4) 32 else 128
if (prefixLength < 0 || prefixLength > maxPrefix) {
throw IllegalArgumentException("Invalid prefix length for IP version")
}
return CIDR(address, prefixLength)
}
fun ConnectivityManager.resolveDns(network: Network?): List<String> {
val properties = getLinkProperties(network) ?: return listOf()

View File

@@ -0,0 +1,26 @@
package com.follow.clash.models
import android.net.NetworkCapabilities
import android.os.Build
val TRANSPORT_PRIORITY = sequence {
yield(NetworkCapabilities.TRANSPORT_CELLULAR)
if (Build.VERSION.SDK_INT >= 27) {
yield(NetworkCapabilities.TRANSPORT_LOWPAN)
}
yield(NetworkCapabilities.TRANSPORT_BLUETOOTH)
if (Build.VERSION.SDK_INT >= 26) {
yield(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
}
yield(NetworkCapabilities.TRANSPORT_WIFI)
if (Build.VERSION.SDK_INT >= 31) {
yield(NetworkCapabilities.TRANSPORT_USB)
}
yield(NetworkCapabilities.TRANSPORT_ETHERNET)
}.toList()

View File

@@ -1,7 +1,5 @@
package com.follow.clash.models
import java.net.InetAddress
enum class AccessControlMode {
acceptSelected,
rejectSelected,
@@ -13,16 +11,19 @@ data class AccessControl(
val rejectList: List<String>,
)
data class CIDR(val address: InetAddress, val prefixLength: Int)
data class VpnOptions(
val enable: Boolean,
val port: Int,
data class Props(
val enable: Boolean?,
val accessControl: AccessControl?,
val allowBypass: Boolean,
val systemProxy: Boolean,
val bypassDomain: List<String>,
val ipv4Address: String,
val ipv6Address: String,
val dnsServerAddress: String,
val allowBypass: Boolean?,
val systemProxy: Boolean?,
)
data class TunProps(
val fd: Int,
val gateway: String,
val gateway6: String,
val portal: String,
val portal6: String,
val dns: String,
val dns6: String
)

View File

@@ -1,5 +1,6 @@
package com.follow.clash.plugins
import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -16,6 +17,8 @@ import com.follow.clash.GlobalState
import com.follow.clash.RunState
import com.follow.clash.extensions.getProtocol
import com.follow.clash.extensions.resolveDns
import com.follow.clash.models.Props
import com.follow.clash.models.TunProps
import com.follow.clash.services.FlClashService
import com.follow.clash.services.FlClashVpnService
import com.google.gson.Gson
@@ -29,14 +32,14 @@ import kotlinx.coroutines.withContext
import java.net.InetSocketAddress
import kotlin.concurrent.withLock
import com.follow.clash.models.Process
import com.follow.clash.models.VpnOptions
class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
private lateinit var flutterMethodChannel: MethodChannel
private lateinit var context: Context
private var flClashService: BaseServiceInterface? = null
private lateinit var options: VpnOptions
private var port: Int = 7890
private var props: Props? = null
private lateinit var scope: CoroutineScope
private val connectivity by lazy {
@@ -76,9 +79,11 @@ class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"start" -> {
val data = call.argument<String>("data")
options = Gson().fromJson(data, VpnOptions::class.java)
when (options.enable) {
port = call.argument<Int>("port")!!
val args = call.argument<String>("args")
props =
if (args != null) Gson().fromJson(args, Props::class.java) else null
when (props?.enable == true) {
true -> handleStartVpn()
false -> start()
}
@@ -158,7 +163,8 @@ class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
}
}
private fun handleStartVpn() {
@SuppressLint("ForegroundServiceType")
fun handleStartVpn() {
GlobalState.getCurrentAppPlugin()?.requestVpnPermission(context) {
start()
}
@@ -222,6 +228,7 @@ class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
onUpdateNetwork()
}
@SuppressLint("ForegroundServiceType")
private fun startForeground(title: String, content: String) {
GlobalState.runLock.withLock {
if (GlobalState.runState.value != RunState.START) return
@@ -237,10 +244,10 @@ class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
GlobalState.runLock.withLock {
if (GlobalState.runState.value == RunState.START) return
GlobalState.runState.value = RunState.START
val fd = flClashService?.start(options)
val tunProps = flClashService?.start(port, props)
flutterMethodChannel.invokeMethod(
"started",
fd
Gson().toJson(tunProps, TunProps::class.java)
)
}
}
@@ -255,7 +262,7 @@ class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
}
private fun bindService() {
val intent = when (options.enable) {
val intent = when (props?.enable == true) {
true -> Intent(context, FlClashVpnService::class.java)
false -> Intent(context, FlClashService::class.java)
}

View File

@@ -11,12 +11,14 @@ import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import com.follow.clash.BaseServiceInterface
import com.follow.clash.MainActivity
import com.follow.clash.models.VpnOptions
import com.follow.clash.models.Props
@SuppressLint("WrongConstant")
class FlClashService : Service(), BaseServiceInterface {
private val binder = LocalBinder()
@@ -71,7 +73,7 @@ class FlClashService : Service(), BaseServiceInterface {
}
}
override fun start(options: VpnOptions) = 0
override fun start(port: Int, props: Props?) = null
override fun stop() {
stopSelf()

View File

@@ -1,6 +1,5 @@
package com.follow.clash.services
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
@@ -38,7 +37,6 @@ class FlClashTileService : TileService() {
GlobalState.runState.observeForever(observer)
}
@SuppressLint("StartActivityAndCollapseDeprecated")
private fun activityTransfer() {
val intent = Intent(this, TempActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)

View File

@@ -15,41 +15,71 @@ import android.os.Build
import android.os.IBinder
import android.os.Parcel
import android.os.RemoteException
import android.util.Log
import androidx.core.app.NotificationCompat
import com.follow.clash.BaseServiceInterface
import com.follow.clash.GlobalState
import com.follow.clash.MainActivity
import com.follow.clash.R
import com.follow.clash.TempActivity
import com.follow.clash.extensions.toCIDR
import com.follow.clash.models.AccessControlMode
import com.follow.clash.models.VpnOptions
import com.follow.clash.models.Props
import com.follow.clash.models.TunProps
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@SuppressLint("WrongConstant")
class FlClashVpnService : VpnService(), BaseServiceInterface {
companion object {
private val passList = listOf(
"*zhihu.com",
"*zhimg.com",
"*jd.com",
"100ime-iat-api.xfyun.cn",
"*360buyimg.com",
"localhost",
"*.local",
"127.*",
"10.*",
"172.16.*",
"172.17.*",
"172.18.*",
"172.19.*",
"172.2*",
"172.30.*",
"172.31.*",
"192.168.*"
)
private const val TUN_MTU = 9000
private const val TUN_SUBNET_PREFIX = 30
private const val TUN_GATEWAY = "172.19.0.1"
private const val TUN_SUBNET_PREFIX6 = 126
private const val TUN_GATEWAY6 = "fdfe:dcba:9876::1"
private const val TUN_PORTAL = "172.19.0.2"
private const val TUN_PORTAL6 = "fdfe:dcba:9876::2"
private const val TUN_DNS = TUN_PORTAL
private const val TUN_DNS6 = TUN_PORTAL6
private const val NET_ANY = "0.0.0.0"
private const val NET_ANY6 = "::"
}
override fun onCreate() {
super.onCreate()
GlobalState.initServiceEngine(applicationContext)
}
override fun start(options: VpnOptions): Int {
override fun start(port: Int, props: Props?): TunProps {
return with(Builder()) {
if (options.ipv4Address.isNotEmpty()) {
val cidr = options.ipv4Address.toCIDR()
addAddress(cidr.address, cidr.prefixLength)
addRoute("0.0.0.0", 0)
}
if (options.ipv6Address.isNotEmpty()) {
val cidr = options.ipv6Address.toCIDR()
addAddress(cidr.address, cidr.prefixLength)
addRoute("::", 0)
}
addDnsServer(options.dnsServerAddress)
setMtu(9000)
options.accessControl?.let { accessControl ->
addAddress(TUN_GATEWAY, TUN_SUBNET_PREFIX)
addAddress(TUN_GATEWAY6, TUN_SUBNET_PREFIX6)
addRoute(NET_ANY, 0)
addRoute(NET_ANY6, 0)
addDnsServer(TUN_DNS)
addDnsServer(TUN_DNS6)
setMtu(TUN_MTU)
props?.accessControl?.let { accessControl ->
when (accessControl.mode) {
AccessControlMode.acceptSelected -> {
(accessControl.acceptList + packageName).forEach {
@@ -69,24 +99,32 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
if (Build.VERSION.SDK_INT >= 29) {
setMetered(false)
}
if (options.allowBypass) {
if (props?.allowBypass == true) {
allowBypass()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && options.systemProxy) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && props?.systemProxy == true) {
setHttpProxy(
ProxyInfo.buildDirectProxy(
"127.0.0.1",
options.port,
options.bypassDomain
port,
passList
)
)
}
establish()?.detachFd()
?: throw NullPointerException("Establish VPN rejected by system")
TunProps(
fd = establish()?.detachFd()
?: throw NullPointerException("Establish VPN rejected by system"),
gateway = "$TUN_GATEWAY/$TUN_SUBNET_PREFIX",
gateway6 = "$TUN_GATEWAY6/$TUN_SUBNET_PREFIX6",
portal = TUN_PORTAL,
portal6 = TUN_PORTAL6,
dns = TUN_DNS,
dns6 = TUN_DNS6
)
}
}
fun updateUnderlyingNetworks(networks: Array<Network>) {
fun updateUnderlyingNetworks( networks: Array<Network>){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
this.setUnderlyingNetworks(networks)
}
@@ -122,23 +160,18 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
)
}
val stopIntent = Intent(this, TempActivity::class.java)
stopIntent.action = "com.follow.clash.action.STOP"
stopIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
val stopPendingIntent = if (Build.VERSION.SDK_INT >= 31) {
PendingIntent.getActivity(
this,
0,
stopIntent,
Intent("com.follow.clash.action.STOP"),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
} else {
PendingIntent.getActivity(
this,
0,
stopIntent,
Intent("com.follow.clash.action.STOP"),
PendingIntent.FLAG_UPDATE_CURRENT
)
}

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@@ -3,12 +3,9 @@ package main
import "C"
import (
"context"
"core/state"
"errors"
"fmt"
"github.com/metacubex/mihomo/constant/features"
"github.com/metacubex/mihomo/hub/route"
"github.com/samber/lo"
route "github.com/metacubex/mihomo/hub/route"
"math"
"os"
"os/exec"
"path/filepath"
@@ -157,16 +154,6 @@ func getRawConfigWithId(id string) *config.RawConfig {
continue
}
mapping["path"] = filepath.Join(getProfileProvidersPath(id), value)
if configParams.TestURL != nil {
if mapping["health-check"] != nil {
hc := mapping["health-check"].(map[string]any)
if hc != nil {
if hc["url"] != nil {
hc["url"] = *configParams.TestURL
}
}
}
}
}
for _, mapping := range prof.RuleProvider {
value, exist := mapping["path"].(string)
@@ -224,16 +211,16 @@ func sideUpdateExternalProvider(p cp.Provider, bytes []byte) error {
switch p.(type) {
case *provider.ProxySetProvider:
psp := p.(*provider.ProxySetProvider)
_, _, err := psp.SideUpdate(bytes)
if err == nil {
return err
elm, same, err := psp.SideUpdate(bytes)
if err == nil && !same {
psp.OnUpdate(elm)
}
return nil
case rp.RuleSetProvider:
rsp := p.(*rp.RuleSetProvider)
_, _, err := rsp.SideUpdate(bytes)
if err == nil {
return err
elm, same, err := rsp.SideUpdate(bytes)
if err == nil && !same {
rsp.OnUpdate(elm)
}
return nil
default:
@@ -247,151 +234,151 @@ func decorationConfig(profileId string, cfg config.RawConfig) *config.RawConfig
return prof
}
//func Reduce[T any, U any](s []T, initVal U, f func(U, T) U) U {
// for _, v := range s {
// initVal = f(initVal, v)
// }
// return initVal
//}
//
//func Map[T, U any](slice []T, fn func(T) U) []U {
// result := make([]U, len(slice))
// for i, v := range slice {
// result[i] = fn(v)
// }
// return result
//}
//
//func replaceFromMap(s string, m map[string]string) string {
// for k, v := range m {
// s = strings.ReplaceAll(s, k, v)
// }
// return s
//}
//
//func removeDuplicateFromSlice[T any](slice []T) []T {
// result := make([]T, 0)
// seen := make(map[any]struct{})
// for _, value := range slice {
// if _, ok := seen[value]; !ok {
// result = append(result, value)
// seen[value] = struct{}{}
// }
// }
// return result
//}
func Reduce[T any, U any](s []T, initVal U, f func(U, T) U) U {
for _, v := range s {
initVal = f(initVal, v)
}
return initVal
}
//func generateProxyGroupAndRule(proxyGroup *[]map[string]any, rule *[]string) {
// var replacements = map[string]string{}
// var selectArr []map[string]any
// var urlTestArr []map[string]any
// var fallbackArr []map[string]any
// for _, group := range *proxyGroup {
// switch group["type"] {
// case "select":
// selectArr = append(selectArr, group)
// replacements[group["name"].(string)] = "Proxy"
// break
// case "url-test":
// urlTestArr = append(urlTestArr, group)
// replacements[group["name"].(string)] = "Auto"
// break
// case "fallback":
// fallbackArr = append(fallbackArr, group)
// replacements[group["name"].(string)] = "Fallback"
// break
// default:
// break
// }
// }
//
// ProxyProxies := Reduce(selectArr, []string{}, func(res []string, cur map[string]any) []string {
// if cur["proxies"] == nil {
// return res
// }
// for _, proxyName := range cur["proxies"].([]interface{}) {
// if str, ok := proxyName.(string); ok {
// str = replaceFromMap(str, replacements)
// if str != "Proxy" {
// res = append(res, str)
// }
// }
// }
// return res
// })
//
// ProxyProxies = removeDuplicateFromSlice(ProxyProxies)
//
// AutoProxies := Reduce(urlTestArr, []string{}, func(res []string, cur map[string]any) []string {
// if cur["proxies"] == nil {
// return res
// }
// for _, proxyName := range cur["proxies"].([]interface{}) {
// if str, ok := proxyName.(string); ok {
// str = replaceFromMap(str, replacements)
// if str != "Auto" {
// res = append(res, str)
// }
// }
// }
// return res
// })
//
// AutoProxies = removeDuplicateFromSlice(AutoProxies)
//
// FallbackProxies := Reduce(fallbackArr, []string{}, func(res []string, cur map[string]any) []string {
// if cur["proxies"] == nil {
// return res
// }
// for _, proxyName := range cur["proxies"].([]interface{}) {
// if str, ok := proxyName.(string); ok {
// str = replaceFromMap(str, replacements)
// if str != "Fallback" {
// res = append(res, str)
// }
// }
// }
// return res
// })
//
// FallbackProxies = removeDuplicateFromSlice(FallbackProxies)
//
// var computedProxyGroup []map[string]any
//
// if len(ProxyProxies) > 0 {
// computedProxyGroup = append(computedProxyGroup,
// map[string]any{
// "name": "Proxy",
// "type": "select",
// "proxies": ProxyProxies,
// })
// }
//
// if len(AutoProxies) > 0 {
// computedProxyGroup = append(computedProxyGroup,
// map[string]any{
// "name": "Auto",
// "type": "url-test",
// "proxies": AutoProxies,
// })
// }
//
// if len(FallbackProxies) > 0 {
// computedProxyGroup = append(computedProxyGroup,
// map[string]any{
// "name": "Fallback",
// "type": "fallback",
// "proxies": FallbackProxies,
// })
// }
//
// computedRule := Map(*rule, func(value string) string {
// return replaceFromMap(value, replacements)
// })
//
// *proxyGroup = computedProxyGroup
// *rule = computedRule
//}
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
func replaceFromMap(s string, m map[string]string) string {
for k, v := range m {
s = strings.ReplaceAll(s, k, v)
}
return s
}
func removeDuplicateFromSlice[T any](slice []T) []T {
result := make([]T, 0)
seen := make(map[any]struct{})
for _, value := range slice {
if _, ok := seen[value]; !ok {
result = append(result, value)
seen[value] = struct{}{}
}
}
return result
}
func generateProxyGroupAndRule(proxyGroup *[]map[string]any, rule *[]string) {
var replacements = map[string]string{}
var selectArr []map[string]any
var urlTestArr []map[string]any
var fallbackArr []map[string]any
for _, group := range *proxyGroup {
switch group["type"] {
case "select":
selectArr = append(selectArr, group)
replacements[group["name"].(string)] = "Proxy"
break
case "url-test":
urlTestArr = append(urlTestArr, group)
replacements[group["name"].(string)] = "Auto"
break
case "fallback":
fallbackArr = append(fallbackArr, group)
replacements[group["name"].(string)] = "Fallback"
break
default:
break
}
}
ProxyProxies := Reduce(selectArr, []string{}, func(res []string, cur map[string]any) []string {
if cur["proxies"] == nil {
return res
}
for _, proxyName := range cur["proxies"].([]interface{}) {
if str, ok := proxyName.(string); ok {
str = replaceFromMap(str, replacements)
if str != "Proxy" {
res = append(res, str)
}
}
}
return res
})
ProxyProxies = removeDuplicateFromSlice(ProxyProxies)
AutoProxies := Reduce(urlTestArr, []string{}, func(res []string, cur map[string]any) []string {
if cur["proxies"] == nil {
return res
}
for _, proxyName := range cur["proxies"].([]interface{}) {
if str, ok := proxyName.(string); ok {
str = replaceFromMap(str, replacements)
if str != "Auto" {
res = append(res, str)
}
}
}
return res
})
AutoProxies = removeDuplicateFromSlice(AutoProxies)
FallbackProxies := Reduce(fallbackArr, []string{}, func(res []string, cur map[string]any) []string {
if cur["proxies"] == nil {
return res
}
for _, proxyName := range cur["proxies"].([]interface{}) {
if str, ok := proxyName.(string); ok {
str = replaceFromMap(str, replacements)
if str != "Fallback" {
res = append(res, str)
}
}
}
return res
})
FallbackProxies = removeDuplicateFromSlice(FallbackProxies)
var computedProxyGroup []map[string]any
if len(ProxyProxies) > 0 {
computedProxyGroup = append(computedProxyGroup,
map[string]any{
"name": "Proxy",
"type": "select",
"proxies": ProxyProxies,
})
}
if len(AutoProxies) > 0 {
computedProxyGroup = append(computedProxyGroup,
map[string]any{
"name": "Auto",
"type": "url-test",
"proxies": AutoProxies,
})
}
if len(FallbackProxies) > 0 {
computedProxyGroup = append(computedProxyGroup,
map[string]any{
"name": "Fallback",
"type": "fallback",
"proxies": FallbackProxies,
})
}
computedRule := Map(*rule, func(value string) string {
return replaceFromMap(value, replacements)
})
*proxyGroup = computedProxyGroup
*rule = computedRule
}
func genHosts(hosts, patchHosts map[string]any) {
for k, v := range patchHosts {
@@ -399,37 +386,6 @@ func genHosts(hosts, patchHosts map[string]any) {
}
}
func trimArr(arr []string) (r []string) {
for _, e := range arr {
r = append(r, strings.Trim(e, " "))
}
return
}
var ips = []string{"ipinfo.io", "ipapi.co", "api.ip.sb", "ipwho.is"}
func overrideRules(rules *[]string) {
var target = ""
for _, line := range *rules {
rule := trimArr(strings.Split(line, ","))
l := len(rule)
if l != 2 {
return
}
if strings.ToUpper(rule[0]) == "MATCH" {
target = rule[1]
break
}
}
if target == "" {
return
}
var rulesExt = lo.Map(ips, func(ip string, index int) string {
return fmt.Sprintf("DOMAIN %s %s", ip, target)
})
*rules = append(rulesExt, *rules...)
}
func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig) {
targetConfig.ExternalController = patchConfig.ExternalController
targetConfig.ExternalUI = ""
@@ -454,12 +410,6 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
targetConfig.Profile.StoreSelected = false
targetConfig.GeoXUrl = patchConfig.GeoXUrl
targetConfig.GlobalUA = patchConfig.GlobalUA
if configParams.TestURL != nil {
constant.DefaultTestURL = *configParams.TestURL
}
for idx := range targetConfig.ProxyGroup {
targetConfig.ProxyGroup[idx]["url"] = ""
}
genHosts(targetConfig.Hosts, patchConfig.Hosts)
if configParams.OverrideDns {
targetConfig.DNS = patchConfig.DNS
@@ -468,7 +418,6 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
targetConfig.DNS.Enable = true
}
}
overrideRules(&targetConfig.Rule)
//if runtime.GOOS == "android" {
// targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, "dhcp://"+dns.SystemDNSPlaceholder)
//} else if runtime.GOOS == "windows" {
@@ -481,8 +430,9 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi
//}
}
func patchConfig(general *config.General, controller *config.Controller, tls *config.TLS) {
func patchConfig(general *config.General, controller *config.Controller) {
log.Infoln("[Apply] patch")
route.ReStartServer(controller.ExternalController)
tunnel.SetSniffing(general.Sniffing)
tunnel.SetFindProcessMode(general.FindProcessMode)
dialer.SetTcpConcurrent(general.TCPConcurrent)
@@ -491,22 +441,6 @@ func patchConfig(general *config.General, controller *config.Controller, tls *co
tunnel.SetMode(general.Mode)
log.SetLevel(general.LogLevel)
resolver.DisableIPv6 = !general.IPv6
route.ReCreateServer(&route.Config{
Addr: controller.ExternalController,
TLSAddr: controller.ExternalControllerTLS,
UnixAddr: controller.ExternalControllerUnix,
PipeAddr: controller.ExternalControllerPipe,
Secret: controller.Secret,
Certificate: tls.Certificate,
PrivateKey: tls.PrivateKey,
DohServer: controller.ExternalDohServer,
IsDebug: false,
Cors: route.Cors{
AllowOrigins: controller.Cors.AllowOrigins,
AllowPrivateNetwork: controller.Cors.AllowPrivateNetwork,
},
})
}
var isRunning = false
@@ -519,7 +453,7 @@ func updateListeners(general *config.General, listeners map[string]constant.Inbo
}
runLock.Lock()
defer runLock.Unlock()
stopListeners()
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
listener.SetAllowLan(general.AllowLan)
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
@@ -534,15 +468,34 @@ func updateListeners(general *config.General, listeners map[string]constant.Inbo
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
listener.ReCreateVmess(general.VmessConfig, tunnel.Tunnel)
listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel)
if !features.Android {
listener.ReCreateTun(general.Tun, tunnel.Tunnel)
}
listener.ReCreateTun(general.Tun, tunnel.Tunnel)
}
func stopListeners() {
listener.StopListener()
}
func hcCompatibleProvider(proxyProviders map[string]cp.ProxyProvider) {
wg := sync.WaitGroup{}
ch := make(chan struct{}, math.MaxInt)
for _, proxyProvider := range proxyProviders {
proxyProvider := proxyProvider
if proxyProvider.VehicleType() == cp.Compatible {
log.Infoln("Start initial Compatible provider %s", proxyProvider.Name())
wg.Add(1)
ch <- struct{}{}
go func() {
defer func() { <-ch; wg.Done() }()
if err := proxyProvider.Initial(); err != nil {
log.Errorln("initial Compatible provider %s error: %v", proxyProvider.Name(), err)
}
}()
}
}
}
func patchSelectGroup() {
mapping := configParams.SelectedMap
if mapping == nil {
@@ -569,19 +522,25 @@ func patchSelectGroup() {
}
func applyConfig() error {
cfg, err := config.ParseRawConfig(state.CurrentRawConfig)
cfg, err := config.ParseRawConfig(currentRawConfig)
if err != nil {
cfg, _ = config.ParseRawConfig(config.DefaultRawConfig())
}
if configParams.TestURL != nil {
constant.DefaultTestURL = *configParams.TestURL
}
if configParams.IsPatch {
patchConfig(cfg.General, cfg.Controller, cfg.TLS)
patchConfig(cfg.General, cfg.Controller)
} else {
closeConnections()
runtime.GC()
hub.ApplyConfig(cfg)
hub.UltraApplyConfig(cfg)
patchSelectGroup()
}
updateListeners(cfg.General, cfg.Listeners)
if isRunning {
hcCompatibleProvider(cfg.Providers)
}
externalProviders = getExternalProvidersRaw()
return err
}

View File

@@ -6,14 +6,6 @@ replace github.com/metacubex/mihomo => ./Clash.Meta
require github.com/metacubex/mihomo v1.17.1
require (
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
github.com/sagernet/cors v1.2.1 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
)
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297
require (
@@ -25,7 +17,7 @@ require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/coreos/go-iptables v0.8.0 // indirect
github.com/coreos/go-iptables v0.7.0 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
@@ -34,6 +26,7 @@ require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect
github.com/go-chi/cors v1.2.1 // indirect
github.com/go-chi/render v1.0.3 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
@@ -45,7 +38,7 @@ require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 // indirect
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
@@ -65,8 +58,8 @@ require (
github.com/metacubex/sing-shadowsocks2 v0.2.2 // indirect
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 // indirect
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3 // indirect
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa // indirect
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd // indirect
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785 // indirect
github.com/metacubex/utls v1.6.6 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
@@ -86,7 +79,8 @@ require (
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/samber/lo v1.47.0
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e // indirect
github.com/samber/lo v1.47.0 // indirect
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
@@ -103,13 +97,13 @@ require (
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect

View File

@@ -19,8 +19,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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=
@@ -42,6 +42,8 @@ github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXb
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
@@ -74,8 +76,8 @@ github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7s
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 h1:hxST5pwMBEOWmxpkX20w9oZG+hXdhKmAIPQ3NGGAxas=
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5 h1:GkMacU5ftc+IEg1449N3UEy2XLDz58W4fkrRu2fibb8=
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
@@ -94,8 +96,6 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc=
@@ -120,14 +120,12 @@ github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 h1:ypfofGDZbP
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE=
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3 h1:xg71VmzLS6ByAzi/57phwDvjE+dLLs+ozH00k4DnOns=
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3/go.mod h1:6nitcmzPDL3MXnLdhu6Hm126Zk4S1fBbX3P7jxUxSFw=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd h1:r7alry8u4qlUFLNMwGvG1A8ZcfPM6AMSmrm6E2yKdB4=
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785 h1:NNmI+ZV0DzNuqaAInRQuZFLHlWVuyHeow8jYpdKjHjo=
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
@@ -158,8 +156,6 @@ 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/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/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=
@@ -172,6 +168,8 @@ github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnV
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2FzELOFNFQtvsxH7NPmlo7X5JizEK51UCojo=
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
@@ -211,10 +209,6 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
@@ -229,18 +223,18 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
@@ -260,13 +254,13 @@ 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.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=

View File

@@ -7,7 +7,6 @@ import "C"
import (
"context"
bridge "core/dart-bridge"
"core/state"
"encoding/json"
"fmt"
"github.com/metacubex/mihomo/common/utils"
@@ -31,6 +30,8 @@ import (
"github.com/metacubex/mihomo/tunnel/statistic"
)
var currentRawConfig = config.DefaultRawConfig()
var configParams = ConfigExtendedParams{}
var externalProviders = map[string]cp.Provider{}
@@ -123,7 +124,7 @@ func updateConfig(s *C.char, port C.longlong) {
}
configParams = params.Params
prof := decorationConfig(params.ProfileId, params.Config)
state.CurrentRawConfig = prof
currentRawConfig = prof
err = applyConfig()
if err != nil {
bridge.SendToPort(i, err.Error())
@@ -183,7 +184,7 @@ func changeProxy(s *C.char) {
//export getTraffic
func getTraffic() *C.char {
up, down := statistic.DefaultManager.Current(state.CurrentState.OnlyProxy)
up, down := statistic.DefaultManager.Current(state.OnlyProxy)
traffic := map[string]int64{
"up": up,
"down": down,
@@ -198,7 +199,7 @@ func getTraffic() *C.char {
//export getTotalTraffic
func getTotalTraffic() *C.char {
up, down := statistic.DefaultManager.Total(state.CurrentState.OnlyProxy)
up, down := statistic.DefaultManager.Total(state.OnlyProxy)
traffic := map[string]int64{
"up": up,
"down": down,

View File

@@ -2,33 +2,36 @@ package main
import "C"
import (
"core/state"
"encoding/json"
"fmt"
)
//export getCurrentProfileName
func getCurrentProfileName() *C.char {
if state.CurrentState == nil {
return C.CString("")
}
return C.CString(state.CurrentState.CurrentProfileName)
type AccessControl struct {
Mode string `json:"mode"`
AcceptList []string `json:"acceptList"`
RejectList []string `json:"rejectList"`
IsFilterSystemApp bool `json:"isFilterSystemApp"`
}
//export getAndroidVpnOptions
func getAndroidVpnOptions() *C.char {
options := state.AndroidVpnOptions{
Enable: state.CurrentState.Enable,
Port: state.CurrentRawConfig.MixedPort,
Ipv4Address: state.DefaultIpv4Address,
Ipv6Address: state.GetIpv6Address(),
AccessControl: state.CurrentState.AccessControl,
SystemProxy: state.CurrentState.SystemProxy,
AllowBypass: state.CurrentState.AllowBypass,
BypassDomain: state.CurrentState.BypassDomain,
DnsServerAddress: state.GetDnsServerAddress(),
}
data, err := json.Marshal(options)
type AndroidProps struct {
Enable bool `json:"enable"`
AccessControl *AccessControl `json:"accessControl"`
AllowBypass bool `json:"allowBypass"`
SystemProxy bool `json:"systemProxy"`
}
type State struct {
AndroidProps
CurrentProfileName string `json:"currentProfileName"`
MixedPort int `json:"mixedPort"`
OnlyProxy bool `json:"onlyProxy"`
}
var state State
//export getState
func getState() *C.char {
data, err := json.Marshal(state)
if err != nil {
fmt.Println("Error:", err)
return C.CString("")
@@ -39,7 +42,7 @@ func getAndroidVpnOptions() *C.char {
//export setState
func setState(s *C.char) {
paramsString := C.GoString(s)
err := json.Unmarshal([]byte(paramsString), state.CurrentState)
err := json.Unmarshal([]byte(paramsString), &state)
if err != nil {
return
}

View File

@@ -1,59 +0,0 @@
package state
import "github.com/metacubex/mihomo/config"
var DefaultIpv4Address = "172.19.0.1/30"
var DefaultDnsAddress = "172.19.0.2"
var DefaultIpv6Address = "fdfe:dcba:9876::1/126"
var CurrentRawConfig = config.DefaultRawConfig()
type AndroidVpnOptions struct {
Enable bool `json:"enable"`
Port int `json:"port"`
AccessControl *AccessControl `json:"accessControl"`
AllowBypass bool `json:"allowBypass"`
SystemProxy bool `json:"systemProxy"`
BypassDomain []string `json:"bypassDomain"`
Ipv4Address string `json:"ipv4Address"`
Ipv6Address string `json:"ipv6Address"`
DnsServerAddress string `json:"dnsServerAddress"`
}
type AccessControl struct {
Mode string `json:"mode"`
AcceptList []string `json:"acceptList"`
RejectList []string `json:"rejectList"`
IsFilterSystemApp bool `json:"isFilterSystemApp"`
}
type AndroidVpnRawOptions struct {
Enable bool `json:"enable"`
AccessControl *AccessControl `json:"accessControl"`
AllowBypass bool `json:"allowBypass"`
SystemProxy bool `json:"systemProxy"`
Ipv6 bool `json:"ipv6"`
BypassDomain []string `json:"bypassDomain"`
}
type State struct {
AndroidVpnRawOptions
CurrentProfileName string `json:"currentProfileName"`
OnlyProxy bool `json:"onlyProxy"`
}
var CurrentState = &State{}
func GetIpv6Address() string {
if CurrentState.Ipv6 {
return DefaultIpv6Address
} else {
return ""
}
}
func GetDnsServerAddress() string {
//prefix, _ := netip.ParsePrefix(DefaultIpv4Address)
//return prefix.Addr().String()
return DefaultDnsAddress
}

View File

@@ -6,6 +6,7 @@ import "C"
import (
"core/platform"
t "core/tun"
"encoding/json"
"errors"
"github.com/metacubex/mihomo/listener/sing_tun"
"strconv"
@@ -41,10 +42,11 @@ var (
)
//export startTUN
func startTUN(fd C.int, port C.longlong) {
func startTUN(s *C.char, port C.longlong) {
i := int64(port)
ServicePort = i
if fd == 0 {
paramsString := C.GoString(s)
if paramsString == "" {
tunLock.Lock()
defer tunLock.Unlock()
now := time.Now()
@@ -59,8 +61,20 @@ func startTUN(fd C.int, port C.longlong) {
go func() {
tunLock.Lock()
defer tunLock.Unlock()
f := int(fd)
tunListener, _ = t.Start(f)
var tunProps = &t.Props{}
err := json.Unmarshal([]byte(paramsString), tunProps)
if err != nil {
log.Errorln("startTUN error: %v", err)
return
}
tunListener, err = t.Start(*tunProps)
if err != nil {
return
}
if tunListener != nil {
log.Infoln("TUN address: %v", tunListener.Address())
}

View File

@@ -4,7 +4,7 @@ package tun
import "C"
import (
"core/state"
"github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing_tun"
"github.com/metacubex/mihomo/log"
@@ -23,38 +23,38 @@ type Props struct {
Dns6 string `json:"dns6"`
}
func Start(fd int) (*sing_tun.Listener, error) {
func Start(tunProps Props) (*sing_tun.Listener, error) {
var prefix4 []netip.Prefix
tempPrefix4, err := netip.ParsePrefix(state.DefaultIpv4Address)
tempPrefix4, err := netip.ParsePrefix(tunProps.Gateway)
if err != nil {
log.Errorln("startTUN error:", err)
return nil, err
}
prefix4 = append(prefix4, tempPrefix4)
var prefix6 []netip.Prefix
if state.CurrentState.Ipv6 {
tempPrefix6, err := netip.ParsePrefix(state.DefaultIpv6Address)
if err != nil {
log.Errorln("startTUN error:", err)
return nil, err
}
prefix6 = append(prefix6, tempPrefix6)
tempPrefix6, err := netip.ParsePrefix(tunProps.Gateway6)
if err != nil {
log.Errorln("startTUN error:", err)
return nil, err
}
prefix6 = append(prefix6, tempPrefix6)
var dnsHijack []string
dnsHijack = append(dnsHijack, net.JoinHostPort(state.GetDnsServerAddress(), "53"))
dnsHijack = append(dnsHijack, net.JoinHostPort(tunProps.Dns, "53"))
dnsHijack = append(dnsHijack, net.JoinHostPort(tunProps.Dns6, "53"))
options := LC.Tun{
Enable: true,
Device: state.CurrentRawConfig.Tun.Device,
Stack: state.CurrentRawConfig.Tun.Stack,
Device: sing_tun.InterfaceName,
Stack: constant.TunMixed,
DNSHijack: dnsHijack,
AutoRoute: false,
AutoDetectInterface: false,
Inet4Address: prefix4,
Inet6Address: prefix6,
MTU: 9000,
FileDescriptor: fd,
FileDescriptor: tunProps.Fd,
}
listener, err := sing_tun.New(options, tunnel.Tunnel)

View File

@@ -34,6 +34,7 @@ runAppWithPreferences(
create: (_) => appState,
update: (_, config, clashConfig, appState) {
appState?.mode = clashConfig.mode;
appState?.isCompatible = config.isCompatible;
appState?.selectedMap = config.currentSelectedMap;
return appState!;
},
@@ -162,11 +163,10 @@ class ApplicationState extends State<Application> {
child: ClashManager(
child: Selector2<AppState, Config, ApplicationSelectorState>(
selector: (_, appState, config) => ApplicationSelectorState(
locale: config.appSetting.locale,
themeMode: config.themeProps.themeMode,
primaryColor: config.themeProps.primaryColor,
prueBlack: config.themeProps.prueBlack,
fontFamily: config.themeProps.fontFamily,
locale: config.locale,
themeMode: config.themeMode,
primaryColor: config.primaryColor,
prueBlack: config.prueBlack,
),
builder: (_, state, child) {
return DynamicColorBuilder(
@@ -200,7 +200,6 @@ class ApplicationState extends State<Application> {
themeMode: state.themeMode,
theme: ThemeData(
useMaterial3: true,
fontFamily: state.fontFamily.value,
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.light,
@@ -210,7 +209,6 @@ class ApplicationState extends State<Application> {
),
darkTheme: ThemeData(
useMaterial3: true,
fontFamily: state.fontFamily.value,
pageTransitionsTheme: _pageTransitionsTheme,
colorScheme: _getAppColorScheme(
brightness: Brightness.dark,

View File

@@ -287,18 +287,13 @@ class ClashCore {
malloc.free(stateChar);
}
String getCurrentProfileName() {
final currentProfileRaw = clashFFI.getCurrentProfileName();
final currentProfile = currentProfileRaw.cast<Utf8>().toDartString();
clashFFI.freeCString(currentProfileRaw);
return currentProfile;
}
AndroidVpnOptions getAndroidVpnOptions() {
final vpnOptionsRaw = clashFFI.getAndroidVpnOptions();
final vpnOptions = json.decode(vpnOptionsRaw.cast<Utf8>().toDartString());
clashFFI.freeCString(vpnOptionsRaw);
return AndroidVpnOptions.fromJson(vpnOptions);
CoreState getState() {
final stateRaw = clashFFI.getState();
final state = json.decode(
stateRaw.cast<Utf8>().toDartString(),
);
clashFFI.freeCString(stateRaw);
return CoreState.fromJson(state);
}
Traffic getTraffic() {
@@ -327,9 +322,11 @@ class ClashCore {
clashFFI.stopLog();
}
startTun(int fd, int port) {
startTun(TunProps? tunProps, int port) {
if (!Platform.isAndroid) return;
clashFFI.startTUN(fd, port);
final tunPropsChar = json.encode(tunProps).toNativeUtf8().cast<Char>();
clashFFI.startTUN(tunPropsChar, port);
malloc.free(tunPropsChar);
}
updateDns(String dns) {

View File

@@ -5144,20 +5144,6 @@ class ClashFFI {
late final __FCmulcr =
__FCmulcrPtr.asFunction<_Fcomplex Function(_Fcomplex, double)>();
void updateDns(
ffi.Pointer<ffi.Char> s,
) {
return _updateDns(
s,
);
}
late final _updateDnsPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
'updateDns');
late final _updateDns =
_updateDnsPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
void start() {
return _start();
}
@@ -5278,6 +5264,20 @@ class ClashFFI {
late final _getProxies =
_getProxiesPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
void updateDns(
ffi.Pointer<ffi.Char> s,
) {
return _updateDns(
s,
);
}
late final _updateDnsPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
'updateDns');
late final _updateDns =
_updateDnsPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
void changeProxy(
ffi.Pointer<ffi.Char> s,
) {
@@ -5557,25 +5557,14 @@ class ClashFFI {
late final _setProcessMap =
_setProcessMapPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
ffi.Pointer<ffi.Char> getCurrentProfileName() {
return _getCurrentProfileName();
ffi.Pointer<ffi.Char> getState() {
return _getState();
}
late final _getCurrentProfileNamePtr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
'getCurrentProfileName');
late final _getCurrentProfileName =
_getCurrentProfileNamePtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
ffi.Pointer<ffi.Char> getAndroidVpnOptions() {
return _getAndroidVpnOptions();
}
late final _getAndroidVpnOptionsPtr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
'getAndroidVpnOptions');
late final _getAndroidVpnOptions =
_getAndroidVpnOptionsPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
late final _getStatePtr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>('getState');
late final _getState =
_getStatePtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
void setState(
ffi.Pointer<ffi.Char> s,
@@ -5592,19 +5581,20 @@ class ClashFFI {
_setStatePtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
void startTUN(
int fd,
ffi.Pointer<ffi.Char> s,
int port,
) {
return _startTUN(
fd,
s,
port,
);
}
late final _startTUNPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int, ffi.LongLong)>>(
'startTUN');
late final _startTUN = _startTUNPtr.asFunction<void Function(int, int)>();
late final _startTUNPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(ffi.Pointer<ffi.Char>, ffi.LongLong)>>('startTUN');
late final _startTUN =
_startTUNPtr.asFunction<void Function(ffi.Pointer<ffi.Char>, int)>();
ffi.Pointer<ffi.Char> getRunTime() {
return _getRunTime();

View File

@@ -1,13 +1,10 @@
import 'dart:io';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart';
class Android {
init() async {
app?.onExit = () async {
await globalState.appController.savePreferences();
};
app?.onExit = () {};
}
}

View File

@@ -28,5 +28,4 @@ export 'iterable.dart';
export 'scroll.dart';
export 'icons.dart';
export 'http.dart';
export 'keyboard.dart';
export 'network.dart';
export 'keyboard.dart';

View File

@@ -1,9 +1,8 @@
import 'dart:io';
import 'dart:ui';
import 'package:collection/collection.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:flutter/material.dart';
import 'system.dart';
@@ -27,9 +26,9 @@ const GeoXMap defaultGeoXMap = {
"mmdb":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
"asn":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb",
"https://github.com/xishang0128/geoip/releases/download/latest/GeoLite2-ASN.mmdb",
"geoip":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat",
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoIP.dat",
"geosite":
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat"
};
@@ -52,21 +51,6 @@ final filter = ImageFilter.blur(
tileMode: TileMode.mirror,
);
const navigationItemListEquality = ListEquality<NavigationItem>();
const connectionListEquality = ListEquality<Connection>();
const stringListEquality = ListEquality<String>();
const logListEquality = ListEquality<Log>();
const groupListEquality = ListEquality<Group>();
const externalProviderListEquality = ListEquality<ExternalProvider>();
const packageListEquality = ListEquality<Package>();
const hotKeyActionListEquality = ListEquality<HotKeyAction>();
const stringAndStringMapEquality = MapEquality<String, String>();
const stringAndStringMapEntryIterableEquality =
IterableEquality<MapEntry<String, String>>();
const stringAndIntQMapEquality = MapEquality<String, int?>();
const stringSetEquality = SetEquality<String>();
const keyboardModifierListEquality = SetEquality<KeyboardModifier>();
const viewModeColumnsMap = {
ViewMode.mobile: [2, 1],
ViewMode.laptop: [3, 2],

View File

@@ -15,6 +15,9 @@ class FlClashHttpOverrides extends HttpOverrides {
final port = appController.clashConfig.mixedPort;
final isStart = appController.appFlowingState.isStart;
if (!isStart) return "DIRECT";
if (appController.appState.groups.isEmpty) {
return "DIRECT";
}
return "PROXY localhost:$port";
};
return client;

View File

@@ -58,7 +58,22 @@ class AutoLaunch {
Future<bool> windowsEnable() async {
await disable();
return await windows?.registerTask(appName) ?? false;
return windows?.runas(
'schtasks',
[
'/Create',
'/SC',
'ONLOGON',
'/TN',
appName,
'/TR',
'"${Platform.resolvedExecutable}"',
'/RL',
'HIGHEST',
'/F'
].join(" "),
) ??
false;
}
Future<bool> disable() async {
@@ -66,9 +81,9 @@ class AutoLaunch {
}
updateStatus(AutoLaunchState state) async {
final isAdminAutoLaunch = state.isAdminAutoLaunch;
final isOpenTun = state.isOpenTun;
final isAutoLaunch = state.isAutoLaunch;
if (Platform.isWindows && isAdminAutoLaunch) {
if (Platform.isWindows && isOpenTun) {
if (await windowsIsEnable == isAutoLaunch) return;
if (isAutoLaunch) {
final isEnable = await windowsEnable();

View File

@@ -15,10 +15,4 @@ extension ListExtension<T> on List<T> {
}
return res;
}
List<T> safeSublist(int start) {
if(start <= 0) return this;
if(start > length) return [];
return sublist(start);
}
}

View File

@@ -8,8 +8,7 @@ class Measure {
Measure.of(this.context)
: _textScale = TextScaler.linear(
WidgetsBinding.instance.platformDispatcher.textScaleFactor,
);
WidgetsBinding.instance.platformDispatcher.textScaleFactor);
Size computeTextSize(Text text) {
final textPainter = TextPainter(
@@ -22,7 +21,6 @@ class Measure {
}
double? _bodyMediumHeight;
Size? _bodyLargeSize;
double? _bodySmallHeight;
double? _labelSmallHeight;
double? _labelMediumHeight;
@@ -32,27 +30,17 @@ class Measure {
double get bodyMediumHeight {
_bodyMediumHeight ??= computeTextSize(
Text(
"X",
"",
style: context.textTheme.bodyMedium,
),
).height;
return _bodyMediumHeight!;
}
Size get bodyLargeSize {
_bodyLargeSize ??= computeTextSize(
Text(
"X",
style: context.textTheme.bodyLarge,
),
);
return _bodyLargeSize!;
}
double get bodySmallHeight {
_bodySmallHeight ??= computeTextSize(
Text(
"X",
"",
style: context.textTheme.bodySmall,
),
).height;
@@ -62,7 +50,7 @@ class Measure {
double get labelSmallHeight {
_labelSmallHeight ??= computeTextSize(
Text(
"X",
"",
style: context.textTheme.labelSmall,
),
).height;
@@ -72,7 +60,7 @@ class Measure {
double get labelMediumHeight {
_labelMediumHeight ??= computeTextSize(
Text(
"X",
"",
style: context.textTheme.labelMedium,
),
).height;
@@ -82,7 +70,7 @@ class Measure {
double get titleLargeHeight {
_titleLargeHeight ??= computeTextSize(
Text(
"X",
"",
style: context.textTheme.titleLarge,
),
).height;
@@ -92,7 +80,7 @@ class Measure {
double get titleMediumHeight {
_titleMediumHeight ??= computeTextSize(
Text(
"X",
"",
style: context.textTheme.titleMedium,
),
).height;

View File

@@ -1,25 +0,0 @@
import 'dart:io';
extension NetworkInterfaceExt on NetworkInterface {
bool get isWifi {
final nameLowCase = name.toLowerCase();
if (nameLowCase.contains('wlan') ||
nameLowCase.contains('wi-fi') ||
nameLowCase == 'en0' ||
nameLowCase == 'eth0') {
return true;
}
return false;
}
bool get includesIPv4 {
return addresses.any((addr) => addr.isIPv4);
}
}
extension InternetAddressExt on InternetAddress {
bool get isIPv4 {
return type == InternetAddressType.IPv4;
}
}

View File

@@ -104,17 +104,14 @@ class Other {
required bool isStart,
required Brightness brightness,
}) {
if(Platform.isMacOS){
return "assets/images/icon_white.png";
}
final suffix = Platform.isWindows ? "ico" : "png";
if (isStart && Platform.isWindows) {
return "assets/images/icon.$suffix";
if (!isStart && Platform.isWindows) {
return switch (brightness) {
Brightness.dark => "assets/images/icon_white.$suffix",
Brightness.light => "assets/images/icon_black.$suffix",
};
}
return switch (brightness) {
Brightness.dark => "assets/images/icon_white.$suffix",
Brightness.light => "assets/images/icon_black.$suffix",
};
return "assets/images/icon.$suffix";
}
int compareVersions(String version1, String version2) {
@@ -174,18 +171,14 @@ class Other {
if (disposition == null) return null;
final parseValue = HeaderValue.parse(disposition);
final parameters = parseValue.parameters;
final fileNamePointKey = parameters.keys
.firstWhere((key) => key == "filename*", orElse: () => "");
if (fileNamePointKey.isNotEmpty) {
final res = parameters[fileNamePointKey]?.split("''") ?? [];
if (res.length >= 2) {
return Uri.decodeComponent(res[1]);
}
final key = parameters.keys
.firstWhere((key) => key.startsWith("filename"), orElse: () => '');
if (key.isEmpty) return null;
if (key == "filename*") {
return Uri.decodeComponent((parameters[key] ?? "").split("'").last);
} else {
return parameters[key];
}
final fileNameKey = parameters.keys
.firstWhere((key) => key == "filename", orElse: () => "");
if (fileNameKey.isEmpty) return null;
return parameters[fileNameKey];
}
double getViewWidth() {
@@ -196,7 +189,7 @@ class Other {
List<String> parseReleaseBody(String? body) {
if (body == null) return [];
const pattern = r'- \s*(.*)';
const pattern = r'- (.+?)\. \[.+?\]';
final regex = RegExp(pattern);
return regex
.allMatches(body)
@@ -228,14 +221,11 @@ class Other {
return "${appName}_backup_${DateTime.now().show}.zip";
}
String get logFile {
return "${appName}_${DateTime.now().show}.log";
}
Size getScreenSize() {
final view = WidgetsBinding.instance.platformDispatcher.views.first;
return view.physicalSize / view.devicePixelRatio;
}
}
final other = Other();

View File

@@ -8,12 +8,11 @@ import 'constant.dart';
class AppPath {
static AppPath? _instance;
Completer<Directory> dataDir = Completer();
Completer<Directory> cacheDir = Completer();
Completer<Directory> downloadDir = Completer();
Completer<Directory> tempDir = Completer();
late String appDirPath;
// Future<Directory> _createDesktopCacheDir() async {
// final path = join(dirname(Platform.resolvedExecutable), 'cache');
// final dir = Directory(path);
// if (await dir.exists()) {
// await dir.create(recursive: true);
@@ -22,12 +21,8 @@ class AppPath {
// }
AppPath._internal() {
appDirPath = join(dirname(Platform.resolvedExecutable));
getApplicationSupportDirectory().then((value) {
dataDir.complete(value);
});
getTemporaryDirectory().then((value){
tempDir.complete(value);
cacheDir.complete(value);
});
getDownloadsDirectory().then((value) {
downloadDir.complete(value);
@@ -54,12 +49,12 @@ class AppPath {
}
Future<String> getHomeDirPath() async {
final directory = await dataDir.future;
final directory = await cacheDir.future;
return directory.path;
}
Future<String> getProfilesPath() async {
final directory = await dataDir.future;
final directory = await cacheDir.future;
return join(directory.path, profilesDirectoryName);
}
@@ -68,11 +63,6 @@ class AppPath {
final directory = await getProfilesPath();
return join(directory, "$id.yaml");
}
Future<String> get tempPath async {
final directory = await tempDir.future;
return directory.path;
}
}
final appPath = AppPath();

View File

@@ -1,7 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/models.dart';
@@ -29,8 +28,7 @@ class Preferences {
try {
return ClashConfig.fromJson(clashConfigMap);
} catch (e) {
debugPrint(e.toString());
return null;
throw e.toString();
}
}
@@ -50,8 +48,7 @@ class Preferences {
try {
return Config.fromJson(configMap);
} catch (e) {
debugPrint(e.toString());
return null;
throw e.toString();
}
}

View File

@@ -1,3 +1,4 @@
import 'dart:math';
import 'dart:typed_data';
import 'package:dio/dio.dart';
@@ -76,7 +77,7 @@ class Request {
};
Future<IpInfo?> checkIp({CancelToken? cancelToken}) async {
for (final source in _ipInfoSources.entries) {
for (final source in _ipInfoSources.entries.toList()..shuffle(Random())) {
try {
final response = await _dio
.get<Map<String, dynamic>>(

View File

@@ -25,18 +25,3 @@ class HiddenBarScrollBehavior extends BaseScrollBehavior {
return child;
}
}
class ShowBarScrollBehavior extends BaseScrollBehavior {
@override
Widget buildScrollbar(
BuildContext context,
Widget child,
ScrollableDetails details,
) {
return Scrollbar(
interactive: true,
controller: details.controller,
child: child,
);
}
}

View File

@@ -1,8 +1,3 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
extension StringExtension on String {
bool get isUrl {
return RegExp(r'^(http|https|ftp)://').hasMatch(this);
@@ -13,38 +8,4 @@ extension StringExtension on String {
other.toLowerCase(),
);
}
List<int> get encodeUtf16LeWithBom {
final byteData = ByteData(length * 2);
final bom = [0xFF, 0xFE];
for (int i = 0; i < length; i++) {
int charCode = codeUnitAt(i);
byteData.setUint16(i * 2, charCode, Endian.little);
}
return bom + byteData.buffer.asUint8List();
}
Uint8List? get getBase64 {
final regExp = RegExp(r'base64,(.*)');
final match = regExp.firstMatch(this);
final realValue = match?.group(1) ?? '';
if (realValue.isEmpty) {
return null;
}
try {
return base64.decode(realValue);
} catch (e) {
return null;
}
}
bool get isRegex {
try {
RegExp(this);
return true;
} catch (e) {
debugPrint(e.toString());
return false;
}
}
}

View File

@@ -1,8 +1,6 @@
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:fl_clash/common/common.dart';
import 'package:path/path.dart';
class Windows {
static Windows? _instance;
@@ -56,62 +54,6 @@ class Windows {
}
return true;
}
Future<bool> registerTask(String appName) async {
final taskXml = '''
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.3" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<Principals>
<Principal id="Author">
<LogonType>InteractiveToken</LogonType>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Triggers>
<LogonTrigger/>
</Triggers>
<Settings>
<MultipleInstancesPolicy>Parallel</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>false</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>false</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>"${Platform.resolvedExecutable}"</Command>
</Exec>
</Actions>
</Task>''';
final taskPath = join(await appPath.tempPath, "task.xml");
await File(taskPath).create(recursive: true);
await File(taskPath)
.writeAsBytes(taskXml.encodeUtf16LeWithBom, flush: true);
final commandLine = [
'/Create',
'/TN',
appName,
'/XML',
"%s",
'/F',
].join(" ");
return runas(
'schtasks',
commandLine.replaceFirst("%s", taskPath),
);
}
}
final windows = Platform.isWindows ? Windows() : null;

View File

@@ -2,17 +2,14 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:archive/archive.dart';
import 'package:fl_clash/common/archive.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:path/path.dart';
import 'package:provider/provider.dart';
import 'package:tray_manager/tray_manager.dart';
import 'package:url_launcher/url_launcher.dart';
import 'clash/core.dart';
@@ -61,9 +58,7 @@ class AppController {
updateRunTime,
updateTraffic,
];
if (!Platform.isAndroid) {
applyProfileDebounce();
}
applyProfileDebounce();
} else {
await globalState.handleStop();
clashCore.resetTraffic();
@@ -123,15 +118,11 @@ class AppController {
}
Future<void> updateClashConfig({bool isPatch = true}) async {
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return;
await commonScaffoldState?.loadingRun(() async {
await globalState.updateClashConfig(
clashConfig: clashConfig,
config: config,
isPatch: isPatch,
);
});
await globalState.updateClashConfig(
clashConfig: clashConfig,
config: config,
isPatch: isPatch,
);
}
Future applyProfile({bool isPrue = false}) async {
@@ -229,7 +220,7 @@ class AppController {
}
handleBackOrExit() async {
if (config.appSetting.minimizeOnExit) {
if (config.isMinimizeOnExit) {
if (system.isDesktop) {
await savePreferences();
}
@@ -248,7 +239,7 @@ class AppController {
}
updateLogStatus() {
if (config.appSetting.openLogs) {
if (config.openLogs) {
clashCore.startLog();
} else {
clashCore.stopLog();
@@ -257,7 +248,7 @@ class AppController {
}
autoCheckUpdate() async {
if (!config.appSetting.autoCheckUpdate) return;
if (!config.autoCheckUpdate) return;
final res = await request.checkForUpdate();
checkUpdateResultHandle(data: res);
}
@@ -308,10 +299,10 @@ class AppController {
init() async {
final isDisclaimerAccepted = await handlerDisclaimer();
if (!isDisclaimerAccepted) {
handleExit();
system.exit();
}
updateLogStatus();
if (!config.appSetting.silentLaunch) {
if (!config.silentLaunch) {
window?.show();
}
if (Platform.isAndroid) {
@@ -320,7 +311,7 @@ class AppController {
if (globalState.isStart) {
await updateStatus(true);
} else {
await updateStatus(config.appSetting.autoRun);
await updateStatus(config.autoRun);
}
autoUpdateProfiles();
autoCheckUpdate();
@@ -335,7 +326,7 @@ class AppController {
return;
}
appState.currentLabel = appState.currentNavigationItems[index].label;
if ((config.appSetting.isAnimateToPage || hasAnimate)) {
if ((config.isAnimateToPage || hasAnimate)) {
globalState.pageController?.animateToPage(
index,
duration: kTabScrollDuration,
@@ -411,9 +402,7 @@ class AppController {
),
TextButton(
onPressed: () {
config.appSetting = config.appSetting.copyWith(
disclaimerAccepted: true,
);
config.isDisclaimerAccepted = true;
Navigator.of(context).pop<bool>(true);
},
child: Text(appLocalizations.agree),
@@ -425,7 +414,7 @@ class AppController {
}
Future<bool> handlerDisclaimer() async {
if (config.appSetting.disclaimerAccepted) {
if (config.isDisclaimerAccepted) {
return true;
}
return showDisclaimer();
@@ -517,7 +506,7 @@ class AppController {
}
List<Proxy> getSortProxies(List<Proxy> proxies) {
return switch (config.proxiesStyle.sortType) {
return switch (config.proxiesSortType) {
ProxiesSortType.none => proxies,
ProxiesSortType.delay => _sortOfDelay(proxies),
ProxiesSortType.name => _sortOfName(proxies),
@@ -531,6 +520,21 @@ class AppController {
'';
}
Future<List<int>> backupData() async {
final homeDirPath = await appPath.getHomeDirPath();
final profilesPath = await appPath.getProfilesPath();
final configJson = config.toJson();
final clashConfigJson = clashConfig.toJson();
return Isolate.run<List<int>>(() async {
final archive = Archive();
archive.add("config.json", configJson);
archive.add("clashConfig.json", clashConfigJson);
await archive.addDirectoryToArchive(profilesPath, homeDirPath);
final zipEncoder = ZipEncoder();
return zipEncoder.encode(archive) ?? [];
});
}
updateTun() {
clashConfig.tun = clashConfig.tun.copyWith(
enable: !clashConfig.tun.enable,
@@ -548,15 +552,7 @@ class AppController {
}
updateAutoLaunch() {
config.appSetting = config.appSetting.copyWith(
autoLaunch: !config.appSetting.autoLaunch,
);
}
updateAdminAutoLaunch() {
config.appSetting = config.appSetting.copyWith(
adminAutoLaunch: !config.appSetting.adminAutoLaunch,
);
config.autoLaunch = !config.autoLaunch;
}
updateVisible() async {
@@ -577,154 +573,6 @@ class AppController {
clashConfig.mode = Mode.values[nextIndex];
}
Future<bool> exportLogs() async {
final logsRaw = appFlowingState.logs.map(
(item) => item.toString(),
);
final data = await Isolate.run<List<int>>(() async {
final logsRawString = logsRaw.join("\n");
return utf8.encode(logsRawString);
});
return await picker.saveFile(
other.logFile,
Uint8List.fromList(data),
) !=
null;
}
Future<List<int>> backupData() async {
final homeDirPath = await appPath.getHomeDirPath();
final profilesPath = await appPath.getProfilesPath();
final configJson = config.toJson();
final clashConfigJson = clashConfig.toJson();
return Isolate.run<List<int>>(() async {
final archive = Archive();
archive.add("config.json", configJson);
archive.add("clashConfig.json", clashConfigJson);
await archive.addDirectoryToArchive(profilesPath, homeDirPath);
final zipEncoder = ZipEncoder();
return zipEncoder.encode(archive) ?? [];
});
}
Future _updateSystemTray({
required bool isStart,
required Brightness? brightness,
bool force = false,
}) async {
if (Platform.isLinux || force) {
await trayManager.destroy();
}
await trayManager.setIcon(
other.getTrayIconPath(
isStart: isStart,
brightness: brightness ??
WidgetsBinding.instance.platformDispatcher.platformBrightness,
),
isTemplate: true,
);
if (!Platform.isLinux) {
await trayManager.setToolTip(
appName,
);
}
}
updateTray([bool focus = false]) async {
if (!Platform.isLinux) {
await _updateSystemTray(
isStart: appFlowingState.isStart,
brightness: appState.brightness,
force: focus,
);
}
List<MenuItem> menuItems = [];
final showMenuItem = MenuItem(
label: appLocalizations.show,
onClick: (_) {
window?.show();
},
);
menuItems.add(showMenuItem);
final startMenuItem = MenuItem.checkbox(
label: appFlowingState.isStart
? appLocalizations.stop
: appLocalizations.start,
onClick: (_) async {
globalState.appController.updateStart();
},
checked: false,
);
menuItems.add(startMenuItem);
menuItems.add(MenuItem.separator());
for (final mode in Mode.values) {
menuItems.add(
MenuItem.checkbox(
label: Intl.message(mode.name),
onClick: (_) {
globalState.appController.clashConfig.mode = mode;
},
checked: mode == clashConfig.mode,
),
);
}
menuItems.add(MenuItem.separator());
if (appFlowingState.isStart) {
menuItems.add(
MenuItem.checkbox(
label: appLocalizations.tun,
onClick: (_) {
globalState.appController.updateTun();
},
checked: clashConfig.tun.enable,
),
);
menuItems.add(
MenuItem.checkbox(
label: appLocalizations.systemProxy,
onClick: (_) {
globalState.appController.updateSystemProxy();
},
checked: config.desktopProps.systemProxy,
),
);
menuItems.add(MenuItem.separator());
}
final autoStartMenuItem = MenuItem.checkbox(
label: appLocalizations.autoLaunch,
onClick: (_) async {
globalState.appController.updateAutoLaunch();
},
checked: config.appSetting.autoLaunch,
);
final adminAutoStartMenuItem = MenuItem.checkbox(
label: appLocalizations.adminAutoLaunch,
onClick: (_) async {
globalState.appController.updateAdminAutoLaunch();
},
checked: config.appSetting.adminAutoLaunch,
);
menuItems.add(autoStartMenuItem);
menuItems.add(adminAutoStartMenuItem);
menuItems.add(MenuItem.separator());
final exitMenuItem = MenuItem(
label: appLocalizations.exit,
onClick: (_) async {
await globalState.appController.handleExit();
},
);
menuItems.add(exitMenuItem);
final menu = Menu(items: menuItems);
await trayManager.setContextMenu(menu);
if (Platform.isLinux) {
await _updateSystemTray(
isStart: appFlowingState.isStart,
brightness: appState.brightness,
force: focus,
);
}
}
recoveryData(
List<int> data,
RecoveryOption recoveryOption,

View File

@@ -15,10 +15,6 @@ extension GroupTypeExtension on GroupType {
)
.toList();
bool get isURLTestOrFallback {
return [GroupType.URLTest, GroupType.Fallback].contains(this);
}
static GroupType? getGroupType(String value) {
final index = GroupTypeExtension.valueList.indexOf(value);
if (index == -1) return null;
@@ -156,20 +152,3 @@ enum HotAction {
proxy,
tun,
}
enum ProxiesIconStyle {
standard,
none,
icon,
}
enum FontFamily {
system(),
miSans("MiSans"),
twEmoji("Twemoji"),
icon("Icons");
final String? value;
const FontFamily([this.value]);
}

View File

@@ -8,84 +8,6 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
class CloseConnectionsSwitch extends StatelessWidget {
const CloseConnectionsSwitch({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.appSetting.closeConnections,
builder: (_, closeConnections, __) {
return ListItem.switchItem(
title: Text(appLocalizations.autoCloseConnections),
subtitle: Text(appLocalizations.autoCloseConnectionsDesc),
delegate: SwitchDelegate(
value: closeConnections,
onChanged: (value) async {
final config = globalState.appController.config;
config.appSetting = config.appSetting.copyWith(
closeConnections: value,
);
},
),
);
},
);
}
}
class UsageSwitch extends StatelessWidget {
const UsageSwitch({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.appSetting.onlyProxy,
builder: (_, onlyProxy, __) {
return ListItem.switchItem(
title: Text(appLocalizations.onlyStatisticsProxy),
subtitle: Text(appLocalizations.onlyStatisticsProxyDesc),
delegate: SwitchDelegate(
value: onlyProxy,
onChanged: (bool value) async {
final config = globalState.appController.config;
config.appSetting = config.appSetting.copyWith(
onlyProxy: value,
);
},
),
);
},
);
}
}
class AdminAutoLaunchItem extends StatelessWidget {
const AdminAutoLaunchItem({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.appSetting.adminAutoLaunch,
builder: (_, adminAutoLaunch, __) {
return ListItem.switchItem(
title: Text(appLocalizations.adminAutoLaunch),
subtitle: Text(appLocalizations.adminAutoLaunchDesc),
delegate: SwitchDelegate(
value: adminAutoLaunch,
onChanged: (bool value) async {
final config = globalState.appController.config;
config.appSetting = config.appSetting.copyWith(
adminAutoLaunch: value,
);
},
),
);
},
);
}
}
class ApplicationSettingFragment extends StatelessWidget {
const ApplicationSettingFragment({super.key});
@@ -98,18 +20,17 @@ class ApplicationSettingFragment extends StatelessWidget {
Widget build(BuildContext context) {
List<Widget> items = [
Selector<Config, bool>(
selector: (_, config) => config.appSetting.minimizeOnExit,
selector: (_, config) => config.isMinimizeOnExit,
builder: (_, isMinimizeOnExit, child) {
return ListItem.switchItem(
leading: const Icon(Icons.back_hand),
title: Text(appLocalizations.minimizeOnExit),
subtitle: Text(appLocalizations.minimizeOnExitDesc),
delegate: SwitchDelegate(
value: isMinimizeOnExit,
onChanged: (bool value) {
final config = context.read<Config>();
config.appSetting = config.appSetting.copyWith(
minimizeOnExit: value,
);
config.isMinimizeOnExit = value;
},
),
);
@@ -117,57 +38,52 @@ class ApplicationSettingFragment extends StatelessWidget {
),
if (system.isDesktop)
Selector<Config, bool>(
selector: (_, config) => config.appSetting.autoLaunch,
selector: (_, config) => config.autoLaunch,
builder: (_, autoLaunch, child) {
return ListItem.switchItem(
leading: const Icon(Icons.rocket_launch),
title: Text(appLocalizations.autoLaunch),
subtitle: Text(appLocalizations.autoLaunchDesc),
delegate: SwitchDelegate(
value: autoLaunch,
onChanged: (bool value) {
final config = globalState.appController.config;
config.appSetting = config.appSetting.copyWith(
autoLaunch: value,
);
final config = context.read<Config>();
config.autoLaunch = value;
},
),
);
},
),
if(Platform.isWindows)
const AdminAutoLaunchItem(),
if (system.isDesktop)
Selector<Config, bool>(
selector: (_, config) => config.appSetting.silentLaunch,
selector: (_, config) => config.silentLaunch,
builder: (_, silentLaunch, child) {
return ListItem.switchItem(
leading: const Icon(Icons.expand_circle_down),
title: Text(appLocalizations.silentLaunch),
subtitle: Text(appLocalizations.silentLaunchDesc),
delegate: SwitchDelegate(
value: silentLaunch,
onChanged: (bool value) {
final config = globalState.appController.config;
config.appSetting = config.appSetting.copyWith(
silentLaunch: value,
);
final config = context.read<Config>();
config.silentLaunch = value;
},
),
);
},
),
Selector<Config, bool>(
selector: (_, config) => config.appSetting.autoRun,
selector: (_, config) => config.autoRun,
builder: (_, autoRun, child) {
return ListItem.switchItem(
leading: const Icon(Icons.not_started),
title: Text(appLocalizations.autoRun),
subtitle: Text(appLocalizations.autoRunDesc),
delegate: SwitchDelegate(
value: autoRun,
onChanged: (bool value) {
final config = globalState.appController.config;
config.appSetting = config.appSetting.copyWith(
autoRun: value,
);
final config = context.read<Config>();
config.autoRun = value;
},
),
);
@@ -175,18 +91,17 @@ class ApplicationSettingFragment extends StatelessWidget {
),
if (Platform.isAndroid)
Selector<Config, bool>(
selector: (_, config) => config.appSetting.hidden,
selector: (_, config) => config.isExclude,
builder: (_, isExclude, child) {
return ListItem.switchItem(
leading: const Icon(Icons.visibility_off),
title: Text(appLocalizations.exclude),
subtitle: Text(appLocalizations.excludeDesc),
delegate: SwitchDelegate(
value: isExclude,
onChanged: (value) {
final config = globalState.appController.config;
config.appSetting = config.appSetting.copyWith(
hidden: value,
);
final config = context.read<Config>();
config.isExclude = value;
},
),
);
@@ -194,56 +109,52 @@ class ApplicationSettingFragment extends StatelessWidget {
),
if (Platform.isAndroid)
Selector<Config, bool>(
selector: (_, config) => config.appSetting.isAnimateToPage,
selector: (_, config) => config.isAnimateToPage,
builder: (_, isAnimateToPage, child) {
return ListItem.switchItem(
leading: const Icon(Icons.animation),
title: Text(appLocalizations.tabAnimation),
subtitle: Text(appLocalizations.tabAnimationDesc),
delegate: SwitchDelegate(
value: isAnimateToPage,
onChanged: (value) {
final config = globalState.appController.config;
config.appSetting = config.appSetting.copyWith(
isAnimateToPage: value,
);
final config = context.read<Config>();
config.isAnimateToPage = value;
},
),
);
},
),
Selector<Config, bool>(
selector: (_, config) => config.appSetting.openLogs,
selector: (_, config) => config.openLogs,
builder: (_, openLogs, child) {
return ListItem.switchItem(
leading: const Icon(Icons.bug_report),
title: Text(appLocalizations.logcat),
subtitle: Text(appLocalizations.logcatDesc),
delegate: SwitchDelegate(
value: openLogs,
onChanged: (bool value) {
final config = globalState.appController.config;
config.appSetting = config.appSetting.copyWith(
openLogs: value,
);
final config = context.read<Config>();
config.openLogs = value;
globalState.appController.updateLogStatus();
},
),
);
},
),
const CloseConnectionsSwitch(),
const UsageSwitch(),
Selector<Config, bool>(
selector: (_, config) => config.appSetting.autoCheckUpdate,
selector: (_, config) => config.autoCheckUpdate,
builder: (_, autoCheckUpdate, child) {
return ListItem.switchItem(
leading: const Icon(Icons.system_update),
title: Text(appLocalizations.autoCheckUpdate),
subtitle: Text(appLocalizations.autoCheckUpdateDesc),
delegate: SwitchDelegate(
value: autoCheckUpdate,
onChanged: (bool value) {
final config = globalState.appController.config;
config.appSetting = config.appSetting.copyWith(
autoCheckUpdate: value,
);
final config = context.read<Config>();
config.autoCheckUpdate = value;
},
),
);

View File

@@ -0,0 +1,61 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/config.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CloseConnectionsSwitch extends StatelessWidget {
const CloseConnectionsSwitch({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.isCloseConnections,
builder: (_, isCloseConnections, __) {
return ListItem.switchItem(
leading: const Icon(Icons.auto_delete_outlined),
title: Text(appLocalizations.autoCloseConnections),
subtitle: Text(appLocalizations.autoCloseConnectionsDesc),
delegate: SwitchDelegate(
value: isCloseConnections,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.config.isCloseConnections = value;
},
),
);
},
);
}
}
class UsageSwitch extends StatelessWidget {
const UsageSwitch({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.onlyProxy,
builder: (_, onlyProxy, __) {
return ListItem.switchItem(
leading: const Icon(Icons.data_usage_outlined),
title: Text(appLocalizations.onlyStatisticsProxy),
subtitle: Text(appLocalizations.onlyStatisticsProxyDesc),
delegate: SwitchDelegate(
value: onlyProxy,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.config.onlyProxy = value;
},
),
);
},
);
}
}
final appItems = [
const CloseConnectionsSwitch(),
const UsageSwitch(),
];

View File

@@ -1,7 +1,10 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/fragments/config/app.dart';
import 'package:fl_clash/fragments/config/dns.dart';
import 'package:fl_clash/fragments/config/general.dart';
import 'package:fl_clash/fragments/config/network.dart';
import 'package:fl_clash/fragments/config/vpn.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
@@ -17,16 +20,36 @@ class _ConfigFragmentState extends State<ConfigFragment> {
Widget build(BuildContext context) {
List<Widget> items = [
ListItem.open(
title: Text(appLocalizations.network),
subtitle: Text(appLocalizations.networkDesc),
leading: const Icon(Icons.vpn_key),
title: Text(appLocalizations.app),
subtitle: Text(appLocalizations.appDesc),
leading: const Icon(Icons.settings_applications),
delegate: OpenDelegate(
title: appLocalizations.network,
isScaffold: true,
title: appLocalizations.app,
isBlur: false,
widget: const NetworkListView(),
widget: generateListView(
appItems
.separated(
const Divider(
height: 0,
),
)
.toList(),
),
),
),
if (Platform.isAndroid)
ListItem.open(
title: const Text("VPN"),
subtitle: Text(appLocalizations.vpnDesc),
leading: const Icon(Icons.vpn_key),
delegate: OpenDelegate(
title: "VPN",
isBlur: false,
widget: generateListView(
vpnItems,
),
),
),
ListItem.open(
title: Text(appLocalizations.general),
subtitle: Text(appLocalizations.generalDesc),
@@ -44,9 +67,11 @@ class _ConfigFragmentState extends State<ConfigFragment> {
title: const Text("DNS"),
subtitle: Text(appLocalizations.dnsDesc),
leading: const Icon(Icons.dns),
delegate: const OpenDelegate(
delegate: OpenDelegate(
title: "DNS",
widget: DnsListView(),
widget: generateListView(
dnsItems,
),
isScaffold: true,
isBlur: false,
extendPageWidth: 360,

View File

@@ -10,8 +10,35 @@ import 'package:provider/provider.dart';
class OverrideItem extends StatelessWidget {
const OverrideItem({super.key});
_initActions(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [
IconButton(
onPressed: () {
globalState.showMessage(
title: appLocalizations.resetDns,
message: TextSpan(
text: appLocalizations.dnsResetTip,
),
onTab: () {
globalState.appController.clashConfig.dns = const Dns();
Navigator.of(context).pop();
});
},
tooltip: appLocalizations.resetDns,
icon: const Icon(
Icons.replay,
),
)
];
});
}
@override
Widget build(BuildContext context) {
_initActions(context);
return Selector<Config, bool>(
selector: (_, config) => config.overrideDns,
builder: (_, override, __) {
@@ -31,6 +58,35 @@ class OverrideItem extends StatelessWidget {
}
}
class DnsDisabledContainer extends StatelessWidget {
final Widget child;
const DnsDisabledContainer(
this.child, {
super.key,
});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.overrideDns,
builder: (_, enable, child) {
return AbsorbPointer(
absorbing: !enable,
child: DisabledMask(
status: !enable,
child: Container(
color: context.colorScheme.surface,
child: child!,
),
),
);
},
child: child,
);
}
}
class StatusItem extends StatelessWidget {
const StatusItem({super.key});
@@ -219,17 +275,26 @@ class FakeIpFilterItem extends StatelessWidget {
title: appLocalizations.fakeipFilter,
widget: Selector<ClashConfig, List<String>>(
selector: (_, clashConfig) => clashConfig.dns.fakeIpFilter,
shouldRebuild: (prev, next) => !stringListEquality.equals(prev, next),
shouldRebuild: (prev, next) =>
!const ListEquality<String>().equals(prev, next),
builder: (_, fakeIpFilter, __) {
return ListPage(
return UpdatePage(
title: appLocalizations.fakeipFilter,
items: fakeIpFilter,
titleBuilder: (item) => Text(item),
onChange: (items){
onRemove: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
clashConfig.dns = dns.copyWith(
fakeIpFilter: List.from(items),
fakeIpFilter: List.from(dns.fakeIpFilter)..remove(value),
);
},
onAdd: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
if (fakeIpFilter.contains(value)) return;
clashConfig.dns = dns.copyWith(
fakeIpFilter: List.from(dns.fakeIpFilter)..add(value),
);
},
);
@@ -254,17 +319,28 @@ class DefaultNameserverItem extends StatelessWidget {
title: appLocalizations.defaultNameserver,
widget: Selector<ClashConfig, List<String>>(
selector: (_, clashConfig) => clashConfig.dns.defaultNameserver,
shouldRebuild: (prev, next) => !stringListEquality.equals(prev, next),
shouldRebuild: (prev, next) =>
!const ListEquality<String>().equals(prev, next),
builder: (_, defaultNameserver, __) {
return ListPage(
return UpdatePage(
title: appLocalizations.defaultNameserver,
items: defaultNameserver,
titleBuilder: (item) => Text(item),
onChange: (items){
onRemove: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
clashConfig.dns = dns.copyWith(
defaultNameserver: List.from(items),
defaultNameserver: List.from(dns.defaultNameserver)
..remove(value),
);
},
onAdd: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
if (defaultNameserver.contains(value)) return;
clashConfig.dns = dns.copyWith(
defaultNameserver: List.from(dns.defaultNameserver)
..add(value),
);
},
);
@@ -289,17 +365,26 @@ class NameserverItem extends StatelessWidget {
isBlur: false,
widget: Selector<ClashConfig, List<String>>(
selector: (_, clashConfig) => clashConfig.dns.nameserver,
shouldRebuild: (prev, next) => !stringListEquality.equals(prev, next),
shouldRebuild: (prev, next) =>
!const ListEquality<String>().equals(prev, next),
builder: (_, nameserver, __) {
return ListPage(
return UpdatePage(
title: "域名服务器",
items: nameserver,
titleBuilder: (item) => Text(item),
onChange: (items){
onRemove: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
clashConfig.dns = dns.copyWith(
nameserver: List.from(items),
nameserver: List.from(dns.nameserver)..remove(value),
);
},
onAdd: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
if (nameserver.contains(value)) return;
clashConfig.dns = dns.copyWith(
nameserver: List.from(dns.nameserver)..add(value),
);
},
);
@@ -379,16 +464,26 @@ class NameserverPolicyItem extends StatelessWidget {
shouldRebuild: (prev, next) =>
!const MapEquality<String, String>().equals(prev, next),
builder: (_, nameserverPolicy, __) {
return ListPage(
return UpdatePage(
title: appLocalizations.nameserverPolicy,
items: nameserverPolicy.entries,
titleBuilder: (item) => Text(item.key),
subtitleBuilder: (item) => Text(item.value),
onChange: (items){
isMap: true,
onRemove: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
clashConfig.dns = dns.copyWith(
nameserverPolicy: Map.fromEntries(items),
nameserverPolicy: Map.from(dns.nameserverPolicy)
..remove(value.key),
);
},
onAdd: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
clashConfig.dns = dns.copyWith(
nameserverPolicy: Map.from(dns.nameserverPolicy)
..addEntries([value]),
);
},
);
@@ -413,17 +508,28 @@ class ProxyServerNameserverItem extends StatelessWidget {
title: appLocalizations.proxyNameserver,
widget: Selector<ClashConfig, List<String>>(
selector: (_, clashConfig) => clashConfig.dns.proxyServerNameserver,
shouldRebuild: (prev, next) => !stringListEquality.equals(prev, next),
shouldRebuild: (prev, next) =>
!const ListEquality<String>().equals(prev, next),
builder: (_, proxyServerNameserver, __) {
return ListPage(
return UpdatePage(
title: appLocalizations.proxyNameserver,
items: proxyServerNameserver,
titleBuilder: (item) => Text(item),
onChange: (items){
onRemove: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
clashConfig.dns = dns.copyWith(
proxyServerNameserver: List.from(items),
proxyServerNameserver: List.from(dns.proxyServerNameserver)
..remove(value),
);
},
onAdd: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
if (proxyServerNameserver.contains(value)) return;
clashConfig.dns = dns.copyWith(
proxyServerNameserver: List.from(dns.proxyServerNameserver)
..add(value),
);
},
);
@@ -448,17 +554,26 @@ class FallbackItem extends StatelessWidget {
title: appLocalizations.fallback,
widget: Selector<ClashConfig, List<String>>(
selector: (_, clashConfig) => clashConfig.dns.fallback,
shouldRebuild: (prev, next) => !stringListEquality.equals(prev, next),
shouldRebuild: (prev, next) =>
!const ListEquality<String>().equals(prev, next),
builder: (_, fallback, __) {
return ListPage(
return UpdatePage(
title: appLocalizations.fallback,
items: fallback,
titleBuilder: (item) => Text(item),
onChange: (items){
onRemove: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
clashConfig.dns = dns.copyWith(
fallback: List.from(items),
fallback: List.from(dns.fallback)..remove(value),
);
},
onAdd: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
if (fallback.contains(value)) return;
clashConfig.dns = dns.copyWith(
fallback: List.from(dns.fallback)..add(value),
);
},
);
@@ -549,18 +664,28 @@ class GeositeItem extends StatelessWidget {
title: "Geosite",
widget: Selector<ClashConfig, List<String>>(
selector: (_, clashConfig) => clashConfig.dns.fallbackFilter.geosite,
shouldRebuild: (prev, next) => !stringListEquality.equals(prev, next),
shouldRebuild: (prev, next) =>
!const ListEquality<String>().equals(prev, next),
builder: (_, geosite, __) {
return ListPage(
return UpdatePage(
title: "Geosite",
items: geosite,
titleBuilder: (item) => Text(item),
onChange: (items){
onRemove: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
clashConfig.dns = dns.copyWith(
fallbackFilter: dns.fallbackFilter.copyWith(
geosite: List.from(items),
geosite: List.from(geosite)..remove(value),
),
);
},
onAdd: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
clashConfig.dns = dns.copyWith(
fallbackFilter: dns.fallbackFilter.copyWith(
geosite: List.from(geosite)..add(value),
),
);
},
@@ -585,18 +710,28 @@ class IpcidrItem extends StatelessWidget {
title: appLocalizations.ipcidr,
widget: Selector<ClashConfig, List<String>>(
selector: (_, clashConfig) => clashConfig.dns.fallbackFilter.ipcidr,
shouldRebuild: (prev, next) => !stringListEquality.equals(prev, next),
shouldRebuild: (prev, next) =>
!const ListEquality<String>().equals(prev, next),
builder: (_, ipcidr, __) {
return ListPage(
return UpdatePage(
title: appLocalizations.ipcidr,
items: ipcidr,
titleBuilder: (item) => Text(item),
onChange: (items){
onRemove: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
clashConfig.dns = dns.copyWith(
fallbackFilter: dns.fallbackFilter.copyWith(
ipcidr: List.from(items),
ipcidr: List.from(ipcidr)..remove(value),
),
);
},
onAdd: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
clashConfig.dns = dns.copyWith(
fallbackFilter: dns.fallbackFilter.copyWith(
ipcidr: List.from(ipcidr)..add(value),
),
);
},
@@ -621,18 +756,28 @@ class DomainItem extends StatelessWidget {
title: appLocalizations.domain,
widget: Selector<ClashConfig, List<String>>(
selector: (_, clashConfig) => clashConfig.dns.fallbackFilter.domain,
shouldRebuild: (prev, next) => !stringListEquality.equals(prev, next),
shouldRebuild: (prev, next) =>
!const ListEquality<String>().equals(prev, next),
builder: (_, domain, __) {
return ListPage(
return UpdatePage(
title: appLocalizations.domain,
items: domain,
titleBuilder: (item) => Text(item),
onChange: (items){
onRemove: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
clashConfig.dns = dns.copyWith(
fallbackFilter: dns.fallbackFilter.copyWith(
domain: List.from(items),
domain: List.from(domain)..remove(value),
),
);
},
onAdd: (value) {
final clashConfig = globalState.appController.clashConfig;
final dns = clashConfig.dns;
clashConfig.dns = dns.copyWith(
fallbackFilter: dns.fallbackFilter.copyWith(
domain: List.from(domain)..add(value),
),
);
},
@@ -650,25 +795,27 @@ class DnsOptions extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: generateSection(
title: appLocalizations.options,
items: [
const StatusItem(),
const UseHostsItem(),
const UseSystemHostsItem(),
const IPv6Item(),
const RespectRulesItem(),
const PreferH3Item(),
const DnsModeItem(),
const FakeIpRangeItem(),
const FakeIpFilterItem(),
const DefaultNameserverItem(),
const NameserverPolicyItem(),
const NameserverItem(),
const FallbackItem(),
const ProxyServerNameserverItem(),
],
return DnsDisabledContainer(
Column(
children: generateSection(
title: appLocalizations.options,
items: [
const StatusItem(),
const UseHostsItem(),
const UseSystemHostsItem(),
const IPv6Item(),
const RespectRulesItem(),
const PreferH3Item(),
const DnsModeItem(),
const FakeIpRangeItem(),
const FakeIpFilterItem(),
const DefaultNameserverItem(),
const NameserverPolicyItem(),
const NameserverItem(),
const FallbackItem(),
const ProxyServerNameserverItem(),
],
),
),
);
}
@@ -679,16 +826,18 @@ class FallbackFilterOptions extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: generateSection(
title: appLocalizations.fallbackFilter,
items: [
const GeoipItem(),
const GeoipCodeItem(),
const GeositeItem(),
const IpcidrItem(),
const DomainItem(),
],
return DnsDisabledContainer(
Column(
children: generateSection(
title: appLocalizations.fallbackFilter,
items: [
const GeoipItem(),
const GeoipCodeItem(),
const GeositeItem(),
const IpcidrItem(),
const DomainItem(),
],
),
),
);
}
@@ -699,41 +848,3 @@ const dnsItems = <Widget>[
DnsOptions(),
FallbackFilterOptions(),
];
class DnsListView extends StatelessWidget {
const DnsListView({super.key});
_initActions(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [
IconButton(
onPressed: () {
globalState.showMessage(
title: appLocalizations.reset,
message: TextSpan(
text: appLocalizations.resetTip,
),
onTab: () {
globalState.appController.clashConfig.dns = defaultDns;
Navigator.of(context).pop();
});
},
tooltip: appLocalizations.reset,
icon: const Icon(
Icons.replay,
),
)
];
});
}
@override
Widget build(BuildContext context) {
_initActions(context);
return generateListView(
dnsItems,
);
}
}

View File

@@ -119,7 +119,7 @@ class TestUrlItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Selector<Config, String>(
selector: (_, config) => config.appSetting.testUrl,
selector: (_, config) => config.testUrl,
builder: (_, value, __) {
return ListItem.input(
leading: const Icon(Icons.timeline),
@@ -135,10 +135,7 @@ class TestUrlItem extends StatelessWidget {
if (!value.isUrl) {
throw "Invalid url";
}
final config = globalState.appController.config;
config.appSetting = config.appSetting.copyWith(
testUrl: value,
);
globalState.appController.config.testUrl = value;
} catch (e) {
globalState.showMessage(
title: appLocalizations.testUrl,
@@ -215,15 +212,21 @@ class HostsItem extends StatelessWidget {
!const MapEquality<String, String>().equals(prev, next),
builder: (_, hosts, ___) {
final entries = hosts.entries;
return ListPage(
return UpdatePage(
title: "Hosts",
items: entries,
titleBuilder: (item) => Text(item.key),
subtitleBuilder: (item) => Text(item.value),
onChange: (items){
onRemove: (value) {
final clashConfig = globalState.appController.clashConfig;
clashConfig.hosts = Map.fromEntries(items);
clashConfig.hosts = Map.from(hosts)..remove(value.key);
},
onAdd: (value) {
final clashConfig = globalState.appController.clashConfig;
clashConfig.hosts = Map.from(clashConfig.hosts)
..addEntries([value]);
},
isMap: true,
);
},
),

View File

@@ -1,270 +0,0 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class VPNSwitch extends StatelessWidget {
const VPNSwitch({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.vpnProps.enable,
builder: (_, enable, __) {
return ListItem.switchItem(
title: const Text("VPN"),
subtitle: Text(appLocalizations.vpnEnableDesc),
delegate: SwitchDelegate(
value: enable,
onChanged: (value) async {
final config = globalState.appController.config;
config.vpnProps = config.vpnProps.copyWith(
enable: value,
);
},
),
);
},
);
}
}
class TUNItem extends StatelessWidget {
const TUNItem({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.vpnProps.enable,
builder: (_, enable, __) {
return ListItem.switchItem(
title: Text(appLocalizations.tun),
subtitle: Text(appLocalizations.tunDesc),
delegate: SwitchDelegate(
value: enable,
onChanged: (value) async {
final clashConfig = globalState.appController.clashConfig;
clashConfig.tun = clashConfig.tun.copyWith(
enable: value,
);
},
),
);
},
);
}
}
class AllowBypassSwitch extends StatelessWidget {
const AllowBypassSwitch({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.vpnProps.allowBypass,
builder: (_, allowBypass, __) {
return ListItem.switchItem(
title: Text(appLocalizations.allowBypass),
subtitle: Text(appLocalizations.allowBypassDesc),
delegate: SwitchDelegate(
value: allowBypass,
onChanged: (bool value) async {
final config = globalState.appController.config;
final vpnProps = config.vpnProps;
config.vpnProps = vpnProps.copyWith(
allowBypass: value,
);
},
),
);
},
);
}
}
class SystemProxySwitch extends StatelessWidget {
const SystemProxySwitch({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.vpnProps.systemProxy,
builder: (_, systemProxy, __) {
return ListItem.switchItem(
title: Text(appLocalizations.systemProxy),
subtitle: Text(appLocalizations.systemProxyDesc),
delegate: SwitchDelegate(
value: systemProxy,
onChanged: (bool value) async {
final config = globalState.appController.config;
final vpnProps = config.vpnProps;
config.vpnProps = vpnProps.copyWith(
systemProxy: value,
);
},
),
);
},
);
}
}
class Ipv6Switch extends StatelessWidget {
const Ipv6Switch({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.vpnProps.ipv6,
builder: (_, ipv6, __) {
return ListItem.switchItem(
title: const Text("IPv6"),
subtitle: Text(appLocalizations.ipv6InboundDesc),
delegate: SwitchDelegate(
value: ipv6,
onChanged: (bool value) async {
final config = globalState.appController.config;
final vpnProps = config.vpnProps;
config.vpnProps = vpnProps.copyWith(
ipv6: value,
);
},
),
);
},
);
}
}
class TunStackItem extends StatelessWidget {
const TunStackItem({super.key});
@override
Widget build(BuildContext context) {
return Selector<ClashConfig, TunStack>(
selector: (_, clashConfig) => clashConfig.tun.stack,
builder: (_, stack, __) {
return ListItem.options(
title: Text(appLocalizations.stackMode),
subtitle: Text(stack.name),
delegate: OptionsDelegate<TunStack>(
value: stack,
options: TunStack.values,
textBuilder: (value) => value.name,
onChanged: (value) {
if (value == null) {
return;
}
final clashConfig = globalState.appController.clashConfig;
clashConfig.tun = clashConfig.tun.copyWith(
stack: value,
);
},
title: appLocalizations.stackMode,
),
);
},
);
}
}
class BypassDomainItem extends StatelessWidget {
const BypassDomainItem({super.key});
@override
Widget build(BuildContext context) {
return ListItem.open(
title: Text(appLocalizations.bypassDomain),
subtitle: Text(appLocalizations.bypassDomainDesc),
delegate: OpenDelegate(
isBlur: false,
title: appLocalizations.bypassDomain,
widget: Selector<Config, List<String>>(
selector: (_, config) => config.vpnProps.bypassDomain,
shouldRebuild: (prev, next) =>
!stringListEquality.equals(prev, next),
builder: (_, bypassDomain, __) {
return ListPage(
title: appLocalizations.bypassDomain,
items: bypassDomain,
titleBuilder: (item) => Text(item),
onChange: (items){
final config = globalState.appController.config;
config.vpnProps = config.vpnProps.copyWith(
bypassDomain: List.from(items),
);
},
);
},
),
extendPageWidth: 360,
),
);
}
}
final networkItems = [
Platform.isAndroid ? const VPNSwitch() : const TUNItem(),
if (Platform.isAndroid)
...generateSection(
title: "VPN",
items: [
const SystemProxySwitch(),
const AllowBypassSwitch(),
const Ipv6Switch(),
const BypassDomainItem(),
],
),
...generateSection(
title: appLocalizations.options,
items: [
const TunStackItem(),
],
),
];
class NetworkListView extends StatelessWidget {
const NetworkListView({super.key});
_initActions(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final commonScaffoldState =
context.findAncestorStateOfType<CommonScaffoldState>();
commonScaffoldState?.actions = [
IconButton(
onPressed: () {
globalState.showMessage(
title: appLocalizations.reset,
message: TextSpan(
text: appLocalizations.resetTip,
),
onTab: () {
final appController = globalState.appController;
appController.config.vpnProps = defaultVpnProps;
appController.clashConfig.tun = defaultTun;
Navigator.of(context).pop();
},
);
},
tooltip: appLocalizations.reset,
icon: const Icon(
Icons.replay,
),
)
];
});
}
@override
Widget build(BuildContext context) {
_initActions(context);
return generateListView(
networkItems,
);
}
}

View File

@@ -0,0 +1,140 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class VPNSwitch extends StatelessWidget {
const VPNSwitch({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.vpnProps.enable,
builder: (_, enable, __) {
return ListItem.switchItem(
leading: const Icon(Icons.stacked_line_chart),
title: const Text("VPN"),
subtitle: Text(appLocalizations.vpnEnableDesc),
delegate: SwitchDelegate(
value: enable,
onChanged: (bool value) async {
final config = globalState.appController.config;
final vpnProps = config.vpnProps;
config.vpnProps = vpnProps.copyWith(
enable: value,
);
},
),
);
},
);
}
}
class VPNDisabledContainer extends StatelessWidget {
final Widget child;
const VPNDisabledContainer(
this.child, {
super.key,
});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.vpnProps.enable,
builder: (_, enable, child) {
return AbsorbPointer(
absorbing: !enable,
child: DisabledMask(
status: !enable,
child: child!,
),
);
},
child: child,
);
}
}
class AllowBypassSwitch extends StatelessWidget {
const AllowBypassSwitch({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.vpnProps.allowBypass,
builder: (_, allowBypass, __) {
return ListItem.switchItem(
leading: const Icon(Icons.arrow_forward_outlined),
title: Text(appLocalizations.allowBypass),
subtitle: Text(appLocalizations.allowBypassDesc),
delegate: SwitchDelegate(
value: allowBypass,
onChanged: (bool value) async {
final config = globalState.appController.config;
final vpnProps = config.vpnProps;
config.vpnProps = vpnProps.copyWith(
allowBypass: value,
);
},
),
);
},
);
}
}
class SystemProxySwitch extends StatelessWidget {
const SystemProxySwitch({super.key});
@override
Widget build(BuildContext context) {
return Selector<Config, bool>(
selector: (_, config) => config.vpnProps.systemProxy,
builder: (_, systemProxy, __) {
return ListItem.switchItem(
leading: const Icon(Icons.settings_ethernet),
title: Text(appLocalizations.systemProxy),
subtitle: Text(appLocalizations.systemProxyDesc),
delegate: SwitchDelegate(
value: systemProxy,
onChanged: (bool value) async {
final config = globalState.appController.config;
final vpnProps = config.vpnProps;
config.vpnProps = vpnProps.copyWith(
systemProxy: value,
);
},
),
);
},
);
}
}
class VpnOptions extends StatelessWidget {
const VpnOptions({super.key});
@override
Widget build(BuildContext context) {
return VPNDisabledContainer(
Column(
children: generateSection(
title: appLocalizations.options,
items: [
const SystemProxySwitch(),
const AllowBypassSwitch(),
],
),
),
);
}
}
final vpnItems = [
const VPNSwitch(),
const VpnOptions(),
];

View File

@@ -13,55 +13,16 @@ class IntranetIP extends StatefulWidget {
}
class _IntranetIPState extends State<IntranetIP> {
final ipNotifier = ValueNotifier<String?>("");
Future<String> getNetworkType() async {
try {
List<NetworkInterface> interfaces = await NetworkInterface.list(
includeLoopback: false,
type: InternetAddressType.any,
);
for (var interface in interfaces) {
if (interface.name.toLowerCase().contains('wlan') ||
interface.name.toLowerCase().contains('wi-fi')) {
return 'WiFi';
}
if (interface.name.toLowerCase().contains('rmnet') ||
interface.name.toLowerCase().contains('ccmni') ||
interface.name.toLowerCase().contains('cellular')) {
return 'Mobile Data';
}
}
return 'Unknown';
} catch (e) {
return 'Error';
}
}
final ipNotifier = ValueNotifier<String>("");
Future<String?> getLocalIpAddress() async {
List<NetworkInterface> interfaces = await NetworkInterface.list(
includeLoopback: false,
)
..sort((a, b) {
if (a.isWifi && !b.isWifi) return -1;
if (!a.isWifi && b.isWifi) return 1;
if (a.includesIPv4 && !b.includesIPv4) return -1;
if (!a.includesIPv4 && b.includesIPv4) return 1;
return 0;
});
List<NetworkInterface> interfaces = await NetworkInterface.list();
for (final interface in interfaces) {
final addresses = interface.addresses;
if (addresses.isEmpty) {
continue;
for (final address in interface.addresses) {
if (!address.isLoopback) {
return address.address;
}
}
addresses.sort((a, b) {
if (a.isIPv4 && !b.isIPv4) return -1;
if (!a.isIPv4 && b.isIPv4) return 1;
return 0;
});
return addresses.first.address;
}
return null;
}
@@ -87,15 +48,17 @@ class _IntranetIPState extends State<IntranetIP> {
label: appLocalizations.intranetIP,
iconData: Icons.devices,
),
onPressed: () {},
onPressed: (){
},
child: Container(
padding: const EdgeInsets.all(16).copyWith(top: 0),
height: globalState.measure.titleMediumHeight + 24 - 2,
height: globalState.measure.titleLargeHeight + 24 - 2,
child: ValueListenableBuilder(
valueListenable: ipNotifier,
builder: (_, value, __) {
return FadeBox(
child: value != null
child: value.isNotEmpty
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
@@ -104,9 +67,8 @@ class _IntranetIPState extends State<IntranetIP> {
flex: 1,
child: TooltipText(
text: Text(
value.isNotEmpty ? value : appLocalizations.noNetwork,
style: context
.textTheme.titleLarge?.toSoftBold.toMinus,
value,
style: context.textTheme.titleLarge?.toSoftBold.toMinus,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),

View File

@@ -1,8 +1,5 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
@@ -25,7 +22,6 @@ class _NetworkDetectionState extends State<NetworkDetection> {
);
bool? _preIsStart;
Function? _checkIpDebounce;
Timer? _setTimeoutTimer;
CancelToken? cancelToken;
_checkIp() async {
@@ -35,7 +31,6 @@ class _NetworkDetectionState extends State<NetworkDetection> {
if (!isInit) return;
final isStart = appFlowingState.isStart;
if (_preIsStart == false && _preIsStart == isStart) return;
_clearSetTimeoutTimer();
networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: true,
ipInfo: null,
@@ -48,32 +43,11 @@ class _NetworkDetectionState extends State<NetworkDetection> {
cancelToken = CancelToken();
try {
final ipInfo = await request.checkIp(cancelToken: cancelToken);
if (ipInfo != null) {
networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: false,
ipInfo: ipInfo,
);
return;
}
_setTimeoutTimer = Timer(const Duration(milliseconds: 2000), () {
networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: false,
ipInfo: null,
);
});
} catch (_) {
networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: true,
ipInfo: null,
isTesting: false,
ipInfo: ipInfo,
);
}
}
_clearSetTimeoutTimer() {
if (_setTimeoutTimer != null) {
_setTimeoutTimer?.cancel();
_setTimeoutTimer = null;
}
} catch (_) {}
}
_checkIpContainer(Widget child) {
@@ -156,7 +130,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
.textTheme
.titleLarge
?.copyWith(
fontFamily: FontFamily.twEmoji.value,
fontFamily: "Twemoji",
),
),
)

View File

@@ -146,7 +146,7 @@ class _HotKeyRecorderState extends State<HotKeyRecorder> {
final index = hotKeyActions.indexWhere(
(item) =>
item.key == currentHotkeyAction.key &&
keyboardModifierListEquality.equals(
keyboardModifiersEquality.equals(
item.modifiers,
currentHotkeyAction.modifiers,
),

View File

@@ -1,4 +1,6 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/state.dart';
@@ -19,6 +21,7 @@ class _LogsFragmentState extends State<LogsFragment> {
final scrollController = ScrollController(
keepScrollOffset: false,
);
List<GlobalObjectKey<_LogItemState>> keys = [];
Timer? timer;
@@ -27,21 +30,18 @@ class _LogsFragmentState extends State<LogsFragment> {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final appFlowingState = globalState.appController.appFlowingState;
logsNotifier.value =
logsNotifier.value.copyWith(logs: appFlowingState.logs);
logsNotifier.value = logsNotifier.value.copyWith(logs: appFlowingState.logs);
if (timer != null) {
timer?.cancel();
timer = null;
}
timer = Timer.periodic(const Duration(milliseconds: 200), (timer) {
final logs = appFlowingState.logs;
if (!logListEquality.equals(
if (!const ListEquality<Log>().equals(
logsNotifier.value.logs,
logs,
)) {
logsNotifier.value = logsNotifier.value.copyWith(
logs: logs,
);
logsNotifier.value = logsNotifier.value.copyWith(logs: logs);
}
});
});
@@ -56,21 +56,6 @@ class _LogsFragmentState extends State<LogsFragment> {
timer = null;
}
_handleExport() async {
final commonScaffoldState = context.commonScaffoldState;
final res = await commonScaffoldState?.loadingRun<bool>(
() async {
return await globalState.appController.exportLogs();
},
title: appLocalizations.exportLogs,
);
if (res != true) return;
globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(text: appLocalizations.exportSuccess),
);
}
_initActions() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
final commonScaffoldState =
@@ -87,17 +72,6 @@ class _LogsFragmentState extends State<LogsFragment> {
},
icon: const Icon(Icons.search),
),
const SizedBox(
width: 8,
),
IconButton(
onPressed: () {
_handleExport();
},
icon: const Icon(
Icons.file_download_outlined,
),
),
];
});
}
@@ -138,26 +112,15 @@ class _LogsFragmentState extends State<LogsFragment> {
child: ValueListenableBuilder<LogsAndKeywords>(
valueListenable: logsNotifier,
builder: (_, state, __) {
final logs = state.filteredLogs;
var logs = state.filteredLogs;
if (logs.isEmpty) {
return NullStatus(
label: appLocalizations.nullLogsDesc,
);
}
final reversedLogs = logs.reversed.toList();
final logWidgets = reversedLogs
.map<Widget>(
(log) => LogItem(
key: Key(log.dateTime.toString()),
log: log,
onClick: _addKeyword,
),
)
.separated(
const Divider(
height: 0,
),
)
logs = logs.reversed.toList();
keys = logs
.map((log) => GlobalObjectKey<_LogItemState>(log.dateTime))
.toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -184,38 +147,22 @@ class _LogsFragmentState extends State<LogsFragment> {
),
),
Expanded(
child: LayoutBuilder(
builder: (_, constraints) {
return ScrollConfiguration(
behavior: ShowBarScrollBehavior(),
child: ListView.builder(
controller: scrollController,
itemExtentBuilder: (index, __) {
final widget = logWidgets[index];
if (widget.runtimeType == Divider) {
return 0;
}
final measure = globalState.measure;
final bodyLargeSize = measure.bodyLargeSize;
final bodySmallHeight = measure.bodySmallHeight;
final bodyMediumHeight = measure.bodyMediumHeight;
final log = reversedLogs[(index / 2).floor()];
final width = (log.payload?.length ?? 0) *
bodyLargeSize.width +
200;
final lines = (width / constraints.maxWidth).ceil();
return lines * bodyLargeSize.height +
bodySmallHeight +
8 +
bodyMediumHeight +
40;
},
itemBuilder: (_, index) {
return logWidgets[index];
},
itemCount: logWidgets.length,
));
child: ListView.separated(
controller: scrollController,
itemBuilder: (_, index) {
final log = logs[index];
return LogItem(
key: Key(log.dateTime.toString()),
log: log,
onClick: _addKeyword,
);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider(
height: 0,
);
},
itemCount: logs.length,
),
)
],
@@ -289,8 +236,7 @@ class LogsSearchDelegate extends SearchDelegate {
_addKeyword(String keyword) {
final isContains = logsNotifier.value.keywords.contains(keyword);
if (isContains) return;
final keywords = List<String>.from(logsNotifier.value.keywords)
..add(keyword);
final keywords = List<String>.from(logsNotifier.value.keywords)..add(keyword);
logsNotifier.value = logsNotifier.value.copyWith(
keywords: keywords,
);
@@ -299,8 +245,7 @@ class LogsSearchDelegate extends SearchDelegate {
_deleteKeyword(String keyword) {
final isContains = logsNotifier.value.keywords.contains(keyword);
if (!isContains) return;
final keywords = List<String>.from(logsNotifier.value.keywords)
..remove(keyword);
final keywords = List<String>.from(logsNotifier.value.keywords)..remove(keyword);
logsNotifier.value = logsNotifier.value.copyWith(
keywords: keywords,
);
@@ -385,9 +330,7 @@ class _LogItemState extends State<LogItem> {
horizontal: 16,
vertical: 4,
),
title: SelectableText(
log.payload ?? '',
),
title: SelectableText(log.payload ?? ''),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -396,9 +339,7 @@ class _LogItemState extends State<LogItem> {
style: context.textTheme.bodySmall
?.copyWith(color: context.colorScheme.primary),
),
const SizedBox(
height: 8,
),
const SizedBox(height: 8,),
Container(
alignment: Alignment.centerLeft,
child: CommonChip(

View File

@@ -459,9 +459,10 @@ class _ReorderableProfilesState extends State<ReorderableProfiles> {
flex: 1,
child: ReorderableListView.builder(
buildDefaultDragHandles: false,
padding: const EdgeInsets.symmetric(horizontal: 12),
padding: const EdgeInsets.all(12),
proxyDecorator: proxyDecorator,
onReorder: (oldIndex, newIndex) {
onReorder: (int oldIndex, int newIndex) {
if (oldIndex == newIndex) return;
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;

View File

@@ -166,20 +166,20 @@ class ContextMenuControllerImpl implements SelectionToolbarController {
// _removeOverLayEntry();
}
// _handleCut(CodeLineEditingController controller) {
// controller.cut();
// _removeOverLayEntry();
// }
//
// _handleCopy(CodeLineEditingController controller) async {
// await controller.copy();
// _removeOverLayEntry();
// }
//
// _handlePaste(CodeLineEditingController controller) {
// controller.paste();
// _removeOverLayEntry();
// }
_handleCut(CodeLineEditingController controller) {
controller.cut();
_removeOverLayEntry();
}
_handleCopy(CodeLineEditingController controller) async {
await controller.copy();
_removeOverLayEntry();
}
_handlePaste(CodeLineEditingController controller) {
controller.paste();
_removeOverLayEntry();
}
@override
void show({

View File

@@ -102,12 +102,12 @@ class ProxyCard extends StatelessWidget {
_changeProxy(BuildContext context) async {
final appController = globalState.appController;
final isURLTestOrFallback = groupType.isURLTestOrFallback;
final isUrlTest = groupType == GroupType.URLTest;
final isSelector = groupType == GroupType.Selector;
if (isURLTestOrFallback || isSelector) {
if (isUrlTest || isSelector) {
final currentProxyName =
appController.config.currentSelectedMap[groupName];
final nextProxyName = switch (isURLTestOrFallback) {
final nextProxyName = switch (isUrlTest) {
true => currentProxyName == proxy.name ? "" : proxy.name,
false => proxy.name,
};
@@ -207,7 +207,7 @@ class ProxyCard extends StatelessWidget {
),
),
),
if (groupType.isURLTestOrFallback)
if (groupType == GroupType.URLTest)
Selector<Config, String>(
selector: (_, config) {
final selectedProxyName =

View File

@@ -81,9 +81,9 @@ double getScrollToSelectedOffset({
final appController = globalState.appController;
final columns = other.getProxiesColumns(
appController.appState.viewWidth,
appController.config.proxiesStyle.layout,
appController.config.proxiesLayout,
);
final proxyCardType = appController.config.proxiesStyle.cardType;
final proxyCardType = appController.config.proxyCardType;
final selectedName = appController.getCurrentSelectedName(groupName);
final findSelectedIndex = proxies.indexWhere(
(proxy) => proxy.name == selectedName,

View File

@@ -1,10 +1,14 @@
import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:fl_clash/widgets/builder.dart';
import 'package:fl_clash/widgets/card.dart';
import 'package:fl_clash/widgets/text.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@@ -240,17 +244,18 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
return ProxiesListSelectorState(
groupNames: groupNames,
currentUnfoldSet: config.currentUnfoldSet,
proxyCardType: config.proxiesStyle.cardType,
proxiesSortType: config.proxiesStyle.sortType,
proxyCardType: config.proxyCardType,
proxiesSortType: config.proxiesSortType,
columns: other.getProxiesColumns(
appState.viewWidth,
config.proxiesStyle.layout,
config.proxiesLayout,
),
sortNum: appState.sortNum,
);
},
shouldRebuild: (prev, next) {
if (!stringListEquality.equals(prev.groupNames, next.groupNames)) {
if (!const ListEquality<String>()
.equals(prev.groupNames, next.groupNames)) {
_headerStateNotifier.value = const ProxiesListHeaderSelectorState(
offset: 0,
currentIndex: 0,
@@ -259,73 +264,75 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
return prev != next;
},
builder: (_, state, __) {
final items = _buildItems(
groupNames: state.groupNames,
currentUnfoldSet: state.currentUnfoldSet,
columns: state.columns,
type: state.proxyCardType,
);
final itemsOffset = _getItemHeightList(items, state.proxyCardType);
return Scrollbar(
controller: _controller,
thumbVisibility: true,
trackVisibility: true,
thickness: 8,
radius: const Radius.circular(8),
interactive: true,
child: Stack(
children: [
Positioned.fill(
child: ScrollConfiguration(
behavior: HiddenBarScrollBehavior(),
child: ListView.builder(
padding: const EdgeInsets.all(16),
controller: _controller,
itemExtentBuilder: (index, __) {
return itemsOffset[index];
},
itemCount: items.length,
itemBuilder: (_, index) {
return items[index];
},
return ScaleBuilder(builder: (_) {
final items = _buildItems(
groupNames: state.groupNames,
currentUnfoldSet: state.currentUnfoldSet,
columns: state.columns,
type: state.proxyCardType,
);
final itemsOffset = _getItemHeightList(items, state.proxyCardType);
return Scrollbar(
controller: _controller,
thumbVisibility: true,
trackVisibility: true,
thickness: 8,
radius: const Radius.circular(8),
interactive: true,
child: Stack(
children: [
Positioned.fill(
child: ScrollConfiguration(
behavior: HiddenBarScrollBehavior(),
child: ListView.builder(
padding: const EdgeInsets.all(16),
controller: _controller,
itemExtentBuilder: (index, __) {
return itemsOffset[index];
},
itemCount: items.length,
itemBuilder: (_, index) {
return items[index];
},
),
),
),
),
LayoutBuilder(builder: (_, container) {
return ValueListenableBuilder(
valueListenable: _headerStateNotifier,
builder: (_, headerState, ___) {
final index =
headerState.currentIndex > state.groupNames.length - 1
? 0
: headerState.currentIndex;
return Stack(
children: [
Positioned(
top: -headerState.offset,
child: Container(
width: container.maxWidth,
color: context.colorScheme.surface,
padding: const EdgeInsets.only(
top: 16,
left: 16,
right: 16,
bottom: 8,
),
child: _buildHeader(
groupName: state.groupNames[index],
currentUnfoldSet: state.currentUnfoldSet,
LayoutBuilder(builder: (_, container) {
return ValueListenableBuilder(
valueListenable: _headerStateNotifier,
builder: (_, headerState, ___) {
final index =
headerState.currentIndex > state.groupNames.length - 1
? 0
: headerState.currentIndex;
return Stack(
children: [
Positioned(
top: -headerState.offset,
child: Container(
width: container.maxWidth,
color: context.colorScheme.surface,
padding: const EdgeInsets.only(
top: 16,
left: 16,
right: 16,
bottom: 8,
),
child: _buildHeader(
groupName: state.groupNames[index],
currentUnfoldSet: state.currentUnfoldSet,
),
),
),
),
],
);
},
);
}),
],
),
);
],
);
},
);
}),
],
),
);
});
},
);
}
@@ -372,6 +379,11 @@ class _ListHeaderState extends State<ListHeader>
}
_handleChange(String groupName) {
if (isExpand) {
_animationController.reverse();
} else {
_animationController.forward();
}
widget.onChange(groupName);
}
@@ -401,69 +413,13 @@ class _ListHeaderState extends State<ListHeader>
super.didUpdateWidget(oldWidget);
if (oldWidget.isExpand != widget.isExpand) {
if (isExpand) {
_animationController.forward();
_animationController.value = 1.0;
} else {
_animationController.reverse();
_animationController.value = 0.0;
}
}
}
Widget _buildIcon() {
return Selector<Config, ProxiesIconStyle>(
selector: (_, config) => config.proxiesStyle.iconStyle,
builder: (_, iconStyle, child) {
return Selector<Config, String>(
selector: (_, config) {
final iconMapEntryList =
config.proxiesStyle.iconMap.entries.toList();
final index = iconMapEntryList.indexWhere((item) {
try{
return RegExp(item.key).hasMatch(groupName);
}catch(_){
return false;
}
});
if (index != -1) {
return iconMapEntryList[index].value;
}
return icon;
},
builder: (_, icon, __) {
return switch (iconStyle) {
ProxiesIconStyle.standard => Container(
height: 48,
width: 48,
margin: const EdgeInsets.only(
right: 16,
),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: context.colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(16),
),
clipBehavior: Clip.antiAlias,
child: CommonIcon(
src: icon,
size: 32,
),
),
ProxiesIconStyle.icon => Container(
margin: const EdgeInsets.only(
right: 16,
),
child: CommonIcon(
src: icon,
size: 42,
),
),
ProxiesIconStyle.none => Container(),
};
},
);
},
);
}
@override
Widget build(BuildContext context) {
return CommonCard(
@@ -471,17 +427,41 @@ class _ListHeaderState extends State<ListHeader>
radius: 24,
type: CommonCardType.filled,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
padding: const EdgeInsets.all(12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Row(
children: [
_buildIcon(),
const SizedBox(
width: 4,
),
Container(
height: 48,
width: 48,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: context.colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(16),
),
clipBehavior: Clip.antiAlias,
child: icon.isNotEmpty
? CachedNetworkImage(
imageUrl: icon,
errorWidget: (_, __, ___) => const Icon(
IconsExt.target,
size: 32,
),
)
: const Icon(
IconsExt.target,
size: 32,
),
),
const SizedBox(
width: 16,
),
Flexible(
child: Column(
mainAxisSize: MainAxisSize.min,

View File

@@ -1,11 +1,11 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/common/app_localizations.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/fragments/proxies/list.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers.dart';
import 'setting.dart';
import 'tab.dart';
@@ -37,7 +37,7 @@ class _ProxiesFragmentState extends State<ProxiesFragment> {
);
},
icon: const Icon(
Icons.poll_outlined,
Icons.swap_vert_circle_outlined,
),
),
const SizedBox(
@@ -56,63 +56,6 @@ class _ProxiesFragmentState extends State<ProxiesFragment> {
const SizedBox(
width: 8,
)
] else ...[
IconButton(
onPressed: () {
showExtendPage(
context,
extendPageWidth: 360,
title: appLocalizations.iconConfiguration,
body: Selector<Config, Map<String, String>>(
selector: (_, config) => config.proxiesStyle.iconMap,
shouldRebuild: (prev, next) {
return !stringAndStringMapEntryIterableEquality.equals(
prev.entries,
next.entries,
);
},
builder: (_, iconMap, __) {
final entries = iconMap.entries.toList();
return ListPage(
title: appLocalizations.iconConfiguration,
items: entries,
keyLabel: appLocalizations.regExp,
valueLabel: appLocalizations.icon,
keyBuilder: (item) => Key(item.key),
titleBuilder: (item) => Text(item.key),
leadingBuilder: (item) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
),
clipBehavior: Clip.antiAlias,
child: CommonIcon(
src: item.value,
size: 42,
),
),
subtitleBuilder: (item) => Text(
item.value,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
onChange: (entries) {
final config = globalState.appController.config;
config.proxiesStyle = config.proxiesStyle.copyWith(
iconMap: Map.fromEntries(entries),
);
},
);
},
),
);
},
icon: const Icon(
Icons.style_outlined,
),
),
const SizedBox(
width: 8,
)
],
IconButton(
onPressed: () {
@@ -120,7 +63,7 @@ class _ProxiesFragmentState extends State<ProxiesFragment> {
title: appLocalizations.proxiesSetting,
context: context,
builder: (context) {
return const ProxiesSetting();
return const ProxiesSettingWidget();
},
);
},
@@ -135,7 +78,7 @@ class _ProxiesFragmentState extends State<ProxiesFragment> {
@override
Widget build(BuildContext context) {
return Selector<Config, ProxiesType>(
selector: (_, config) => config.proxiesStyle.type,
selector: (_, config) => config.proxiesType,
builder: (_, proxiesType, __) {
return ProxiesActionsBuilder(
builder: (state, child) {

View File

@@ -7,8 +7,8 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
class ProxiesSetting extends StatelessWidget {
const ProxiesSetting({super.key});
class ProxiesSettingWidget extends StatelessWidget {
const ProxiesSettingWidget({super.key});
IconData _getIconWithProxiesType(ProxiesType type) {
return switch (type) {
@@ -41,14 +41,6 @@ class ProxiesSetting extends StatelessWidget {
};
}
String _getTextWithProxiesIconStyle(ProxiesIconStyle style) {
return switch (style) {
ProxiesIconStyle.standard => appLocalizations.standard,
ProxiesIconStyle.none => appLocalizations.noIcon,
ProxiesIconStyle.icon => appLocalizations.onlyIcon,
};
}
List<Widget> _buildStyleSetting() {
return generateSection(
title: appLocalizations.style,
@@ -57,7 +49,7 @@ class ProxiesSetting extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Selector<Config, ProxiesType>(
selector: (_, config) => config.proxiesStyle.type,
selector: (_, config) => config.proxiesType,
builder: (_, proxiesType, __) {
final config = globalState.appController.config;
return Wrap(
@@ -71,9 +63,7 @@ class ProxiesSetting extends StatelessWidget {
),
isSelected: proxiesType == item,
onPressed: () {
config.proxiesStyle = config.proxiesStyle.copyWith(
type: item,
);
config.proxiesType = item;
},
)
],
@@ -93,7 +83,7 @@ class ProxiesSetting extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Selector<Config, ProxiesSortType>(
selector: (_, config) => config.proxiesStyle.sortType,
selector: (_, config) => config.proxiesSortType,
builder: (_, proxiesSortType, __) {
final config = globalState.appController.config;
return Wrap(
@@ -107,9 +97,7 @@ class ProxiesSetting extends StatelessWidget {
),
isSelected: proxiesSortType == item,
onPressed: () {
config.proxiesStyle = config.proxiesStyle.copyWith(
sortType: item,
);
config.proxiesSortType = item;
},
),
],
@@ -129,7 +117,7 @@ class ProxiesSetting extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Selector<Config, ProxyCardType>(
selector: (_, config) => config.proxiesStyle.cardType,
selector: (_, config) => config.proxyCardType,
builder: (_, proxyCardType, __) {
final config = globalState.appController.config;
return Wrap(
@@ -140,9 +128,7 @@ class ProxiesSetting extends StatelessWidget {
Intl.message(item.name),
isSelected: item == proxyCardType,
onPressed: () {
config.proxiesStyle = config.proxiesStyle.copyWith(
cardType: item,
);
config.proxyCardType = item;
},
)
],
@@ -163,8 +149,8 @@ class ProxiesSetting extends StatelessWidget {
horizontal: 16,
),
scrollDirection: Axis.horizontal,
child: Selector<Config, ProxiesLayout>(
selector: (_, config) => config.proxiesStyle.layout,
child: Selector< Config, ProxiesLayout>(
selector: (_, config) => config.proxiesLayout,
builder: (_, proxiesLayout, __) {
final config = globalState.appController.config;
return Wrap(
@@ -175,9 +161,7 @@ class ProxiesSetting extends StatelessWidget {
getTextForProxiesLayout(item),
isSelected: item == proxiesLayout,
onPressed: () {
config.proxiesStyle = config.proxiesStyle.copyWith(
layout: item,
);
config.proxiesLayout = item;
},
)
],
@@ -189,39 +173,6 @@ class ProxiesSetting extends StatelessWidget {
);
}
_buildGroupStyleSetting() {
return generateSection(
title: "图标样式",
items: [
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Selector<Config, ProxiesIconStyle>(
selector: (_, config) => config.proxiesStyle.iconStyle,
builder: (_, iconStyle, __) {
return Wrap(
spacing: 16,
children: [
for (final item in ProxiesIconStyle.values)
SettingTextCard(
_getTextWithProxiesIconStyle(item),
isSelected: iconStyle == item,
onPressed: () {
final config = globalState.appController.config;
config.proxiesStyle = config.proxiesStyle.copyWith(
iconStyle: item,
);
},
),
],
);
},
),
),
],
);
}
@override
Widget build(BuildContext context) {
return Padding(
@@ -234,22 +185,6 @@ class ProxiesSetting extends StatelessWidget {
..._buildSortSetting(),
..._buildLayoutSetting(),
..._buildSizeSetting(),
Selector<Config, bool>(
selector: (_, config) =>
config.proxiesStyle.type == ProxiesType.list,
builder: (_, value, child) {
if (value) {
return child!;
}
return Container();
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
..._buildGroupStyleSetting(),
],
),
),
],
),
);

View File

@@ -1,4 +1,6 @@
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
@@ -118,7 +120,8 @@ class ProxiesTabFragmentState extends State<ProxiesTabFragment>
);
},
shouldRebuild: (prev, next) {
if (!stringListEquality.equals(prev.groupNames, next.groupNames)) {
if (!const ListEquality<String>()
.equals(prev.groupNames, next.groupNames)) {
_tabController?.dispose();
_tabController = null;
return true;
@@ -284,11 +287,11 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
selector: (_, appState, config) {
final group = appState.getGroupWithName(groupName)!;
return ProxyGroupSelectorState(
proxyCardType: config.proxiesStyle.cardType,
proxiesSortType: config.proxiesStyle.sortType,
proxyCardType: config.proxyCardType,
proxiesSortType: config.proxiesSortType,
columns: other.getProxiesColumns(
appState.viewWidth,
config.proxiesStyle.layout,
config.proxiesLayout,
),
sortNum: appState.sortNum,
proxies: group.all,
@@ -311,31 +314,33 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
},
child: Align(
alignment: Alignment.topCenter,
child: GridView.builder(
controller: _controller,
padding: const EdgeInsets.only(
top: 16,
left: 16,
right: 16,
bottom: 96,
child: ScaleBuilder(
builder: (_) => GridView.builder(
controller: _controller,
padding: const EdgeInsets.only(
top: 16,
left: 16,
right: 16,
bottom: 96,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
mainAxisExtent: getItemHeight(proxyCardType),
),
itemCount: sortedProxies.length,
itemBuilder: (_, index) {
final proxy = sortedProxies[index];
return ProxyCard(
groupType: state.groupType,
type: proxyCardType,
key: ValueKey('$groupName.${proxy.name}'),
proxy: proxy,
groupName: groupName,
);
},
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
mainAxisExtent: getItemHeight(proxyCardType),
),
itemCount: sortedProxies.length,
itemBuilder: (_, index) {
final proxy = sortedProxies[index];
return ProxyCard(
groupType: state.groupType,
type: proxyCardType,
key: ValueKey('$groupName.${proxy.name}'),
proxy: proxy,
groupName: groupName,
);
},
),
),
);

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
@@ -36,11 +37,8 @@ class _RequestsFragmentState extends State<RequestsFragment> {
timer = null;
}
timer = Timer.periodic(const Duration(milliseconds: 200), (timer) {
final maxLength = Platform.isAndroid ? 1000 : 60;
final requests = appState.requests.safeSublist(
appState.requests.length - maxLength,
);
if (!connectionListEquality.equals(
final requests = appState.requests;
if (!const ListEquality<Connection>().equals(
requestsNotifier.value.connections,
requests,
)) {

View File

@@ -1,5 +1,4 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
@@ -19,16 +18,6 @@ class ThemeModeItem {
});
}
class FontFamilyItem {
final FontFamily fontFamily;
final String label;
const FontFamilyItem({
required this.fontFamily,
required this.label,
});
}
class ThemeFragment extends StatelessWidget {
const ThemeFragment({super.key});
@@ -103,11 +92,7 @@ class _ThemeColorsBoxState extends State<ThemeColorsBox> {
return CommonCard(
isSelected: isSelected,
onPressed: () {
final appController = globalState.appController;
appController.config.themeProps =
appController.config.themeProps.copyWith(
themeMode: themeModeItem.themeMode,
);
globalState.appController.config.themeMode = themeModeItem.themeMode;
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
@@ -140,45 +125,11 @@ class _ThemeColorsBoxState extends State<ThemeColorsBox> {
isSelected: isSelected,
primaryColor: color,
onPressed: () {
final appController = globalState.appController;
appController.config.themeProps =
appController.config.themeProps.copyWith(
primaryColor: color?.value,
);
globalState.appController.config.primaryColor = color?.value;
},
);
}
Widget _fontFamilyCheckBox({
bool? isSelected,
required FontFamilyItem fontFamilyItem,
}) {
return CommonCard(
isSelected: isSelected,
onPressed: () {
final appController = globalState.appController;
appController.config.themeProps =
appController.config.themeProps.copyWith(
fontFamily: fontFamilyItem.fontFamily,
);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Flexible(
child: Text(
fontFamilyItem.label,
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
List<ThemeModeItem> themeModeItems = [
@@ -207,59 +158,15 @@ class _ThemeColorsBoxState extends State<ThemeColorsBox> {
Colors.yellowAccent,
Colors.purple,
];
List<FontFamilyItem> fontFamilyItems = [
FontFamilyItem(
label: appLocalizations.systemFont,
fontFamily: FontFamily.system,
),
const FontFamilyItem(
label: "MiSans",
fontFamily: FontFamily.miSans,
),
];
return Column(
children: [
ItemCard(
info: Info(
label: appLocalizations.fontFamily,
iconData: Icons.text_fields,
),
child: Container(
margin: const EdgeInsets.only(
left: 16,
right: 16,
),
height: 48,
child: Selector<Config, FontFamily>(
selector: (_, config) => config.themeProps.fontFamily,
builder: (_, fontFamily, __) {
return ListView.separated(
scrollDirection: Axis.horizontal,
itemBuilder: (_, index) {
final fontFamilyItem = fontFamilyItems[index];
return _fontFamilyCheckBox(
isSelected: fontFamily == fontFamilyItem.fontFamily,
fontFamilyItem: fontFamilyItem,
);
},
separatorBuilder: (_, __) {
return const SizedBox(
width: 16,
);
},
itemCount: fontFamilyItems.length,
);
},
),
),
),
ItemCard(
info: Info(
label: appLocalizations.themeMode,
iconData: Icons.brightness_high,
),
child: Selector<Config, ThemeMode>(
selector: (_, config) => config.themeProps.themeMode,
selector: (_, config) => config.themeMode,
builder: (_, themeMode, __) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
@@ -297,7 +204,7 @@ class _ThemeColorsBoxState extends State<ThemeColorsBox> {
),
height: 88,
child: Selector<Config, int?>(
selector: (_, config) => config.themeProps.primaryColor,
selector: (_, config) => config.primaryColor,
builder: (_, currentPrimaryColor, __) {
return ListView.separated(
scrollDirection: Axis.horizontal,
@@ -322,7 +229,7 @@ class _ThemeColorsBoxState extends State<ThemeColorsBox> {
Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Selector<Config, bool>(
selector: (_, config) => config.themeProps.prueBlack,
selector: (_, config) => config.prueBlack,
builder: (_, value, ___) {
return ListItem.switchItem(
leading: Icon(
@@ -331,19 +238,63 @@ class _ThemeColorsBoxState extends State<ThemeColorsBox> {
),
title: Text(appLocalizations.prueBlackMode),
delegate: SwitchDelegate(
value: value,
onChanged: (value) {
final appController = globalState.appController;
appController.config.themeProps =
appController.config.themeProps.copyWith(
prueBlack: value,
);
},
),
value: value,
onChanged: (value) {
globalState.appController.config.prueBlack = value;
}),
);
},
),
),
// Padding(
// padding: const EdgeInsets.symmetric(vertical: 16),
// child: Selector<Config, bool>(
// selector: (_, config) => config.scaleProps.custom,
// builder: (_, value, ___) {
// return ListItem.switchItem(
// leading: Icon(
// Icons.format_size_sharp,
// color: context.colorScheme.primary,
// ),
// title: const Text("自定义字体大小"),
// delegate: SwitchDelegate(
// value: value,
// onChanged: (value) {
// globalState.appController.config.scaleProps =
// globalState.appController.config.scaleProps.copyWith(
// custom: value,
// );
// },
// ),
// );
// },
// ),
// ),
// SizedBox(
// height: 20,
// child: Selector<Config, ScaleProps>(
// selector: (_, config) => config.scaleProps,
// builder: (_, props, ___) {
// return AbsorbPointer(
// absorbing: !props.custom,
// child: DisabledMask(
// status: !props.custom,
// child: Slider(
// value: props.scale,
// min: 0.8,
// max: 1.2,
// onChanged: (value) {
// globalState.appController.config.scaleProps =
// globalState.appController.config.scaleProps.copyWith(
// scale: value,
// );
// },
// ),
// ),
// );
// },
// ),
// ),
const SizedBox(
height: 64,
),

View File

@@ -70,7 +70,7 @@ class _ToolboxFragmentState extends State<ToolsFragment> {
final isDisclaimerAccepted =
await globalState.appController.showDisclaimer();
if (!isDisclaimerAccepted) {
globalState.appController.handleExit();
system.exit();
}
},
),
@@ -91,7 +91,7 @@ class _ToolboxFragmentState extends State<ToolsFragment> {
title: appLocalizations.settings,
items: [
Selector<Config, String?>(
selector: (_, config) => config.appSetting.locale,
selector: (_, config) => config.locale,
builder: (_, localeString, __) {
final subTitle = localeString ?? appLocalizations.defaultText;
final currentLocale = other.getLocaleForString(localeString);
@@ -103,10 +103,8 @@ class _ToolboxFragmentState extends State<ToolsFragment> {
title: appLocalizations.language,
options: [null, ...AppLocalizations.delegate.supportedLocales],
onChanged: (Locale? value) {
final config = globalState.appController.config;
config.appSetting = config.appSetting.copyWith(
locale: value?.toString(),
);
final config = context.read<Config>();
config.locale = value?.toString();
},
textBuilder: (locale) => _getLocaleString(locale),
value: currentLocale,

View File

@@ -41,7 +41,7 @@
"tunDesc": "only effective in administrator mode",
"minimizeOnExit": "Minimize on exit",
"minimizeOnExitDesc": "Modify the default system exit event",
"autoLaunch": "Auto launch",
"autoLaunch": "AutoLaunch",
"autoLaunchDesc": "Follow the system self startup",
"silentLaunch": "SilentLaunch",
"silentLaunchDesc": "Start in the background",
@@ -123,7 +123,7 @@
"tabAnimation": "Tab animation",
"tabAnimationDesc": "When enabled, the home tab will add a toggle animation",
"desc": "A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.",
"startVpn": "Starting VPN...",
"startVpn": "Staring VPN...",
"stopVpn": "Stopping VPN...",
"discovery": "Discovery a new version",
"compatible": "Compatibility mode",
@@ -250,7 +250,8 @@
"dnsDesc": "Update DNS related settings",
"key": "Key",
"value": "Value",
"notEmpty": "Cannot be empty",
"keyNotEmpty": "The key cannot be empty",
"valueNotEmpty": "The value cannot be empty",
"hostsDesc": "Add Hosts",
"vpnTip": "Changes take effect after restarting the VPN",
"vpnEnableDesc": "Auto routes all system traffic through VpnService",
@@ -286,6 +287,7 @@
"geoipCode": "Geoip code",
"ipcidr": "Ipcidr",
"domain": "Domain",
"resetDns": "Reset Dns",
"reset": "Reset",
"action_view": "Show/Hide",
"action_start": "Start/Stop",
@@ -302,25 +304,5 @@
"hotkeyConflict": "Hotkey conflict",
"remove": "Remove",
"noHotKey": "No HotKey",
"noNetwork": "No network",
"ipv6InboundDesc": "Allow IPv6 inbound",
"exportLogs": "Export logs",
"exportSuccess": "Export Success",
"iconStyle": "Icon style",
"onlyIcon": "Icon",
"noIcon": "None",
"stackMode": "Stack mode",
"network": "Network",
"networkDesc": "Modify network-related settings",
"bypassDomain": "Bypass domain",
"bypassDomainDesc": "Only takes effect when the system proxy is enabled",
"resetTip": "Make sure to reset",
"regExp": "RegExp",
"icon": "Icon",
"iconConfiguration": "Icon configuration",
"noData": "No data",
"adminAutoLaunch": "Admin auto launch",
"adminAutoLaunchDesc": "Boot up by using admin mode",
"fontFamily": "FontFamily",
"systemFont": "System font"
"dnsResetTip": "Make sure to reset the DNS"
}

View File

@@ -250,7 +250,8 @@
"dnsDesc": "更新DNS相关设置",
"key": "键",
"value": "值",
"notEmpty": "不能为空",
"keyNotEmpty": "不能为空",
"valueNotEmpty": "值不能为空",
"hostsDesc": "追加Hosts",
"vpnTip": "重启VPN后改变生效",
"vpnEnableDesc": "通过VpnService自动路由系统所有流量",
@@ -286,6 +287,7 @@
"geoipCode": "Geoip代码",
"ipcidr": "IP/掩码",
"domain": "域名",
"resetDns": "重置DNS",
"reset": "重置",
"action_view": "显示/隐藏",
"action_start": "启动/停止",
@@ -302,25 +304,5 @@
"hotkeyConflict": "快捷键冲突",
"remove": "移除",
"noHotKey": "暂无快捷键",
"noNetwork": "无网络",
"ipv6InboundDesc": "允许IPv6入站",
"exportLogs": "导出日志",
"exportSuccess": "导出成功",
"iconStyle": "图标样式",
"onlyIcon": "仅图标",
"noIcon": "无图标",
"stackMode": "栈模式",
"network": "网络",
"networkDesc": "修改网络相关设置",
"bypassDomain": "排除域名",
"bypassDomainDesc": "仅在系统代理启用时生效",
"resetTip": "确定要重置吗?",
"regExp": "正则",
"icon": "图片",
"iconConfiguration": "图片配置",
"noData": "暂无数据",
"adminAutoLaunch": "管理员自启动",
"adminAutoLaunchDesc": "使用管理员模式开机自启动",
"fontFamily": "字体",
"systemFont": "系统字体"
"dnsResetTip": "确定重置DNS"
}

View File

@@ -45,10 +45,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("WebDAV server address"),
"addressTip": MessageLookupByLibrary.simpleMessage(
"Please enter a valid WebDAV address"),
"adminAutoLaunch":
MessageLookupByLibrary.simpleMessage("Admin auto launch"),
"adminAutoLaunchDesc":
MessageLookupByLibrary.simpleMessage("Boot up by using admin mode"),
"ago": MessageLookupByLibrary.simpleMessage(" Ago"),
"agree": MessageLookupByLibrary.simpleMessage("Agree"),
"allApps": MessageLookupByLibrary.simpleMessage("All apps"),
@@ -76,7 +72,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Auto lose connections"),
"autoCloseConnectionsDesc": MessageLookupByLibrary.simpleMessage(
"Auto close connections after change node"),
"autoLaunch": MessageLookupByLibrary.simpleMessage("Auto launch"),
"autoLaunch": MessageLookupByLibrary.simpleMessage("AutoLaunch"),
"autoLaunchDesc": MessageLookupByLibrary.simpleMessage(
"Follow the system self startup"),
"autoRun": MessageLookupByLibrary.simpleMessage("AutoRun"),
@@ -93,9 +89,6 @@ class MessageLookup extends MessageLookupByLibrary {
"backupSuccess": MessageLookupByLibrary.simpleMessage("Backup success"),
"bind": MessageLookupByLibrary.simpleMessage("Bind"),
"blacklistMode": MessageLookupByLibrary.simpleMessage("Blacklist mode"),
"bypassDomain": MessageLookupByLibrary.simpleMessage("Bypass domain"),
"bypassDomainDesc": MessageLookupByLibrary.simpleMessage(
"Only takes effect when the system proxy is enabled"),
"cancelFilterSystemApp":
MessageLookupByLibrary.simpleMessage("Cancel filter system app"),
"cancelSelectAll":
@@ -153,6 +146,8 @@ class MessageLookup extends MessageLookupByLibrary {
"dnsDesc":
MessageLookupByLibrary.simpleMessage("Update DNS related settings"),
"dnsMode": MessageLookupByLibrary.simpleMessage("DNS mode"),
"dnsResetTip":
MessageLookupByLibrary.simpleMessage("Make sure to reset the DNS"),
"doYouWantToPass":
MessageLookupByLibrary.simpleMessage("Do you want to pass"),
"domain": MessageLookupByLibrary.simpleMessage("Domain"),
@@ -168,8 +163,6 @@ class MessageLookup extends MessageLookupByLibrary {
"expand": MessageLookupByLibrary.simpleMessage("Standard"),
"expirationTime":
MessageLookupByLibrary.simpleMessage("Expiration time"),
"exportLogs": MessageLookupByLibrary.simpleMessage("Export logs"),
"exportSuccess": MessageLookupByLibrary.simpleMessage("Export Success"),
"externalController":
MessageLookupByLibrary.simpleMessage("ExternalController"),
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
@@ -192,7 +185,6 @@ class MessageLookup extends MessageLookupByLibrary {
"findProcessMode": MessageLookupByLibrary.simpleMessage("Find process"),
"findProcessModeDesc": MessageLookupByLibrary.simpleMessage(
"There is a risk of flashback after opening"),
"fontFamily": MessageLookupByLibrary.simpleMessage("FontFamily"),
"fourColumns": MessageLookupByLibrary.simpleMessage("Four columns"),
"general": MessageLookupByLibrary.simpleMessage("General"),
"generalDesc":
@@ -214,10 +206,6 @@ class MessageLookup extends MessageLookupByLibrary {
"hotkeyManagementDesc": MessageLookupByLibrary.simpleMessage(
"Use keyboard to control applications"),
"hours": MessageLookupByLibrary.simpleMessage("Hours"),
"icon": MessageLookupByLibrary.simpleMessage("Icon"),
"iconConfiguration":
MessageLookupByLibrary.simpleMessage("Icon configuration"),
"iconStyle": MessageLookupByLibrary.simpleMessage("Icon style"),
"importFromURL":
MessageLookupByLibrary.simpleMessage("Import from URL"),
"infiniteTime":
@@ -231,12 +219,12 @@ class MessageLookup extends MessageLookupByLibrary {
"ipcidr": MessageLookupByLibrary.simpleMessage("Ipcidr"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage(
"When turned on it will be able to receive IPv6 traffic"),
"ipv6InboundDesc":
MessageLookupByLibrary.simpleMessage("Allow IPv6 inbound"),
"just": MessageLookupByLibrary.simpleMessage("Just"),
"keepAliveIntervalDesc":
MessageLookupByLibrary.simpleMessage("Tcp keep alive interval"),
"key": MessageLookupByLibrary.simpleMessage("Key"),
"keyNotEmpty":
MessageLookupByLibrary.simpleMessage("The key cannot be empty"),
"language": MessageLookupByLibrary.simpleMessage("Language"),
"layout": MessageLookupByLibrary.simpleMessage("Layout"),
"light": MessageLookupByLibrary.simpleMessage("Light"),
@@ -275,22 +263,15 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Nameserver policy"),
"nameserverPolicyDesc": MessageLookupByLibrary.simpleMessage(
"Specify the corresponding nameserver policy"),
"network": MessageLookupByLibrary.simpleMessage("Network"),
"networkDesc": MessageLookupByLibrary.simpleMessage(
"Modify network-related settings"),
"networkDetection":
MessageLookupByLibrary.simpleMessage("Network detection"),
"networkSpeed": MessageLookupByLibrary.simpleMessage("Network speed"),
"noData": MessageLookupByLibrary.simpleMessage("No data"),
"noHotKey": MessageLookupByLibrary.simpleMessage("No HotKey"),
"noIcon": MessageLookupByLibrary.simpleMessage("None"),
"noInfo": MessageLookupByLibrary.simpleMessage("No info"),
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("No more info"),
"noNetwork": MessageLookupByLibrary.simpleMessage("No network"),
"noProxy": MessageLookupByLibrary.simpleMessage("No proxy"),
"noProxyDesc": MessageLookupByLibrary.simpleMessage(
"Please create a profile or add a valid profile"),
"notEmpty": MessageLookupByLibrary.simpleMessage("Cannot be empty"),
"notSelectedTip": MessageLookupByLibrary.simpleMessage(
"The current proxy group cannot be selected."),
"nullConnectionsDesc":
@@ -302,7 +283,6 @@ class MessageLookup extends MessageLookupByLibrary {
"No profile, Please add a profile"),
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("No requests"),
"oneColumn": MessageLookupByLibrary.simpleMessage("One column"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("Icon"),
"onlyOtherApps":
MessageLookupByLibrary.simpleMessage("Only third-party apps"),
"onlyStatisticsProxy":
@@ -380,7 +360,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Only recovery profiles"),
"recoverySuccess":
MessageLookupByLibrary.simpleMessage("Recovery success"),
"regExp": MessageLookupByLibrary.simpleMessage("RegExp"),
"remote": MessageLookupByLibrary.simpleMessage("Remote"),
"remoteBackupDesc":
MessageLookupByLibrary.simpleMessage("Backup local data to WebDAV"),
@@ -391,7 +370,7 @@ class MessageLookup extends MessageLookupByLibrary {
"requestsDesc": MessageLookupByLibrary.simpleMessage(
"View recently request records"),
"reset": MessageLookupByLibrary.simpleMessage("Reset"),
"resetTip": MessageLookupByLibrary.simpleMessage("Make sure to reset"),
"resetDns": MessageLookupByLibrary.simpleMessage("Reset Dns"),
"resources": MessageLookupByLibrary.simpleMessage("Resources"),
"resourcesDesc": MessageLookupByLibrary.simpleMessage(
"External resource related info"),
@@ -414,10 +393,9 @@ class MessageLookup extends MessageLookupByLibrary {
"size": MessageLookupByLibrary.simpleMessage("Size"),
"sort": MessageLookupByLibrary.simpleMessage("Sort"),
"source": MessageLookupByLibrary.simpleMessage("Source"),
"stackMode": MessageLookupByLibrary.simpleMessage("Stack mode"),
"standard": MessageLookupByLibrary.simpleMessage("Standard"),
"start": MessageLookupByLibrary.simpleMessage("Start"),
"startVpn": MessageLookupByLibrary.simpleMessage("Starting VPN..."),
"startVpn": MessageLookupByLibrary.simpleMessage("Staring VPN..."),
"status": MessageLookupByLibrary.simpleMessage("Status"),
"statusDesc": MessageLookupByLibrary.simpleMessage(
"System DNS will be used when turned off"),
@@ -426,7 +404,6 @@ class MessageLookup extends MessageLookupByLibrary {
"style": MessageLookupByLibrary.simpleMessage("Style"),
"submit": MessageLookupByLibrary.simpleMessage("Submit"),
"sync": MessageLookupByLibrary.simpleMessage("Sync"),
"systemFont": MessageLookupByLibrary.simpleMessage("System font"),
"systemProxy": MessageLookupByLibrary.simpleMessage("System proxy"),
"systemProxyDesc": MessageLookupByLibrary.simpleMessage(
"Attach HTTP proxy to VpnService"),
@@ -469,6 +446,8 @@ class MessageLookup extends MessageLookupByLibrary {
"useSystemHosts":
MessageLookupByLibrary.simpleMessage("Use system hosts"),
"value": MessageLookupByLibrary.simpleMessage("Value"),
"valueNotEmpty":
MessageLookupByLibrary.simpleMessage("The value cannot be empty"),
"view": MessageLookupByLibrary.simpleMessage("View"),
"vpnDesc":
MessageLookupByLibrary.simpleMessage("Modify VPN related settings"),

View File

@@ -41,9 +41,6 @@ class MessageLookup extends MessageLookupByLibrary {
"address": MessageLookupByLibrary.simpleMessage("地址"),
"addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"),
"addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"),
"adminAutoLaunch": MessageLookupByLibrary.simpleMessage("管理员自启动"),
"adminAutoLaunchDesc":
MessageLookupByLibrary.simpleMessage("使用管理员模式开机自启动"),
"ago": MessageLookupByLibrary.simpleMessage(""),
"agree": MessageLookupByLibrary.simpleMessage("同意"),
"allApps": MessageLookupByLibrary.simpleMessage("所有应用"),
@@ -78,8 +75,6 @@ class MessageLookup extends MessageLookupByLibrary {
"backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"),
"bind": MessageLookupByLibrary.simpleMessage("绑定"),
"blacklistMode": MessageLookupByLibrary.simpleMessage("黑名单模式"),
"bypassDomain": MessageLookupByLibrary.simpleMessage("排除域名"),
"bypassDomainDesc": MessageLookupByLibrary.simpleMessage("仅在系统代理启用时生效"),
"cancelFilterSystemApp":
MessageLookupByLibrary.simpleMessage("取消过滤系统应用"),
"cancelSelectAll": MessageLookupByLibrary.simpleMessage("取消全选"),
@@ -125,6 +120,7 @@ class MessageLookup extends MessageLookupByLibrary {
"discovery": MessageLookupByLibrary.simpleMessage("发现新版本"),
"dnsDesc": MessageLookupByLibrary.simpleMessage("更新DNS相关设置"),
"dnsMode": MessageLookupByLibrary.simpleMessage("DNS模式"),
"dnsResetTip": MessageLookupByLibrary.simpleMessage("确定重置DNS"),
"doYouWantToPass": MessageLookupByLibrary.simpleMessage("是否要通过"),
"domain": MessageLookupByLibrary.simpleMessage("域名"),
"download": MessageLookupByLibrary.simpleMessage("下载"),
@@ -137,8 +133,6 @@ class MessageLookup extends MessageLookupByLibrary {
"exit": MessageLookupByLibrary.simpleMessage("退出"),
"expand": MessageLookupByLibrary.simpleMessage("标准"),
"expirationTime": MessageLookupByLibrary.simpleMessage("到期时间"),
"exportLogs": MessageLookupByLibrary.simpleMessage("导出日志"),
"exportSuccess": MessageLookupByLibrary.simpleMessage("导出成功"),
"externalController": MessageLookupByLibrary.simpleMessage("外部控制器"),
"externalControllerDesc":
MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制Clash内核"),
@@ -155,7 +149,6 @@ class MessageLookup extends MessageLookupByLibrary {
"findProcessMode": MessageLookupByLibrary.simpleMessage("查找进程"),
"findProcessModeDesc":
MessageLookupByLibrary.simpleMessage("开启后存在闪退风险"),
"fontFamily": MessageLookupByLibrary.simpleMessage("字体"),
"fourColumns": MessageLookupByLibrary.simpleMessage("四列"),
"general": MessageLookupByLibrary.simpleMessage("基础"),
"generalDesc": MessageLookupByLibrary.simpleMessage("覆写基础设置"),
@@ -173,9 +166,6 @@ class MessageLookup extends MessageLookupByLibrary {
"hotkeyManagementDesc":
MessageLookupByLibrary.simpleMessage("使用键盘控制应用程序"),
"hours": MessageLookupByLibrary.simpleMessage("小时"),
"icon": MessageLookupByLibrary.simpleMessage("图片"),
"iconConfiguration": MessageLookupByLibrary.simpleMessage("图片配置"),
"iconStyle": MessageLookupByLibrary.simpleMessage("图标样式"),
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
"init": MessageLookupByLibrary.simpleMessage("初始化"),
@@ -184,11 +174,11 @@ class MessageLookup extends MessageLookupByLibrary {
"intranetIP": MessageLookupByLibrary.simpleMessage("内网 IP"),
"ipcidr": MessageLookupByLibrary.simpleMessage("IP/掩码"),
"ipv6Desc": MessageLookupByLibrary.simpleMessage("开启后将可以接收IPv6流量"),
"ipv6InboundDesc": MessageLookupByLibrary.simpleMessage("允许IPv6入站"),
"just": MessageLookupByLibrary.simpleMessage("刚刚"),
"keepAliveIntervalDesc":
MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"),
"key": MessageLookupByLibrary.simpleMessage(""),
"keyNotEmpty": MessageLookupByLibrary.simpleMessage("键不能为空"),
"language": MessageLookupByLibrary.simpleMessage("语言"),
"layout": MessageLookupByLibrary.simpleMessage("布局"),
"light": MessageLookupByLibrary.simpleMessage("浅色"),
@@ -219,20 +209,14 @@ class MessageLookup extends MessageLookupByLibrary {
"nameserverPolicy": MessageLookupByLibrary.simpleMessage("域名服务器策略"),
"nameserverPolicyDesc":
MessageLookupByLibrary.simpleMessage("指定对应域名服务器策略"),
"network": MessageLookupByLibrary.simpleMessage("网络"),
"networkDesc": MessageLookupByLibrary.simpleMessage("修改网络相关设置"),
"networkDetection": MessageLookupByLibrary.simpleMessage("网络检测"),
"networkSpeed": MessageLookupByLibrary.simpleMessage("网络速度"),
"noData": MessageLookupByLibrary.simpleMessage("暂无数据"),
"noHotKey": MessageLookupByLibrary.simpleMessage("暂无快捷键"),
"noIcon": MessageLookupByLibrary.simpleMessage("无图标"),
"noInfo": MessageLookupByLibrary.simpleMessage("暂无信息"),
"noMoreInfoDesc": MessageLookupByLibrary.simpleMessage("暂无更多信息"),
"noNetwork": MessageLookupByLibrary.simpleMessage("无网络"),
"noProxy": MessageLookupByLibrary.simpleMessage("暂无代理"),
"noProxyDesc":
MessageLookupByLibrary.simpleMessage("请创建配置文件或者添加有效配置文件"),
"notEmpty": MessageLookupByLibrary.simpleMessage("不能为空"),
"notSelectedTip": MessageLookupByLibrary.simpleMessage("当前代理组无法选中"),
"nullConnectionsDesc": MessageLookupByLibrary.simpleMessage("暂无连接"),
"nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage("无法获取内核信息"),
@@ -241,7 +225,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("暂无请求"),
"oneColumn": MessageLookupByLibrary.simpleMessage("一列"),
"onlyIcon": MessageLookupByLibrary.simpleMessage("仅图标"),
"onlyOtherApps": MessageLookupByLibrary.simpleMessage("仅第三方应用"),
"onlyStatisticsProxy": MessageLookupByLibrary.simpleMessage("仅统计代理"),
"onlyStatisticsProxyDesc":
@@ -299,7 +282,6 @@ class MessageLookup extends MessageLookupByLibrary {
"recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
"regExp": MessageLookupByLibrary.simpleMessage("正则"),
"remote": MessageLookupByLibrary.simpleMessage("远程"),
"remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"),
"remoteRecoveryDesc":
@@ -308,7 +290,7 @@ class MessageLookup extends MessageLookupByLibrary {
"requests": MessageLookupByLibrary.simpleMessage("请求"),
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求记录"),
"reset": MessageLookupByLibrary.simpleMessage("重置"),
"resetTip": MessageLookupByLibrary.simpleMessage("确定要重置吗?"),
"resetDns": MessageLookupByLibrary.simpleMessage("重置DNS"),
"resources": MessageLookupByLibrary.simpleMessage("资源"),
"resourcesDesc": MessageLookupByLibrary.simpleMessage("外部资源相关信息"),
"respectRules": MessageLookupByLibrary.simpleMessage("遵守规则"),
@@ -329,7 +311,6 @@ class MessageLookup extends MessageLookupByLibrary {
"size": MessageLookupByLibrary.simpleMessage("尺寸"),
"sort": MessageLookupByLibrary.simpleMessage("排序"),
"source": MessageLookupByLibrary.simpleMessage("来源"),
"stackMode": MessageLookupByLibrary.simpleMessage("栈模式"),
"standard": MessageLookupByLibrary.simpleMessage("标准"),
"start": MessageLookupByLibrary.simpleMessage("启动"),
"startVpn": MessageLookupByLibrary.simpleMessage("正在启动VPN..."),
@@ -340,7 +321,6 @@ class MessageLookup extends MessageLookupByLibrary {
"style": MessageLookupByLibrary.simpleMessage("风格"),
"submit": MessageLookupByLibrary.simpleMessage("提交"),
"sync": MessageLookupByLibrary.simpleMessage("同步"),
"systemFont": MessageLookupByLibrary.simpleMessage("系统字体"),
"systemProxy": MessageLookupByLibrary.simpleMessage("系统代理"),
"systemProxyDesc":
MessageLookupByLibrary.simpleMessage("为VpnService附加HTTP代理"),
@@ -376,6 +356,7 @@ class MessageLookup extends MessageLookupByLibrary {
"useHosts": MessageLookupByLibrary.simpleMessage("使用Hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"),
"value": MessageLookupByLibrary.simpleMessage(""),
"valueNotEmpty": MessageLookupByLibrary.simpleMessage("值不能为空"),
"view": MessageLookupByLibrary.simpleMessage("查看"),
"vpnDesc": MessageLookupByLibrary.simpleMessage("修改VPN相关设置"),
"vpnEnableDesc":

View File

@@ -470,10 +470,10 @@ class AppLocalizations {
);
}
/// `Auto launch`
/// `AutoLaunch`
String get autoLaunch {
return Intl.message(
'Auto launch',
'AutoLaunch',
name: 'autoLaunch',
desc: '',
args: [],
@@ -1290,10 +1290,10 @@ class AppLocalizations {
);
}
/// `Starting VPN...`
/// `Staring VPN...`
String get startVpn {
return Intl.message(
'Starting VPN...',
'Staring VPN...',
name: 'startVpn',
desc: '',
args: [],
@@ -2560,11 +2560,21 @@ class AppLocalizations {
);
}
/// `Cannot be empty`
String get notEmpty {
/// `The key cannot be empty`
String get keyNotEmpty {
return Intl.message(
'Cannot be empty',
name: 'notEmpty',
'The key cannot be empty',
name: 'keyNotEmpty',
desc: '',
args: [],
);
}
/// `The value cannot be empty`
String get valueNotEmpty {
return Intl.message(
'The value cannot be empty',
name: 'valueNotEmpty',
desc: '',
args: [],
);
@@ -2920,6 +2930,16 @@ class AppLocalizations {
);
}
/// `Reset Dns`
String get resetDns {
return Intl.message(
'Reset Dns',
name: 'resetDns',
desc: '',
args: [],
);
}
/// `Reset`
String get reset {
return Intl.message(
@@ -3080,211 +3100,11 @@ class AppLocalizations {
);
}
/// `No network`
String get noNetwork {
/// `Make sure to reset the DNS`
String get dnsResetTip {
return Intl.message(
'No network',
name: 'noNetwork',
desc: '',
args: [],
);
}
/// `Allow IPv6 inbound`
String get ipv6InboundDesc {
return Intl.message(
'Allow IPv6 inbound',
name: 'ipv6InboundDesc',
desc: '',
args: [],
);
}
/// `Export logs`
String get exportLogs {
return Intl.message(
'Export logs',
name: 'exportLogs',
desc: '',
args: [],
);
}
/// `Export Success`
String get exportSuccess {
return Intl.message(
'Export Success',
name: 'exportSuccess',
desc: '',
args: [],
);
}
/// `Icon style`
String get iconStyle {
return Intl.message(
'Icon style',
name: 'iconStyle',
desc: '',
args: [],
);
}
/// `Icon`
String get onlyIcon {
return Intl.message(
'Icon',
name: 'onlyIcon',
desc: '',
args: [],
);
}
/// `None`
String get noIcon {
return Intl.message(
'None',
name: 'noIcon',
desc: '',
args: [],
);
}
/// `Stack mode`
String get stackMode {
return Intl.message(
'Stack mode',
name: 'stackMode',
desc: '',
args: [],
);
}
/// `Network`
String get network {
return Intl.message(
'Network',
name: 'network',
desc: '',
args: [],
);
}
/// `Modify network-related settings`
String get networkDesc {
return Intl.message(
'Modify network-related settings',
name: 'networkDesc',
desc: '',
args: [],
);
}
/// `Bypass domain`
String get bypassDomain {
return Intl.message(
'Bypass domain',
name: 'bypassDomain',
desc: '',
args: [],
);
}
/// `Only takes effect when the system proxy is enabled`
String get bypassDomainDesc {
return Intl.message(
'Only takes effect when the system proxy is enabled',
name: 'bypassDomainDesc',
desc: '',
args: [],
);
}
/// `Make sure to reset`
String get resetTip {
return Intl.message(
'Make sure to reset',
name: 'resetTip',
desc: '',
args: [],
);
}
/// `RegExp`
String get regExp {
return Intl.message(
'RegExp',
name: 'regExp',
desc: '',
args: [],
);
}
/// `Icon`
String get icon {
return Intl.message(
'Icon',
name: 'icon',
desc: '',
args: [],
);
}
/// `Icon configuration`
String get iconConfiguration {
return Intl.message(
'Icon configuration',
name: 'iconConfiguration',
desc: '',
args: [],
);
}
/// `No data`
String get noData {
return Intl.message(
'No data',
name: 'noData',
desc: '',
args: [],
);
}
/// `Admin auto launch`
String get adminAutoLaunch {
return Intl.message(
'Admin auto launch',
name: 'adminAutoLaunch',
desc: '',
args: [],
);
}
/// `Boot up by using admin mode`
String get adminAutoLaunchDesc {
return Intl.message(
'Boot up by using admin mode',
name: 'adminAutoLaunchDesc',
desc: '',
args: [],
);
}
/// `FontFamily`
String get fontFamily {
return Intl.message(
'FontFamily',
name: 'fontFamily',
desc: '',
args: [],
);
}
/// `System font`
String get systemFont {
return Intl.message(
'System font',
name: 'systemFont',
'Make sure to reset the DNS',
name: 'dnsResetTip',
desc: '',
args: [],
);

View File

@@ -19,16 +19,18 @@ Future<void> main() async {
globalState.packageInfo = await PackageInfo.fromPlatform();
final version = await system.version;
final config = await preferences.getConfig() ?? Config();
globalState.autoRun = config.autoRun;
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
await android?.init();
await window?.init(config.windowProps, version);
final appState = AppState(
mode: clashConfig.mode,
version: version,
isCompatible: config.isCompatible,
selectedMap: config.currentSelectedMap,
);
appState.navigationItems = navigation.getItems(
openLogs: config.appSetting.openLogs,
openLogs: config.openLogs,
hasProxies: false,
);
await globalState.init(
@@ -55,6 +57,7 @@ Future<void> vpnService() async {
final clashConfig = await preferences.getClashConfig() ?? ClashConfig();
final appState = AppState(
mode: clashConfig.mode,
isCompatible: config.isCompatible,
selectedMap: config.currentSelectedMap,
version: version,
);
@@ -99,7 +102,7 @@ Future<void> vpnService() async {
),
);
final appLocalizations = await AppLocalizations.load(
other.getLocaleForString(config.appSetting.locale) ??
other.getLocaleForString(config.locale) ??
WidgetsBinding.instance.platformDispatcher.locale,
);
await app?.tip(appLocalizations.startVpn);

View File

@@ -19,23 +19,39 @@ class AndroidManager extends StatefulWidget {
class _AndroidContainerState extends State<AndroidManager> {
@override
void initState() {
super.initState();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
super.initState();
}
Widget _excludeContainer(Widget child) {
return Selector<Config, bool>(
selector: (_, config) => config.appSetting.hidden,
builder: (_, hidden, child) {
app?.updateExcludeFromRecents(hidden);
selector: (_, config) => config.isExclude,
builder: (_, isExclude, child) {
app?.updateExcludeFromRecents(isExclude);
return child!;
},
child: child,
);
}
Widget _systemUiOverlayContainer(Widget child) {
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Theme.of(context).brightness == Brightness.dark
? Brightness.light
: Brightness.dark,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarDividerColor: Colors.transparent,
),
child: child,
);
}
@override
Widget build(BuildContext context) {
return _excludeContainer(widget.child);
return _systemUiOverlayContainer(
_excludeContainer(widget.child),
);
}
}

View File

@@ -18,14 +18,13 @@ class AppStateManager extends StatefulWidget {
class _AppStateManagerState extends State<AppStateManager>
with WidgetsBindingObserver {
_updateNavigationsContainer(Widget child) {
return Selector2<AppState, Config, UpdateNavigationsSelector>(
selector: (_, appState, config) {
final group = appState.currentGroups;
final hasProfile = config.profiles.isNotEmpty;
return UpdateNavigationsSelector(
openLogs: config.appSetting.openLogs,
openLogs: config.openLogs,
hasProxies: group.isNotEmpty && hasProfile,
);
},

View File

@@ -19,9 +19,9 @@ class ClashManager extends StatefulWidget {
State<ClashManager> createState() => _ClashContainerState();
}
class _ClashContainerState extends State<ClashManager> with AppMessageListener {
class _ClashContainerState extends State<ClashManager>
with AppMessageListener {
Function? updateClashConfigDebounce;
Function? updateDelayDebounce;
Widget _updateContainer(Widget child) {
return Selector2<Config, ClashConfig, ClashConfigState>(
@@ -64,13 +64,12 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
Widget _updateCoreState(Widget child) {
return Selector2<Config, ClashConfig, CoreState>(
selector: (_, config, clashConfig) => CoreState(
enable: config.vpnProps.enable,
accessControl: config.isAccessControl ? config.accessControl : null,
ipv6: config.vpnProps.ipv6,
enable: config.vpnProps.enable,
allowBypass: config.vpnProps.allowBypass,
bypassDomain: config.vpnProps.bypassDomain,
systemProxy: config.vpnProps.systemProxy,
onlyProxy: config.appSetting.onlyProxy,
mixedPort: clashConfig.mixedPort,
onlyProxy: config.onlyProxy,
currentProfileName:
config.currentProfile?.label ?? config.currentProfileId ?? "",
),
@@ -84,6 +83,10 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
_changeProfile() async {
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (globalState.autoRun) {
globalState.autoRun = false;
return;
}
final appController = globalState.appController;
appController.appState.delayMap = {};
await appController.applyProfile();
@@ -129,11 +132,7 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
final appController = globalState.appController;
appController.setDelay(delay);
super.onDelay(delay);
updateDelayDebounce ??= debounce(() async {
await appController.updateGroupDebounce();
await appController.addCheckIpNumDebounce();
}, milliseconds: 5000);
updateDelayDebounce!();
await globalState.appController.updateGroupDebounce();
}
@override
@@ -142,15 +141,10 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
if (log.logLevel == LogLevel.error) {
globalState.appController.showSnackBar(log.payload ?? '');
}
debugPrint("$log");
super.onLog(log);
}
@override
void onStarted(String runTime) {
super.onStarted(runTime);
globalState.appController.applyProfileDebounce();
}
@override
void onRequest(Connection connection) async {
globalState.appController.appState.addRequest(connection);
@@ -158,14 +152,14 @@ class _ClashContainerState extends State<ClashManager> with AppMessageListener {
}
@override
Future<void> onLoaded(String providerName) async {
void onLoaded(String providerName) {
final appController = globalState.appController;
appController.appState.setProvider(
clashCore.getExternalProvider(
providerName,
),
);
await appController.updateGroupDebounce();
appController.addCheckIpNumDebounce();
super.onLoaded(providerName);
}
}

View File

@@ -1,4 +1,3 @@
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/models/config.dart';
@@ -64,7 +63,7 @@ class HotKeyManager extends StatelessWidget {
return Selector<Config, List<HotKeyAction>>(
selector: (_, config) => config.hotKeyActions,
shouldRebuild: (prev, next) {
return !hotKeyActionListEquality.equals(prev, next);
return !hotKeyActionsEquality.equals(prev, next);
},
builder: (_, hotKeyActions, __) {
_updateHotKeys(hotKeyActions: hotKeyActions);

View File

@@ -1,6 +1,8 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/config.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class MediaManager extends StatelessWidget {
final Widget child;
@@ -12,7 +14,28 @@ class MediaManager extends StatelessWidget {
@override
Widget build(BuildContext context) {
globalState.measure = Measure.of(context);
return child;
return Selector<Config, ScaleProps>(
selector: (_, config) => config.scaleProps,
builder: (_, props, child) {
globalState.measure = Measure.of(context);
return child!;
// final textScaleFactor =
// WidgetsBinding.instance.platformDispatcher.textScaleFactor;
// return MediaQuery(
// data: MediaQuery.of(context).copyWith(
// textScaler: props.custom
// ? TextScaler.linear(props.scale * textScaleFactor)
// : null,
// ),
// child: Builder(
// builder: (context) {
// globalState.measure = Measure.of(context);
// return child!;
// },
// ),
// );
},
child: child,
);
}
}

View File

@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:tray_manager/tray_manager.dart';
import 'package:window_ext/window_ext.dart';
class TrayManager extends StatefulWidget {
final Widget child;
@@ -22,24 +21,130 @@ class TrayManager extends StatefulWidget {
State<TrayManager> createState() => _TrayContainerState();
}
class _TrayContainerState extends State<TrayManager>
with TrayListener {
class _TrayContainerState extends State<TrayManager> with TrayListener {
@override
void initState() {
super.initState();
trayManager.addListener(this);
}
_updateSystemTray({
required bool isStart,
required Brightness? brightness,
}) async {
if (Platform.isLinux) {
await trayManager.destroy();
}
await trayManager.setIcon(
other.getTrayIconPath(
isStart: isStart,
brightness: brightness ??
WidgetsBinding.instance.platformDispatcher.platformBrightness,
),
);
if (!Platform.isLinux) {
await trayManager.setToolTip(
appName,
);
}
}
_updateTray(TrayState trayState) async {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!Platform.isLinux) {
_updateSystemTray(
isStart: trayState.isStart,
brightness: trayState.brightness,
);
}
List<MenuItem> menuItems = [];
final showMenuItem = MenuItem(
label: appLocalizations.show,
onClick: (_) {
window?.show();
},
);
menuItems.add(showMenuItem);
final startMenuItem = MenuItem.checkbox(
label:
trayState.isStart ? appLocalizations.stop : appLocalizations.start,
onClick: (_) async {
globalState.appController.updateStart();
},
checked: false,
);
menuItems.add(startMenuItem);
menuItems.add(MenuItem.separator());
for (final mode in Mode.values) {
menuItems.add(
MenuItem.checkbox(
label: Intl.message(mode.name),
onClick: (_) {
globalState.appController.clashConfig.mode = mode;
},
checked: mode == trayState.mode,
),
);
}
menuItems.add(MenuItem.separator());
if (trayState.isStart) {
menuItems.add(
MenuItem.checkbox(
label: appLocalizations.tun,
onClick: (_) {
globalState.appController.updateTun();
},
checked: trayState.tunEnable,
),
);
menuItems.add(
MenuItem.checkbox(
label: appLocalizations.systemProxy,
onClick: (_) {
globalState.appController.updateSystemProxy();
},
checked: trayState.systemProxy,
),
);
menuItems.add(MenuItem.separator());
}
final autoStartMenuItem = MenuItem.checkbox(
label: appLocalizations.autoLaunch,
onClick: (_) async {
globalState.appController.updateAutoLaunch();
},
checked: trayState.autoLaunch,
);
menuItems.add(autoStartMenuItem);
menuItems.add(MenuItem.separator());
final exitMenuItem = MenuItem(
label: appLocalizations.exit,
onClick: (_) async {
await globalState.appController.handleExit();
},
);
menuItems.add(exitMenuItem);
final menu = Menu();
menu.items = menuItems;
trayManager.setContextMenu(menu);
if (Platform.isLinux) {
_updateSystemTray(
isStart: trayState.isStart,
brightness: trayState.brightness,
);
}
});
}
@override
Widget build(BuildContext context) {
return Selector4<AppState, AppFlowingState, Config, ClashConfig, TrayState>(
selector: (_, appState, appFlowingState, config, clashConfig) =>
TrayState(
mode: clashConfig.mode,
adminAutoLaunch: config.appSetting.adminAutoLaunch,
autoLaunch: config.appSetting.autoLaunch,
autoLaunch: config.autoLaunch,
isStart: appFlowingState.isStart,
locale: config.appSetting.locale,
locale: config.locale,
systemProxy: config.desktopProps.systemProxy,
tunEnable: clashConfig.tun.enable,
brightness: appState.brightness,
@@ -48,7 +153,7 @@ class _TrayContainerState extends State<TrayManager>
return prev != next;
},
builder: (_, state, child) {
globalState.appController.updateTray();
_updateTray(state);
return child!;
},
child: widget.child,

View File

@@ -1,5 +1,5 @@
import 'package:fl_clash/common/app_localizations.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/models/config.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@@ -38,14 +38,13 @@ class _VpnContainerState extends State<VpnManager> {
@override
Widget build(BuildContext context) {
return Selector2<Config, ClashConfig, VPNState>(
selector: (_, config, clashConfig) => VPNState(
return Selector<Config, VPNState>(
selector: (_, config) => VPNState(
accessControl: config.accessControl,
vpnProps: config.vpnProps,
stack: clashConfig.tun.stack,
),
shouldRebuild: (prev, next) {
if (prev != next) {
shouldRebuild: (prev,next){
if(prev != next){
showTip();
}
return prev != next;

View File

@@ -5,7 +5,6 @@ import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:window_ext/window_ext.dart';
import 'package:window_manager/window_manager.dart';
class WindowManager extends StatefulWidget {
@@ -20,15 +19,13 @@ class WindowManager extends StatefulWidget {
State<WindowManager> createState() => _WindowContainerState();
}
class _WindowContainerState extends State<WindowManager> with WindowListener, WindowExtListener {
class _WindowContainerState extends State<WindowManager> with WindowListener {
Function? updateLaunchDebounce;
_autoLaunchContainer(Widget child) {
return Selector<Config, AutoLaunchState>(
selector: (_, config) => AutoLaunchState(
isAutoLaunch: config.appSetting.autoLaunch,
isAdminAutoLaunch: config.appSetting.adminAutoLaunch,
),
return Selector2<Config, ClashConfig, AutoLaunchState>(
selector: (_, config, clashConfig) => AutoLaunchState(
isAutoLaunch: config.autoLaunch, isOpenTun: clashConfig.tun.enable),
builder: (_, state, child) {
updateLaunchDebounce ??= debounce((AutoLaunchState state) {
autoLaunch?.updateStatus(state);
@@ -48,7 +45,6 @@ class _WindowContainerState extends State<WindowManager> with WindowListener, Wi
@override
void initState() {
super.initState();
windowExtManager.addListener(this);
windowManager.addListener(this);
}
@@ -86,16 +82,9 @@ class _WindowContainerState extends State<WindowManager> with WindowListener, Wi
super.onWindowMinimize();
}
@override
void onTaskbarCreated() {
globalState.appController.updateTray(true);
super.onTaskbarCreated();
}
@override
Future<void> dispose() async {
windowManager.removeListener(this);
windowExtManager.removeListener(this);
super.dispose();
}
}

View File

@@ -1,3 +1,6 @@
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
@@ -17,6 +20,7 @@ class AppState with ChangeNotifier {
Mode _mode;
DelayMap _delayMap;
SelectedMap _selectedMap;
bool _isCompatible;
List<Group> _groups;
double _viewWidth;
List<Connection> _requests;
@@ -28,6 +32,7 @@ class AppState with ChangeNotifier {
AppState({
required Mode mode,
required bool isCompatible,
required SelectedMap selectedMap,
required int version,
}) : _navigationItems = [],
@@ -44,6 +49,7 @@ class AppState with ChangeNotifier {
_groups = [],
_providers = [],
_packages = [],
_isCompatible = isCompatible,
_systemColorSchemes = const SystemColorSchemes(),
_version = version;
@@ -59,7 +65,7 @@ class AppState with ChangeNotifier {
List<NavigationItem> get navigationItems => _navigationItems;
set navigationItems(List<NavigationItem> value) {
if (!navigationItemListEquality.equals(_navigationItems, value)) {
if (!const ListEquality<NavigationItem>().equals(_navigationItems, value)) {
_navigationItems = value;
notifyListeners();
}
@@ -145,8 +151,10 @@ class AppState with ChangeNotifier {
addRequest(Connection value) {
_requests = List.from(_requests)..add(value);
const maxLength = 1000;
_requests = _requests.safeSublist(_requests.length - maxLength);
final maxLength = Platform.isAndroid ? 1000 : 60;
if (_requests.length > maxLength) {
_requests = _requests.sublist(_requests.length - maxLength);
}
notifyListeners();
}
@@ -162,7 +170,7 @@ class AppState with ChangeNotifier {
List<Group> get groups => _groups;
set groups(List<Group> value) {
if (!groupListEquality.equals(_groups, value)) {
if (!const ListEquality<Group>().equals(_groups, value)) {
_groups = value;
notifyListeners();
}
@@ -195,12 +203,23 @@ class AppState with ChangeNotifier {
}
}
bool get isCompatible {
return _isCompatible;
}
set isCompatible(bool value) {
if (_isCompatible != value) {
_isCompatible = value;
notifyListeners();
}
}
SelectedMap get selectedMap {
return _selectedMap;
}
set selectedMap(SelectedMap value) {
if (!stringAndStringMapEquality.equals(_selectedMap, value)) {
if (!const MapEquality<String, String>().equals(_selectedMap, value)) {
_selectedMap = value;
notifyListeners();
}
@@ -238,7 +257,7 @@ class AppState with ChangeNotifier {
}
set delayMap(DelayMap value) {
if (!stringAndIntQMapEquality.equals(_delayMap, value)) {
if (!const MapEquality<String, int?>().equals(_delayMap, value)) {
_delayMap = value;
notifyListeners();
}
@@ -254,7 +273,7 @@ class AppState with ChangeNotifier {
List<Package> get packages => _packages;
set packages(List<Package> value) {
if (!packageListEquality.equals(_packages, value)) {
if (!const ListEquality<Package>().equals(_packages, value)) {
_packages = value;
notifyListeners();
}
@@ -263,7 +282,7 @@ class AppState with ChangeNotifier {
List<ExternalProvider> get providers => _providers;
set providers(List<ExternalProvider> value) {
if (!externalProviderListEquality.equals(_providers, value)) {
if (!const ListEquality<ExternalProvider>().equals(_providers, value)) {
_providers = value;
notifyListeners();
}
@@ -335,8 +354,10 @@ class AppFlowingState with ChangeNotifier {
addLog(Log log) {
_logs = List.from(_logs)..add(log);
const maxLength = 1000;
_logs = _logs.safeSublist(_logs.length - maxLength);
final maxLength = Platform.isAndroid ? 1000 : 60;
if (_logs.length > maxLength) {
_logs = _logs.sublist(_logs.length - maxLength);
}
notifyListeners();
}
@@ -352,7 +373,9 @@ class AppFlowingState with ChangeNotifier {
addTraffic(Traffic traffic) {
_traffics = List.from(_traffics)..add(traffic);
const maxLength = 60;
_traffics = _traffics.safeSublist(_traffics.length - maxLength);
if (_traffics.length > maxLength) {
_traffics = _traffics.sublist(_traffics.length - maxLength);
}
notifyListeners();
}

View File

@@ -1,5 +1,8 @@
// ignore_for_file: invalid_annotation_target
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
@@ -11,8 +14,6 @@ part 'generated/clash_config.g.dart';
part 'generated/clash_config.freezed.dart';
const defaultTun = Tun();
@freezed
class Tun with _$Tun {
const factory Tun({
@@ -23,17 +24,6 @@ class Tun with _$Tun {
}) = _Tun;
factory Tun.fromJson(Map<String, Object?> json) => _$TunFromJson(json);
factory Tun.realFromJson(Map<String, Object?>? json) {
if (json == null) {
return defaultTun;
}
try {
return Tun.fromJson(json);
} catch (_) {
return defaultTun;
}
}
}
@freezed
@@ -55,8 +45,6 @@ class FallbackFilter with _$FallbackFilter {
_$FallbackFilterFromJson(json);
}
const defaultDns = Dns();
@freezed
class Dns with _$Dns {
const factory Dns({
@@ -159,7 +147,7 @@ class ClashConfig extends ChangeNotifier {
_geodataLoader = geodataLoaderMemconservative,
_externalController = '',
_keepAliveInterval = defaultKeepAliveInterval,
_dns = defaultDns,
_dns = const Dns(),
_geoXUrl = defaultGeoXMap,
_rules = [],
_hosts = {};
@@ -275,6 +263,9 @@ class ClashConfig extends ChangeNotifier {
}
Tun get tun {
if (Platform.isAndroid) {
return _tun.copyWith(enable: false);
}
return _tun;
}
@@ -327,7 +318,7 @@ class ClashConfig extends ChangeNotifier {
GeoXMap get geoXUrl => _geoXUrl;
set geoXUrl(GeoXMap value) {
if (!stringAndStringMapEquality.equals(value, _geoXUrl)) {
if (!const MapEquality<String, String>().equals(value, _geoXUrl)) {
_geoXUrl = value;
notifyListeners();
}
@@ -337,7 +328,7 @@ class ClashConfig extends ChangeNotifier {
HostsMap get hosts => _hosts;
set hosts(HostsMap value) {
if (!stringAndStringMapEquality.equals(value, _hosts)) {
if (!const MapEquality<String, String>().equals(value, _hosts)) {
_hosts = value;
notifyListeners();
}

View File

@@ -1,5 +1,6 @@
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
@@ -302,7 +303,7 @@ extension GroupExt on Group {
String get realNow => now ?? "";
String getCurrentSelectedName(String proxyName) {
if (type.isURLTestOrFallback) {
if (type == GroupType.URLTest) {
return realNow.isNotEmpty ? realNow : proxyName;
}
return proxyName.isNotEmpty ? proxyName : realNow;
@@ -431,14 +432,6 @@ class HotKeyAction with _$HotKeyAction {
_$HotKeyActionFromJson(json);
}
const keyboardModifiersEquality = SetEquality<KeyboardModifier>();
const hotKeyActionsEquality = ListEquality<HotKeyAction>();
typedef Validator = String? Function(String? value);
@freezed
class Field with _$Field {
const factory Field({
required String label,
required String value,
Validator? validator,
}) = _Field;
}

View File

@@ -1,49 +1,17 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../enum/enum.dart';
import '../common/common.dart';
import 'models.dart';
part 'generated/config.g.dart';
part 'generated/config.freezed.dart';
const defaultAppSetting = AppSetting();
@freezed
class AppSetting with _$AppSetting {
const factory AppSetting({
String? locale,
@Default(false) bool onlyProxy,
@Default(false) bool autoLaunch,
@Default(false) bool adminAutoLaunch,
@Default(false) bool silentLaunch,
@Default(false) bool autoRun,
@Default(false) bool openLogs,
@Default(true) bool closeConnections,
@Default(defaultTestUrl) String testUrl,
@Default(true) bool isAnimateToPage,
@Default(true) bool autoCheckUpdate,
@Default(false) bool showLabel,
@Default(false) bool disclaimerAccepted,
@Default(true) bool minimizeOnExit,
@Default(false) bool hidden,
}) = _AppSetting;
factory AppSetting.fromJson(Map<String, Object?> json) =>
_$AppSettingFromJson(json);
factory AppSetting.realFromJson(Map<String, Object?>? json) {
final appSetting =
json == null ? defaultAppSetting : AppSetting.fromJson(json);
return appSetting.copyWith(
isAnimateToPage: system.isDesktop ? false : true,
);
}
}
@freezed
class AccessControl with _$AccessControl {
const factory AccessControl({
@@ -65,6 +33,33 @@ extension AccessControlExt on AccessControl {
};
}
@freezed
class CoreState with _$CoreState {
const factory CoreState({
AccessControl? accessControl,
required String currentProfileName,
required bool enable,
required bool allowBypass,
required bool systemProxy,
required int mixedPort,
required bool onlyProxy,
}) = _CoreState;
factory CoreState.fromJson(Map<String, Object?> json) =>
_$CoreStateFromJson(json);
}
@freezed
class VPNState with _$VPNState {
const factory VPNState({
required AccessControl? accessControl,
required VpnProps vpnProps,
}) = _VPNState;
factory VPNState.fromJson(Map<String, Object?> json) =>
_$VPNStateFromJson(json);
}
@freezed
class WindowProps with _$WindowProps {
const factory WindowProps({
@@ -78,36 +73,12 @@ class WindowProps with _$WindowProps {
json == null ? const WindowProps() : _$WindowPropsFromJson(json);
}
const defaultBypassDomain = [
"*zhihu.com",
"*zhimg.com",
"*jd.com",
"100ime-iat-api.xfyun.cn",
"*360buyimg.com",
"localhost",
"*.local",
"127.*",
"10.*",
"172.16.*",
"172.17.*",
"172.18.*",
"172.19.*",
"172.2*",
"172.30.*",
"172.31.*",
"192.168.*"
];
const defaultVpnProps = VpnProps();
@freezed
class VpnProps with _$VpnProps {
const factory VpnProps({
@Default(true) bool enable,
@Default(true) bool systemProxy,
@Default(false) bool ipv6,
@Default(false) bool systemProxy,
@Default(true) bool allowBypass,
@Default(defaultBypassDomain) List<String> bypassDomain,
}) = _VpnProps;
factory VpnProps.fromJson(Map<String, Object?>? json) =>
@@ -124,92 +95,88 @@ class DesktopProps with _$DesktopProps {
json == null ? const DesktopProps() : _$DesktopPropsFromJson(json);
}
const defaultProxiesStyle = ProxiesStyle();
@freezed
class ProxiesStyle with _$ProxiesStyle {
const factory ProxiesStyle({
@Default(ProxiesType.tab) ProxiesType type,
@Default(ProxiesSortType.none) ProxiesSortType sortType,
@Default(ProxiesLayout.standard) ProxiesLayout layout,
@Default(ProxiesIconStyle.standard) ProxiesIconStyle iconStyle,
@Default(ProxyCardType.expand) ProxyCardType cardType,
@Default({}) Map<String, String> iconMap,
}) = _ProxiesStyle;
factory ProxiesStyle.fromJson(Map<String, Object?>? json) =>
json == null ? defaultProxiesStyle : _$ProxiesStyleFromJson(json);
}
const defaultThemeProps = ThemeProps();
@freezed
class ThemeProps with _$ThemeProps {
const factory ThemeProps({
@Default(0xFF795548) int? primaryColor,
@Default(ThemeMode.system) ThemeMode themeMode,
@Default(false) bool prueBlack,
@Default(FontFamily.system) FontFamily fontFamily,
}) = _ThemeProps;
factory ThemeProps.fromJson(Map<String, Object?> json) => _$ThemePropsFromJson(json);
factory ThemeProps.realFromJson(Map<String, Object?>? json) {
if (json == null) {
return Platform.isWindows
? defaultThemeProps.copyWith(fontFamily: FontFamily.miSans)
: defaultThemeProps;
}
try {
return ThemeProps.fromJson(json);
} catch (_) {
return Platform.isWindows
? defaultThemeProps.copyWith(fontFamily: FontFamily.miSans)
: defaultThemeProps;
}
}
}
const defaultCustomFontSizeScale = 1.0;
const defaultScaleProps = ScaleProps();
@freezed
class ScaleProps with _$ScaleProps {
const factory ScaleProps({
@Default(false) bool custom,
@Default(defaultCustomFontSizeScale) double scale,
}) = _ScaleProps;
factory ScaleProps.fromJson(Map<String, Object?>? json) =>
json == null ? defaultScaleProps : _$ScalePropsFromJson(json);
}
@JsonSerializable()
class Config extends ChangeNotifier {
AppSetting _appSetting;
List<Profile> _profiles;
bool _isCompatible;
String? _currentProfileId;
bool _autoLaunch;
bool _silentLaunch;
bool _autoRun;
bool _openLog;
ThemeMode _themeMode;
String? _locale;
int? _primaryColor;
ProxiesSortType _proxiesSortType;
bool _isMinimizeOnExit;
bool _isAccessControl;
AccessControl _accessControl;
bool _isAnimateToPage;
bool _autoCheckUpdate;
bool _isExclude;
DAV? _dav;
bool _isCloseConnections;
ProxiesType _proxiesType;
ProxyCardType _proxyCardType;
ProxiesLayout _proxiesLayout;
String _testUrl;
WindowProps _windowProps;
ThemeProps _themeProps;
bool _onlyProxy;
bool _prueBlack;
VpnProps _vpnProps;
ScaleProps _scaleProps;
DesktopProps _desktopProps;
bool _showLabel;
bool _overrideDns;
List<HotKeyAction> _hotKeyActions;
ProxiesStyle _proxiesStyle;
bool _isDisclaimerAccepted;
Config()
: _profiles = [],
_autoLaunch = false,
_silentLaunch = false,
_autoRun = false,
_isCloseConnections = false,
_themeMode = ThemeMode.system,
_openLog = false,
_isCompatible = true,
_primaryColor = defaultPrimaryColor.value,
_proxiesSortType = ProxiesSortType.none,
_isMinimizeOnExit = true,
_isAccessControl = false,
_autoCheckUpdate = true,
_testUrl = defaultTestUrl,
_accessControl = const AccessControl(),
_isAnimateToPage = true,
_isExclude = false,
_proxyCardType = ProxyCardType.expand,
_windowProps = const WindowProps(),
_vpnProps = defaultVpnProps,
_proxiesType = ProxiesType.tab,
_prueBlack = false,
_onlyProxy = false,
_proxiesLayout = ProxiesLayout.standard,
_vpnProps = const VpnProps(),
_desktopProps = const DesktopProps(),
_showLabel = false,
_overrideDns = false,
_appSetting = defaultAppSetting,
_hotKeyActions = [],
_proxiesStyle = defaultProxiesStyle,
_themeProps = defaultThemeProps;
@JsonKey(fromJson: AppSetting.realFromJson)
AppSetting get appSetting => _appSetting;
set appSetting(AppSetting value) {
if (_appSetting != value) {
_appSetting = value;
notifyListeners();
}
}
_scaleProps = const ScaleProps(),
_isDisclaimerAccepted = false,
_hotKeyActions = [];
deleteProfileById(String id) {
_profiles = profiles.where((element) => element.id != id).toList();
@@ -289,7 +256,7 @@ class Config extends ChangeNotifier {
Set<String> get currentUnfoldSet => currentProfile?.unfoldSet ?? {};
updateCurrentUnfoldSet(Set<String> value) {
if (!stringSetEquality.equals(currentUnfoldSet, value)) {
if (!const SetEquality<String>().equals(currentUnfoldSet, value)) {
_setProfile(
currentProfile!.copyWith(
unfoldSet: value,
@@ -330,6 +297,107 @@ class Config extends ChangeNotifier {
}
}
@JsonKey(defaultValue: false)
bool get autoLaunch {
if (!system.isDesktop) return false;
return _autoLaunch;
}
set autoLaunch(bool value) {
if (_autoLaunch != value) {
_autoLaunch = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get silentLaunch => _silentLaunch;
set silentLaunch(bool value) {
if (_silentLaunch != value) {
_silentLaunch = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get autoRun => _autoRun;
set autoRun(bool value) {
if (_autoRun != value) {
_autoRun = value;
notifyListeners();
}
}
@JsonKey(defaultValue: ThemeMode.system)
ThemeMode get themeMode => _themeMode;
set themeMode(ThemeMode value) {
if (_themeMode != value) {
_themeMode = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get openLogs => _openLog;
set openLogs(bool value) {
if (_openLog != value) {
_openLog = value;
notifyListeners();
}
}
String? get locale => _locale;
set locale(String? value) {
if (_locale != value) {
_locale = value;
notifyListeners();
}
}
int? get primaryColor => _primaryColor;
set primaryColor(int? value) {
if (_primaryColor != value) {
_primaryColor = value;
notifyListeners();
}
}
@JsonKey(defaultValue: ProxiesSortType.none)
ProxiesSortType get proxiesSortType => _proxiesSortType;
set proxiesSortType(ProxiesSortType value) {
if (_proxiesSortType != value) {
_proxiesSortType = value;
notifyListeners();
}
}
@JsonKey(defaultValue: ProxiesLayout.standard)
ProxiesLayout get proxiesLayout => _proxiesLayout;
set proxiesLayout(ProxiesLayout value) {
if (_proxiesLayout != value) {
_proxiesLayout = value;
notifyListeners();
}
}
@JsonKey(defaultValue: true)
bool get isMinimizeOnExit => _isMinimizeOnExit;
set isMinimizeOnExit(bool value) {
if (_isMinimizeOnExit != value) {
_isMinimizeOnExit = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get isAccessControl {
if (!Platform.isAndroid) return false;
@@ -361,6 +429,122 @@ class Config extends ChangeNotifier {
}
}
@JsonKey(defaultValue: true)
bool get isAnimateToPage {
if (!Platform.isAndroid) return false;
return _isAnimateToPage;
}
set isAnimateToPage(bool value) {
if (_isAnimateToPage != value) {
_isAnimateToPage = value;
notifyListeners();
}
}
@JsonKey(defaultValue: true)
bool get isCompatible {
return _isCompatible;
}
set isCompatible(bool value) {
if (_isCompatible != value) {
_isCompatible = value;
notifyListeners();
}
}
@JsonKey(defaultValue: true)
bool get autoCheckUpdate {
return _autoCheckUpdate;
}
set autoCheckUpdate(bool value) {
if (_autoCheckUpdate != value) {
_autoCheckUpdate = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get onlyProxy {
return _onlyProxy;
}
set onlyProxy(bool value) {
if (_onlyProxy != value) {
_onlyProxy = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get prueBlack {
return _prueBlack;
}
set prueBlack(bool value) {
if (_prueBlack != value) {
_prueBlack = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get isCloseConnections {
return _isCloseConnections;
}
set isCloseConnections(bool value) {
if (_isCloseConnections != value) {
_isCloseConnections = value;
notifyListeners();
}
}
@JsonKey(
defaultValue: ProxiesType.tab,
unknownEnumValue: ProxiesType.tab,
)
ProxiesType get proxiesType => _proxiesType;
set proxiesType(ProxiesType value) {
if (_proxiesType != value) {
_proxiesType = value;
notifyListeners();
}
}
@JsonKey(defaultValue: ProxyCardType.expand)
ProxyCardType get proxyCardType => _proxyCardType;
set proxyCardType(ProxyCardType value) {
if (_proxyCardType != value) {
_proxyCardType = value;
notifyListeners();
}
}
@JsonKey(name: "test-url", defaultValue: defaultTestUrl)
String get testUrl => _testUrl;
set testUrl(String value) {
if (_testUrl != value) {
_testUrl = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get isExclude => _isExclude;
set isExclude(bool value) {
if (_isExclude != value) {
_isExclude = value;
notifyListeners();
}
}
WindowProps get windowProps => _windowProps;
set windowProps(WindowProps value) {
@@ -388,6 +572,25 @@ class Config extends ChangeNotifier {
}
}
ScaleProps get scaleProps => _scaleProps;
set scaleProps(ScaleProps value) {
if (_scaleProps != value) {
_scaleProps = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get showLabel => _showLabel;
set showLabel(bool value) {
if (_showLabel != value) {
_showLabel = value;
notifyListeners();
}
}
@JsonKey(defaultValue: false)
bool get overrideDns => _overrideDns;
@@ -398,6 +601,16 @@ class Config extends ChangeNotifier {
}
}
@JsonKey(defaultValue: false)
bool get isDisclaimerAccepted => _isDisclaimerAccepted;
set isDisclaimerAccepted(bool value) {
if (_isDisclaimerAccepted != value) {
_isDisclaimerAccepted = value;
notifyListeners();
}
}
@JsonKey(defaultValue: [])
List<HotKeyAction> get hotKeyActions => _hotKeyActions;
@@ -408,29 +621,6 @@ class Config extends ChangeNotifier {
}
}
ProxiesStyle get proxiesStyle => _proxiesStyle;
set proxiesStyle(ProxiesStyle value) {
if (_proxiesStyle != value ||
!stringAndStringMapEntryIterableEquality.equals(
_proxiesStyle.iconMap.entries,
value.iconMap.entries,
)) {
_proxiesStyle = value;
notifyListeners();
}
}
@JsonKey(fromJson: ThemeProps.realFromJson)
ThemeProps get themeProps => _themeProps;
set themeProps(ThemeProps value) {
if (_themeProps != value) {
_themeProps = value;
notifyListeners();
}
}
updateOrAddHotKeyAction(HotKeyAction hotKeyAction) {
final index =
_hotKeyActions.indexWhere((item) => item.action == hotKeyAction.action);
@@ -456,14 +646,28 @@ class Config extends ChangeNotifier {
_currentProfileId = _profiles.first.id;
}
if (onlyProfiles) return;
_appSetting = config._appSetting;
_currentProfileId = config._currentProfileId;
_isCloseConnections = config._isCloseConnections;
_isCompatible = config._isCompatible;
_autoLaunch = config._autoLaunch;
_dav = config._dav;
_silentLaunch = config._silentLaunch;
_autoRun = config._autoRun;
_proxiesType = config._proxiesType;
_openLog = config._openLog;
_themeMode = config._themeMode;
_locale = config._locale;
_primaryColor = config._primaryColor;
_proxiesSortType = config._proxiesSortType;
_isMinimizeOnExit = config._isMinimizeOnExit;
_isAccessControl = config._isAccessControl;
_accessControl = config._accessControl;
_themeProps = config._themeProps;
_isAnimateToPage = config._isAnimateToPage;
_autoCheckUpdate = config._autoCheckUpdate;
_prueBlack = config._prueBlack;
_testUrl = config._testUrl;
_isExclude = config._isExclude;
_windowProps = config._windowProps;
_proxiesStyle = config._proxiesStyle;
_vpnProps = config._vpnProps;
_overrideDns = config._overrideDns;
_desktopProps = config._desktopProps;

View File

@@ -8,41 +8,6 @@ part 'generated/ffi.g.dart';
part 'generated/ffi.freezed.dart';
@freezed
class CoreState with _$CoreState {
const factory CoreState({
required bool enable,
AccessControl? accessControl,
required String currentProfileName,
required bool allowBypass,
required bool systemProxy,
required List<String> bypassDomain,
required bool ipv6,
required bool onlyProxy,
}) = _CoreState;
factory CoreState.fromJson(Map<String, Object?> json) =>
_$CoreStateFromJson(json);
}
@freezed
class AndroidVpnOptions with _$AndroidVpnOptions {
const factory AndroidVpnOptions({
required bool enable,
required int port,
required AccessControl? accessControl,
required bool allowBypass,
required bool systemProxy,
required List<String> bypassDomain,
required String ipv4Address,
required String ipv6Address,
required String dnsServerAddress,
}) = _AndroidVpnOptions;
factory AndroidVpnOptions.fromJson(Map<String, Object?> json) =>
_$AndroidVpnOptionsFromJson(json);
}
@freezed
class ConfigExtendedParams with _$ConfigExtendedParams {
const factory ConfigExtendedParams({

View File

@@ -32,9 +32,9 @@ ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => ClashConfig()
'mmdb':
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb',
'asn':
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb',
'https://github.com/xishang0128/geoip/releases/download/latest/GeoLite2-ASN.mmdb',
'geoip':
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat',
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoIP.dat',
'geosite':
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat'
}

View File

@@ -2468,153 +2468,3 @@ abstract class _HotKeyAction implements HotKeyAction {
_$$HotKeyActionImplCopyWith<_$HotKeyActionImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$Field {
String get label => throw _privateConstructorUsedError;
String get value => throw _privateConstructorUsedError;
Validator? get validator => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$FieldCopyWith<Field> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $FieldCopyWith<$Res> {
factory $FieldCopyWith(Field value, $Res Function(Field) then) =
_$FieldCopyWithImpl<$Res, Field>;
@useResult
$Res call({String label, String value, Validator? validator});
}
/// @nodoc
class _$FieldCopyWithImpl<$Res, $Val extends Field>
implements $FieldCopyWith<$Res> {
_$FieldCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? label = null,
Object? value = null,
Object? validator = freezed,
}) {
return _then(_value.copyWith(
label: null == label
? _value.label
: label // ignore: cast_nullable_to_non_nullable
as String,
value: null == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as String,
validator: freezed == validator
? _value.validator
: validator // ignore: cast_nullable_to_non_nullable
as Validator?,
) as $Val);
}
}
/// @nodoc
abstract class _$$FieldImplCopyWith<$Res> implements $FieldCopyWith<$Res> {
factory _$$FieldImplCopyWith(
_$FieldImpl value, $Res Function(_$FieldImpl) then) =
__$$FieldImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String label, String value, Validator? validator});
}
/// @nodoc
class __$$FieldImplCopyWithImpl<$Res>
extends _$FieldCopyWithImpl<$Res, _$FieldImpl>
implements _$$FieldImplCopyWith<$Res> {
__$$FieldImplCopyWithImpl(
_$FieldImpl _value, $Res Function(_$FieldImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? label = null,
Object? value = null,
Object? validator = freezed,
}) {
return _then(_$FieldImpl(
label: null == label
? _value.label
: label // ignore: cast_nullable_to_non_nullable
as String,
value: null == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as String,
validator: freezed == validator
? _value.validator
: validator // ignore: cast_nullable_to_non_nullable
as Validator?,
));
}
}
/// @nodoc
class _$FieldImpl implements _Field {
const _$FieldImpl({required this.label, required this.value, this.validator});
@override
final String label;
@override
final String value;
@override
final Validator? validator;
@override
String toString() {
return 'Field(label: $label, value: $value, validator: $validator)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$FieldImpl &&
(identical(other.label, label) || other.label == label) &&
(identical(other.value, value) || other.value == value) &&
(identical(other.validator, validator) ||
other.validator == validator));
}
@override
int get hashCode => Object.hash(runtimeType, label, value, validator);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$FieldImplCopyWith<_$FieldImpl> get copyWith =>
__$$FieldImplCopyWithImpl<_$FieldImpl>(this, _$identity);
}
abstract class _Field implements Field {
const factory _Field(
{required final String label,
required final String value,
final Validator? validator}) = _$FieldImpl;
@override
String get label;
@override
String get value;
@override
Validator? get validator;
@override
@JsonKey(ignore: true)
_$$FieldImplCopyWith<_$FieldImpl> get copyWith =>
throw _privateConstructorUsedError;
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,88 +7,127 @@ part of '../config.dart';
// **************************************************************************
Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
..appSetting =
AppSetting.realFromJson(json['appSetting'] as Map<String, Object?>?)
..profiles = (json['profiles'] as List<dynamic>?)
?.map((e) => Profile.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..currentProfileId = json['currentProfileId'] as String?
..autoLaunch = json['autoLaunch'] as bool? ?? false
..silentLaunch = json['silentLaunch'] as bool? ?? false
..autoRun = json['autoRun'] as bool? ?? false
..themeMode = $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
ThemeMode.system
..openLogs = json['openLogs'] as bool? ?? false
..locale = json['locale'] as String?
..primaryColor = (json['primaryColor'] as num?)?.toInt()
..proxiesSortType =
$enumDecodeNullable(_$ProxiesSortTypeEnumMap, json['proxiesSortType']) ??
ProxiesSortType.none
..proxiesLayout =
$enumDecodeNullable(_$ProxiesLayoutEnumMap, json['proxiesLayout']) ??
ProxiesLayout.standard
..isMinimizeOnExit = json['isMinimizeOnExit'] as bool? ?? true
..isAccessControl = json['isAccessControl'] as bool? ?? false
..accessControl =
AccessControl.fromJson(json['accessControl'] as Map<String, dynamic>)
..dav = json['dav'] == null
? null
: DAV.fromJson(json['dav'] as Map<String, dynamic>)
..isAnimateToPage = json['isAnimateToPage'] as bool? ?? true
..isCompatible = json['isCompatible'] as bool? ?? true
..autoCheckUpdate = json['autoCheckUpdate'] as bool? ?? true
..onlyProxy = json['onlyProxy'] as bool? ?? false
..prueBlack = json['prueBlack'] as bool? ?? false
..isCloseConnections = json['isCloseConnections'] as bool? ?? false
..proxiesType = $enumDecodeNullable(_$ProxiesTypeEnumMap, json['proxiesType'],
unknownValue: ProxiesType.tab) ??
ProxiesType.tab
..proxyCardType =
$enumDecodeNullable(_$ProxyCardTypeEnumMap, json['proxyCardType']) ??
ProxyCardType.expand
..testUrl =
json['test-url'] as String? ?? 'https://www.gstatic.com/generate_204'
..isExclude = json['isExclude'] as bool? ?? false
..windowProps =
WindowProps.fromJson(json['windowProps'] as Map<String, dynamic>?)
..vpnProps = VpnProps.fromJson(json['vpnProps'] as Map<String, dynamic>?)
..desktopProps =
DesktopProps.fromJson(json['desktopProps'] as Map<String, dynamic>?)
..scaleProps =
ScaleProps.fromJson(json['scaleProps'] as Map<String, dynamic>?)
..showLabel = json['showLabel'] as bool? ?? false
..overrideDns = json['overrideDns'] as bool? ?? false
..isDisclaimerAccepted = json['isDisclaimerAccepted'] as bool? ?? false
..hotKeyActions = (json['hotKeyActions'] as List<dynamic>?)
?.map((e) => HotKeyAction.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..proxiesStyle =
ProxiesStyle.fromJson(json['proxiesStyle'] as Map<String, dynamic>?)
..themeProps =
ThemeProps.realFromJson(json['themeProps'] as Map<String, Object?>?);
[];
Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
'appSetting': instance.appSetting,
'profiles': instance.profiles,
'currentProfileId': instance.currentProfileId,
'autoLaunch': instance.autoLaunch,
'silentLaunch': instance.silentLaunch,
'autoRun': instance.autoRun,
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
'openLogs': instance.openLogs,
'locale': instance.locale,
'primaryColor': instance.primaryColor,
'proxiesSortType': _$ProxiesSortTypeEnumMap[instance.proxiesSortType]!,
'proxiesLayout': _$ProxiesLayoutEnumMap[instance.proxiesLayout]!,
'isMinimizeOnExit': instance.isMinimizeOnExit,
'isAccessControl': instance.isAccessControl,
'accessControl': instance.accessControl,
'dav': instance.dav,
'isAnimateToPage': instance.isAnimateToPage,
'isCompatible': instance.isCompatible,
'autoCheckUpdate': instance.autoCheckUpdate,
'onlyProxy': instance.onlyProxy,
'prueBlack': instance.prueBlack,
'isCloseConnections': instance.isCloseConnections,
'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!,
'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!,
'test-url': instance.testUrl,
'isExclude': instance.isExclude,
'windowProps': instance.windowProps,
'vpnProps': instance.vpnProps,
'desktopProps': instance.desktopProps,
'overrideDns': instance.overrideDns,
'hotKeyActions': instance.hotKeyActions,
'proxiesStyle': instance.proxiesStyle,
'themeProps': instance.themeProps,
};
_$AppSettingImpl _$$AppSettingImplFromJson(Map<String, dynamic> json) =>
_$AppSettingImpl(
locale: json['locale'] as String?,
onlyProxy: json['onlyProxy'] as bool? ?? false,
autoLaunch: json['autoLaunch'] as bool? ?? false,
adminAutoLaunch: json['adminAutoLaunch'] as bool? ?? false,
silentLaunch: json['silentLaunch'] as bool? ?? false,
autoRun: json['autoRun'] as bool? ?? false,
openLogs: json['openLogs'] as bool? ?? false,
closeConnections: json['closeConnections'] as bool? ?? true,
testUrl: json['testUrl'] as String? ?? defaultTestUrl,
isAnimateToPage: json['isAnimateToPage'] as bool? ?? true,
autoCheckUpdate: json['autoCheckUpdate'] as bool? ?? true,
showLabel: json['showLabel'] as bool? ?? false,
disclaimerAccepted: json['disclaimerAccepted'] as bool? ?? false,
minimizeOnExit: json['minimizeOnExit'] as bool? ?? true,
hidden: json['hidden'] as bool? ?? false,
);
Map<String, dynamic> _$$AppSettingImplToJson(_$AppSettingImpl instance) =>
<String, dynamic>{
'locale': instance.locale,
'onlyProxy': instance.onlyProxy,
'autoLaunch': instance.autoLaunch,
'adminAutoLaunch': instance.adminAutoLaunch,
'silentLaunch': instance.silentLaunch,
'autoRun': instance.autoRun,
'openLogs': instance.openLogs,
'closeConnections': instance.closeConnections,
'testUrl': instance.testUrl,
'isAnimateToPage': instance.isAnimateToPage,
'autoCheckUpdate': instance.autoCheckUpdate,
'scaleProps': instance.scaleProps,
'showLabel': instance.showLabel,
'disclaimerAccepted': instance.disclaimerAccepted,
'minimizeOnExit': instance.minimizeOnExit,
'hidden': instance.hidden,
'overrideDns': instance.overrideDns,
'isDisclaimerAccepted': instance.isDisclaimerAccepted,
'hotKeyActions': instance.hotKeyActions,
};
const _$ThemeModeEnumMap = {
ThemeMode.system: 'system',
ThemeMode.light: 'light',
ThemeMode.dark: 'dark',
};
const _$ProxiesSortTypeEnumMap = {
ProxiesSortType.none: 'none',
ProxiesSortType.delay: 'delay',
ProxiesSortType.name: 'name',
};
const _$ProxiesLayoutEnumMap = {
ProxiesLayout.loose: 'loose',
ProxiesLayout.standard: 'standard',
ProxiesLayout.tight: 'tight',
};
const _$ProxiesTypeEnumMap = {
ProxiesType.tab: 'tab',
ProxiesType.list: 'list',
};
const _$ProxyCardTypeEnumMap = {
ProxyCardType.expand: 'expand',
ProxyCardType.shrink: 'shrink',
ProxyCardType.min: 'min',
};
_$AccessControlImpl _$$AccessControlImplFromJson(Map<String, dynamic> json) =>
_$AccessControlImpl(
mode: $enumDecodeNullable(_$AccessControlModeEnumMap, json['mode']) ??
@@ -126,6 +165,46 @@ const _$AccessSortTypeEnumMap = {
AccessSortType.time: 'time',
};
_$CoreStateImpl _$$CoreStateImplFromJson(Map<String, dynamic> json) =>
_$CoreStateImpl(
accessControl: json['accessControl'] == null
? null
: AccessControl.fromJson(
json['accessControl'] as Map<String, dynamic>),
currentProfileName: json['currentProfileName'] as String,
enable: json['enable'] as bool,
allowBypass: json['allowBypass'] as bool,
systemProxy: json['systemProxy'] as bool,
mixedPort: (json['mixedPort'] as num).toInt(),
onlyProxy: json['onlyProxy'] as bool,
);
Map<String, dynamic> _$$CoreStateImplToJson(_$CoreStateImpl instance) =>
<String, dynamic>{
'accessControl': instance.accessControl,
'currentProfileName': instance.currentProfileName,
'enable': instance.enable,
'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy,
'mixedPort': instance.mixedPort,
'onlyProxy': instance.onlyProxy,
};
_$VPNStateImpl _$$VPNStateImplFromJson(Map<String, dynamic> json) =>
_$VPNStateImpl(
accessControl: json['accessControl'] == null
? null
: AccessControl.fromJson(
json['accessControl'] as Map<String, dynamic>),
vpnProps: VpnProps.fromJson(json['vpnProps'] as Map<String, dynamic>?),
);
Map<String, dynamic> _$$VPNStateImplToJson(_$VPNStateImpl instance) =>
<String, dynamic>{
'accessControl': instance.accessControl,
'vpnProps': instance.vpnProps,
};
_$WindowPropsImpl _$$WindowPropsImplFromJson(Map<String, dynamic> json) =>
_$WindowPropsImpl(
width: (json['width'] as num?)?.toDouble() ?? 1000,
@@ -145,22 +224,15 @@ Map<String, dynamic> _$$WindowPropsImplToJson(_$WindowPropsImpl instance) =>
_$VpnPropsImpl _$$VpnPropsImplFromJson(Map<String, dynamic> json) =>
_$VpnPropsImpl(
enable: json['enable'] as bool? ?? true,
systemProxy: json['systemProxy'] as bool? ?? true,
ipv6: json['ipv6'] as bool? ?? false,
systemProxy: json['systemProxy'] as bool? ?? false,
allowBypass: json['allowBypass'] as bool? ?? true,
bypassDomain: (json['bypassDomain'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
defaultBypassDomain,
);
Map<String, dynamic> _$$VpnPropsImplToJson(_$VpnPropsImpl instance) =>
<String, dynamic>{
'enable': instance.enable,
'systemProxy': instance.systemProxy,
'ipv6': instance.ipv6,
'allowBypass': instance.allowBypass,
'bypassDomain': instance.bypassDomain,
};
_$DesktopPropsImpl _$$DesktopPropsImplFromJson(Map<String, dynamic> json) =>
@@ -173,93 +245,14 @@ Map<String, dynamic> _$$DesktopPropsImplToJson(_$DesktopPropsImpl instance) =>
'systemProxy': instance.systemProxy,
};
_$ProxiesStyleImpl _$$ProxiesStyleImplFromJson(Map<String, dynamic> json) =>
_$ProxiesStyleImpl(
type: $enumDecodeNullable(_$ProxiesTypeEnumMap, json['type']) ??
ProxiesType.tab,
sortType:
$enumDecodeNullable(_$ProxiesSortTypeEnumMap, json['sortType']) ??
ProxiesSortType.none,
layout: $enumDecodeNullable(_$ProxiesLayoutEnumMap, json['layout']) ??
ProxiesLayout.standard,
iconStyle:
$enumDecodeNullable(_$ProxiesIconStyleEnumMap, json['iconStyle']) ??
ProxiesIconStyle.standard,
cardType: $enumDecodeNullable(_$ProxyCardTypeEnumMap, json['cardType']) ??
ProxyCardType.expand,
iconMap: (json['iconMap'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
) ??
const {},
_$ScalePropsImpl _$$ScalePropsImplFromJson(Map<String, dynamic> json) =>
_$ScalePropsImpl(
custom: json['custom'] as bool? ?? false,
scale: (json['scale'] as num?)?.toDouble() ?? defaultCustomFontSizeScale,
);
Map<String, dynamic> _$$ProxiesStyleImplToJson(_$ProxiesStyleImpl instance) =>
Map<String, dynamic> _$$ScalePropsImplToJson(_$ScalePropsImpl instance) =>
<String, dynamic>{
'type': _$ProxiesTypeEnumMap[instance.type]!,
'sortType': _$ProxiesSortTypeEnumMap[instance.sortType]!,
'layout': _$ProxiesLayoutEnumMap[instance.layout]!,
'iconStyle': _$ProxiesIconStyleEnumMap[instance.iconStyle]!,
'cardType': _$ProxyCardTypeEnumMap[instance.cardType]!,
'iconMap': instance.iconMap,
'custom': instance.custom,
'scale': instance.scale,
};
const _$ProxiesTypeEnumMap = {
ProxiesType.tab: 'tab',
ProxiesType.list: 'list',
};
const _$ProxiesSortTypeEnumMap = {
ProxiesSortType.none: 'none',
ProxiesSortType.delay: 'delay',
ProxiesSortType.name: 'name',
};
const _$ProxiesLayoutEnumMap = {
ProxiesLayout.loose: 'loose',
ProxiesLayout.standard: 'standard',
ProxiesLayout.tight: 'tight',
};
const _$ProxiesIconStyleEnumMap = {
ProxiesIconStyle.standard: 'standard',
ProxiesIconStyle.none: 'none',
ProxiesIconStyle.icon: 'icon',
};
const _$ProxyCardTypeEnumMap = {
ProxyCardType.expand: 'expand',
ProxyCardType.shrink: 'shrink',
ProxyCardType.min: 'min',
};
_$ThemePropsImpl _$$ThemePropsImplFromJson(Map<String, dynamic> json) =>
_$ThemePropsImpl(
primaryColor: (json['primaryColor'] as num?)?.toInt() ?? 0xFF795548,
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
ThemeMode.system,
prueBlack: json['prueBlack'] as bool? ?? false,
fontFamily:
$enumDecodeNullable(_$FontFamilyEnumMap, json['fontFamily']) ??
FontFamily.system,
);
Map<String, dynamic> _$$ThemePropsImplToJson(_$ThemePropsImpl instance) =>
<String, dynamic>{
'primaryColor': instance.primaryColor,
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
'prueBlack': instance.prueBlack,
'fontFamily': _$FontFamilyEnumMap[instance.fontFamily]!,
};
const _$ThemeModeEnumMap = {
ThemeMode.system: 'system',
ThemeMode.light: 'light',
ThemeMode.dark: 'dark',
};
const _$FontFamilyEnumMap = {
FontFamily.system: 'system',
FontFamily.miSans: 'miSans',
FontFamily.twEmoji: 'twEmoji',
FontFamily.icon: 'icon',
};

View File

@@ -14,666 +14,6 @@ T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
CoreState _$CoreStateFromJson(Map<String, dynamic> json) {
return _CoreState.fromJson(json);
}
/// @nodoc
mixin _$CoreState {
bool get enable => throw _privateConstructorUsedError;
AccessControl? get accessControl => throw _privateConstructorUsedError;
String get currentProfileName => throw _privateConstructorUsedError;
bool get allowBypass => throw _privateConstructorUsedError;
bool get systemProxy => throw _privateConstructorUsedError;
List<String> get bypassDomain => throw _privateConstructorUsedError;
bool get ipv6 => throw _privateConstructorUsedError;
bool get onlyProxy => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$CoreStateCopyWith<CoreState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $CoreStateCopyWith<$Res> {
factory $CoreStateCopyWith(CoreState value, $Res Function(CoreState) then) =
_$CoreStateCopyWithImpl<$Res, CoreState>;
@useResult
$Res call(
{bool enable,
AccessControl? accessControl,
String currentProfileName,
bool allowBypass,
bool systemProxy,
List<String> bypassDomain,
bool ipv6,
bool onlyProxy});
$AccessControlCopyWith<$Res>? get accessControl;
}
/// @nodoc
class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
implements $CoreStateCopyWith<$Res> {
_$CoreStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? enable = null,
Object? accessControl = freezed,
Object? currentProfileName = null,
Object? allowBypass = null,
Object? systemProxy = null,
Object? bypassDomain = null,
Object? ipv6 = null,
Object? onlyProxy = null,
}) {
return _then(_value.copyWith(
enable: null == enable
? _value.enable
: enable // ignore: cast_nullable_to_non_nullable
as bool,
accessControl: freezed == accessControl
? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?,
currentProfileName: null == currentProfileName
? _value.currentProfileName
: currentProfileName // ignore: cast_nullable_to_non_nullable
as String,
allowBypass: null == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
as bool,
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
bypassDomain: null == bypassDomain
? _value.bypassDomain
: bypassDomain // ignore: cast_nullable_to_non_nullable
as List<String>,
ipv6: null == ipv6
? _value.ipv6
: ipv6 // ignore: cast_nullable_to_non_nullable
as bool,
onlyProxy: null == onlyProxy
? _value.onlyProxy
: onlyProxy // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$AccessControlCopyWith<$Res>? get accessControl {
if (_value.accessControl == null) {
return null;
}
return $AccessControlCopyWith<$Res>(_value.accessControl!, (value) {
return _then(_value.copyWith(accessControl: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$CoreStateImplCopyWith<$Res>
implements $CoreStateCopyWith<$Res> {
factory _$$CoreStateImplCopyWith(
_$CoreStateImpl value, $Res Function(_$CoreStateImpl) then) =
__$$CoreStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{bool enable,
AccessControl? accessControl,
String currentProfileName,
bool allowBypass,
bool systemProxy,
List<String> bypassDomain,
bool ipv6,
bool onlyProxy});
@override
$AccessControlCopyWith<$Res>? get accessControl;
}
/// @nodoc
class __$$CoreStateImplCopyWithImpl<$Res>
extends _$CoreStateCopyWithImpl<$Res, _$CoreStateImpl>
implements _$$CoreStateImplCopyWith<$Res> {
__$$CoreStateImplCopyWithImpl(
_$CoreStateImpl _value, $Res Function(_$CoreStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? enable = null,
Object? accessControl = freezed,
Object? currentProfileName = null,
Object? allowBypass = null,
Object? systemProxy = null,
Object? bypassDomain = null,
Object? ipv6 = null,
Object? onlyProxy = null,
}) {
return _then(_$CoreStateImpl(
enable: null == enable
? _value.enable
: enable // ignore: cast_nullable_to_non_nullable
as bool,
accessControl: freezed == accessControl
? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?,
currentProfileName: null == currentProfileName
? _value.currentProfileName
: currentProfileName // ignore: cast_nullable_to_non_nullable
as String,
allowBypass: null == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
as bool,
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
bypassDomain: null == bypassDomain
? _value._bypassDomain
: bypassDomain // ignore: cast_nullable_to_non_nullable
as List<String>,
ipv6: null == ipv6
? _value.ipv6
: ipv6 // ignore: cast_nullable_to_non_nullable
as bool,
onlyProxy: null == onlyProxy
? _value.onlyProxy
: onlyProxy // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
@JsonSerializable()
class _$CoreStateImpl implements _CoreState {
const _$CoreStateImpl(
{required this.enable,
this.accessControl,
required this.currentProfileName,
required this.allowBypass,
required this.systemProxy,
required final List<String> bypassDomain,
required this.ipv6,
required this.onlyProxy})
: _bypassDomain = bypassDomain;
factory _$CoreStateImpl.fromJson(Map<String, dynamic> json) =>
_$$CoreStateImplFromJson(json);
@override
final bool enable;
@override
final AccessControl? accessControl;
@override
final String currentProfileName;
@override
final bool allowBypass;
@override
final bool systemProxy;
final List<String> _bypassDomain;
@override
List<String> get bypassDomain {
if (_bypassDomain is EqualUnmodifiableListView) return _bypassDomain;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_bypassDomain);
}
@override
final bool ipv6;
@override
final bool onlyProxy;
@override
String toString() {
return 'CoreState(enable: $enable, accessControl: $accessControl, currentProfileName: $currentProfileName, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, ipv6: $ipv6, onlyProxy: $onlyProxy)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$CoreStateImpl &&
(identical(other.enable, enable) || other.enable == enable) &&
(identical(other.accessControl, accessControl) ||
other.accessControl == accessControl) &&
(identical(other.currentProfileName, currentProfileName) ||
other.currentProfileName == currentProfileName) &&
(identical(other.allowBypass, allowBypass) ||
other.allowBypass == allowBypass) &&
(identical(other.systemProxy, systemProxy) ||
other.systemProxy == systemProxy) &&
const DeepCollectionEquality()
.equals(other._bypassDomain, _bypassDomain) &&
(identical(other.ipv6, ipv6) || other.ipv6 == ipv6) &&
(identical(other.onlyProxy, onlyProxy) ||
other.onlyProxy == onlyProxy));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
enable,
accessControl,
currentProfileName,
allowBypass,
systemProxy,
const DeepCollectionEquality().hash(_bypassDomain),
ipv6,
onlyProxy);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$CoreStateImplCopyWith<_$CoreStateImpl> get copyWith =>
__$$CoreStateImplCopyWithImpl<_$CoreStateImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$CoreStateImplToJson(
this,
);
}
}
abstract class _CoreState implements CoreState {
const factory _CoreState(
{required final bool enable,
final AccessControl? accessControl,
required final String currentProfileName,
required final bool allowBypass,
required final bool systemProxy,
required final List<String> bypassDomain,
required final bool ipv6,
required final bool onlyProxy}) = _$CoreStateImpl;
factory _CoreState.fromJson(Map<String, dynamic> json) =
_$CoreStateImpl.fromJson;
@override
bool get enable;
@override
AccessControl? get accessControl;
@override
String get currentProfileName;
@override
bool get allowBypass;
@override
bool get systemProxy;
@override
List<String> get bypassDomain;
@override
bool get ipv6;
@override
bool get onlyProxy;
@override
@JsonKey(ignore: true)
_$$CoreStateImplCopyWith<_$CoreStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
AndroidVpnOptions _$AndroidVpnOptionsFromJson(Map<String, dynamic> json) {
return _AndroidVpnOptions.fromJson(json);
}
/// @nodoc
mixin _$AndroidVpnOptions {
bool get enable => throw _privateConstructorUsedError;
int get port => throw _privateConstructorUsedError;
AccessControl? get accessControl => throw _privateConstructorUsedError;
bool get allowBypass => throw _privateConstructorUsedError;
bool get systemProxy => throw _privateConstructorUsedError;
List<String> get bypassDomain => throw _privateConstructorUsedError;
String get ipv4Address => throw _privateConstructorUsedError;
String get ipv6Address => throw _privateConstructorUsedError;
String get dnsServerAddress => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$AndroidVpnOptionsCopyWith<AndroidVpnOptions> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $AndroidVpnOptionsCopyWith<$Res> {
factory $AndroidVpnOptionsCopyWith(
AndroidVpnOptions value, $Res Function(AndroidVpnOptions) then) =
_$AndroidVpnOptionsCopyWithImpl<$Res, AndroidVpnOptions>;
@useResult
$Res call(
{bool enable,
int port,
AccessControl? accessControl,
bool allowBypass,
bool systemProxy,
List<String> bypassDomain,
String ipv4Address,
String ipv6Address,
String dnsServerAddress});
$AccessControlCopyWith<$Res>? get accessControl;
}
/// @nodoc
class _$AndroidVpnOptionsCopyWithImpl<$Res, $Val extends AndroidVpnOptions>
implements $AndroidVpnOptionsCopyWith<$Res> {
_$AndroidVpnOptionsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? enable = null,
Object? port = null,
Object? accessControl = freezed,
Object? allowBypass = null,
Object? systemProxy = null,
Object? bypassDomain = null,
Object? ipv4Address = null,
Object? ipv6Address = null,
Object? dnsServerAddress = null,
}) {
return _then(_value.copyWith(
enable: null == enable
? _value.enable
: enable // ignore: cast_nullable_to_non_nullable
as bool,
port: null == port
? _value.port
: port // ignore: cast_nullable_to_non_nullable
as int,
accessControl: freezed == accessControl
? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?,
allowBypass: null == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
as bool,
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
bypassDomain: null == bypassDomain
? _value.bypassDomain
: bypassDomain // ignore: cast_nullable_to_non_nullable
as List<String>,
ipv4Address: null == ipv4Address
? _value.ipv4Address
: ipv4Address // ignore: cast_nullable_to_non_nullable
as String,
ipv6Address: null == ipv6Address
? _value.ipv6Address
: ipv6Address // ignore: cast_nullable_to_non_nullable
as String,
dnsServerAddress: null == dnsServerAddress
? _value.dnsServerAddress
: dnsServerAddress // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$AccessControlCopyWith<$Res>? get accessControl {
if (_value.accessControl == null) {
return null;
}
return $AccessControlCopyWith<$Res>(_value.accessControl!, (value) {
return _then(_value.copyWith(accessControl: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$AndroidVpnOptionsImplCopyWith<$Res>
implements $AndroidVpnOptionsCopyWith<$Res> {
factory _$$AndroidVpnOptionsImplCopyWith(_$AndroidVpnOptionsImpl value,
$Res Function(_$AndroidVpnOptionsImpl) then) =
__$$AndroidVpnOptionsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{bool enable,
int port,
AccessControl? accessControl,
bool allowBypass,
bool systemProxy,
List<String> bypassDomain,
String ipv4Address,
String ipv6Address,
String dnsServerAddress});
@override
$AccessControlCopyWith<$Res>? get accessControl;
}
/// @nodoc
class __$$AndroidVpnOptionsImplCopyWithImpl<$Res>
extends _$AndroidVpnOptionsCopyWithImpl<$Res, _$AndroidVpnOptionsImpl>
implements _$$AndroidVpnOptionsImplCopyWith<$Res> {
__$$AndroidVpnOptionsImplCopyWithImpl(_$AndroidVpnOptionsImpl _value,
$Res Function(_$AndroidVpnOptionsImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? enable = null,
Object? port = null,
Object? accessControl = freezed,
Object? allowBypass = null,
Object? systemProxy = null,
Object? bypassDomain = null,
Object? ipv4Address = null,
Object? ipv6Address = null,
Object? dnsServerAddress = null,
}) {
return _then(_$AndroidVpnOptionsImpl(
enable: null == enable
? _value.enable
: enable // ignore: cast_nullable_to_non_nullable
as bool,
port: null == port
? _value.port
: port // ignore: cast_nullable_to_non_nullable
as int,
accessControl: freezed == accessControl
? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?,
allowBypass: null == allowBypass
? _value.allowBypass
: allowBypass // ignore: cast_nullable_to_non_nullable
as bool,
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
as bool,
bypassDomain: null == bypassDomain
? _value._bypassDomain
: bypassDomain // ignore: cast_nullable_to_non_nullable
as List<String>,
ipv4Address: null == ipv4Address
? _value.ipv4Address
: ipv4Address // ignore: cast_nullable_to_non_nullable
as String,
ipv6Address: null == ipv6Address
? _value.ipv6Address
: ipv6Address // ignore: cast_nullable_to_non_nullable
as String,
dnsServerAddress: null == dnsServerAddress
? _value.dnsServerAddress
: dnsServerAddress // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$AndroidVpnOptionsImpl implements _AndroidVpnOptions {
const _$AndroidVpnOptionsImpl(
{required this.enable,
required this.port,
required this.accessControl,
required this.allowBypass,
required this.systemProxy,
required final List<String> bypassDomain,
required this.ipv4Address,
required this.ipv6Address,
required this.dnsServerAddress})
: _bypassDomain = bypassDomain;
factory _$AndroidVpnOptionsImpl.fromJson(Map<String, dynamic> json) =>
_$$AndroidVpnOptionsImplFromJson(json);
@override
final bool enable;
@override
final int port;
@override
final AccessControl? accessControl;
@override
final bool allowBypass;
@override
final bool systemProxy;
final List<String> _bypassDomain;
@override
List<String> get bypassDomain {
if (_bypassDomain is EqualUnmodifiableListView) return _bypassDomain;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_bypassDomain);
}
@override
final String ipv4Address;
@override
final String ipv6Address;
@override
final String dnsServerAddress;
@override
String toString() {
return 'AndroidVpnOptions(enable: $enable, port: $port, accessControl: $accessControl, allowBypass: $allowBypass, systemProxy: $systemProxy, bypassDomain: $bypassDomain, ipv4Address: $ipv4Address, ipv6Address: $ipv6Address, dnsServerAddress: $dnsServerAddress)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AndroidVpnOptionsImpl &&
(identical(other.enable, enable) || other.enable == enable) &&
(identical(other.port, port) || other.port == port) &&
(identical(other.accessControl, accessControl) ||
other.accessControl == accessControl) &&
(identical(other.allowBypass, allowBypass) ||
other.allowBypass == allowBypass) &&
(identical(other.systemProxy, systemProxy) ||
other.systemProxy == systemProxy) &&
const DeepCollectionEquality()
.equals(other._bypassDomain, _bypassDomain) &&
(identical(other.ipv4Address, ipv4Address) ||
other.ipv4Address == ipv4Address) &&
(identical(other.ipv6Address, ipv6Address) ||
other.ipv6Address == ipv6Address) &&
(identical(other.dnsServerAddress, dnsServerAddress) ||
other.dnsServerAddress == dnsServerAddress));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
enable,
port,
accessControl,
allowBypass,
systemProxy,
const DeepCollectionEquality().hash(_bypassDomain),
ipv4Address,
ipv6Address,
dnsServerAddress);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$AndroidVpnOptionsImplCopyWith<_$AndroidVpnOptionsImpl> get copyWith =>
__$$AndroidVpnOptionsImplCopyWithImpl<_$AndroidVpnOptionsImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$AndroidVpnOptionsImplToJson(
this,
);
}
}
abstract class _AndroidVpnOptions implements AndroidVpnOptions {
const factory _AndroidVpnOptions(
{required final bool enable,
required final int port,
required final AccessControl? accessControl,
required final bool allowBypass,
required final bool systemProxy,
required final List<String> bypassDomain,
required final String ipv4Address,
required final String ipv6Address,
required final String dnsServerAddress}) = _$AndroidVpnOptionsImpl;
factory _AndroidVpnOptions.fromJson(Map<String, dynamic> json) =
_$AndroidVpnOptionsImpl.fromJson;
@override
bool get enable;
@override
int get port;
@override
AccessControl? get accessControl;
@override
bool get allowBypass;
@override
bool get systemProxy;
@override
List<String> get bypassDomain;
@override
String get ipv4Address;
@override
String get ipv6Address;
@override
String get dnsServerAddress;
@override
@JsonKey(ignore: true)
_$$AndroidVpnOptionsImplCopyWith<_$AndroidVpnOptionsImpl> get copyWith =>
throw _privateConstructorUsedError;
}
ConfigExtendedParams _$ConfigExtendedParamsFromJson(Map<String, dynamic> json) {
return _ConfigExtendedParams.fromJson(json);
}

View File

@@ -6,68 +6,6 @@ part of '../ffi.dart';
// JsonSerializableGenerator
// **************************************************************************
_$CoreStateImpl _$$CoreStateImplFromJson(Map<String, dynamic> json) =>
_$CoreStateImpl(
enable: json['enable'] as bool,
accessControl: json['accessControl'] == null
? null
: AccessControl.fromJson(
json['accessControl'] as Map<String, dynamic>),
currentProfileName: json['currentProfileName'] as String,
allowBypass: json['allowBypass'] as bool,
systemProxy: json['systemProxy'] as bool,
bypassDomain: (json['bypassDomain'] as List<dynamic>)
.map((e) => e as String)
.toList(),
ipv6: json['ipv6'] as bool,
onlyProxy: json['onlyProxy'] as bool,
);
Map<String, dynamic> _$$CoreStateImplToJson(_$CoreStateImpl instance) =>
<String, dynamic>{
'enable': instance.enable,
'accessControl': instance.accessControl,
'currentProfileName': instance.currentProfileName,
'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy,
'bypassDomain': instance.bypassDomain,
'ipv6': instance.ipv6,
'onlyProxy': instance.onlyProxy,
};
_$AndroidVpnOptionsImpl _$$AndroidVpnOptionsImplFromJson(
Map<String, dynamic> json) =>
_$AndroidVpnOptionsImpl(
enable: json['enable'] as bool,
port: (json['port'] as num).toInt(),
accessControl: json['accessControl'] == null
? null
: AccessControl.fromJson(
json['accessControl'] as Map<String, dynamic>),
allowBypass: json['allowBypass'] as bool,
systemProxy: json['systemProxy'] as bool,
bypassDomain: (json['bypassDomain'] as List<dynamic>)
.map((e) => e as String)
.toList(),
ipv4Address: json['ipv4Address'] as String,
ipv6Address: json['ipv6Address'] as String,
dnsServerAddress: json['dnsServerAddress'] as String,
);
Map<String, dynamic> _$$AndroidVpnOptionsImplToJson(
_$AndroidVpnOptionsImpl instance) =>
<String, dynamic>{
'enable': instance.enable,
'port': instance.port,
'accessControl': instance.accessControl,
'allowBypass': instance.allowBypass,
'systemProxy': instance.systemProxy,
'bypassDomain': instance.bypassDomain,
'ipv4Address': instance.ipv4Address,
'ipv6Address': instance.ipv6Address,
'dnsServerAddress': instance.dnsServerAddress,
};
_$ConfigExtendedParamsImpl _$$ConfigExtendedParamsImplFromJson(
Map<String, dynamic> json) =>
_$ConfigExtendedParamsImpl(

View File

@@ -772,7 +772,6 @@ mixin _$ApplicationSelectorState {
ThemeMode? get themeMode => throw _privateConstructorUsedError;
int? get primaryColor => throw _privateConstructorUsedError;
bool get prueBlack => throw _privateConstructorUsedError;
FontFamily get fontFamily => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ApplicationSelectorStateCopyWith<ApplicationSelectorState> get copyWith =>
@@ -789,8 +788,7 @@ abstract class $ApplicationSelectorStateCopyWith<$Res> {
{String? locale,
ThemeMode? themeMode,
int? primaryColor,
bool prueBlack,
FontFamily fontFamily});
bool prueBlack});
}
/// @nodoc
@@ -811,7 +809,6 @@ class _$ApplicationSelectorStateCopyWithImpl<$Res,
Object? themeMode = freezed,
Object? primaryColor = freezed,
Object? prueBlack = null,
Object? fontFamily = null,
}) {
return _then(_value.copyWith(
locale: freezed == locale
@@ -830,10 +827,6 @@ class _$ApplicationSelectorStateCopyWithImpl<$Res,
? _value.prueBlack
: prueBlack // ignore: cast_nullable_to_non_nullable
as bool,
fontFamily: null == fontFamily
? _value.fontFamily
: fontFamily // ignore: cast_nullable_to_non_nullable
as FontFamily,
) as $Val);
}
}
@@ -851,8 +844,7 @@ abstract class _$$ApplicationSelectorStateImplCopyWith<$Res>
{String? locale,
ThemeMode? themeMode,
int? primaryColor,
bool prueBlack,
FontFamily fontFamily});
bool prueBlack});
}
/// @nodoc
@@ -872,7 +864,6 @@ class __$$ApplicationSelectorStateImplCopyWithImpl<$Res>
Object? themeMode = freezed,
Object? primaryColor = freezed,
Object? prueBlack = null,
Object? fontFamily = null,
}) {
return _then(_$ApplicationSelectorStateImpl(
locale: freezed == locale
@@ -891,10 +882,6 @@ class __$$ApplicationSelectorStateImplCopyWithImpl<$Res>
? _value.prueBlack
: prueBlack // ignore: cast_nullable_to_non_nullable
as bool,
fontFamily: null == fontFamily
? _value.fontFamily
: fontFamily // ignore: cast_nullable_to_non_nullable
as FontFamily,
));
}
}
@@ -906,8 +893,7 @@ class _$ApplicationSelectorStateImpl implements _ApplicationSelectorState {
{required this.locale,
required this.themeMode,
required this.primaryColor,
required this.prueBlack,
required this.fontFamily});
required this.prueBlack});
@override
final String? locale;
@@ -917,12 +903,10 @@ class _$ApplicationSelectorStateImpl implements _ApplicationSelectorState {
final int? primaryColor;
@override
final bool prueBlack;
@override
final FontFamily fontFamily;
@override
String toString() {
return 'ApplicationSelectorState(locale: $locale, themeMode: $themeMode, primaryColor: $primaryColor, prueBlack: $prueBlack, fontFamily: $fontFamily)';
return 'ApplicationSelectorState(locale: $locale, themeMode: $themeMode, primaryColor: $primaryColor, prueBlack: $prueBlack)';
}
@override
@@ -936,14 +920,12 @@ class _$ApplicationSelectorStateImpl implements _ApplicationSelectorState {
(identical(other.primaryColor, primaryColor) ||
other.primaryColor == primaryColor) &&
(identical(other.prueBlack, prueBlack) ||
other.prueBlack == prueBlack) &&
(identical(other.fontFamily, fontFamily) ||
other.fontFamily == fontFamily));
other.prueBlack == prueBlack));
}
@override
int get hashCode => Object.hash(
runtimeType, locale, themeMode, primaryColor, prueBlack, fontFamily);
int get hashCode =>
Object.hash(runtimeType, locale, themeMode, primaryColor, prueBlack);
@JsonKey(ignore: true)
@override
@@ -958,8 +940,7 @@ abstract class _ApplicationSelectorState implements ApplicationSelectorState {
{required final String? locale,
required final ThemeMode? themeMode,
required final int? primaryColor,
required final bool prueBlack,
required final FontFamily fontFamily}) = _$ApplicationSelectorStateImpl;
required final bool prueBlack}) = _$ApplicationSelectorStateImpl;
@override
String? get locale;
@@ -970,8 +951,6 @@ abstract class _ApplicationSelectorState implements ApplicationSelectorState {
@override
bool get prueBlack;
@override
FontFamily get fontFamily;
@override
@JsonKey(ignore: true)
_$$ApplicationSelectorStateImplCopyWith<_$ApplicationSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError;
@@ -981,7 +960,6 @@ abstract class _ApplicationSelectorState implements ApplicationSelectorState {
mixin _$TrayState {
Mode get mode => throw _privateConstructorUsedError;
bool get autoLaunch => throw _privateConstructorUsedError;
bool get adminAutoLaunch => throw _privateConstructorUsedError;
bool get systemProxy => throw _privateConstructorUsedError;
bool get tunEnable => throw _privateConstructorUsedError;
bool get isStart => throw _privateConstructorUsedError;
@@ -1001,7 +979,6 @@ abstract class $TrayStateCopyWith<$Res> {
$Res call(
{Mode mode,
bool autoLaunch,
bool adminAutoLaunch,
bool systemProxy,
bool tunEnable,
bool isStart,
@@ -1024,7 +1001,6 @@ class _$TrayStateCopyWithImpl<$Res, $Val extends TrayState>
$Res call({
Object? mode = null,
Object? autoLaunch = null,
Object? adminAutoLaunch = null,
Object? systemProxy = null,
Object? tunEnable = null,
Object? isStart = null,
@@ -1040,10 +1016,6 @@ class _$TrayStateCopyWithImpl<$Res, $Val extends TrayState>
? _value.autoLaunch
: autoLaunch // ignore: cast_nullable_to_non_nullable
as bool,
adminAutoLaunch: null == adminAutoLaunch
? _value.adminAutoLaunch
: adminAutoLaunch // ignore: cast_nullable_to_non_nullable
as bool,
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
@@ -1079,7 +1051,6 @@ abstract class _$$TrayStateImplCopyWith<$Res>
$Res call(
{Mode mode,
bool autoLaunch,
bool adminAutoLaunch,
bool systemProxy,
bool tunEnable,
bool isStart,
@@ -1100,7 +1071,6 @@ class __$$TrayStateImplCopyWithImpl<$Res>
$Res call({
Object? mode = null,
Object? autoLaunch = null,
Object? adminAutoLaunch = null,
Object? systemProxy = null,
Object? tunEnable = null,
Object? isStart = null,
@@ -1116,10 +1086,6 @@ class __$$TrayStateImplCopyWithImpl<$Res>
? _value.autoLaunch
: autoLaunch // ignore: cast_nullable_to_non_nullable
as bool,
adminAutoLaunch: null == adminAutoLaunch
? _value.adminAutoLaunch
: adminAutoLaunch // ignore: cast_nullable_to_non_nullable
as bool,
systemProxy: null == systemProxy
? _value.systemProxy
: systemProxy // ignore: cast_nullable_to_non_nullable
@@ -1150,7 +1116,6 @@ class _$TrayStateImpl implements _TrayState {
const _$TrayStateImpl(
{required this.mode,
required this.autoLaunch,
required this.adminAutoLaunch,
required this.systemProxy,
required this.tunEnable,
required this.isStart,
@@ -1162,8 +1127,6 @@ class _$TrayStateImpl implements _TrayState {
@override
final bool autoLaunch;
@override
final bool adminAutoLaunch;
@override
final bool systemProxy;
@override
final bool tunEnable;
@@ -1176,7 +1139,7 @@ class _$TrayStateImpl implements _TrayState {
@override
String toString() {
return 'TrayState(mode: $mode, autoLaunch: $autoLaunch, adminAutoLaunch: $adminAutoLaunch, systemProxy: $systemProxy, tunEnable: $tunEnable, isStart: $isStart, locale: $locale, brightness: $brightness)';
return 'TrayState(mode: $mode, autoLaunch: $autoLaunch, systemProxy: $systemProxy, tunEnable: $tunEnable, isStart: $isStart, locale: $locale, brightness: $brightness)';
}
@override
@@ -1187,8 +1150,6 @@ class _$TrayStateImpl implements _TrayState {
(identical(other.mode, mode) || other.mode == mode) &&
(identical(other.autoLaunch, autoLaunch) ||
other.autoLaunch == autoLaunch) &&
(identical(other.adminAutoLaunch, adminAutoLaunch) ||
other.adminAutoLaunch == adminAutoLaunch) &&
(identical(other.systemProxy, systemProxy) ||
other.systemProxy == systemProxy) &&
(identical(other.tunEnable, tunEnable) ||
@@ -1200,8 +1161,8 @@ class _$TrayStateImpl implements _TrayState {
}
@override
int get hashCode => Object.hash(runtimeType, mode, autoLaunch,
adminAutoLaunch, systemProxy, tunEnable, isStart, locale, brightness);
int get hashCode => Object.hash(runtimeType, mode, autoLaunch, systemProxy,
tunEnable, isStart, locale, brightness);
@JsonKey(ignore: true)
@override
@@ -1214,7 +1175,6 @@ abstract class _TrayState implements TrayState {
const factory _TrayState(
{required final Mode mode,
required final bool autoLaunch,
required final bool adminAutoLaunch,
required final bool systemProxy,
required final bool tunEnable,
required final bool isStart,
@@ -1226,8 +1186,6 @@ abstract class _TrayState implements TrayState {
@override
bool get autoLaunch;
@override
bool get adminAutoLaunch;
@override
bool get systemProxy;
@override
bool get tunEnable;
@@ -2961,7 +2919,7 @@ abstract class _ProxiesActionsState implements ProxiesActionsState {
/// @nodoc
mixin _$AutoLaunchState {
bool get isAutoLaunch => throw _privateConstructorUsedError;
bool get isAdminAutoLaunch => throw _privateConstructorUsedError;
bool get isOpenTun => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$AutoLaunchStateCopyWith<AutoLaunchState> get copyWith =>
@@ -2974,7 +2932,7 @@ abstract class $AutoLaunchStateCopyWith<$Res> {
AutoLaunchState value, $Res Function(AutoLaunchState) then) =
_$AutoLaunchStateCopyWithImpl<$Res, AutoLaunchState>;
@useResult
$Res call({bool isAutoLaunch, bool isAdminAutoLaunch});
$Res call({bool isAutoLaunch, bool isOpenTun});
}
/// @nodoc
@@ -2991,16 +2949,16 @@ class _$AutoLaunchStateCopyWithImpl<$Res, $Val extends AutoLaunchState>
@override
$Res call({
Object? isAutoLaunch = null,
Object? isAdminAutoLaunch = null,
Object? isOpenTun = null,
}) {
return _then(_value.copyWith(
isAutoLaunch: null == isAutoLaunch
? _value.isAutoLaunch
: isAutoLaunch // ignore: cast_nullable_to_non_nullable
as bool,
isAdminAutoLaunch: null == isAdminAutoLaunch
? _value.isAdminAutoLaunch
: isAdminAutoLaunch // ignore: cast_nullable_to_non_nullable
isOpenTun: null == isOpenTun
? _value.isOpenTun
: isOpenTun // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
@@ -3014,7 +2972,7 @@ abstract class _$$AutoLaunchStateImplCopyWith<$Res>
__$$AutoLaunchStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool isAutoLaunch, bool isAdminAutoLaunch});
$Res call({bool isAutoLaunch, bool isOpenTun});
}
/// @nodoc
@@ -3029,16 +2987,16 @@ class __$$AutoLaunchStateImplCopyWithImpl<$Res>
@override
$Res call({
Object? isAutoLaunch = null,
Object? isAdminAutoLaunch = null,
Object? isOpenTun = null,
}) {
return _then(_$AutoLaunchStateImpl(
isAutoLaunch: null == isAutoLaunch
? _value.isAutoLaunch
: isAutoLaunch // ignore: cast_nullable_to_non_nullable
as bool,
isAdminAutoLaunch: null == isAdminAutoLaunch
? _value.isAdminAutoLaunch
: isAdminAutoLaunch // ignore: cast_nullable_to_non_nullable
isOpenTun: null == isOpenTun
? _value.isOpenTun
: isOpenTun // ignore: cast_nullable_to_non_nullable
as bool,
));
}
@@ -3048,16 +3006,16 @@ class __$$AutoLaunchStateImplCopyWithImpl<$Res>
class _$AutoLaunchStateImpl implements _AutoLaunchState {
const _$AutoLaunchStateImpl(
{required this.isAutoLaunch, required this.isAdminAutoLaunch});
{required this.isAutoLaunch, required this.isOpenTun});
@override
final bool isAutoLaunch;
@override
final bool isAdminAutoLaunch;
final bool isOpenTun;
@override
String toString() {
return 'AutoLaunchState(isAutoLaunch: $isAutoLaunch, isAdminAutoLaunch: $isAdminAutoLaunch)';
return 'AutoLaunchState(isAutoLaunch: $isAutoLaunch, isOpenTun: $isOpenTun)';
}
@override
@@ -3067,12 +3025,12 @@ class _$AutoLaunchStateImpl implements _AutoLaunchState {
other is _$AutoLaunchStateImpl &&
(identical(other.isAutoLaunch, isAutoLaunch) ||
other.isAutoLaunch == isAutoLaunch) &&
(identical(other.isAdminAutoLaunch, isAdminAutoLaunch) ||
other.isAdminAutoLaunch == isAdminAutoLaunch));
(identical(other.isOpenTun, isOpenTun) ||
other.isOpenTun == isOpenTun));
}
@override
int get hashCode => Object.hash(runtimeType, isAutoLaunch, isAdminAutoLaunch);
int get hashCode => Object.hash(runtimeType, isAutoLaunch, isOpenTun);
@JsonKey(ignore: true)
@override
@@ -3085,12 +3043,12 @@ class _$AutoLaunchStateImpl implements _AutoLaunchState {
abstract class _AutoLaunchState implements AutoLaunchState {
const factory _AutoLaunchState(
{required final bool isAutoLaunch,
required final bool isAdminAutoLaunch}) = _$AutoLaunchStateImpl;
required final bool isOpenTun}) = _$AutoLaunchStateImpl;
@override
bool get isAutoLaunch;
@override
bool get isAdminAutoLaunch;
bool get isOpenTun;
@override
@JsonKey(ignore: true)
_$$AutoLaunchStateImplCopyWith<_$AutoLaunchStateImpl> get copyWith =>
@@ -3925,31 +3883,30 @@ abstract class _ClashConfigState implements ClashConfigState {
}
/// @nodoc
mixin _$VPNState {
AccessControl? get accessControl => throw _privateConstructorUsedError;
TunStack get stack => throw _privateConstructorUsedError;
VpnProps get vpnProps => throw _privateConstructorUsedError;
mixin _$ThemeState {
String? get locale => throw _privateConstructorUsedError;
ScaleProps get scaleProps => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$VPNStateCopyWith<VPNState> get copyWith =>
$ThemeStateCopyWith<ThemeState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $VPNStateCopyWith<$Res> {
factory $VPNStateCopyWith(VPNState value, $Res Function(VPNState) then) =
_$VPNStateCopyWithImpl<$Res, VPNState>;
abstract class $ThemeStateCopyWith<$Res> {
factory $ThemeStateCopyWith(
ThemeState value, $Res Function(ThemeState) then) =
_$ThemeStateCopyWithImpl<$Res, ThemeState>;
@useResult
$Res call({AccessControl? accessControl, TunStack stack, VpnProps vpnProps});
$Res call({String? locale, ScaleProps scaleProps});
$AccessControlCopyWith<$Res>? get accessControl;
$VpnPropsCopyWith<$Res> get vpnProps;
$ScalePropsCopyWith<$Res> get scaleProps;
}
/// @nodoc
class _$VPNStateCopyWithImpl<$Res, $Val extends VPNState>
implements $VPNStateCopyWith<$Res> {
_$VPNStateCopyWithImpl(this._value, this._then);
class _$ThemeStateCopyWithImpl<$Res, $Val extends ThemeState>
implements $ThemeStateCopyWith<$Res> {
_$ThemeStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
@@ -3959,151 +3916,117 @@ class _$VPNStateCopyWithImpl<$Res, $Val extends VPNState>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accessControl = freezed,
Object? stack = null,
Object? vpnProps = null,
Object? locale = freezed,
Object? scaleProps = null,
}) {
return _then(_value.copyWith(
accessControl: freezed == accessControl
? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?,
stack: null == stack
? _value.stack
: stack // ignore: cast_nullable_to_non_nullable
as TunStack,
vpnProps: null == vpnProps
? _value.vpnProps
: vpnProps // ignore: cast_nullable_to_non_nullable
as VpnProps,
locale: freezed == locale
? _value.locale
: locale // ignore: cast_nullable_to_non_nullable
as String?,
scaleProps: null == scaleProps
? _value.scaleProps
: scaleProps // ignore: cast_nullable_to_non_nullable
as ScaleProps,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$AccessControlCopyWith<$Res>? get accessControl {
if (_value.accessControl == null) {
return null;
}
return $AccessControlCopyWith<$Res>(_value.accessControl!, (value) {
return _then(_value.copyWith(accessControl: value) as $Val);
});
}
@override
@pragma('vm:prefer-inline')
$VpnPropsCopyWith<$Res> get vpnProps {
return $VpnPropsCopyWith<$Res>(_value.vpnProps, (value) {
return _then(_value.copyWith(vpnProps: value) as $Val);
$ScalePropsCopyWith<$Res> get scaleProps {
return $ScalePropsCopyWith<$Res>(_value.scaleProps, (value) {
return _then(_value.copyWith(scaleProps: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$VPNStateImplCopyWith<$Res>
implements $VPNStateCopyWith<$Res> {
factory _$$VPNStateImplCopyWith(
_$VPNStateImpl value, $Res Function(_$VPNStateImpl) then) =
__$$VPNStateImplCopyWithImpl<$Res>;
abstract class _$$ThemeStateImplCopyWith<$Res>
implements $ThemeStateCopyWith<$Res> {
factory _$$ThemeStateImplCopyWith(
_$ThemeStateImpl value, $Res Function(_$ThemeStateImpl) then) =
__$$ThemeStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({AccessControl? accessControl, TunStack stack, VpnProps vpnProps});
$Res call({String? locale, ScaleProps scaleProps});
@override
$AccessControlCopyWith<$Res>? get accessControl;
@override
$VpnPropsCopyWith<$Res> get vpnProps;
$ScalePropsCopyWith<$Res> get scaleProps;
}
/// @nodoc
class __$$VPNStateImplCopyWithImpl<$Res>
extends _$VPNStateCopyWithImpl<$Res, _$VPNStateImpl>
implements _$$VPNStateImplCopyWith<$Res> {
__$$VPNStateImplCopyWithImpl(
_$VPNStateImpl _value, $Res Function(_$VPNStateImpl) _then)
class __$$ThemeStateImplCopyWithImpl<$Res>
extends _$ThemeStateCopyWithImpl<$Res, _$ThemeStateImpl>
implements _$$ThemeStateImplCopyWith<$Res> {
__$$ThemeStateImplCopyWithImpl(
_$ThemeStateImpl _value, $Res Function(_$ThemeStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accessControl = freezed,
Object? stack = null,
Object? vpnProps = null,
Object? locale = freezed,
Object? scaleProps = null,
}) {
return _then(_$VPNStateImpl(
accessControl: freezed == accessControl
? _value.accessControl
: accessControl // ignore: cast_nullable_to_non_nullable
as AccessControl?,
stack: null == stack
? _value.stack
: stack // ignore: cast_nullable_to_non_nullable
as TunStack,
vpnProps: null == vpnProps
? _value.vpnProps
: vpnProps // ignore: cast_nullable_to_non_nullable
as VpnProps,
return _then(_$ThemeStateImpl(
locale: freezed == locale
? _value.locale
: locale // ignore: cast_nullable_to_non_nullable
as String?,
scaleProps: null == scaleProps
? _value.scaleProps
: scaleProps // ignore: cast_nullable_to_non_nullable
as ScaleProps,
));
}
}
/// @nodoc
class _$VPNStateImpl implements _VPNState {
const _$VPNStateImpl(
{required this.accessControl,
required this.stack,
required this.vpnProps});
class _$ThemeStateImpl implements _ThemeState {
const _$ThemeStateImpl({required this.locale, required this.scaleProps});
@override
final AccessControl? accessControl;
final String? locale;
@override
final TunStack stack;
@override
final VpnProps vpnProps;
final ScaleProps scaleProps;
@override
String toString() {
return 'VPNState(accessControl: $accessControl, stack: $stack, vpnProps: $vpnProps)';
return 'ThemeState(locale: $locale, scaleProps: $scaleProps)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$VPNStateImpl &&
(identical(other.accessControl, accessControl) ||
other.accessControl == accessControl) &&
(identical(other.stack, stack) || other.stack == stack) &&
(identical(other.vpnProps, vpnProps) ||
other.vpnProps == vpnProps));
other is _$ThemeStateImpl &&
(identical(other.locale, locale) || other.locale == locale) &&
(identical(other.scaleProps, scaleProps) ||
other.scaleProps == scaleProps));
}
@override
int get hashCode => Object.hash(runtimeType, accessControl, stack, vpnProps);
int get hashCode => Object.hash(runtimeType, locale, scaleProps);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$VPNStateImplCopyWith<_$VPNStateImpl> get copyWith =>
__$$VPNStateImplCopyWithImpl<_$VPNStateImpl>(this, _$identity);
_$$ThemeStateImplCopyWith<_$ThemeStateImpl> get copyWith =>
__$$ThemeStateImplCopyWithImpl<_$ThemeStateImpl>(this, _$identity);
}
abstract class _VPNState implements VPNState {
const factory _VPNState(
{required final AccessControl? accessControl,
required final TunStack stack,
required final VpnProps vpnProps}) = _$VPNStateImpl;
abstract class _ThemeState implements ThemeState {
const factory _ThemeState(
{required final String? locale,
required final ScaleProps scaleProps}) = _$ThemeStateImpl;
@override
AccessControl? get accessControl;
String? get locale;
@override
TunStack get stack;
@override
VpnProps get vpnProps;
ScaleProps get scaleProps;
@override
@JsonKey(ignore: true)
_$$VPNStateImplCopyWith<_$VPNStateImpl> get copyWith =>
_$$ThemeStateImplCopyWith<_$ThemeStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -55,7 +55,6 @@ class ApplicationSelectorState with _$ApplicationSelectorState {
required ThemeMode? themeMode,
required int? primaryColor,
required bool prueBlack,
required FontFamily fontFamily,
}) = _ApplicationSelectorState;
}
@@ -64,7 +63,6 @@ class TrayState with _$TrayState {
const factory TrayState({
required Mode mode,
required bool autoLaunch,
required bool adminAutoLaunch,
required bool systemProxy,
required bool tunEnable,
required bool isStart,
@@ -91,6 +89,7 @@ class HomeState with _$HomeState {
}) = _HomeState;
}
@freezed
class ProxiesCardSelectorState with _$ProxiesCardSelectorState {
const factory ProxiesCardSelectorState({
@@ -197,7 +196,7 @@ class ProxiesActionsState with _$ProxiesActionsState {
class AutoLaunchState with _$AutoLaunchState {
const factory AutoLaunchState({
required bool isAutoLaunch,
required bool isAdminAutoLaunch,
required bool isOpenTun,
}) = _AutoLaunchState;
}
@@ -218,8 +217,6 @@ class HttpOverridesState with _$HttpOverridesState {
}) = _HttpOverridesState;
}
@freezed
class ClashConfigState with _$ClashConfigState {
const factory ClashConfigState({
@@ -245,10 +242,10 @@ class ClashConfigState with _$ClashConfigState {
}
@freezed
class VPNState with _$VPNState {
const factory VPNState({
required AccessControl? accessControl,
required TunStack stack,
required VpnProps vpnProps,
}) = _VPNState;
}
class ThemeState with _$ThemeState {
const factory ThemeState({
required String? locale,
required ScaleProps scaleProps,
}) = _ThemeState;
}

View File

@@ -48,7 +48,7 @@ class HomePage extends StatelessWidget {
child: SingleChildScrollView(
child: IntrinsicHeight(
child: Selector<Config, bool>(
selector: (_, config) => config.appSetting.showLabel,
selector: (_, config) => config.showLabel,
builder: (_, showLabel, __) {
return NavigationRail(
backgroundColor:
@@ -96,10 +96,7 @@ class HomePage extends StatelessWidget {
IconButton(
onPressed: () {
final config = globalState.appController.config;
final appSetting = config.appSetting;
config.appSetting = appSetting.copyWith(
showLabel: !appSetting.showLabel,
);
config.showLabel = !config.showLabel;
},
icon: const Icon(Icons.menu),
)
@@ -163,7 +160,7 @@ class HomePage extends StatelessWidget {
currentLabel: appState.currentLabel,
navigationItems: appState.currentNavigationItems,
viewMode: appState.viewMode,
locale: config.appSetting.locale,
locale: config.locale,
);
},
shouldRebuild: (prev, next) {
@@ -174,7 +171,7 @@ class HomePage extends StatelessWidget {
final navigationItems = state.navigationItems;
final currentLabel = state.currentLabel;
final index = navigationItems.lastIndexWhere(
(element) => element.label == currentLabel,
(element) => element.label == currentLabel,
);
final currentIndex = index == -1 ? 0 : index;
final navigationBar = _getNavigationBar(
@@ -184,9 +181,9 @@ class HomePage extends StatelessWidget {
currentIndex: currentIndex,
);
final bottomNavigationBar =
viewMode == ViewMode.mobile ? navigationBar : null;
viewMode == ViewMode.mobile ? navigationBar : null;
final sideNavigationBar =
viewMode != ViewMode.mobile ? navigationBar : null;
viewMode != ViewMode.mobile ? navigationBar : null;
return CommonScaffold(
key: globalState.homeScaffoldKey,
title: Intl.message(

Some files were not shown because too many files have changed in this diff Show More